@loom-framework/core 0.1.0-alpha.151 → 0.1.0-alpha.153

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 (110) hide show
  1. package/builtin-skills/app-skill/SKILL.md +15 -5
  2. package/builtin-skills/app-skill/references/auth.md +23 -19
  3. package/builtin-skills/app-skill/references/evolution.md +90 -0
  4. package/builtin-skills/app-skill/references/process-builder.md +140 -0
  5. package/builtin-skills/app-skill/references/process-metrics.md +93 -0
  6. package/builtin-skills/app-skill/references/process.md +222 -0
  7. package/builtin-skills/loom/SKILL.md +9 -7
  8. package/dist/backend/index.d.ts +4 -0
  9. package/dist/backend/index.d.ts.map +1 -1
  10. package/dist/backend/index.js +52 -2
  11. package/dist/backend/index.js.map +1 -1
  12. package/dist/backend/process/engine.d.ts +84 -0
  13. package/dist/backend/process/engine.d.ts.map +1 -0
  14. package/dist/backend/process/engine.js +511 -0
  15. package/dist/backend/process/engine.js.map +1 -0
  16. package/dist/backend/process/index.d.ts +7 -0
  17. package/dist/backend/process/index.d.ts.map +1 -0
  18. package/dist/backend/process/index.js +6 -0
  19. package/dist/backend/process/index.js.map +1 -0
  20. package/dist/backend/process/logger.d.ts +30 -0
  21. package/dist/backend/process/logger.d.ts.map +1 -0
  22. package/dist/backend/process/logger.js +132 -0
  23. package/dist/backend/process/logger.js.map +1 -0
  24. package/dist/backend/process/queue.d.ts +31 -0
  25. package/dist/backend/process/queue.d.ts.map +1 -0
  26. package/dist/backend/process/queue.js +80 -0
  27. package/dist/backend/process/queue.js.map +1 -0
  28. package/dist/backend/process/registry.d.ts +25 -0
  29. package/dist/backend/process/registry.d.ts.map +1 -0
  30. package/dist/backend/process/registry.js +98 -0
  31. package/dist/backend/process/registry.js.map +1 -0
  32. package/dist/backend/process/trigger.d.ts +29 -0
  33. package/dist/backend/process/trigger.d.ts.map +1 -0
  34. package/dist/backend/process/trigger.js +108 -0
  35. package/dist/backend/process/trigger.js.map +1 -0
  36. package/dist/backend/routes/auth-routes.d.ts +5 -0
  37. package/dist/backend/routes/auth-routes.d.ts.map +1 -1
  38. package/dist/backend/routes/auth-routes.js +221 -1
  39. package/dist/backend/routes/auth-routes.js.map +1 -1
  40. package/dist/backend/routes/index.d.ts +2 -0
  41. package/dist/backend/routes/index.d.ts.map +1 -1
  42. package/dist/backend/routes/index.js +1 -0
  43. package/dist/backend/routes/index.js.map +1 -1
  44. package/dist/backend/routes/process-routes.d.ts +15 -0
  45. package/dist/backend/routes/process-routes.d.ts.map +1 -0
  46. package/dist/backend/routes/process-routes.js +237 -0
  47. package/dist/backend/routes/process-routes.js.map +1 -0
  48. package/dist/cli/commands/auth.d.ts.map +1 -1
  49. package/dist/cli/commands/auth.js +30 -22
  50. package/dist/cli/commands/auth.js.map +1 -1
  51. package/dist/cli/commands/data.d.ts.map +1 -1
  52. package/dist/cli/commands/data.js +36 -47
  53. package/dist/cli/commands/data.js.map +1 -1
  54. package/dist/cli/commands/generate-system-settings.d.ts +3 -2
  55. package/dist/cli/commands/generate-system-settings.d.ts.map +1 -1
  56. package/dist/cli/commands/generate-system-settings.js +50 -7
  57. package/dist/cli/commands/generate-system-settings.js.map +1 -1
  58. package/dist/cli/commands/init.js +2 -2
  59. package/dist/cli/commands/init.js.map +1 -1
  60. package/dist/cli/commands/process.d.ts +8 -0
  61. package/dist/cli/commands/process.d.ts.map +1 -0
  62. package/dist/cli/commands/process.js +444 -0
  63. package/dist/cli/commands/process.js.map +1 -0
  64. package/dist/cli/commands/role.d.ts +5 -2
  65. package/dist/cli/commands/role.d.ts.map +1 -1
  66. package/dist/cli/commands/role.js +145 -18
  67. package/dist/cli/commands/role.js.map +1 -1
  68. package/dist/cli/commands/user-cmd.d.ts.map +1 -1
  69. package/dist/cli/commands/user-cmd.js +41 -50
  70. package/dist/cli/commands/user-cmd.js.map +1 -1
  71. package/dist/cli/generators/capability-generator.d.ts.map +1 -1
  72. package/dist/cli/generators/capability-generator.js +121 -6
  73. package/dist/cli/generators/capability-generator.js.map +1 -1
  74. package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
  75. package/dist/cli/helpers/app-tsx-wiring.js +21 -14
  76. package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
  77. package/dist/cli/helpers/auth-client.d.ts +19 -0
  78. package/dist/cli/helpers/auth-client.d.ts.map +1 -0
  79. package/dist/cli/helpers/auth-client.js +42 -0
  80. package/dist/cli/helpers/auth-client.js.map +1 -0
  81. package/dist/cli/index.d.ts.map +1 -1
  82. package/dist/cli/index.js +2 -0
  83. package/dist/cli/index.js.map +1 -1
  84. package/dist/cli/templates/index.d.ts +1 -0
  85. package/dist/cli/templates/index.d.ts.map +1 -1
  86. package/dist/cli/templates/index.js +1 -0
  87. package/dist/cli/templates/index.js.map +1 -1
  88. package/dist/cli/templates/process-management-page.d.ts +8 -0
  89. package/dist/cli/templates/process-management-page.d.ts.map +1 -0
  90. package/dist/cli/templates/process-management-page.js +824 -0
  91. package/dist/cli/templates/process-management-page.js.map +1 -0
  92. package/dist/cli/templates/user-management-page.d.ts +2 -1
  93. package/dist/cli/templates/user-management-page.d.ts.map +1 -1
  94. package/dist/cli/templates/user-management-page.js +321 -62
  95. package/dist/cli/templates/user-management-page.js.map +1 -1
  96. package/dist/config.d.ts +43 -23
  97. package/dist/config.d.ts.map +1 -1
  98. package/dist/config.js +8 -2
  99. package/dist/config.js.map +1 -1
  100. package/dist/types/auth.d.ts +0 -2
  101. package/dist/types/auth.d.ts.map +1 -1
  102. package/dist/types/config.d.ts +2 -0
  103. package/dist/types/config.d.ts.map +1 -1
  104. package/dist/types/index.d.ts +1 -0
  105. package/dist/types/index.d.ts.map +1 -1
  106. package/dist/types/process.d.ts +106 -0
  107. package/dist/types/process.d.ts.map +1 -0
  108. package/dist/types/process.js +5 -0
  109. package/dist/types/process.js.map +1 -0
  110. package/package.json +3 -1
@@ -0,0 +1,824 @@
1
+ /**
2
+ * ProcessManagementPage Template
3
+ *
4
+ * Follows SkillManagementPage patterns: flex column layout, breadcrumb,
5
+ * theme tokens, showLine/blockNode tree, i18n, Card wrapper.
6
+ */
7
+ export function processManagementPageTemplate() {
8
+ return `import React, { useState, useEffect, useCallback, useMemo, useContext } from 'react';
9
+ import {
10
+ Card,
11
+ Tree,
12
+ Tabs,
13
+ Table,
14
+ Tag,
15
+ Button,
16
+ Modal,
17
+ Spin,
18
+ Segmented,
19
+ Dropdown,
20
+ Flex,
21
+ Breadcrumb,
22
+ Space,
23
+ Descriptions,
24
+ Tooltip,
25
+ Timeline,
26
+ Empty,
27
+ message,
28
+ Typography,
29
+ theme,
30
+ } from 'antd';
31
+ import {
32
+ PlayCircleOutlined,
33
+ PauseCircleOutlined,
34
+ DeleteOutlined,
35
+ ReloadOutlined,
36
+ PlusOutlined,
37
+ FileTextOutlined,
38
+ FolderOutlined,
39
+ ClockCircleOutlined,
40
+ ThunderboltOutlined,
41
+ CalendarOutlined,
42
+ ExclamationCircleOutlined,
43
+ MoreOutlined,
44
+ HomeOutlined,
45
+ EyeOutlined,
46
+ CodeOutlined,
47
+ MessageOutlined,
48
+ } from '@ant-design/icons';
49
+ import {
50
+ useLocale, useLoomTheme, useAppShell, registerMessages,
51
+ SkillFileViewer,
52
+ fetchProcesses, fetchProcess,
53
+ fetchProcessLogs, fetchProcessMetrics, fetchProcessMetricsConfig,
54
+ fetchProcessFiles, fetchProcessFileContent,
55
+ runProcess, pauseProcess, resumeProcess, deleteProcess,
56
+ getFileIcon,
57
+ AIContext,
58
+ } from '@loom-framework/frontend-antd';
59
+ import type {
60
+ ProcessInfo, ProcessLogEntry, ProcessMetricsAggregate, ProcessFileInfo, FileContent,
61
+ } from '@loom-framework/frontend-antd';
62
+ import { DashboardChart } from '@loom-framework/frontend-antd';
63
+
64
+ const { Text } = Typography;
65
+
66
+ interface ProcessTreeNode {
67
+ key: string;
68
+ title: string;
69
+ icon: React.ReactNode;
70
+ isLeaf: boolean;
71
+ isFile?: boolean;
72
+ isDir?: boolean;
73
+ isProcess?: boolean;
74
+ filePath?: string;
75
+ processName?: string;
76
+ children?: ProcessTreeNode[];
77
+ }
78
+
79
+ registerMessages('zh-CN', {
80
+ 'process.title': '过程管理',
81
+ 'process.create': '创建过程',
82
+ 'process.createDisabledHint': '过程创建比较复杂,在 AI 对话框输入「我要创建一个 xxx 过程」,让 Loom 帮助你吧!^_^',
83
+ 'process.delete': '删除',
84
+ 'process.run': '立即执行',
85
+ 'process.pause': '暂停',
86
+ 'process.resume': '恢复',
87
+ 'process.overview': '概览',
88
+ 'process.files': '文件',
89
+ 'process.logs': '日志',
90
+ 'process.metrics': '指标',
91
+ 'process.name': '名称',
92
+ 'process.status': '状态',
93
+ 'process.trigger': '触发器',
94
+ 'process.owner': '负责人',
95
+ 'process.mode': '模式',
96
+ 'process.modeLightweight': '轻量',
97
+ 'process.modeFull': '完整',
98
+ 'process.statusIdle': '空闲',
99
+ 'process.statusRunning': '运行中',
100
+ 'process.statusPaused': '已暂停',
101
+ 'process.statusError': '错误',
102
+ 'process.noProcesses': '暂无过程',
103
+ 'process.noFiles': '轻量过程没有文件',
104
+ 'process.noMetrics': '未配置指标',
105
+ 'process.noLogs': '暂无执行日志',
106
+ 'process.queue': '执行队列',
107
+ 'process.queueRunning': '运行中',
108
+ 'process.queueQueued': '等待中',
109
+ 'process.cron': '定时触发',
110
+ 'process.event': '事件触发',
111
+ 'process.manual': '手动触发',
112
+ 'process.lastRun': '上次执行',
113
+ 'process.nextRun': '下次执行',
114
+ 'process.duration': '耗时',
115
+ 'process.confirmDelete': '确认删除',
116
+ 'process.confirmDeleteContent': '确定要删除过程「{name}」吗?',
117
+ 'process.processNotFound': '未找到 PROCESS.md,请先运行 loom process generate',
118
+ 'process.createViaAI': '使用 AI 创建过程',
119
+ 'process.refresh': '刷新',
120
+ 'process.more': '更多',
121
+ 'process.prompt': '提示词',
122
+ 'process.description': '描述',
123
+ 'process.runTriggered': '已触发执行,Run ID: {runId}',
124
+ 'process.runFailed': '执行触发失败',
125
+ 'process.pauseFailed': '暂停失败',
126
+ 'process.resumeFailed': '恢复失败',
127
+ 'process.deleteFailed': '删除失败',
128
+ 'process.loadFailed': '加载过程列表失败',
129
+ 'process.loadDetailFailed': '加载过程详情失败',
130
+ 'process.processDetail': '过程详情',
131
+ 'process.selectToView': '选择左侧过程或文件以查看详情',
132
+ 'process.preview': '预览',
133
+ 'process.source': '源码',
134
+ 'process.view': '查看',
135
+ 'process.colName': '名称',
136
+ 'process.colSize': '大小',
137
+ 'process.colAction': '操作',
138
+ 'process.colDate': '日期',
139
+ 'process.colTotal': '总执行',
140
+ 'process.colCompleted': '成功',
141
+ 'process.colFailed': '失败',
142
+ 'process.colTimeout': '超时',
143
+ 'process.colAvgDuration': '平均耗时',
144
+ 'process.colRunId': '执行 ID',
145
+ 'process.colTriggerType': '触发类型',
146
+ 'process.colResult': '结果',
147
+ 'process.colTime': '时间',
148
+ 'process.viewSession': '查看 AI 会话',
149
+ });
150
+
151
+ registerMessages('en-US', {
152
+ 'process.title': 'Process Management',
153
+ 'process.create': 'Create Process',
154
+ 'process.createDisabledHint': 'Process creation is complex. Type "I want to create a xxx process" in the AI chat and let Loom help you! ^_^',
155
+ 'process.delete': 'Delete',
156
+ 'process.run': 'Run Now',
157
+ 'process.pause': 'Pause',
158
+ 'process.resume': 'Resume',
159
+ 'process.overview': 'Overview',
160
+ 'process.files': 'Files',
161
+ 'process.logs': 'Logs',
162
+ 'process.metrics': 'Metrics',
163
+ 'process.name': 'Name',
164
+ 'process.status': 'Status',
165
+ 'process.trigger': 'Trigger',
166
+ 'process.owner': 'Owner',
167
+ 'process.mode': 'Mode',
168
+ 'process.modeLightweight': 'Lightweight',
169
+ 'process.modeFull': 'Full',
170
+ 'process.statusIdle': 'Idle',
171
+ 'process.statusRunning': 'Running',
172
+ 'process.statusPaused': 'Paused',
173
+ 'process.statusError': 'Error',
174
+ 'process.noProcesses': 'No processes',
175
+ 'process.noFiles': 'Lightweight process has no files',
176
+ 'process.noMetrics': 'No metrics configured',
177
+ 'process.noLogs': 'No execution logs',
178
+ 'process.queue': 'Execution Queue',
179
+ 'process.queueRunning': 'Running',
180
+ 'process.queueQueued': 'Queued',
181
+ 'process.cron': 'Cron Trigger',
182
+ 'process.event': 'Event Trigger',
183
+ 'process.manual': 'Manual Trigger',
184
+ 'process.lastRun': 'Last Run',
185
+ 'process.nextRun': 'Next Run',
186
+ 'process.duration': 'Duration',
187
+ 'process.confirmDelete': 'Confirm Delete',
188
+ 'process.confirmDeleteContent': 'Are you sure you want to delete process "{name}"?',
189
+ 'process.processNotFound': 'PROCESS.md not found. Run loom process generate first',
190
+ 'process.createViaAI': 'Create Process with AI',
191
+ 'process.refresh': 'Refresh',
192
+ 'process.more': 'More',
193
+ 'process.prompt': 'Prompt',
194
+ 'process.description': 'Description',
195
+ 'process.runTriggered': 'Process triggered. Run ID: {runId}',
196
+ 'process.runFailed': 'Failed to trigger process',
197
+ 'process.pauseFailed': 'Failed to pause',
198
+ 'process.resumeFailed': 'Failed to resume',
199
+ 'process.deleteFailed': 'Failed to delete',
200
+ 'process.loadFailed': 'Failed to load processes',
201
+ 'process.loadDetailFailed': 'Failed to load process detail',
202
+ 'process.processDetail': 'Process Details',
203
+ 'process.selectToView': 'Select a process or file to view details',
204
+ 'process.preview': 'Preview',
205
+ 'process.source': 'Source',
206
+ 'process.view': 'View',
207
+ 'process.colName': 'Name',
208
+ 'process.colSize': 'Size',
209
+ 'process.colAction': 'Action',
210
+ 'process.colDate': 'Date',
211
+ 'process.colTotal': 'Total',
212
+ 'process.colCompleted': 'Completed',
213
+ 'process.colFailed': 'Failed',
214
+ 'process.colTimeout': 'Timeout',
215
+ 'process.colAvgDuration': 'Avg Duration',
216
+ 'process.colRunId': 'Run ID',
217
+ 'process.colTriggerType': 'Trigger',
218
+ 'process.colResult': 'Result',
219
+ 'process.colTime': 'Time',
220
+ 'process.viewSession': 'View AI Session',
221
+ });
222
+
223
+ const STATUS_COLORS: Record<string, string> = {
224
+ idle: 'default',
225
+ running: 'processing',
226
+ paused: 'warning',
227
+ error: 'error',
228
+ };
229
+
230
+ const STATUS_ICONS: Record<string, React.ReactNode> = {
231
+ idle: <ClockCircleOutlined />,
232
+ running: <PlayCircleOutlined />,
233
+ paused: <PauseCircleOutlined />,
234
+ error: <ExclamationCircleOutlined />,
235
+ };
236
+
237
+ const TRIGGER_ICONS: Record<string, React.ReactNode> = {
238
+ cron: <CalendarOutlined />,
239
+ event: <ThunderboltOutlined />,
240
+ manual: <PlayCircleOutlined />,
241
+ };
242
+
243
+ export default function ProcessManagementPage(): React.ReactElement {
244
+ const { mode } = useLoomTheme();
245
+ const { t } = useLocale();
246
+ const { breadcrumbs, onNavClick } = useAppShell();
247
+ const { token } = theme.useToken();
248
+ const aiContext = useContext(AIContext);
249
+
250
+ const [processes, setProcesses] = useState<ProcessInfo[]>([]);
251
+ const [treeData, setTreeData] = useState<ProcessTreeNode[]>([]);
252
+ const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
253
+ const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
254
+ const [selectedProcess, setSelectedProcess] = useState<(ProcessInfo & { status?: string }) | null>(null);
255
+ const [fileContent, setFileContent] = useState<FileContent | null>(null);
256
+ const [loading, setLoading] = useState(false);
257
+ const [viewMode, setViewMode] = useState<'preview' | 'source'>('preview');
258
+ const [activeTab, setActiveTab] = useState('overview');
259
+ const [logs, setLogs] = useState<ProcessLogEntry[]>([]);
260
+ const [metrics, setMetrics] = useState<ProcessMetricsAggregate[]>([]);
261
+ const [metricsConfig, setMetricsConfig] = useState<Record<string, unknown>[]>([]);
262
+ const [files, setFiles] = useState<ProcessFileInfo[]>([]);
263
+ const [executing, setExecuting] = useState(false);
264
+
265
+ const buildTreeData = useCallback((processList: ProcessInfo[]): ProcessTreeNode[] => {
266
+ return processList.map((proc) => {
267
+ const children: ProcessTreeNode[] = [];
268
+
269
+ if (proc.hasProcessFile) {
270
+ children.push({
271
+ key: \`\${proc.name}/PROCESS.md\`,
272
+ title: 'PROCESS.md',
273
+ icon: <FileTextOutlined style={{ color: token.colorPrimary }} />,
274
+ isLeaf: true,
275
+ isFile: true,
276
+ filePath: 'PROCESS.md',
277
+ processName: proc.name,
278
+ });
279
+ children.push({
280
+ key: \`\${proc.name}/references\`,
281
+ title: 'references/',
282
+ icon: <FolderOutlined style={{ color: token.colorWarning }} />,
283
+ isLeaf: false,
284
+ isFile: false,
285
+ isDir: true,
286
+ filePath: 'references',
287
+ children: [],
288
+ processName: proc.name,
289
+ });
290
+ }
291
+
292
+ return {
293
+ key: proc.name,
294
+ title: proc.name,
295
+ icon: proc.hasProcessFile ? <FolderOutlined /> : <ThunderboltOutlined style={{ color: token.colorWarning }} />,
296
+ isLeaf: false,
297
+ isFile: false,
298
+ isProcess: true,
299
+ children,
300
+ processName: proc.name,
301
+ };
302
+ });
303
+ }, [token]);
304
+
305
+ const loadProcesses = useCallback(async () => {
306
+ setLoading(true);
307
+ try {
308
+ const result = await fetchProcesses();
309
+ const list = result.data ?? [];
310
+ setProcesses(list);
311
+ setTreeData(buildTreeData(list));
312
+ setExpandedKeys(list.map((p) => p.name));
313
+ } catch {
314
+ // error handled silently
315
+ } finally {
316
+ setLoading(false);
317
+ }
318
+ }, [buildTreeData]);
319
+
320
+ useEffect(() => { loadProcesses(); }, [loadProcesses]);
321
+
322
+ const loadDirChildren = useCallback(async (processName: string, dirName: string): Promise<ProcessTreeNode[]> => {
323
+ const data = await fetchProcessFiles(processName);
324
+ const dirFiles = data.filter((f) => f.path.startsWith(\`\${dirName}/\`) && !f.isDirectory);
325
+
326
+ const dirChildren: ProcessTreeNode[] = [];
327
+ for (const f of dirFiles) {
328
+ const relativePath = f.path.slice(dirName.length + 1);
329
+ const parts = relativePath.split('/');
330
+
331
+ let currentLevel = dirChildren;
332
+ for (let i = 0; i < parts.length; i++) {
333
+ const part = parts[i]!;
334
+ const isLast = i === parts.length - 1;
335
+ const partialPath = \`\${dirName}/\${parts.slice(0, i + 1).join('/')}\`;
336
+
337
+ if (isLast) {
338
+ currentLevel.push({
339
+ key: \`\${processName}/\${f.path}\`,
340
+ title: part,
341
+ icon: getFileIcon(part, { primary: token.colorPrimary, warning: token.colorWarning, success: token.colorSuccess }),
342
+ isLeaf: true,
343
+ isFile: true,
344
+ filePath: f.path,
345
+ processName,
346
+ });
347
+ } else {
348
+ let dirNode = currentLevel.find((n) => n.isDir && n.filePath === partialPath);
349
+ if (!dirNode) {
350
+ dirNode = {
351
+ key: \`\${processName}/\${partialPath}\`,
352
+ title: \`\${part}/\`,
353
+ icon: <FolderOutlined style={{ color: token.colorWarning }} />,
354
+ isLeaf: false,
355
+ isFile: false,
356
+ isDir: true,
357
+ filePath: partialPath,
358
+ children: [],
359
+ processName,
360
+ };
361
+ currentLevel.push(dirNode);
362
+ }
363
+ currentLevel = dirNode.children!;
364
+ }
365
+ }
366
+ }
367
+ return dirChildren;
368
+ }, [token]);
369
+
370
+ const handleTreeExpand = useCallback(
371
+ async (keys: React.Key[], info: { node: ProcessTreeNode; expanded: boolean }) => {
372
+ setExpandedKeys(keys);
373
+ if (!info.expanded) return;
374
+ const node = info.node;
375
+ if (node.processName && node.isDir) {
376
+ if (node.children && node.children.length > 0) return;
377
+ const dirName = node.filePath || node.title.replace(/\\/$/, '');
378
+ const dirChildren = await loadDirChildren(node.processName, dirName);
379
+
380
+ const updateNode = (nodes: ProcessTreeNode[]): ProcessTreeNode[] =>
381
+ nodes.map((n) => {
382
+ if (n.key === node.key) {
383
+ return { ...n, children: dirChildren };
384
+ }
385
+ if (n.children) {
386
+ return { ...n, children: updateNode(n.children) };
387
+ }
388
+ return n;
389
+ });
390
+
391
+ setTreeData((prev) => updateNode(prev));
392
+ }
393
+ },
394
+ [loadDirChildren],
395
+ );
396
+
397
+ const selectProcess = useCallback(async (name: string) => {
398
+ try {
399
+ const data = await fetchProcess(name);
400
+ setSelectedProcess(data);
401
+ setFileContent(null);
402
+ setLogs([]);
403
+ setMetrics([]);
404
+ setMetricsConfig([]);
405
+ setFiles([]);
406
+ setActiveTab('overview');
407
+ } catch {
408
+ // error handled silently
409
+ }
410
+ }, []);
411
+
412
+ const handleTreeSelect = useCallback(
413
+ async (keys: React.Key[], info: { node: ProcessTreeNode }) => {
414
+ setSelectedKeys(keys);
415
+ if (keys.length === 0) {
416
+ setSelectedProcess(null);
417
+ setFileContent(null);
418
+ return;
419
+ }
420
+ const node = info.node;
421
+
422
+ if (node.isFile && node.processName && node.filePath) {
423
+ setLoading(true);
424
+ try {
425
+ if (node.filePath === 'PROCESS.md') {
426
+ const data = await fetchProcess(node.processName);
427
+ setSelectedProcess(data);
428
+ setFileContent(null);
429
+ setActiveTab('overview');
430
+ } else {
431
+ const content = await fetchProcessFileContent(node.processName, node.filePath);
432
+ setFileContent({ content, path: node.filePath });
433
+ setSelectedProcess(null);
434
+ }
435
+ } catch {
436
+ // error handled silently
437
+ } finally {
438
+ setLoading(false);
439
+ }
440
+ } else if (node.isProcess && node.processName) {
441
+ await selectProcess(node.processName);
442
+ }
443
+ },
444
+ [selectProcess],
445
+ );
446
+
447
+ const handleRun = useCallback(async () => {
448
+ if (!selectedProcess) return;
449
+ setExecuting(true);
450
+ try {
451
+ const result = await runProcess(selectedProcess.name);
452
+ message.success(t('process.runTriggered', { runId: result.runId }));
453
+ } catch {
454
+ message.error(t('process.runFailed'));
455
+ } finally {
456
+ setExecuting(false);
457
+ }
458
+ }, [selectedProcess, t]);
459
+
460
+ const handlePause = useCallback(async () => {
461
+ if (!selectedProcess) return;
462
+ try {
463
+ await pauseProcess(selectedProcess.name);
464
+ loadProcesses();
465
+ selectProcess(selectedProcess.name);
466
+ } catch {
467
+ message.error(t('process.pauseFailed'));
468
+ }
469
+ }, [selectedProcess, loadProcesses, selectProcess]);
470
+
471
+ const handleResume = useCallback(async () => {
472
+ if (!selectedProcess) return;
473
+ try {
474
+ await resumeProcess(selectedProcess.name);
475
+ loadProcesses();
476
+ selectProcess(selectedProcess.name);
477
+ } catch {
478
+ message.error(t('process.resumeFailed'));
479
+ }
480
+ }, [selectedProcess, loadProcesses, selectProcess]);
481
+
482
+ const handleDelete = useCallback(
483
+ async (processName: string) => {
484
+ try {
485
+ await deleteProcess(processName);
486
+ setSelectedProcess(null);
487
+ setFileContent(null);
488
+ setSelectedKeys([]);
489
+ loadProcesses();
490
+ } catch {
491
+ message.error(t('process.deleteFailed'));
492
+ }
493
+ },
494
+ [loadProcesses, t],
495
+ );
496
+
497
+ const loadLogs = useCallback(async () => {
498
+ if (!selectedProcess) return;
499
+ try {
500
+ const data = await fetchProcessLogs(selectedProcess.name, 50);
501
+ setLogs(data);
502
+ } catch { /* ignore */ }
503
+ }, [selectedProcess]);
504
+
505
+ const loadMetrics = useCallback(async () => {
506
+ if (!selectedProcess) return;
507
+ try {
508
+ const [m, c] = await Promise.all([
509
+ fetchProcessMetrics(selectedProcess.name),
510
+ fetchProcessMetricsConfig(selectedProcess.name),
511
+ ]);
512
+ setMetrics(m);
513
+ setMetricsConfig(c);
514
+ } catch { /* ignore */ }
515
+ }, [selectedProcess]);
516
+
517
+ const loadFiles = useCallback(async () => {
518
+ if (!selectedProcess) return;
519
+ try {
520
+ const data = await fetchProcessFiles(selectedProcess.name);
521
+ setFiles(data);
522
+ } catch { /* ignore */ }
523
+ }, [selectedProcess]);
524
+
525
+ const handleTabChange = useCallback((key: string) => {
526
+ setActiveTab(key);
527
+ if (key === 'logs') loadLogs();
528
+ if (key === 'metrics') loadMetrics();
529
+ if (key === 'files') loadFiles();
530
+ }, [loadLogs, loadMetrics, loadFiles]);
531
+
532
+ const rightTitle = useMemo(() => {
533
+ if (selectedProcess) return \`\${selectedProcess.name}\`;
534
+ if (fileContent) return fileContent.path;
535
+ return t('process.processDetail');
536
+ }, [selectedProcess, fileContent, t]);
537
+
538
+ // Build status tag for tree node
539
+ const getStatusColor = useCallback((proc: ProcessInfo) => {
540
+ if (proc.paused) return 'warning';
541
+ return 'default';
542
+ }, []);
543
+
544
+ return (
545
+ <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
546
+ <Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
547
+ <Breadcrumb items={[{ title: <HomeOutlined onClick={() => onNavClick?.('')} style={{ cursor: 'pointer' }} /> }, ...(breadcrumbs || []).map(b => ({ title: b.path ? <a onClick={() => onNavClick?.(b.path!)}>{b.title}</a> : b.title }))]} />
548
+ <Space>
549
+ <Button icon={<ReloadOutlined />} onClick={loadProcesses} loading={loading}>
550
+ {t('process.refresh')}
551
+ </Button>
552
+ <Tooltip title={t('process.createDisabledHint')}>
553
+ <Button type="primary" icon={<PlusOutlined />} disabled>
554
+ {t('process.create')}
555
+ </Button>
556
+ </Tooltip>
557
+ <Dropdown
558
+ menu={{
559
+ items: selectedProcess
560
+ ? [
561
+ {
562
+ key: 'delete',
563
+ label: t('process.delete'),
564
+ icon: <DeleteOutlined />,
565
+ danger: true,
566
+ onClick: () => {
567
+ Modal.confirm({
568
+ title: t('process.confirmDelete'),
569
+ content: t('process.confirmDeleteContent', { name: selectedProcess.name }),
570
+ okType: 'danger',
571
+ onOk: () => handleDelete(selectedProcess.name),
572
+ });
573
+ },
574
+ },
575
+ ]
576
+ : [],
577
+ }}
578
+ >
579
+ <Button icon={<MoreOutlined />} disabled={!selectedProcess}>
580
+ {t('process.more')}
581
+ </Button>
582
+ </Dropdown>
583
+ </Space>
584
+ </Flex>
585
+
586
+ <Spin spinning={loading && !selectedProcess && !fileContent}>
587
+ <div style={{ display: 'flex', height: '100%', padding: 16, gap: 16, overflow: 'hidden' }}>
588
+ <div style={{ width: 300, minWidth: 240, maxWidth: 500, overflow: 'auto', flexShrink: 0 }}>
589
+ <Tree<ProcessTreeNode>
590
+ treeData={treeData as unknown as any}
591
+ selectedKeys={selectedKeys}
592
+ expandedKeys={expandedKeys}
593
+ showLine={{ showLeafIcon: false }}
594
+ blockNode
595
+ onSelect={handleTreeSelect}
596
+ onExpand={handleTreeExpand}
597
+ style={{ fontSize: 14 }}
598
+ titleRender={(node: any) => (
599
+ <Flex align="center" gap={4}>
600
+ {node.icon}
601
+ <span>{node.title}</span>
602
+ {node.isProcess && (() => {
603
+ const proc = processes.find(p => p.name === node.processName);
604
+ if (!proc) return null;
605
+ return (
606
+ <Tag color={getStatusColor(proc)} style={{ fontSize: 11, lineHeight: '16px', padding: '0 4px', marginRight: 0 }}>
607
+ {proc.paused ? t('process.statusPaused') : t('process.statusIdle')}
608
+ </Tag>
609
+ );
610
+ })()}
611
+ </Flex>
612
+ )}
613
+ />
614
+ </div>
615
+
616
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
617
+ <Card
618
+ size="small"
619
+ style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}
620
+ styles={{ body: { padding: 16, flex: 1, overflow: 'auto', minHeight: 0 } }}
621
+ title={
622
+ <Flex align="center" gap="small">
623
+ <span>{rightTitle}</span>
624
+ {(selectedProcess || fileContent) && !selectedProcess?.hasProcessFile && fileContent && (
625
+ <Segmented
626
+ size="small"
627
+ options={[
628
+ { label: t('process.preview'), value: 'preview', icon: <EyeOutlined /> },
629
+ { label: t('process.source'), value: 'source', icon: <CodeOutlined /> },
630
+ ]}
631
+ value={viewMode}
632
+ onChange={(v) => setViewMode(v as 'preview' | 'source')}
633
+ />
634
+ )}
635
+ </Flex>
636
+ }
637
+ >
638
+ {selectedProcess ? (
639
+ <Tabs
640
+ activeKey={activeTab}
641
+ onChange={handleTabChange}
642
+ items={[
643
+ {
644
+ key: 'overview',
645
+ label: t('process.overview'),
646
+ children: (
647
+ <Space direction="vertical" style={{ width: '100%' }} size="middle">
648
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
649
+ <Space>
650
+ <Tag color={STATUS_COLORS[selectedProcess.status ?? 'idle']} icon={STATUS_ICONS[selectedProcess.status ?? 'idle']}>
651
+ {selectedProcess.paused ? t('process.statusPaused') : (selectedProcess.status === 'running' ? t('process.statusRunning') : (selectedProcess.status === 'error' ? t('process.statusError') : t('process.statusIdle')))}
652
+ </Tag>
653
+ <Tag>{selectedProcess.hasProcessFile ? t('process.modeFull') : t('process.modeLightweight')}</Tag>
654
+ </Space>
655
+ <Space>
656
+ <Tooltip title={selectedProcess.paused ? t('process.resume') : t('process.pause')}>
657
+ <Button icon={selectedProcess.paused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
658
+ onClick={selectedProcess.paused ? handleResume : handlePause} />
659
+ </Tooltip>
660
+ <Tooltip title={t('process.run')}>
661
+ <Button type="primary" icon={<PlayCircleOutlined />} onClick={handleRun} loading={executing} />
662
+ </Tooltip>
663
+ </Space>
664
+ </div>
665
+
666
+ <Descriptions bordered column={2} size="small"
667
+ styles={{ label: { width: 120, fontWeight: 500, backgroundColor: token.colorFillQuaternary } }}
668
+ items={[
669
+ { key: 'trigger', label: t('process.trigger'), children: (
670
+ <Space>
671
+ {selectedProcess.cron && <Tag icon={<CalendarOutlined />}>{selectedProcess.cron}</Tag>}
672
+ {selectedProcess.onEvent && <Tag icon={<ThunderboltOutlined />}>{selectedProcess.onEvent}</Tag>}
673
+ {!selectedProcess.cron && !selectedProcess.onEvent && <Tag>{t('process.manual')}</Tag>}
674
+ </Space>
675
+ )},
676
+ { key: 'owner', label: t('process.owner'), children: selectedProcess.owner },
677
+ ...(selectedProcess.description ? [{ key: 'desc', label: t('process.description'), children: selectedProcess.description }] : []),
678
+ ...(selectedProcess.prompt ? [{ key: 'prompt', label: t('process.prompt'), children: <Text code style={{ whiteSpace: 'pre-wrap', fontSize: 12, maxWidth: 400, display: 'inline-block' }}>{selectedProcess.prompt}</Text> }] : []),
679
+ ]}
680
+ />
681
+ </Space>
682
+ ),
683
+ },
684
+ ...(selectedProcess.hasProcessFile ? [{
685
+ key: 'files',
686
+ label: t('process.files'),
687
+ children: fileContent ? (
688
+ <SkillFileViewer
689
+ skillDetail={null}
690
+ fileContent={fileContent}
691
+ viewMode={viewMode}
692
+ onViewModeChange={setViewMode}
693
+ mode={mode}
694
+ token={token}
695
+ t={t}
696
+ />
697
+ ) : (
698
+ <Table
699
+ dataSource={files}
700
+ rowKey="path"
701
+ size="small"
702
+ pagination={false}
703
+ columns={[
704
+ { title: t('process.colName'), dataIndex: 'name', key: 'name' },
705
+ { title: t('process.colSize'), dataIndex: 'size', key: 'size', render: (s: number) => s > 0 ? \`\${(s / 1024).toFixed(1)} KB\` : '-' },
706
+ {
707
+ title: t('process.colAction'), key: 'action', width: 80,
708
+ render: (_: unknown, record: ProcessFileInfo) => !record.isDirectory && (
709
+ <Button type="link" size="small" onClick={async () => {
710
+ if (!selectedProcess) return;
711
+ try {
712
+ const c = await fetchProcessFileContent(selectedProcess.name, record.path);
713
+ setFileContent({ content: c, path: record.path });
714
+ } catch { /* ignore */ }
715
+ }}>{t('process.view')}</Button>
716
+ ),
717
+ },
718
+ ]}
719
+ />
720
+ ),
721
+ }] : [{
722
+ key: 'files',
723
+ label: t('process.files'),
724
+ children: <Empty description={t('process.noFiles')} />,
725
+ }]),
726
+ {
727
+ key: 'logs',
728
+ label: t('process.logs'),
729
+ children: (
730
+ <Space direction="vertical" style={{ width: '100%' }}>
731
+ <Button icon={<ReloadOutlined />} onClick={loadLogs} size="small">
732
+ {t('process.refresh')}
733
+ </Button>
734
+ {logs.length === 0 ? (
735
+ <Empty description={t('process.noLogs')} />
736
+ ) : (
737
+ <Timeline
738
+ items={logs.map(log => ({
739
+ color: log.status === 'completed' ? 'green' : log.status === 'failed' ? 'red' : log.status === 'timeout' ? 'orange' : 'blue',
740
+ content: (
741
+ <div>
742
+ <Space>
743
+ <Tooltip title={log.runId}>
744
+ <Text strong>{log.runId.slice(0, 12)}</Text>
745
+ </Tooltip>
746
+ <Tag icon={TRIGGER_ICONS[log.triggerType]}>{log.triggerType}</Tag>
747
+ <Tag color={log.status === 'completed' ? 'success' : 'error'}>{log.status}</Tag>
748
+ {log.sessionId && aiContext?.switchToSession && (
749
+ <Tooltip title={t('process.viewSession')}>
750
+ <Button type="link" size="small" icon={<MessageOutlined />} onClick={() => aiContext.switchToSession!(log.sessionId)} />
751
+ </Tooltip>
752
+ )}
753
+ </Space>
754
+ <div>
755
+ <Text type="secondary">{new Date(log.startedAt).toLocaleString()}</Text>
756
+ {log.durationMs > 0 && <Text type="secondary"> · {(log.durationMs / 1000).toFixed(1)}s</Text>}
757
+ </div>
758
+ {log.error && <Text type="danger" style={{ fontSize: 12 }}>{log.error}</Text>}
759
+ </div>
760
+ ),
761
+ }))}
762
+ />
763
+ )}
764
+ </Space>
765
+ ),
766
+ },
767
+ {
768
+ key: 'metrics',
769
+ label: t('process.metrics'),
770
+ children: metricsConfig.length === 0 ? (
771
+ <Empty description={t('process.noMetrics')} />
772
+ ) : (
773
+ <Space direction="vertical" style={{ width: '100%' }} size="middle">
774
+ {metricsConfig.map((widget, i) => (
775
+ <DashboardChart key={i} spec={{ ...widget, data: metrics }} />
776
+ ))}
777
+ {metrics.length > 0 && (
778
+ <Table
779
+ dataSource={metrics}
780
+ rowKey="date"
781
+ size="small"
782
+ pagination={{ pageSize: 7 }}
783
+ columns={[
784
+ { title: t('process.colDate'), dataIndex: 'date', key: 'date' },
785
+ { title: t('process.colTotal'), dataIndex: 'totalRuns', key: 'totalRuns' },
786
+ { title: t('process.colCompleted'), dataIndex: 'completedRuns', key: 'completedRuns' },
787
+ { title: t('process.colFailed'), dataIndex: 'failedRuns', key: 'failedRuns' },
788
+ { title: t('process.colTimeout'), dataIndex: 'timeoutRuns', key: 'timeoutRuns' },
789
+ { title: t('process.colAvgDuration'), dataIndex: 'avgDurationMs', key: 'avgDurationMs', render: (ms: number) => \`\${(ms / 1000).toFixed(1)}s\` },
790
+ ]}
791
+ />
792
+ )}
793
+ </Space>
794
+ ),
795
+ },
796
+ ]}
797
+ />
798
+ ) : fileContent ? (
799
+ <SkillFileViewer
800
+ skillDetail={null}
801
+ fileContent={fileContent}
802
+ viewMode={viewMode}
803
+ onViewModeChange={setViewMode}
804
+ mode={mode}
805
+ token={token}
806
+ t={t}
807
+ />
808
+ ) : (
809
+ <Empty
810
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
811
+ description={t('process.selectToView')}
812
+ style={{ marginTop: 120 }}
813
+ />
814
+ )}
815
+ </Card>
816
+ </div>
817
+ </div>
818
+ </Spin>
819
+ </div>
820
+ );
821
+ }
822
+ `;
823
+ }
824
+ //# sourceMappingURL=process-management-page.js.map