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,152 @@
1
+ /**
2
+ * 知识保存工具
3
+ *
4
+ * 提供 save_knowledge 工具,让 AI 可以将当前会话中的有价值内容保存到知识库。
5
+ */
6
+
7
+ import { Type } from '@sinclair/typebox';
8
+ import type { ToolDefinition, AgentToolResult, ExtensionContext } from '@mariozechner/pi-coding-agent';
9
+ import { createDocument, type KnowledgeType, type KnowledgeSourceType } from './db';
10
+
11
+ /**
12
+ * 知识保存工具的参数 Schema
13
+ */
14
+ const saveKnowledgeSchema = Type.Object({
15
+ title: Type.String({
16
+ description: '知识标题',
17
+ }),
18
+ content: Type.String({
19
+ description: '知识内容,包括问题分析、解决方案、最佳实践等',
20
+ }),
21
+ author: Type.Optional(Type.String({
22
+ description: '作者ID,默认为当前用户',
23
+ })),
24
+ category: Type.Optional(Type.String({
25
+ description: '分类,如 "运维"、"开发"、"排查" 等',
26
+ })),
27
+ tags: Type.Optional(
28
+ Type.Array(Type.String(), {
29
+ description: '标签数组,用于分类和检索',
30
+ })
31
+ ),
32
+ type: Type.Optional(Type.String({
33
+ description: '知识类型: documentation(文档), troubleshooting(问题排查), best-practice(最佳实践)',
34
+ })),
35
+ });
36
+
37
+ /**
38
+ * 知识保存工具的详情类型
39
+ */
40
+ interface SaveKnowledgeDetails {
41
+ success: boolean;
42
+ knowledge?: {
43
+ id: string;
44
+ title: string;
45
+ category?: string;
46
+ type?: string;
47
+ };
48
+ error?: string;
49
+ }
50
+
51
+ /**
52
+ * 创建知识保存工具定义
53
+ */
54
+ export const saveKnowledgeTool: ToolDefinition<typeof saveKnowledgeSchema, SaveKnowledgeDetails> = {
55
+ name: 'save_knowledge',
56
+ label: 'Save Knowledge',
57
+ description: `将当前会话中的知识保存到知识库。
58
+
59
+ 使用场景:
60
+ - 用户明确要求"记住这个"、"保存为知识"
61
+ - 讨论了有价值的问题解决方案,值得记录
62
+ - 总结了最佳实践或操作指南
63
+ - 发现了常见问题及其解决方法
64
+
65
+ 保存的知识将在未来的会话中被激活引用,帮助更好地解决类似问题。
66
+
67
+ 参数说明:
68
+ - title: 知识标题,应简洁明了
69
+ - content: 知识内容,应包含问题描述、解决方案、步骤等详细信息
70
+ - author: 作者ID,AI 应自动从会话上下文获取当前用户ID并传入
71
+ - category: 可选分类,如"运维"、"开发"、"排查"等
72
+ - tags: 可选标签数组,便于检索
73
+ - type: 知识类型,默认为 best-practice`,
74
+
75
+ parameters: saveKnowledgeSchema,
76
+
77
+ async execute(
78
+ toolCallId: string,
79
+ params: any,
80
+ signal: AbortSignal | undefined,
81
+ onUpdate: ((result: AgentToolResult<SaveKnowledgeDetails>) => void) | undefined,
82
+ ctx: ExtensionContext
83
+ ): Promise<AgentToolResult<SaveKnowledgeDetails>> {
84
+ try {
85
+ const { title, content, author, category, tags, type } = params;
86
+
87
+ // 验证类型
88
+ const validTypes: KnowledgeType[] = ['documentation', 'troubleshooting', 'best-practice', 'conversation-summary'];
89
+ const knowledgeType: KnowledgeType = validTypes.includes(type) ? type : 'best-practice';
90
+
91
+ // 创建知识条目
92
+ const doc = await createDocument({
93
+ title,
94
+ content,
95
+ category: category || '知识',
96
+ tags: tags || undefined,
97
+ author: author || 'ai-assistant',
98
+ type: knowledgeType,
99
+ sourceType: 'ai-generated' as KnowledgeSourceType,
100
+ isActive: true,
101
+ });
102
+
103
+ const result: AgentToolResult<SaveKnowledgeDetails> = {
104
+ content: [
105
+ {
106
+ type: 'text',
107
+ text: `✅ 知识已保存到知识库
108
+
109
+ **标题:** ${title}
110
+ **分类:** ${category || '知识'}
111
+ **类型:** ${knowledgeType}
112
+
113
+ 此知识已可在未来的会话中被引用。`,
114
+ },
115
+ ],
116
+ details: {
117
+ success: true,
118
+ knowledge: {
119
+ id: doc.id,
120
+ title: doc.title,
121
+ category: doc.category,
122
+ type: doc.type,
123
+ },
124
+ },
125
+ };
126
+
127
+ // 通知更新
128
+ onUpdate?.(result);
129
+
130
+ return result;
131
+ } catch (error: any) {
132
+ const errorResult: AgentToolResult<SaveKnowledgeDetails> = {
133
+ content: [
134
+ {
135
+ type: 'text',
136
+ text: `❌ 保存知识失败: ${error.message}`,
137
+ },
138
+ ],
139
+ details: {
140
+ success: false,
141
+ error: error.message,
142
+ },
143
+ };
144
+
145
+ onUpdate?.(errorResult);
146
+
147
+ return errorResult;
148
+ }
149
+ },
150
+ };
151
+
152
+ export default saveKnowledgeTool;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 知识库类型定义
3
+ *
4
+ * 定义知识库系统的核心类型和接口。
5
+ */
6
+
7
+ import type { KnowledgeType, KnowledgeSourceType } from './db';
8
+
9
+ /**
10
+ * 知识类型描述映射
11
+ */
12
+ export const KNOWLEDGE_TYPE_LABELS: Record<KnowledgeType, string> = {
13
+ 'documentation': '文档',
14
+ 'troubleshooting': '问题排查',
15
+ 'best-practice': '最佳实践',
16
+ 'conversation-summary': '会话总结',
17
+ };
18
+
19
+ /**
20
+ * 知识来源描述映射
21
+ */
22
+ export const KNOWLEDGE_SOURCE_LABELS: Record<KnowledgeSourceType, string> = {
23
+ 'manual': '手动创建',
24
+ 'ai-generated': 'AI生成',
25
+ 'conversation-summary': '会话总结',
26
+ };
27
+
28
+ /**
29
+ * 保存知识请求(用于 save_knowledge 工具)
30
+ */
31
+ export interface SaveKnowledgeRequest {
32
+ title: string;
33
+ content: string;
34
+ category?: string;
35
+ tags?: string[];
36
+ type?: KnowledgeType;
37
+ sourceType?: KnowledgeSourceType;
38
+ sessionId?: string;
39
+ }
40
+
41
+ /**
42
+ * 知识条目(用于前端展示)
43
+ */
44
+ export interface KnowledgeItem {
45
+ id: string;
46
+ title: string;
47
+ content: string;
48
+ category?: string;
49
+ tags?: string[];
50
+ author: string;
51
+ createdAt: string;
52
+ updatedAt: string;
53
+ type?: KnowledgeType;
54
+ sourceType?: KnowledgeSourceType;
55
+ sessionId?: string;
56
+ isActive: boolean;
57
+ }
58
+
59
+ /**
60
+ * 从会话创建知识的请求
61
+ */
62
+ export interface CreateFromSessionRequest {
63
+ sessionId: string;
64
+ title?: string;
65
+ summary: string;
66
+ }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Market Client for AI Assistant
3
+ *
4
+ * This client manages market operations with two modes:
5
+ * - Local mode (no MARKET_SERVICE_URL): Use local SQLite database
6
+ * - Remote mode (MARKET_SERVICE_URL configured): Use remote market service API
7
+ *
8
+ * In remote mode, failures will throw errors instead of falling back to local
9
+ * to prevent data inconsistency.
10
+ */
11
+
12
+ const MARKET_SERVICE_URL = process.env.MARKET_SERVICE_URL;
13
+ const API_KEY = process.env.MARKET_API_KEY || 'market-dev-key-1';
14
+ const TIMEOUT = 5000; // 5 seconds
15
+
16
+ export class MarketClient {
17
+ private mode: 'local' | 'remote';
18
+ private baseUrl: string | null = null;
19
+ private apiKey: string | null = null;
20
+
21
+ constructor() {
22
+ if (MARKET_SERVICE_URL && API_KEY) {
23
+ this.mode = 'remote';
24
+ this.baseUrl = MARKET_SERVICE_URL;
25
+ this.apiKey = API_KEY;
26
+ console.log('[MarketClient] Remote mode enabled:', this.baseUrl);
27
+ } else {
28
+ this.mode = 'local';
29
+ console.log('[MarketClient] Local mode (no remote configured)');
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Check if running in remote mode
35
+ */
36
+ isRemoteMode(): boolean {
37
+ return this.mode === 'remote';
38
+ }
39
+
40
+ /**
41
+ * Check if running in local mode
42
+ */
43
+ isLocalMode(): boolean {
44
+ return this.mode === 'local';
45
+ }
46
+
47
+ /**
48
+ * Check if remote service is configured (backward compatibility)
49
+ */
50
+ isRemoteEnabled(): boolean {
51
+ return this.mode === 'remote';
52
+ }
53
+
54
+ /**
55
+ * Get skill by ID
56
+ */
57
+ async getSkill(skillId: string): Promise<any | null> {
58
+ if (this.mode === 'local') {
59
+ const { getMarketSkillById } = await import('./market-db');
60
+ return getMarketSkillById(skillId);
61
+ }
62
+
63
+ // Remote mode: call API, throw error on failure
64
+ const controller = new AbortController();
65
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
66
+
67
+ try {
68
+ const response = await fetch(
69
+ `${this.baseUrl}/api/market/skills/${skillId}?api_key=${this.apiKey}`,
70
+ { signal: controller.signal }
71
+ );
72
+
73
+ clearTimeout(timeoutId);
74
+
75
+ if (!response.ok) {
76
+ throw new Error(`Market service error: ${response.status}`);
77
+ }
78
+
79
+ return await response.json();
80
+ } catch (error: any) {
81
+ clearTimeout(timeoutId);
82
+ console.error('[MarketClient] Get skill failed:', error.message);
83
+ throw new Error('远程市场服务不可用,请联系系统管理员');
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Search skills
89
+ */
90
+ async searchSkills(options: {
91
+ search?: string;
92
+ status?: 'all' | 'pending' | 'approved' | 'rejected' | 'archived';
93
+ author?: string;
94
+ isOfficial?: boolean;
95
+ limit?: number;
96
+ offset?: number;
97
+ } = {}): Promise<{ skills: any[]; total: number }> {
98
+ if (this.mode === 'local') {
99
+ const { listMarketSkills } = await import('./market-db');
100
+ return listMarketSkills(options);
101
+ }
102
+
103
+ // Remote mode: call API, throw error on failure
104
+ const params = new URLSearchParams({ api_key: this.apiKey! });
105
+ if (options.search) params.set('search', options.search);
106
+ if (options.status) params.set('status', options.status);
107
+ if (options.author) params.set('author', options.author);
108
+ if (options.isOfficial !== undefined) params.set('isOfficial', options.isOfficial.toString());
109
+ if (options.limit) params.set('limit', options.limit.toString());
110
+ if (options.offset) params.set('offset', options.offset.toString());
111
+
112
+ const controller = new AbortController();
113
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
114
+
115
+ try {
116
+ const response = await fetch(
117
+ `${this.baseUrl}/api/market/skills?${params}`,
118
+ { signal: controller.signal }
119
+ );
120
+
121
+ clearTimeout(timeoutId);
122
+
123
+ if (!response.ok) {
124
+ throw new Error(`Market service error: ${response.status}`);
125
+ }
126
+
127
+ return await response.json();
128
+ } catch (error: any) {
129
+ clearTimeout(timeoutId);
130
+ console.error('[MarketClient] Search skills failed:', error.message);
131
+ throw new Error('远程市场服务不可用,请联系系统管理员');
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Install skill
137
+ * Note: Skill files are still saved to user's local directory
138
+ */
139
+ async installSkill(userId: string, skillId: string): Promise<any> {
140
+ if (this.mode === 'local') {
141
+ const { addUserInstalledSkill } = await import('./market-db');
142
+ addUserInstalledSkill(userId, skillId);
143
+ const skill = await this.getSkill(skillId);
144
+ return skill;
145
+ }
146
+
147
+ // Remote mode: download from market service
148
+ const controller = new AbortController();
149
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT * 2);
150
+
151
+ try {
152
+ const response = await fetch(
153
+ `${this.baseUrl}/api/market/skills/${skillId}/download?api_key=${this.apiKey}`,
154
+ { signal: controller.signal }
155
+ );
156
+
157
+ clearTimeout(timeoutId);
158
+
159
+ if (!response.ok) {
160
+ throw new Error('Failed to download skill');
161
+ }
162
+
163
+ // Save to user directory
164
+ const { join } = await import('path');
165
+ const { writeFileSync, mkdirSync, existsSync } = await import('fs');
166
+
167
+ const userSkillDir = join(process.cwd(), 'data', 'skills', 'users', userId);
168
+
169
+ if (!existsSync(userSkillDir)) {
170
+ mkdirSync(userSkillDir, { recursive: true });
171
+ }
172
+
173
+ const zipPath = join(userSkillDir, `${skillId}.zip`);
174
+
175
+ const arrayBuffer = await response.arrayBuffer();
176
+ writeFileSync(zipPath, Buffer.from(arrayBuffer));
177
+
178
+ const { unpackSkillFromZip } = await import('./zip-tool');
179
+ unpackSkillFromZip(zipPath, userSkillDir);
180
+
181
+ const { addUserInstalledSkill } = await import('./market-db');
182
+ addUserInstalledSkill(userId, skillId);
183
+
184
+ return await this.getSkill(skillId);
185
+ } catch (error: any) {
186
+ clearTimeout(timeoutId);
187
+ console.error('[MarketClient] Install skill failed:', error.message);
188
+ throw new Error('安装技能失败,请稍后重试或联系管理员');
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Get knowledge list from market
194
+ */
195
+ async getKnowledge(options: {
196
+ category?: string;
197
+ search?: string;
198
+ limit?: number;
199
+ } = {}): Promise<any[]> {
200
+ if (this.mode === 'local') {
201
+ // Local mode: query local published knowledge
202
+ const { getMarketKnowledge } = await import('./db');
203
+ const result = getMarketKnowledge(options);
204
+ return Array.isArray(result) ? result : result.documents || [];
205
+ }
206
+
207
+ // Remote mode: call API, throw error on failure
208
+ const params = new URLSearchParams({ api_key: this.apiKey! });
209
+ if (options.category) params.set('category', options.category);
210
+ if (options.search) params.set('search', options.search);
211
+ if (options.limit) params.set('limit', options.limit.toString());
212
+
213
+ const controller = new AbortController();
214
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
215
+
216
+ try {
217
+ const response = await fetch(
218
+ `${this.baseUrl}/api/market/knowledge?${params}`,
219
+ { signal: controller.signal }
220
+ );
221
+
222
+ clearTimeout(timeoutId);
223
+
224
+ if (!response.ok) {
225
+ throw new Error(`Market service error: ${response.status}`);
226
+ }
227
+
228
+ const data = await response.json();
229
+ return Array.isArray(data) ? data : data.knowledge || data.documents || [];
230
+ } catch (error: any) {
231
+ clearTimeout(timeoutId);
232
+ console.error('[MarketClient] Get knowledge failed:', error.message);
233
+ throw new Error('远程市场服务不可用,请联系系统管理员');
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Publish knowledge to market
239
+ */
240
+ async publishKnowledge(knowledgeId: string, author: string): Promise<any | null> {
241
+ if (this.mode === 'local') {
242
+ // Local mode: update local market_status
243
+ const { publishKnowledgeToMarket } = await import('./db');
244
+ return publishKnowledgeToMarket(knowledgeId, author);
245
+ }
246
+
247
+ // Remote mode: push to remote market
248
+ const controller = new AbortController();
249
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
250
+
251
+ try {
252
+ // Get local knowledge content first
253
+ const { getDocument, updateDocument } = await import('./db');
254
+ const doc = getDocument(knowledgeId);
255
+
256
+ if (!doc) {
257
+ throw new Error('知识不存在');
258
+ }
259
+
260
+ // Push to remote market with full content
261
+ const response = await fetch(
262
+ `${this.baseUrl}/api/market/knowledge`,
263
+ {
264
+ method: 'POST',
265
+ signal: controller.signal,
266
+ headers: {
267
+ 'X-API-Key': this.apiKey!,
268
+ 'Content-Type': 'application/json',
269
+ },
270
+ body: JSON.stringify({
271
+ id: knowledgeId,
272
+ title: doc.title,
273
+ content: doc.content,
274
+ category: doc.category,
275
+ tags: doc.tags,
276
+ author,
277
+ }),
278
+ }
279
+ );
280
+
281
+ clearTimeout(timeoutId);
282
+
283
+ if (!response.ok) {
284
+ throw new Error(`Publish failed: ${response.status}`);
285
+ }
286
+
287
+ const data = await response.json();
288
+ console.log('[MarketClient] Published knowledge to remote market:', knowledgeId);
289
+
290
+ // Update local market_status to indicate published
291
+ await updateDocument(knowledgeId, { marketStatus: 'published' } as any);
292
+
293
+ return data.knowledge || data;
294
+ } catch (error: any) {
295
+ clearTimeout(timeoutId);
296
+ console.error('[MarketClient] Publish to remote failed:', error.message);
297
+ throw new Error('发布到远程市场失败,请稍后重试或联系管理员');
298
+ }
299
+ }
300
+ }
301
+
302
+ // Singleton instance
303
+ let client: MarketClient | null = null;
304
+
305
+ /**
306
+ * Get market client instance
307
+ */
308
+ export function getMarketClient(): MarketClient {
309
+ if (!client) {
310
+ client = new MarketClient();
311
+ }
312
+ return client;
313
+ }