@loom-framework/core 0.1.0-alpha.150 → 0.1.0-alpha.152

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 (165) hide show
  1. package/builtin-skills/app-skill/SKILL.md +16 -5
  2. package/builtin-skills/app-skill/references/auth.md +23 -19
  3. package/builtin-skills/app-skill/references/events.md +119 -0
  4. package/builtin-skills/app-skill/references/evolution.md +90 -0
  5. package/builtin-skills/app-skill/references/process-builder.md +140 -0
  6. package/builtin-skills/app-skill/references/process-metrics.md +93 -0
  7. package/builtin-skills/app-skill/references/process.md +222 -0
  8. package/builtin-skills/loom/SKILL.md +9 -7
  9. package/dist/backend/auth/request-context.d.ts +15 -0
  10. package/dist/backend/auth/request-context.d.ts.map +1 -0
  11. package/dist/backend/auth/request-context.js +17 -0
  12. package/dist/backend/auth/request-context.js.map +1 -0
  13. package/dist/backend/events/event-bus.d.ts +39 -0
  14. package/dist/backend/events/event-bus.d.ts.map +1 -0
  15. package/dist/backend/events/event-bus.js +182 -0
  16. package/dist/backend/events/event-bus.js.map +1 -0
  17. package/dist/backend/events/event-emitting-adapter.d.ts +31 -0
  18. package/dist/backend/events/event-emitting-adapter.d.ts.map +1 -0
  19. package/dist/backend/events/event-emitting-adapter.js +70 -0
  20. package/dist/backend/events/event-emitting-adapter.js.map +1 -0
  21. package/dist/backend/events/event-registry.d.ts +24 -0
  22. package/dist/backend/events/event-registry.d.ts.map +1 -0
  23. package/dist/backend/events/event-registry.js +22 -0
  24. package/dist/backend/events/event-registry.js.map +1 -0
  25. package/dist/backend/events/index.d.ts +7 -0
  26. package/dist/backend/events/index.d.ts.map +1 -0
  27. package/dist/backend/events/index.js +7 -0
  28. package/dist/backend/events/index.js.map +1 -0
  29. package/dist/backend/index.d.ts +12 -0
  30. package/dist/backend/index.d.ts.map +1 -1
  31. package/dist/backend/index.js +143 -3
  32. package/dist/backend/index.js.map +1 -1
  33. package/dist/backend/process/engine.d.ts +84 -0
  34. package/dist/backend/process/engine.d.ts.map +1 -0
  35. package/dist/backend/process/engine.js +511 -0
  36. package/dist/backend/process/engine.js.map +1 -0
  37. package/dist/backend/process/index.d.ts +7 -0
  38. package/dist/backend/process/index.d.ts.map +1 -0
  39. package/dist/backend/process/index.js +6 -0
  40. package/dist/backend/process/index.js.map +1 -0
  41. package/dist/backend/process/logger.d.ts +30 -0
  42. package/dist/backend/process/logger.d.ts.map +1 -0
  43. package/dist/backend/process/logger.js +132 -0
  44. package/dist/backend/process/logger.js.map +1 -0
  45. package/dist/backend/process/queue.d.ts +31 -0
  46. package/dist/backend/process/queue.d.ts.map +1 -0
  47. package/dist/backend/process/queue.js +80 -0
  48. package/dist/backend/process/queue.js.map +1 -0
  49. package/dist/backend/process/registry.d.ts +25 -0
  50. package/dist/backend/process/registry.d.ts.map +1 -0
  51. package/dist/backend/process/registry.js +98 -0
  52. package/dist/backend/process/registry.js.map +1 -0
  53. package/dist/backend/process/trigger.d.ts +29 -0
  54. package/dist/backend/process/trigger.d.ts.map +1 -0
  55. package/dist/backend/process/trigger.js +108 -0
  56. package/dist/backend/process/trigger.js.map +1 -0
  57. package/dist/backend/routes/ai-config.d.ts +2 -0
  58. package/dist/backend/routes/ai-config.d.ts.map +1 -1
  59. package/dist/backend/routes/ai-config.js +2 -1
  60. package/dist/backend/routes/ai-config.js.map +1 -1
  61. package/dist/backend/routes/auth-routes.d.ts +7 -0
  62. package/dist/backend/routes/auth-routes.d.ts.map +1 -1
  63. package/dist/backend/routes/auth-routes.js +228 -1
  64. package/dist/backend/routes/auth-routes.js.map +1 -1
  65. package/dist/backend/routes/chat.d.ts +2 -0
  66. package/dist/backend/routes/chat.d.ts.map +1 -1
  67. package/dist/backend/routes/chat.js +3 -2
  68. package/dist/backend/routes/chat.js.map +1 -1
  69. package/dist/backend/routes/data.d.ts +2 -1
  70. package/dist/backend/routes/data.d.ts.map +1 -1
  71. package/dist/backend/routes/data.js +7 -1
  72. package/dist/backend/routes/data.js.map +1 -1
  73. package/dist/backend/routes/event-routes.d.ts +15 -0
  74. package/dist/backend/routes/event-routes.d.ts.map +1 -0
  75. package/dist/backend/routes/event-routes.js +35 -0
  76. package/dist/backend/routes/event-routes.js.map +1 -0
  77. package/dist/backend/routes/index.d.ts +4 -0
  78. package/dist/backend/routes/index.d.ts.map +1 -1
  79. package/dist/backend/routes/index.js +2 -0
  80. package/dist/backend/routes/index.js.map +1 -1
  81. package/dist/backend/routes/process-routes.d.ts +15 -0
  82. package/dist/backend/routes/process-routes.d.ts.map +1 -0
  83. package/dist/backend/routes/process-routes.js +237 -0
  84. package/dist/backend/routes/process-routes.js.map +1 -0
  85. package/dist/backend/routes/session-routes.d.ts +2 -0
  86. package/dist/backend/routes/session-routes.d.ts.map +1 -1
  87. package/dist/backend/routes/session-routes.js +4 -1
  88. package/dist/backend/routes/session-routes.js.map +1 -1
  89. package/dist/backend/routes/skills.d.ts +2 -0
  90. package/dist/backend/routes/skills.d.ts.map +1 -1
  91. package/dist/backend/routes/skills.js +4 -0
  92. package/dist/backend/routes/skills.js.map +1 -1
  93. package/dist/cli/commands/auth.d.ts.map +1 -1
  94. package/dist/cli/commands/auth.js +30 -22
  95. package/dist/cli/commands/auth.js.map +1 -1
  96. package/dist/cli/commands/data.d.ts.map +1 -1
  97. package/dist/cli/commands/data.js +47 -44
  98. package/dist/cli/commands/data.js.map +1 -1
  99. package/dist/cli/commands/generate-system-settings.d.ts +3 -2
  100. package/dist/cli/commands/generate-system-settings.d.ts.map +1 -1
  101. package/dist/cli/commands/generate-system-settings.js +50 -7
  102. package/dist/cli/commands/generate-system-settings.js.map +1 -1
  103. package/dist/cli/commands/init.js +1 -1
  104. package/dist/cli/commands/init.js.map +1 -1
  105. package/dist/cli/commands/process.d.ts +8 -0
  106. package/dist/cli/commands/process.d.ts.map +1 -0
  107. package/dist/cli/commands/process.js +444 -0
  108. package/dist/cli/commands/process.js.map +1 -0
  109. package/dist/cli/commands/role.d.ts +5 -2
  110. package/dist/cli/commands/role.d.ts.map +1 -1
  111. package/dist/cli/commands/role.js +145 -18
  112. package/dist/cli/commands/role.js.map +1 -1
  113. package/dist/cli/commands/user-cmd.d.ts.map +1 -1
  114. package/dist/cli/commands/user-cmd.js +41 -50
  115. package/dist/cli/commands/user-cmd.js.map +1 -1
  116. package/dist/cli/generators/capability-generator.d.ts.map +1 -1
  117. package/dist/cli/generators/capability-generator.js +131 -5
  118. package/dist/cli/generators/capability-generator.js.map +1 -1
  119. package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
  120. package/dist/cli/helpers/app-tsx-wiring.js +21 -14
  121. package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
  122. package/dist/cli/helpers/auth-client.d.ts +19 -0
  123. package/dist/cli/helpers/auth-client.d.ts.map +1 -0
  124. package/dist/cli/helpers/auth-client.js +42 -0
  125. package/dist/cli/helpers/auth-client.js.map +1 -0
  126. package/dist/cli/index.d.ts.map +1 -1
  127. package/dist/cli/index.js +2 -0
  128. package/dist/cli/index.js.map +1 -1
  129. package/dist/cli/templates/index.d.ts +1 -0
  130. package/dist/cli/templates/index.d.ts.map +1 -1
  131. package/dist/cli/templates/index.js +1 -0
  132. package/dist/cli/templates/index.js.map +1 -1
  133. package/dist/cli/templates/process-management-page.d.ts +8 -0
  134. package/dist/cli/templates/process-management-page.d.ts.map +1 -0
  135. package/dist/cli/templates/process-management-page.js +824 -0
  136. package/dist/cli/templates/process-management-page.js.map +1 -0
  137. package/dist/cli/templates/user-management-page.d.ts +2 -1
  138. package/dist/cli/templates/user-management-page.d.ts.map +1 -1
  139. package/dist/cli/templates/user-management-page.js +321 -62
  140. package/dist/cli/templates/user-management-page.js.map +1 -1
  141. package/dist/config.d.ts +371 -35
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +31 -2
  144. package/dist/config.js.map +1 -1
  145. package/dist/index.d.ts +5 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +5 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/types/auth.d.ts +0 -2
  150. package/dist/types/auth.d.ts.map +1 -1
  151. package/dist/types/config.d.ts +4 -0
  152. package/dist/types/config.d.ts.map +1 -1
  153. package/dist/types/event.d.ts +87 -0
  154. package/dist/types/event.d.ts.map +1 -0
  155. package/dist/types/event.js +38 -0
  156. package/dist/types/event.js.map +1 -0
  157. package/dist/types/index.d.ts +3 -0
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/types/index.js +1 -0
  160. package/dist/types/index.js.map +1 -1
  161. package/dist/types/process.d.ts +106 -0
  162. package/dist/types/process.d.ts.map +1 -0
  163. package/dist/types/process.js +5 -0
  164. package/dist/types/process.js.map +1 -0
  165. 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