work-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +234 -0
  2. package/app/(admin)/approvals/page.tsx +16 -0
  3. package/app/(admin)/audit/page.tsx +18 -0
  4. package/app/(admin)/layout.tsx +47 -0
  5. package/app/(admin)/scheduled-tasks/page.tsx +17 -0
  6. package/app/(admin)/settings/page.tsx +46 -0
  7. package/app/(admin)/skills/[name]/page.tsx +378 -0
  8. package/app/(admin)/skills/page.tsx +406 -0
  9. package/app/(admin)/statistics/page.tsx +416 -0
  10. package/app/(admin)/tickets/[id]/page.tsx +348 -0
  11. package/app/(admin)/tickets/new/page.tsx +309 -0
  12. package/app/(admin)/tickets/page.tsx +27 -0
  13. package/app/api/audit/route.ts +30 -0
  14. package/app/api/auth/feishu/callback/route.ts +72 -0
  15. package/app/api/auth/feishu/login/route.ts +17 -0
  16. package/app/api/auth/feishu/sso/route.ts +78 -0
  17. package/app/api/auth/login/route.ts +85 -0
  18. package/app/api/auth/oauth/route.ts +168 -0
  19. package/app/api/config/providers/route.ts +105 -0
  20. package/app/api/config/route.ts +115 -0
  21. package/app/api/config/status/route.ts +56 -0
  22. package/app/api/config/test/route.ts +212 -0
  23. package/app/api/documents/[id]/route.ts +88 -0
  24. package/app/api/documents/route.ts +53 -0
  25. package/app/api/health/route.ts +32 -0
  26. package/app/api/knowledge/[id]/route.ts +152 -0
  27. package/app/api/knowledge/from-session/route.ts +27 -0
  28. package/app/api/knowledge/route.ts +100 -0
  29. package/app/api/market/knowledge/[id]/route.ts +92 -0
  30. package/app/api/market/knowledge/route.ts +130 -0
  31. package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
  32. package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
  33. package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
  34. package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
  35. package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
  36. package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
  37. package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
  38. package/app/api/marketplace/skills/[id]/route.ts +177 -0
  39. package/app/api/marketplace/skills/route.ts +235 -0
  40. package/app/api/memory/route.ts +40 -0
  41. package/app/api/my/files/[id]/route.ts +52 -0
  42. package/app/api/my/files/route.ts +230 -0
  43. package/app/api/my/knowledge/route.ts +36 -0
  44. package/app/api/pi-chat/route.ts +443 -0
  45. package/app/api/recommend/route.ts +38 -0
  46. package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
  47. package/app/api/scheduled-tasks/[id]/route.ts +165 -0
  48. package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
  49. package/app/api/scheduled-tasks/route.ts +101 -0
  50. package/app/api/sessions/[id]/messages/route.ts +212 -0
  51. package/app/api/sessions/route.ts +101 -0
  52. package/app/api/share/file/[id]/route.ts +37 -0
  53. package/app/api/skills/[name]/execute/route.ts +121 -0
  54. package/app/api/skills/[name]/route.ts +167 -0
  55. package/app/api/skills/create/route.ts +65 -0
  56. package/app/api/skills/generate/route.ts +405 -0
  57. package/app/api/skills/installed/route.ts +151 -0
  58. package/app/api/skills/route.ts +174 -0
  59. package/app/api/skills/translate/route.ts +40 -0
  60. package/app/api/skills/user/[name]/route.ts +159 -0
  61. package/app/api/skills/user/route.ts +90 -0
  62. package/app/api/statistics/route.ts +94 -0
  63. package/app/api/task-executions/[id]/route.ts +34 -0
  64. package/app/api/task-executions/route.ts +29 -0
  65. package/app/api/tickets/[id]/approve/route.ts +129 -0
  66. package/app/api/tickets/[id]/execute/route.ts +201 -0
  67. package/app/api/tickets/[id]/route.ts +127 -0
  68. package/app/api/tickets/route.ts +103 -0
  69. package/app/api/user/skills/route.ts +175 -0
  70. package/app/api/users/route.ts +80 -0
  71. package/app/chat/page.tsx +5 -0
  72. package/app/globals.css +84 -0
  73. package/app/h5/layout.tsx +5 -0
  74. package/app/h5/mobile-approvals-page.tsx +167 -0
  75. package/app/h5/mobile-chat-page.tsx +951 -0
  76. package/app/h5/mobile-profile-page.tsx +147 -0
  77. package/app/h5/mobile-tickets-page.tsx +121 -0
  78. package/app/h5/page.tsx +23 -0
  79. package/app/h5/ticket-action-buttons.tsx +80 -0
  80. package/app/layout.tsx +26 -0
  81. package/app/login/page.tsx +318 -0
  82. package/app/market/knowledge/[id]/page.tsx +77 -0
  83. package/app/market/knowledge/page.tsx +358 -0
  84. package/app/market/layout.tsx +29 -0
  85. package/app/market/page.tsx +18 -0
  86. package/app/market/skills/page.tsx +397 -0
  87. package/app/my/files/page.tsx +511 -0
  88. package/app/my/knowledge/[id]/page.tsx +271 -0
  89. package/app/my/knowledge/new/page.tsx +234 -0
  90. package/app/my/knowledge/page.tsx +248 -0
  91. package/app/my/layout.tsx +32 -0
  92. package/app/my/memory/page.tsx +164 -0
  93. package/app/my/page.tsx +18 -0
  94. package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
  95. package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
  96. package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
  97. package/app/my/scheduled-tasks/new/page.tsx +230 -0
  98. package/app/my/scheduled-tasks/page.tsx +27 -0
  99. package/app/my/skills/[name]/page.tsx +320 -0
  100. package/app/my/skills/new/page.tsx +394 -0
  101. package/app/my/skills/page.tsx +303 -0
  102. package/app/page.tsx +2288 -0
  103. package/app/share/[sessionId]/page.tsx +226 -0
  104. package/app/share/file/[id]/page.tsx +140 -0
  105. package/bin/README.md +63 -0
  106. package/bin/generate-api-system +300 -0
  107. package/bin/postinstall.js +95 -0
  108. package/bin/work-agent.js +173 -0
  109. package/components/ai-elements/agent.tsx +142 -0
  110. package/components/ai-elements/artifact.tsx +149 -0
  111. package/components/ai-elements/attachments.tsx +427 -0
  112. package/components/ai-elements/audio-player.tsx +232 -0
  113. package/components/ai-elements/canvas.tsx +26 -0
  114. package/components/ai-elements/chain-of-thought.tsx +223 -0
  115. package/components/ai-elements/checkpoint.tsx +72 -0
  116. package/components/ai-elements/code-block.tsx +555 -0
  117. package/components/ai-elements/commit.tsx +449 -0
  118. package/components/ai-elements/confirmation.tsx +173 -0
  119. package/components/ai-elements/connection.tsx +28 -0
  120. package/components/ai-elements/context.tsx +410 -0
  121. package/components/ai-elements/controls.tsx +19 -0
  122. package/components/ai-elements/conversation.tsx +167 -0
  123. package/components/ai-elements/edge.tsx +144 -0
  124. package/components/ai-elements/environment-variables.tsx +325 -0
  125. package/components/ai-elements/file-tree.tsx +298 -0
  126. package/components/ai-elements/image.tsx +25 -0
  127. package/components/ai-elements/inline-citation.tsx +294 -0
  128. package/components/ai-elements/jsx-preview.tsx +250 -0
  129. package/components/ai-elements/message.tsx +367 -0
  130. package/components/ai-elements/mic-selector.tsx +372 -0
  131. package/components/ai-elements/model-selector.tsx +214 -0
  132. package/components/ai-elements/node.tsx +72 -0
  133. package/components/ai-elements/open-in-chat.tsx +367 -0
  134. package/components/ai-elements/package-info.tsx +235 -0
  135. package/components/ai-elements/panel.tsx +16 -0
  136. package/components/ai-elements/persona.tsx +280 -0
  137. package/components/ai-elements/plan.tsx +144 -0
  138. package/components/ai-elements/prompt-input.tsx +1341 -0
  139. package/components/ai-elements/queue.tsx +275 -0
  140. package/components/ai-elements/reasoning.tsx +355 -0
  141. package/components/ai-elements/sandbox.tsx +133 -0
  142. package/components/ai-elements/schema-display.tsx +473 -0
  143. package/components/ai-elements/shimmer.tsx +78 -0
  144. package/components/ai-elements/snippet.tsx +141 -0
  145. package/components/ai-elements/sources.tsx +78 -0
  146. package/components/ai-elements/speech-input.tsx +324 -0
  147. package/components/ai-elements/stack-trace.tsx +531 -0
  148. package/components/ai-elements/suggestion.tsx +58 -0
  149. package/components/ai-elements/task.tsx +88 -0
  150. package/components/ai-elements/terminal.tsx +277 -0
  151. package/components/ai-elements/test-results.tsx +497 -0
  152. package/components/ai-elements/tool.tsx +174 -0
  153. package/components/ai-elements/toolbar.tsx +17 -0
  154. package/components/ai-elements/transcription.tsx +126 -0
  155. package/components/ai-elements/voice-selector.tsx +525 -0
  156. package/components/ai-elements/web-preview.tsx +282 -0
  157. package/components/audit-log-list.tsx +114 -0
  158. package/components/chat/EmptyPreviewState.tsx +12 -0
  159. package/components/chat/KnowledgePickerDialog.tsx +464 -0
  160. package/components/chat/KnowledgePreview.tsx +70 -0
  161. package/components/chat/KnowledgePreviewPanel.tsx +86 -0
  162. package/components/chat/MentionInput.tsx +309 -0
  163. package/components/chat/OrganizeDialog.tsx +258 -0
  164. package/components/chat/RecommendationBanner.tsx +94 -0
  165. package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
  166. package/components/chat/SkillSelector.tsx +305 -0
  167. package/components/chat/SkillSwitcher.tsx +163 -0
  168. package/components/client-layout.tsx +15 -0
  169. package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
  170. package/components/layout-wrapper.tsx +18 -0
  171. package/components/mobile-layout.tsx +62 -0
  172. package/components/scheduled-task-list.tsx +356 -0
  173. package/components/setup-guide.tsx +484 -0
  174. package/components/sub-nav.tsx +54 -0
  175. package/components/ticket-detail-content.tsx +383 -0
  176. package/components/ticket-list.tsx +366 -0
  177. package/components/top-nav.tsx +132 -0
  178. package/components/ui/accordion.tsx +58 -0
  179. package/components/ui/alert.tsx +59 -0
  180. package/components/ui/avatar.tsx +50 -0
  181. package/components/ui/badge.tsx +36 -0
  182. package/components/ui/button-group.tsx +83 -0
  183. package/components/ui/button.tsx +57 -0
  184. package/components/ui/card.tsx +91 -0
  185. package/components/ui/carousel.tsx +262 -0
  186. package/components/ui/collapsible.tsx +11 -0
  187. package/components/ui/command.tsx +153 -0
  188. package/components/ui/dialog.tsx +122 -0
  189. package/components/ui/dropdown-menu.tsx +200 -0
  190. package/components/ui/hover-card.tsx +29 -0
  191. package/components/ui/input-group.tsx +170 -0
  192. package/components/ui/input.tsx +22 -0
  193. package/components/ui/label.tsx +26 -0
  194. package/components/ui/popover.tsx +31 -0
  195. package/components/ui/progress.tsx +28 -0
  196. package/components/ui/scroll-area.tsx +48 -0
  197. package/components/ui/select.tsx +174 -0
  198. package/components/ui/separator.tsx +31 -0
  199. package/components/ui/spinner.tsx +16 -0
  200. package/components/ui/switch.tsx +29 -0
  201. package/components/ui/table.tsx +120 -0
  202. package/components/ui/tabs.tsx +55 -0
  203. package/components/ui/textarea.tsx +22 -0
  204. package/components/ui/tooltip.tsx +30 -0
  205. package/components/welcome-guide.tsx +182 -0
  206. package/components.json +24 -0
  207. package/lib/command-parser.ts +331 -0
  208. package/lib/dangerous-commands.ts +672 -0
  209. package/lib/db.ts +2250 -0
  210. package/lib/feishu-auth.ts +135 -0
  211. package/lib/file-storage.ts +306 -0
  212. package/lib/file-tool.ts +583 -0
  213. package/lib/knowledge-tool.ts +152 -0
  214. package/lib/knowledge-types.ts +66 -0
  215. package/lib/market-client.ts +313 -0
  216. package/lib/market-db.ts +736 -0
  217. package/lib/market-types.ts +51 -0
  218. package/lib/memory-tool.ts +211 -0
  219. package/lib/memory.ts +197 -0
  220. package/lib/pi-config.ts +436 -0
  221. package/lib/pi-session.ts +799 -0
  222. package/lib/pinyin.ts +13 -0
  223. package/lib/recommendation.ts +227 -0
  224. package/lib/risk-estimator.ts +350 -0
  225. package/lib/scheduled-task-tool.ts +184 -0
  226. package/lib/scheduler-init.ts +43 -0
  227. package/lib/scheduler.ts +416 -0
  228. package/lib/secure-bash-tool.ts +413 -0
  229. package/lib/skill-engine.ts +396 -0
  230. package/lib/skill-generator.ts +269 -0
  231. package/lib/skill-loader.ts +234 -0
  232. package/lib/skill-tool.ts +188 -0
  233. package/lib/skill-types.ts +82 -0
  234. package/lib/skills-init.ts +58 -0
  235. package/lib/ticket-tool.ts +246 -0
  236. package/lib/user-skill-types.ts +30 -0
  237. package/lib/user-skills.ts +362 -0
  238. package/lib/utils.ts +6 -0
  239. package/lib/workflow.ts +154 -0
  240. package/lib/zip-tool.ts +191 -0
  241. package/next.config.js +8 -0
  242. package/package.json +106 -0
  243. package/public/.gitkeep +1 -0
  244. package/public/icon.svg +1 -0
  245. package/tsconfig.json +42 -0
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 定时任务详情 API - GET/PUT/DELETE
3
+ */
4
+
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+ import { getScheduledTask, updateScheduledTask, deleteScheduledTask, createAuditLog } from '@/lib/db';
7
+ import { getNextRunTime, isValidCronExpression } from '@/lib/scheduler';
8
+
9
+ type RouteContext = {
10
+ params: Promise<{ id: string }>;
11
+ };
12
+
13
+ /**
14
+ * GET /api/scheduled-tasks/[id] - 获取单个定时任务
15
+ */
16
+ export async function GET(request: NextRequest, context: RouteContext) {
17
+ try {
18
+ const { id } = await context.params;
19
+ const task = await getScheduledTask(id);
20
+
21
+ if (!task) {
22
+ return NextResponse.json(
23
+ { error: '定时任务不存在' },
24
+ { status: 404 },
25
+ );
26
+ }
27
+
28
+ return NextResponse.json({ task });
29
+ } catch (error) {
30
+ return NextResponse.json(
31
+ { error: error instanceof Error ? error.message : '获取定时任务失败' },
32
+ { status: 500 },
33
+ );
34
+ }
35
+ }
36
+
37
+ /**
38
+ * PUT /api/scheduled-tasks/[id] - 更新定时任务
39
+ */
40
+ export async function PUT(request: NextRequest, context: RouteContext) {
41
+ try {
42
+ const { id } = await context.params;
43
+ const body = await request.json();
44
+ const {
45
+ title,
46
+ description,
47
+ command,
48
+ status,
49
+ scheduleType,
50
+ scheduleExpression,
51
+ updatedBy,
52
+ } = body;
53
+
54
+ const current = await getScheduledTask(id);
55
+ if (!current) {
56
+ return NextResponse.json(
57
+ { error: '定时任务不存在' },
58
+ { status: 404 },
59
+ );
60
+ }
61
+
62
+ // 验证必填字段
63
+ if (!title || !command || !scheduleType || !scheduleExpression) {
64
+ return NextResponse.json(
65
+ { error: '缺少必填字段: title, command, scheduleType, scheduleExpression' },
66
+ { status: 400 },
67
+ );
68
+ }
69
+
70
+ const validScheduleTypes = ['cron', 'interval', 'once'];
71
+ if (!validScheduleTypes.includes(scheduleType)) {
72
+ return NextResponse.json(
73
+ { error: `无效的调度类型,必须是: ${validScheduleTypes.join(', ')}` },
74
+ { status: 400 },
75
+ );
76
+ }
77
+
78
+ const validStatuses = ['active', 'paused', 'disabled'];
79
+ if (status && !validStatuses.includes(status)) {
80
+ return NextResponse.json(
81
+ { error: `无效的状态,必须是: ${validStatuses.join(', ')}` },
82
+ { status: 400 },
83
+ );
84
+ }
85
+
86
+ // 验证 cron 表达式
87
+ if (scheduleType === 'cron' && !isValidCronExpression(scheduleExpression)) {
88
+ return NextResponse.json(
89
+ { error: '无效的 cron 表达式' },
90
+ { status: 400 },
91
+ );
92
+ }
93
+
94
+ // 如果调度表达式改变了,重新计算 nextRunAt
95
+ let nextRunAt = current.nextRunAt;
96
+ if (scheduleExpression !== current.scheduleExpression && scheduleType === 'cron') {
97
+ const calculated = getNextRunTime(scheduleExpression);
98
+ nextRunAt = calculated || undefined;
99
+ }
100
+
101
+ const updates: any = {
102
+ title,
103
+ description: description ?? '',
104
+ command,
105
+ scheduleType,
106
+ scheduleExpression,
107
+ nextRunAt,
108
+ };
109
+
110
+ if (status) {
111
+ updates.status = status;
112
+ }
113
+
114
+ const task = await updateScheduledTask(id, updates);
115
+
116
+ await createAuditLog({
117
+ action: 'scheduled_task_updated',
118
+ userId: updatedBy || 'system',
119
+ details: { taskId: id, updates },
120
+ status: 'success',
121
+ });
122
+
123
+ return NextResponse.json({ task });
124
+ } catch (error) {
125
+ return NextResponse.json(
126
+ { error: error instanceof Error ? error.message : '更新定时任务失败' },
127
+ { status: 500 },
128
+ );
129
+ }
130
+ }
131
+
132
+ /**
133
+ * DELETE /api/scheduled-tasks/[id] - 删除定时任务
134
+ */
135
+ export async function DELETE(request: NextRequest, context: RouteContext) {
136
+ try {
137
+ const { id } = await context.params;
138
+ const { searchParams } = new URL(request.url);
139
+ const deletedBy = searchParams.get('deletedBy') || 'system';
140
+
141
+ const current = await getScheduledTask(id);
142
+ if (!current) {
143
+ return NextResponse.json(
144
+ { error: '定时任务不存在' },
145
+ { status: 404 },
146
+ );
147
+ }
148
+
149
+ await deleteScheduledTask(id);
150
+
151
+ await createAuditLog({
152
+ action: 'scheduled_task_deleted',
153
+ userId: deletedBy,
154
+ details: { taskId: id, title: current.title },
155
+ status: 'success',
156
+ });
157
+
158
+ return NextResponse.json({ success: true });
159
+ } catch (error) {
160
+ return NextResponse.json(
161
+ { error: error instanceof Error ? error.message : '删除定时任务失败' },
162
+ { status: 500 },
163
+ );
164
+ }
165
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * 定时任务启用/禁用 API - POST
3
+ */
4
+
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+ import { getScheduledTask, updateScheduledTask, createAuditLog } from '@/lib/db';
7
+
8
+ type RouteContext = {
9
+ params: Promise<{ id: string }>;
10
+ };
11
+
12
+ /**
13
+ * POST /api/scheduled-tasks/[id]/toggle - 启用/禁用定时任务
14
+ */
15
+ export async function POST(request: NextRequest, context: RouteContext) {
16
+ try {
17
+ const { id } = await context.params;
18
+ const body = await request.json();
19
+ const { userId } = body;
20
+
21
+ const current = await getScheduledTask(id);
22
+ if (!current) {
23
+ return NextResponse.json(
24
+ { error: '定时任务不存在' },
25
+ { status: 404 },
26
+ );
27
+ }
28
+
29
+ // 切换状态:active <-> paused
30
+ const newStatus = current.status === 'active' ? 'paused' : 'active';
31
+
32
+ const task = await updateScheduledTask(id, { status: newStatus });
33
+
34
+ await createAuditLog({
35
+ action: 'scheduled_task_toggled',
36
+ userId: userId || 'system',
37
+ details: {
38
+ taskId: id,
39
+ title: current.title,
40
+ oldStatus: current.status,
41
+ newStatus,
42
+ },
43
+ status: 'success',
44
+ });
45
+
46
+ return NextResponse.json({ task });
47
+ } catch (error) {
48
+ return NextResponse.json(
49
+ { error: error instanceof Error ? error.message : '切换定时任务状态失败' },
50
+ { status: 500 },
51
+ );
52
+ }
53
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * 定时任务 API - GET/POST
3
+ */
4
+
5
+ import { NextRequest, NextResponse } from 'next/server';
6
+ import { getScheduledTasks, createScheduledTask, createAuditLog } from '@/lib/db';
7
+ import { getNextRunTime, isValidCronExpression } from '@/lib/scheduler';
8
+
9
+ /**
10
+ * GET /api/scheduled-tasks - 获取定时任务列表
11
+ */
12
+ export async function GET(request: NextRequest) {
13
+ try {
14
+ const { searchParams } = new URL(request.url);
15
+ const filters = {
16
+ status: searchParams.get('status') ?? undefined,
17
+ createdBy: searchParams.get('createdBy') ?? undefined,
18
+ limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined,
19
+ };
20
+
21
+ const tasks = await getScheduledTasks(filters);
22
+
23
+ return NextResponse.json({ tasks });
24
+ } catch (error) {
25
+ return NextResponse.json(
26
+ { error: error instanceof Error ? error.message : '获取定时任务失败' },
27
+ { status: 500 },
28
+ );
29
+ }
30
+ }
31
+
32
+ /**
33
+ * POST /api/scheduled-tasks - 创建新定时任务
34
+ */
35
+ export async function POST(request: NextRequest) {
36
+ try {
37
+ const body = await request.json();
38
+ const {
39
+ title,
40
+ description,
41
+ command,
42
+ scheduleType,
43
+ scheduleExpression,
44
+ createdBy,
45
+ } = body;
46
+
47
+ // 验证必填字段
48
+ if (!title || !command || !scheduleType || !scheduleExpression || !createdBy) {
49
+ return NextResponse.json(
50
+ { error: '缺少必填字段: title, command, scheduleType, scheduleExpression, createdBy' },
51
+ { status: 400 },
52
+ );
53
+ }
54
+
55
+ const validScheduleTypes = ['cron', 'interval', 'once'];
56
+ if (!validScheduleTypes.includes(scheduleType)) {
57
+ return NextResponse.json(
58
+ { error: `无效的调度类型,必须是: ${validScheduleTypes.join(', ')}` },
59
+ { status: 400 },
60
+ );
61
+ }
62
+
63
+ // 验证 cron 表达式
64
+ if (scheduleType === 'cron' && !isValidCronExpression(scheduleExpression)) {
65
+ return NextResponse.json(
66
+ { error: '无效的 cron 表达式' },
67
+ { status: 400 },
68
+ );
69
+ }
70
+
71
+ // 计算 nextRunAt
72
+ const nextRunAt = scheduleType === 'cron' ? getNextRunTime(scheduleExpression) : undefined;
73
+
74
+ const taskData = {
75
+ title,
76
+ description: description ?? '',
77
+ command,
78
+ status: 'active' as const,
79
+ scheduleType,
80
+ scheduleExpression,
81
+ nextRunAt: nextRunAt || undefined,
82
+ createdBy,
83
+ };
84
+
85
+ const task = await createScheduledTask(taskData);
86
+
87
+ await createAuditLog({
88
+ action: 'scheduled_task_created',
89
+ userId: createdBy,
90
+ details: { taskId: task.id, title, command, scheduleExpression },
91
+ status: 'success',
92
+ });
93
+
94
+ return NextResponse.json({ task }, { status: 201 });
95
+ } catch (error) {
96
+ return NextResponse.json(
97
+ { error: error instanceof Error ? error.message : '创建定时任务失败' },
98
+ { status: 500 },
99
+ );
100
+ }
101
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Session Messages API - GET
3
+ *
4
+ * Returns message history for a session from the JSONL file
5
+ * Includes chain-of-thought data (thinking and tool executions)
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ const PI_SESSIONS_DIR = join(process.cwd(), 'data', 'pi-sessions');
13
+
14
+ interface ToolExecution {
15
+ toolName: string;
16
+ startTime: Date;
17
+ endTime?: Date;
18
+ input?: any;
19
+ output?: any;
20
+ isError?: boolean;
21
+ }
22
+
23
+ interface MessageChainOfThought {
24
+ thinking: string;
25
+ toolExecutions: ToolExecution[];
26
+ }
27
+
28
+ interface SessionMessageWithCoT {
29
+ id: string;
30
+ role: 'user' | 'assistant' | 'system';
31
+ content: string;
32
+ timestamp: string;
33
+ chainOfThought?: MessageChainOfThought;
34
+ }
35
+
36
+ interface ToolCall {
37
+ id: string;
38
+ name: string;
39
+ arguments: {
40
+ command?: string;
41
+ [key: string]: any;
42
+ };
43
+ }
44
+
45
+ interface ContentItem {
46
+ type: 'text' | 'toolCall' | 'toolResult';
47
+ text?: string;
48
+ id?: string;
49
+ name?: string;
50
+ arguments?: any;
51
+ toolCallId?: string;
52
+ isError?: boolean;
53
+ content?: ContentItem[];
54
+ }
55
+
56
+ /**
57
+ * GET /api/sessions/[id]/messages - Get session messages with chain-of-thought
58
+ */
59
+ export async function GET(
60
+ request: NextRequest,
61
+ { params }: { params: Promise<{ id: string }> }
62
+ ) {
63
+ try {
64
+ const { id: sessionId } = await params;
65
+
66
+ const sessionFile = join(PI_SESSIONS_DIR, `${sessionId}.jsonl`);
67
+
68
+ if (!existsSync(sessionFile)) {
69
+ return NextResponse.json({ messages: [] });
70
+ }
71
+
72
+ const content = readFileSync(sessionFile, 'utf-8');
73
+ const lines = content.split('\n').filter(line => line.trim());
74
+ const messages: SessionMessageWithCoT[] = [];
75
+
76
+ // Track chain-of-thought data for assistant messages
77
+ let currentThinking = '';
78
+ let currentToolCalls: ToolCall[] = [];
79
+ let currentToolStartTime: Date | null = null;
80
+ let pendingAssistantMessage: SessionMessageWithCoT | null = null;
81
+
82
+ // Track tool executions by toolCallId to link with results later
83
+ const pendingToolExecutions = new Map<string, ToolExecution>();
84
+
85
+ for (const line of lines) {
86
+ try {
87
+ const data = JSON.parse(line);
88
+
89
+ switch (data.type) {
90
+ case 'message': {
91
+ const msg = data.message;
92
+ if (!msg) break;
93
+
94
+ // Handle toolResult messages
95
+ if (msg.role === 'toolResult') {
96
+ const toolCallId = msg.toolCallId;
97
+ if (toolCallId && pendingToolExecutions.has(toolCallId)) {
98
+ const toolExecution = pendingToolExecutions.get(toolCallId)!;
99
+ // Extract output from content
100
+ const textItems = msg.content?.filter((c: ContentItem) => c.type === 'text') || [];
101
+ const output = textItems.map((c: ContentItem) => c.text || '').join('');
102
+
103
+ toolExecution.output = output;
104
+ toolExecution.endTime = new Date(msg.timestamp || Date.now());
105
+ toolExecution.isError = msg.isError;
106
+ }
107
+ break;
108
+ }
109
+
110
+ // Finalize previous assistant message before starting a new one
111
+ if (pendingAssistantMessage && (msg.role === 'user' || msg.role === 'assistant')) {
112
+ // Add chainOfThought to the pending assistant message
113
+ if (pendingToolExecutions.size > 0) {
114
+ pendingAssistantMessage.chainOfThought = {
115
+ thinking: currentThinking,
116
+ toolExecutions: Array.from(pendingToolExecutions.values())
117
+ };
118
+ }
119
+ messages.push(pendingAssistantMessage);
120
+ pendingAssistantMessage = null;
121
+ }
122
+
123
+ // Reset for new assistant message
124
+ if (msg.role === 'assistant') {
125
+ currentThinking = '';
126
+ currentToolCalls = [];
127
+ currentToolStartTime = new Date(data.timestamp || Date.now());
128
+ pendingToolExecutions.clear();
129
+ }
130
+
131
+ if (msg.content && Array.isArray(msg.content)) {
132
+ // Extract text content
133
+ const textItems = msg.content.filter((c: ContentItem) => c.type === 'text');
134
+ const text = textItems.map((c: ContentItem) => c.text || '').join('');
135
+
136
+ // Extract tool calls from assistant messages
137
+ const toolCalls = msg.content.filter((c: ContentItem) => c.type === 'toolCall');
138
+ if (toolCalls.length > 0) {
139
+ for (const tc of toolCalls) {
140
+ const toolCallId = tc.id || '';
141
+ const toolExecution: ToolExecution = {
142
+ toolName: tc.name || 'unknown',
143
+ startTime: new Date(data.timestamp || Date.now()),
144
+ input: tc.arguments || {}
145
+ };
146
+ pendingToolExecutions.set(toolCallId, toolExecution);
147
+ currentToolCalls.push({
148
+ id: toolCallId,
149
+ name: tc.name || 'unknown',
150
+ arguments: tc.arguments || {}
151
+ });
152
+ }
153
+ }
154
+
155
+ // Only create messages for user and assistant roles
156
+ if (msg.role === 'user' || msg.role === 'assistant') {
157
+ const messageIndex = messages.filter(m => m.role === 'user' || m.role === 'assistant').length;
158
+ const messageId = `${sessionId}-${messageIndex}`;
159
+
160
+ if (msg.role === 'user') {
161
+ // User messages are added immediately
162
+ messages.push({
163
+ id: messageId,
164
+ role: msg.role,
165
+ content: text,
166
+ timestamp: data.timestamp || new Date().toISOString()
167
+ });
168
+ } else {
169
+ // Assistant messages are held pending until tool results are processed
170
+ pendingAssistantMessage = {
171
+ id: messageId,
172
+ role: msg.role,
173
+ content: text,
174
+ timestamp: data.timestamp || new Date().toISOString()
175
+ };
176
+ }
177
+ }
178
+ }
179
+ break;
180
+ }
181
+
182
+ case 'thinking':
183
+ if (data.content) {
184
+ currentThinking += data.content;
185
+ }
186
+ break;
187
+ }
188
+ } catch (e) {
189
+ // Skip parse errors
190
+ console.error('Error parsing line:', e);
191
+ }
192
+ }
193
+
194
+ // Finalize any pending assistant message at the end
195
+ if (pendingAssistantMessage) {
196
+ if (pendingToolExecutions.size > 0) {
197
+ pendingAssistantMessage.chainOfThought = {
198
+ thinking: currentThinking,
199
+ toolExecutions: Array.from(pendingToolExecutions.values())
200
+ };
201
+ }
202
+ messages.push(pendingAssistantMessage);
203
+ }
204
+
205
+ return NextResponse.json({ messages });
206
+ } catch (error) {
207
+ return NextResponse.json(
208
+ { error: error instanceof Error ? error.message : '获取会话消息失败' },
209
+ { status: 500 }
210
+ );
211
+ }
212
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Sessions API - GET, DELETE
3
+ *
4
+ * Lists all available sessions for switching, and delete sessions
5
+ */
6
+
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import { getAllSessionMetadata, cleanupSession, migrateSessionsFromFiles } from '@/lib/pi-session';
9
+ import { existsSync, unlinkSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ // PI sessions directory (same as in pi-session.ts)
13
+ const PI_SESSIONS_DIR = join(process.cwd(), 'data', 'pi-sessions');
14
+ const SESSION_DIR = join(process.cwd(), 'data', 'sessions');
15
+
16
+ // 启动时迁移历史会话数据
17
+ migrateSessionsFromFiles();
18
+
19
+ /**
20
+ * GET /api/sessions - List all sessions
21
+ */
22
+ export async function GET(request: NextRequest) {
23
+ try {
24
+ const { searchParams } = new URL(request.url);
25
+ const userId = searchParams.get('userId');
26
+
27
+ let sessions = getAllSessionMetadata();
28
+
29
+ // Filter by userId if provided
30
+ if (userId) {
31
+ sessions = sessions.filter(s => s.userId === userId);
32
+ }
33
+
34
+ return NextResponse.json({
35
+ sessions: sessions.map((s) => ({
36
+ sessionId: s.sessionId,
37
+ userId: s.userId,
38
+ createdAt: s.createdAt,
39
+ lastAccessedAt: s.lastAccessedAt,
40
+ messageCount: s.messageCount,
41
+ title: s.title,
42
+ })),
43
+ count: sessions.length,
44
+ });
45
+ } catch (error) {
46
+ return NextResponse.json(
47
+ { error: error instanceof Error ? error.message : '获取会话列表失败' },
48
+ { status: 500 },
49
+ );
50
+ }
51
+ }
52
+
53
+ /**
54
+ * DELETE /api/sessions - Delete a session
55
+ *
56
+ * Query params:
57
+ * - sessionId: The session ID to delete
58
+ */
59
+ export async function DELETE(request: NextRequest) {
60
+ try {
61
+ const { searchParams } = new URL(request.url);
62
+ const sessionId = searchParams.get('sessionId');
63
+
64
+ if (!sessionId) {
65
+ return NextResponse.json(
66
+ { error: 'sessionId is required' },
67
+ { status: 400 },
68
+ );
69
+ }
70
+
71
+ // Clean up in-memory session
72
+ cleanupSession(sessionId);
73
+
74
+ // Delete PI session file (JSONL)
75
+ const piSessionFile = join(PI_SESSIONS_DIR, `${sessionId}.jsonl`);
76
+ if (existsSync(piSessionFile)) {
77
+ try {
78
+ unlinkSync(piSessionFile);
79
+ } catch (e) {
80
+ console.error('Failed to delete PI session file:', e);
81
+ }
82
+ }
83
+
84
+ // Delete metadata file (JSON) - this is done by cleanupSession but double-check
85
+ const metadataFile = join(SESSION_DIR, `${sessionId}.json`);
86
+ if (existsSync(metadataFile)) {
87
+ try {
88
+ unlinkSync(metadataFile);
89
+ } catch (e) {
90
+ console.error('Failed to delete metadata file:', e);
91
+ }
92
+ }
93
+
94
+ return NextResponse.json({ success: true, sessionId });
95
+ } catch (error) {
96
+ return NextResponse.json(
97
+ { error: error instanceof Error ? error.message : '删除会话失败' },
98
+ { status: 500 },
99
+ );
100
+ }
101
+ }
@@ -0,0 +1,37 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getDocument } from '@/lib/db';
3
+
4
+ export async function GET(
5
+ request: NextRequest,
6
+ { params }: { params: Promise<{ id: string }> }
7
+ ) {
8
+ try {
9
+ const { id } = await params;
10
+
11
+ const document = getDocument(id);
12
+ if (!document) {
13
+ return NextResponse.json({ error: '文件未找到' }, { status: 404 });
14
+ }
15
+
16
+ if (!document.isPublic) {
17
+ return NextResponse.json({ error: '此文件未公开分享' }, { status: 403 });
18
+ }
19
+
20
+ if (document.type !== 'file') {
21
+ return NextResponse.json({ error: '不是文件类型' }, { status: 400 });
22
+ }
23
+
24
+ return NextResponse.json({
25
+ file: {
26
+ id: document.id,
27
+ title: document.title,
28
+ fileMetadata: document.fileMetadata,
29
+ isPublic: document.isPublic,
30
+ createdAt: document.createdAt.toISOString(),
31
+ },
32
+ });
33
+ } catch (error) {
34
+ console.error('[api/share/file] Error:', error);
35
+ return NextResponse.json({ error: '服务器错误' }, { status: 500 });
36
+ }
37
+ }