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,51 @@
1
+ /**
2
+ * 技能市场类型定义
3
+ */
4
+
5
+ export type MarketSkillStatus = 'pending' | 'approved' | 'rejected' | 'archived';
6
+
7
+ export interface MarketSkill {
8
+ id: string;
9
+ name: string;
10
+ displayName: string;
11
+ description: string;
12
+ version: string;
13
+ author: string;
14
+ authorName?: string;
15
+ status: MarketSkillStatus;
16
+ isOfficial: boolean;
17
+ ratingAvg: number;
18
+ ratingCount: number;
19
+ downloadCount: number;
20
+ reviewedBy?: string;
21
+ reviewedAt?: string;
22
+ reviewComment?: string;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ }
26
+
27
+ export interface UploadToMarketRequest {
28
+ userId: string;
29
+ userName?: string;
30
+ skillName: string; // 用户私有技能的名称
31
+ }
32
+
33
+ export interface MarketSkillDetail extends MarketSkill {
34
+ content: string; // SKILL.md 内容
35
+ riskConfig?: any;
36
+ }
37
+
38
+ export interface SkillRating {
39
+ id: string;
40
+ skillId: string;
41
+ userId: string;
42
+ rating: number;
43
+ comment?: string;
44
+ createdAt: string;
45
+ }
46
+
47
+ export interface RateSkillRequest {
48
+ userId: string;
49
+ rating: number; // 1-5
50
+ comment?: string;
51
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * 用户记忆工具
3
+ *
4
+ * 提供 save_memory 工具,让 AI 可以将用户要求记住的内容保存到记忆文件中。
5
+ */
6
+
7
+ import { Type } from '@sinclair/typebox';
8
+ import type { ToolDefinition, AgentToolResult, ExtensionContext } from '@mariozechner/pi-coding-agent';
9
+ import { saveMemory, loadUserMemories } from './memory';
10
+
11
+ /**
12
+ * 创建带用户上下文的记忆工具
13
+ */
14
+ export function createMemoryTool(userId: string | undefined): ToolDefinition<typeof saveMemorySchema, SaveMemoryDetails> {
15
+ return {
16
+ name: 'save_memory',
17
+ label: 'Save Memory',
18
+ description: `将用户要求记住的内容保存到用户记忆中。
19
+
20
+ 使用场景:
21
+ - 用户明确说"记下来"、"帮我记住"、"这个很重要"
22
+ - 用户提供了重要的信息,如偏好、习惯、配置等
23
+ - 用户要求 AI 记住某些内容以便后续使用
24
+
25
+ 注意:此工具用于保存用户个人的记忆,如偏好、习惯等。
26
+ 如果要保存共享知识(如解决方案、文档),请使用 save_knowledge 工具。
27
+
28
+ 参数说明:
29
+ - content: 要记住的内容,应该简洁明了地描述需要记忆的信息
30
+ - category: 可选分类,如"偏好"、"项目"、"技能"等`,
31
+
32
+ parameters: saveMemorySchema,
33
+
34
+ async execute(
35
+ toolCallId: string,
36
+ params: any,
37
+ signal: AbortSignal | undefined,
38
+ onUpdate: ((result: AgentToolResult<SaveMemoryDetails>) => void) | undefined,
39
+ ctx: ExtensionContext
40
+ ): Promise<AgentToolResult<SaveMemoryDetails>> {
41
+ try {
42
+ const { content, category } = params;
43
+
44
+ if (!content) {
45
+ throw new Error('content is required');
46
+ }
47
+
48
+ // 使用工具创建时传入的 userId
49
+ if (!userId) {
50
+ throw new Error('User ID not available');
51
+ }
52
+
53
+ // 保存记忆
54
+ saveMemory(userId, content, category);
55
+
56
+ const result: AgentToolResult<SaveMemoryDetails> = {
57
+ content: [
58
+ {
59
+ type: 'text',
60
+ text: `✅ 已记住以下内容:
61
+
62
+ ${content}
63
+
64
+ ${category ? `\n分类: ${category}` : ''}
65
+
66
+ 我会在后续对话中参考这些信息。`,
67
+ },
68
+ ],
69
+ details: {
70
+ success: true,
71
+ userId,
72
+ content,
73
+ category,
74
+ },
75
+ };
76
+
77
+ // 通知更新
78
+ onUpdate?.(result);
79
+
80
+ return result;
81
+ } catch (error: any) {
82
+ const errorResult: AgentToolResult<SaveMemoryDetails> = {
83
+ content: [
84
+ {
85
+ type: 'text',
86
+ text: `❌ 保存记忆失败: ${error.message}`,
87
+ },
88
+ ],
89
+ details: {
90
+ success: false,
91
+ error: error.message,
92
+ },
93
+ };
94
+
95
+ onUpdate?.(errorResult);
96
+
97
+ return errorResult;
98
+ }
99
+ },
100
+ };
101
+ }
102
+
103
+ /**
104
+ * 获取用户记忆的工具
105
+ */
106
+ export function createGetMemoriesTool(userId: string | undefined): ToolDefinition<typeof getMemoriesSchema, GetMemoriesDetails> {
107
+ return {
108
+ name: 'get_memories',
109
+ label: 'Get Memories',
110
+ description: `获取用户已保存的记忆内容。
111
+
112
+ 此工具用于在需要时查看用户之前保存的记忆信息。
113
+ 通常不需要主动调用,系统会在每次对话开始时自动加载用户记忆。
114
+
115
+ 返回格式为 Markdown 格式的用户记忆内容。`,
116
+
117
+ parameters: getMemoriesSchema,
118
+
119
+ async execute(
120
+ toolCallId: string,
121
+ params: any,
122
+ signal: AbortSignal | undefined,
123
+ onUpdate: ((result: AgentToolResult<GetMemoriesDetails>) => void) | undefined,
124
+ ctx: ExtensionContext
125
+ ): Promise<AgentToolResult<GetMemoriesDetails>> {
126
+ try {
127
+ if (!userId) {
128
+ throw new Error('User ID not available');
129
+ }
130
+
131
+ const memories = loadUserMemories(userId);
132
+
133
+ const result: AgentToolResult<GetMemoriesDetails> = {
134
+ content: [
135
+ {
136
+ type: 'text',
137
+ text: memories || '暂无记忆内容',
138
+ },
139
+ ],
140
+ details: {
141
+ success: true,
142
+ userId,
143
+ hasMemories: !!memories,
144
+ content: memories,
145
+ },
146
+ };
147
+
148
+ onUpdate?.(result);
149
+
150
+ return result;
151
+ } catch (error: any) {
152
+ const errorResult: AgentToolResult<GetMemoriesDetails> = {
153
+ content: [
154
+ {
155
+ type: 'text',
156
+ text: `❌ 获取记忆失败: ${error.message}`,
157
+ },
158
+ ],
159
+ details: {
160
+ success: false,
161
+ hasMemories: false,
162
+ error: error.message,
163
+ },
164
+ };
165
+
166
+ onUpdate?.(errorResult);
167
+
168
+ return errorResult;
169
+ }
170
+ },
171
+ };
172
+ }
173
+
174
+ /**
175
+ * 记忆保存工具的参数 Schema
176
+ */
177
+ const saveMemorySchema = Type.Object({
178
+ content: Type.String({
179
+ description: '要记住的内容,应该简洁明了地描述需要记忆的信息',
180
+ }),
181
+ category: Type.Optional(Type.String({
182
+ description: '可选分类,如"偏好"、"项目"、"技能"等',
183
+ })),
184
+ });
185
+
186
+ /**
187
+ * 记忆保存工具的详情类型
188
+ */
189
+ interface SaveMemoryDetails {
190
+ success: boolean;
191
+ userId?: string;
192
+ content?: string;
193
+ category?: string;
194
+ error?: string;
195
+ }
196
+
197
+ /**
198
+ * 获取记忆工具的参数 Schema
199
+ */
200
+ const getMemoriesSchema = Type.Object({});
201
+
202
+ /**
203
+ * 获取记忆工具的详情类型
204
+ */
205
+ interface GetMemoriesDetails {
206
+ success: boolean;
207
+ userId?: string;
208
+ hasMemories: boolean;
209
+ content?: string;
210
+ error?: string;
211
+ }
package/lib/memory.ts ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * 用户记忆管理模块
3
+ *
4
+ * 管理用户的长期记忆,存储在 data/users/{userId}/memory/memories.md
5
+ */
6
+
7
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ const DATA_DIR = join(process.cwd(), 'data');
11
+ const MEMORY_DIR_NAME = 'memory';
12
+ const MEMORY_FILE_NAME = 'memories.md';
13
+
14
+ function getUserMemoryDir(userId: string): string {
15
+ return join(DATA_DIR, 'users', userId, MEMORY_DIR_NAME);
16
+ }
17
+
18
+ function getUserMemoryPath(userId: string): string {
19
+ return join(getUserMemoryDir(userId), MEMORY_FILE_NAME);
20
+ }
21
+
22
+ function ensureMemoryDir(userId: string): string {
23
+ const memDir = getUserMemoryDir(userId);
24
+ if (!existsSync(memDir)) {
25
+ mkdirSync(memDir, { recursive: true });
26
+ }
27
+ return memDir;
28
+ }
29
+
30
+ /**
31
+ * 解析 frontmatter 和 body
32
+ */
33
+ function parseFrontmatter(content: string): { fm: string | null; body: string } {
34
+ const match = content.match(/^---\n([\s\S]*?)\n---\n/);
35
+ if (match) {
36
+ return { fm: match[1], body: content.slice(match[0].length) };
37
+ }
38
+ return { fm: null, body: content };
39
+ }
40
+
41
+ /**
42
+ * 从 frontmatter 中提取 created 日期
43
+ */
44
+ function extractCreated(fm: string | null): string | null {
45
+ if (!fm) return null;
46
+ const match = fm.match(/^created:\s*(.+)$/m);
47
+ return match ? match[1].trim() : null;
48
+ }
49
+
50
+ /**
51
+ * 加载用户的记忆
52
+ */
53
+ export function loadUserMemories(userId: string): string {
54
+ if (!userId) {
55
+ return '';
56
+ }
57
+
58
+ const memPath = getUserMemoryPath(userId);
59
+
60
+ if (!existsSync(memPath)) {
61
+ return '';
62
+ }
63
+
64
+ try {
65
+ const content = readFileSync(memPath, 'utf-8');
66
+ return content;
67
+ } catch (error) {
68
+ console.error(`[memory] Failed to load user memories for ${userId}:`, error);
69
+ return '';
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 追加新的记忆内容(AI 调用)
75
+ */
76
+ export function saveMemory(userId: string, newContent: string, category?: string): void {
77
+ if (!userId) {
78
+ throw new Error('userId is required');
79
+ }
80
+
81
+ const memPath = getUserMemoryPath(userId);
82
+ ensureMemoryDir(userId);
83
+
84
+ const now = new Date().toISOString().split('T')[0];
85
+
86
+ let existing = '';
87
+ if (existsSync(memPath)) {
88
+ try {
89
+ existing = readFileSync(memPath, 'utf-8');
90
+ } catch (error) {
91
+ console.error(`[memory] Failed to read existing memories:`, error);
92
+ }
93
+ }
94
+
95
+ const { fm, body } = parseFrontmatter(existing);
96
+ const created = extractCreated(fm);
97
+
98
+ // 追加新内容到 body 末尾
99
+ const newSection = category
100
+ ? `\n## ${category} - ${now}\n${newContent}\n`
101
+ : `\n## ${now}\n${newContent}\n`;
102
+
103
+ const newBody = body.trim() + newSection;
104
+
105
+ const newFm = `created: ${created || now}\nupdated: ${now}`;
106
+ const finalContent = `---
107
+ ${newFm}
108
+ ---
109
+
110
+ ${newBody}`;
111
+
112
+ try {
113
+ writeFileSync(memPath, finalContent, 'utf-8');
114
+ console.log(`[memory] Saved memory for user ${userId}`);
115
+ } catch (error) {
116
+ console.error(`[memory] Failed to save memory for ${userId}:`, error);
117
+ throw error;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 更新整个记忆文件(前端调用)
123
+ */
124
+ export function updateMemories(userId: string, content: string): void {
125
+ if (!userId) {
126
+ throw new Error('userId is required');
127
+ }
128
+
129
+ const memPath = getUserMemoryPath(userId);
130
+ ensureMemoryDir(userId);
131
+
132
+ const now = new Date().toISOString().split('T')[0];
133
+
134
+ // 清理前端传入内容(移除 MDEditor 前缀)
135
+ let cleanContent = content.replace(/^\[Pasted[^\n]*\n*/, '').trim();
136
+
137
+ // 如果新内容带 frontmatter,提取 body
138
+ const { fm: newFm, body: newBody } = parseFrontmatter(cleanContent);
139
+ if (newFm) {
140
+ cleanContent = newBody;
141
+ }
142
+
143
+ let finalContent: string;
144
+
145
+ if (existsSync(memPath)) {
146
+ try {
147
+ const existing = readFileSync(memPath, 'utf-8');
148
+ const { fm: existingFm } = parseFrontmatter(existing);
149
+ const created = extractCreated(existingFm);
150
+
151
+ // 保留原有 created,更新 updated
152
+ const fmStr = `created: ${created || now}\nupdated: ${now}`;
153
+ finalContent = `---
154
+ ${fmStr}
155
+ ---
156
+
157
+ ${cleanContent}`;
158
+ } catch (error) {
159
+ console.error(`[memory] Failed to read existing memories:`, error);
160
+ // 读取失败,添加新 frontmatter
161
+ finalContent = `---
162
+ created: ${now}
163
+ updated: ${now}
164
+ ---
165
+
166
+ ${cleanContent}`;
167
+ }
168
+ } else {
169
+ // 文件不存在,添加 frontmatter
170
+ finalContent = `---
171
+ created: ${now}
172
+ updated: ${now}
173
+ ---
174
+
175
+ ${cleanContent}`;
176
+ }
177
+
178
+ try {
179
+ writeFileSync(memPath, finalContent, 'utf-8');
180
+ console.log(`[memory] Updated memories for user ${userId}`);
181
+ } catch (error) {
182
+ console.error(`[memory] Failed to update memories for ${userId}:`, error);
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * 检查用户是否有记忆
189
+ */
190
+ export function hasUserMemories(userId: string): boolean {
191
+ if (!userId) {
192
+ return false;
193
+ }
194
+
195
+ const memPath = getUserMemoryPath(userId);
196
+ return existsSync(memPath);
197
+ }