foliko 1.1.68 → 1.1.69

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 (142) hide show
  1. package/.claude/settings.local.json +19 -10
  2. package/.dockerignore +45 -45
  3. package/.env.example +56 -56
  4. package/CLAUDE.md +2 -2
  5. package/README.md +13 -13
  6. package/SPEC.md +3 -3
  7. package/cli/src/commands/chat.js +2 -20
  8. package/cli/src/commands/list.js +7 -6
  9. package/cli/src/commands/plugin.js +3 -2
  10. package/cli/src/daemon.js +2 -2
  11. package/cli/src/ui/chat-ui-old.js +15 -4
  12. package/cli/src/ui/chat-ui.js +152 -38
  13. package/cli/src/ui/footer-bar.js +13 -14
  14. package/cli/src/utils/config.js +29 -0
  15. package/cli/src/utils/plugin-config.js +1 -1
  16. package/docker-compose.yml +33 -33
  17. package/docs/features.md +120 -120
  18. package/docs/quick-reference.md +160 -160
  19. package/docs/user-manual.md +1391 -1391
  20. package/examples/ambient-example.js +2 -2
  21. package/examples/bootstrap.js +3 -3
  22. package/examples/test-chat.js +1 -1
  23. package/examples/test-reload.js +1 -1
  24. package/examples/test-telegram.js +1 -1
  25. package/examples/test-tg-bot.js +1 -1
  26. package/examples/test-tg-simple.js +2 -2
  27. package/examples/test-tg.js +1 -1
  28. package/examples/test-think.js +1 -1
  29. package/examples/test-weixin-feishu.js +3 -3
  30. package/package.json +3 -1
  31. package/plugins/ambient-agent/index.js +1 -1
  32. package/plugins/audit-plugin.js +75 -24
  33. package/plugins/default-plugins.js +21 -11
  34. package/plugins/email/index.js +1 -1
  35. package/plugins/extension-executor-plugin.js +85 -7
  36. package/plugins/feishu-plugin.js +118 -16
  37. package/plugins/install-plugin.js +4 -4
  38. package/plugins/memory-plugin.js +1 -1
  39. package/plugins/plugin-manager-plugin.js +3 -3
  40. package/plugins/python-executor-plugin.js +1 -1
  41. package/plugins/python-plugin-loader.js +1 -1
  42. package/plugins/qq-plugin.js +143 -16
  43. package/plugins/rules-plugin.js +1 -1
  44. package/plugins/scheduler-plugin.js +2 -2
  45. package/plugins/session-plugin.js +309 -393
  46. package/plugins/storage-plugin.js +235 -176
  47. package/plugins/subagent-plugin.js +4 -4
  48. package/plugins/telegram-plugin.js +116 -17
  49. package/plugins/think-plugin.js +1 -1
  50. package/plugins/web-plugin.js +2 -2
  51. package/plugins/weixin-plugin.js +102 -19
  52. package/skills/find-skills/AGENTS.md +2 -2
  53. package/skills/find-skills/SKILL.md +133 -133
  54. package/skills/foliko-dev/AGENTS.md +236 -236
  55. package/skills/foliko-dev/SKILL.md +19 -19
  56. package/skills/mcp-usage/SKILL.md +200 -200
  57. package/skills/plugin-guide/SKILL.md +4 -4
  58. package/skills/python-plugin-dev/SKILL.md +5 -5
  59. package/skills/skill-guide/SKILL.md +104 -6
  60. package/skills/subagent-guide/SKILL.md +237 -237
  61. package/skills/workflow-guide/SKILL.md +646 -646
  62. package/src/capabilities/skill-manager.js +101 -2
  63. package/src/capabilities/workflow-engine.js +1 -1
  64. package/src/core/agent-chat.js +17 -14
  65. package/src/core/branch-summary-auto.js +206 -0
  66. package/src/core/chat-session.js +38 -8
  67. package/src/core/command-registry.js +200 -0
  68. package/src/core/constants.js +11 -11
  69. package/src/core/context-compressor.js +700 -149
  70. package/src/core/context-manager.js +0 -1
  71. package/src/core/enhanced-context-compressor.js +210 -0
  72. package/src/core/framework.js +140 -83
  73. package/src/core/jsonl-storage.js +253 -0
  74. package/src/core/plugin-manager.js +15 -10
  75. package/src/core/provider-registry.js +159 -0
  76. package/src/core/provider.js +2 -0
  77. package/src/core/session-entry.js +225 -0
  78. package/src/core/session-manager.js +701 -0
  79. package/src/core/storage-manager.js +494 -0
  80. package/src/core/sub-agent-config.js +1 -1
  81. package/src/core/token-counter.js +177 -58
  82. package/src/core/ui-extension-context.js +174 -0
  83. package/src/executors/mcp-executor.js +15 -6
  84. package/src/utils/logger.js +152 -180
  85. package/src/utils/plugin-helpers.js +2 -2
  86. package/website_v2/docs/api.html +1 -1
  87. package/website_v2/docs/configuration.html +2 -2
  88. package/website_v2/docs/plugin-development.html +4 -4
  89. package/website_v2/docs/project-structure.html +2 -2
  90. package/website_v2/docs/skill-development.html +2 -2
  91. package/website_v2/index.html +1 -1
  92. package/website_v2/styles/animations.css +7 -7
  93. package/.agent/agents/backend-dev.md +0 -102
  94. package/.agent/agents/data-analyst.md +0 -117
  95. package/.agent/agents/devops.md +0 -115
  96. package/.agent/agents/frontend-dev.md +0 -94
  97. package/.agent/agents/network-requester.md +0 -44
  98. package/.agent/agents/poster-designer.md +0 -52
  99. package/.agent/agents/product-manager.md +0 -85
  100. package/.agent/agents/qa-engineer.md +0 -100
  101. package/.agent/agents/security-engineer.md +0 -99
  102. package/.agent/agents/team-lead.md +0 -137
  103. package/.agent/agents/ui-designer.md +0 -116
  104. package/.agent/data/default.json +0 -58
  105. package/.agent/data/email/processed-emails.json +0 -1
  106. package/.agent/data/plugins-state.json +0 -207
  107. package/.agent/data/scheduler/tasks.json +0 -1
  108. package/.agent/data/web/web-config.json +0 -5
  109. package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
  110. package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
  111. package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
  112. package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
  113. package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
  114. package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
  115. package/.agent/data/weixin.json +0 -6
  116. package/.agent/mcp_config.json +0 -14
  117. package/.agent/memory/user/mof6gk94-kneeuh.md +0 -9
  118. package/.agent/package.json +0 -8
  119. package/.agent/plugins/marknative/README.md +0 -134
  120. package/.agent/plugins/marknative/fonts/SegoeUI Emoji.ttf +0 -0
  121. package/.agent/plugins/marknative/fonts.zip +0 -0
  122. package/.agent/plugins/marknative/index.js +0 -256
  123. package/.agent/plugins/marknative/package.json +0 -12
  124. package/.agent/plugins/test-plugin.py +0 -99
  125. package/.agent/plugins.json +0 -14
  126. package/.agent/python-scripts/test_sample.py +0 -24
  127. package/.agent/sessions/cli_default.json +0 -4121
  128. package/.agent/skills/agent-browser/SKILL.md +0 -311
  129. package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
  130. package/.agent/skills/sysinfo/SKILL.md +0 -38
  131. package/.agent/skills/sysinfo/system-info.sh +0 -130
  132. package/.agent/skills/workflow/SKILL.md +0 -324
  133. package/.agent/test-agent.js +0 -35
  134. package/.agent/weixin.json +0 -6
  135. package/.agent/workflows/email-digest.json +0 -50
  136. package/.agent/workflows/file-backup.json +0 -21
  137. package/.agent/workflows/get-ip-notify.json +0 -32
  138. package/.agent/workflows/news-aggregator.json +0 -93
  139. package/.agent/workflows/news-dashboard-v2.json +0 -94
  140. package/.agent/workflows/notification-batch.json +0 -32
  141. package/src/core/session-context.js +0 -346
  142. package/src/core/session-storage.js +0 -295
@@ -1,249 +1,285 @@
1
1
  /**
2
- * Session 会话管理插件
3
- * 管理多个用户会话,支持会话隔离和历史记录
2
+ * Session Plugin - Session management with JSONL tree structure
3
+ * Rewritten to use pi-style SessionManager
4
4
  */
5
5
 
6
- const { Plugin } = require('../src/core/plugin-base')
7
- const { logger } = require('../src/utils/logger')
8
- const log = logger.child('Session')
9
- const { z } = require('zod')
10
- const { EventEmitter } = require('events')
6
+ const { Plugin } = require('../src/core/plugin-base');
7
+ const { logger } = require('../src/utils/logger');
8
+ const log = logger.child('Session');
9
+ const { z } = require('zod');
10
+ const { EventEmitter } = require('events');
11
+ const path = require('path');
12
+ const { SessionManager } = require('../src/core/session-manager');
13
+ const { buildSessionContext, EntryTypes } = require('../src/core/session-entry');
11
14
 
12
15
  /**
13
- * 生成 UUID
16
+ * Get default session directory for a cwd
14
17
  */
15
- function generateId() {
16
- if (require('crypto').randomUUID) {
17
- return require('crypto').randomUUID()
18
- }
19
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
20
- const r = Math.random() * 16 | 0
21
- const v = c === 'x' ? r : (r & 0x3 | 0x8)
22
- return v.toString(16)
23
- })
18
+ function getDefaultSessionDir(cwd) {
19
+ const agentDir = path.join(process.cwd(), '.foliko');
20
+ const sessionDir = path.join(agentDir, 'sessions');
21
+ return sessionDir;
24
22
  }
25
23
 
26
24
  class SessionPlugin extends Plugin {
27
25
  constructor(config = {}) {
28
- super()
29
- this.name = 'session'
30
- this.version = '1.0.0'
31
- this.description = '会话管理插件,支持多会话隔离、历史记录、会话超时清理'
32
- this.priority = 5
26
+ super();
27
+ this.name = 'session';
28
+ this.version = '2.0.0';
29
+ this.description = '会话管理插件,支持多会话隔离、历史记录、树形分支结构';
30
+ this.priority = 5;
33
31
 
34
- this.system = true
32
+ this.system = true;
35
33
 
36
34
  this.config = {
37
- sessionTTL: config.sessionTTL || 30 * 60 * 1000, // 30分钟
35
+ sessionTTL: config.sessionTTL || 30 * 60 * 1000,
38
36
  maxSessions: config.maxSessions || 100,
39
- maxHistoryLength: config.maxHistoryLength || 150, // 放宽到 150,Agent 已有智能压缩
40
- autoCleanup: config.autoCleanup||false, // 默认不开启自动清理,避免误删会话
41
- cleanupInterval: config.cleanupInterval || 5 * 60 * 1000, // 5分钟
42
- persistToStorage: config.persistToStorage !== false, // 默认持久化到 storage
43
- // Session 钩子配置
37
+ maxHistoryLength: config.maxHistoryLength || 150,
38
+ autoCleanup: config.autoCleanup !== undefined ? config.autoCleanup : true,
39
+ cleanupInterval: config.cleanupInterval || 5 * 60 * 1000,
40
+ persistToStorage: config.persistToStorage !== false,
44
41
  onSessionStart: config.onSessionStart || null,
45
42
  onSessionEnd: config.onSessionEnd || null,
46
43
  onSessionCreated: config.onSessionCreated || null
47
- }
48
- // console.log('SessionPlugin config:', this.config)
49
- this._framework = null
50
- this._sessions = new Map()
51
- this._cleanupTimer = null
52
- this._events = new EventEmitter()
53
- this._storageKeyPrefix = 'session:'
54
- }
44
+ };
55
45
 
56
- install(framework) {
57
- this._framework = framework
58
- return this
46
+ this._framework = null;
47
+ this._sessionManagers = new Map(); // sessionId -> SessionManager
48
+ this._cleanupTimer = null;
49
+ this._events = new EventEmitter();
59
50
  }
60
51
 
61
- /**
62
- * 获取存储实例
63
- * @private
64
- */
65
- _getStorage() {
66
- if (!this._framework) return null;
67
- return this._framework.pluginManager.get('storage');
52
+ install(framework) {
53
+ this._framework = framework;
54
+ return this;
68
55
  }
69
56
 
70
57
  /**
71
- * storage 加载会话数据
72
- * @private
58
+ * Get or create a SessionManager for a session
73
59
  */
74
- _loadFromStorage(sessionId) {
75
- const storage = this._getStorage();
76
- if (!storage) return null;
77
-
78
- try {
79
- const store = storage.getStore();
80
- const data = store.get(this._storageKeyPrefix + sessionId);
81
- if (data && data.messages) {
82
- return data;
83
- }
84
- } catch (err) {
85
- // 忽略加载错误
60
+ _getSessionManager(sessionId, cwd = process.cwd()) {
61
+ if (this._sessionManagers.has(sessionId)) {
62
+ return this._sessionManagers.get(sessionId);
86
63
  }
87
- return null;
88
- }
89
-
90
- /**
91
- * 保存会话数据到 storage
92
- * @private
93
- */
94
- _saveToStorage(session) {
95
- const storage = this._getStorage();
96
- if (!storage || !this.config.persistToStorage) return;
97
64
 
98
- try {
99
- storage.setDirect(this._storageKeyPrefix + session.id, {
100
- messages: session.messages,
101
- variables: session.variables,
102
- metadata: session.metadata,
103
- createdAt: session.createdAt,
104
- lastActive: session.lastActive
105
- });
106
- } catch (err) {
107
- // 忽略保存错误
108
- }
65
+ const sessionDir = getDefaultSessionDir(cwd);
66
+ const sessionFile = path.join(sessionDir, `${sessionId}.jsonl`);
67
+ const manager = new SessionManager(cwd, sessionDir, sessionFile, true);
68
+ this._sessionManagers.set(sessionId, manager);
69
+ return manager;
109
70
  }
110
71
 
111
72
  /**
112
- * storage 删除会话数据
113
- * @private
73
+ * List all sessions for current cwd
114
74
  */
115
- _deleteFromStorage(sessionId) {
116
- const storage = this._getStorage();
117
- if (!storage) return;
75
+ async listSessions(cwd = process.cwd()) {
76
+ const sessionDir = getDefaultSessionDir(cwd);
77
+ const sessions = [];
118
78
 
119
79
  try {
120
- storage.deleteDirect(this._storageKeyPrefix + sessionId);
121
- } catch (err) {
122
- // 忽略删除错误
123
- }
124
- }
125
-
126
- /**
127
- * 从 storage 加载所有会话
128
- * @private
129
- */
130
- _loadAllFromStorage() {
131
- const storage = this._getStorage();
132
- if (!storage) return;
80
+ if (!require('fs').existsSync(sessionDir)) {
81
+ return sessions;
82
+ }
133
83
 
134
- try {
135
- const store = storage.getStore();
136
- // 遍历 storage 找出所有 session: 开头的键
137
- for (const [key, value] of store.entries()) {
138
- if (key.startsWith(this._storageKeyPrefix)) {
139
- const sessionId = key.substring(this._storageKeyPrefix.length);
140
- const sessionData = value;
141
- if (sessionId && sessionData && sessionData.messages) {
142
- // 恢复会话到内存
143
- this._sessions.set(sessionId, {
144
- id: sessionId,
145
- messages: sessionData.messages || [],
146
- variables: sessionData.variables || {},
147
- metadata: sessionData.metadata || {},
148
- createdAt: sessionData.createdAt ? new Date(sessionData.createdAt) : new Date(),
149
- lastActive: sessionData.lastActive ? new Date(sessionData.lastActive) : new Date()
150
- });
151
- }
84
+ const files = require('fs').readdirSync(sessionDir)
85
+ .filter(f => f.endsWith('.jsonl'));
86
+
87
+ for (const file of files) {
88
+ const filePath = path.join(sessionDir, file);
89
+ try {
90
+ const manager = SessionManager.open(filePath, sessionDir, cwd);
91
+ sessions.push({
92
+ sessionId: manager.getSessionId(),
93
+ sessionFile: manager.getSessionFile(),
94
+ messageCount: manager.getEntries().length,
95
+ leafId: manager.getLeafId()
96
+ });
97
+ } catch {
98
+ // Skip invalid session files
152
99
  }
153
100
  }
154
- log.info(`Loaded ${this._sessions.size} sessions from storage`);
155
101
  } catch (err) {
156
- log.warn('Failed to load sessions from storage:', err.message);
102
+ log.warn('Failed to list sessions:', err.message);
157
103
  }
104
+
105
+ return sessions;
158
106
  }
159
107
 
160
108
  start(framework) {
161
- // storage 加载所有会话
162
- this._loadAllFromStorage();
163
- // 注册会话管理工具
109
+ // Register session management tools
164
110
  framework.registerTool({
165
111
  name: 'session_create',
166
112
  description: '创建新会话',
167
113
  inputSchema: z.object({
168
114
  sessionId: z.string().optional().describe('可选:指定会话 ID'),
169
- metadata: z.object({}).optional().describe('会话元数据')
115
+ cwd: z.string().optional().describe('工作目录')
170
116
  }),
171
- execute: async (args) => {
172
- const sessionId = args.sessionId || `session_${generateId()}`
173
- const session = this.createSession(sessionId, { metadata: args.metadata })
117
+ execute: async (args, fw) => {
118
+ const cwd = args.cwd || process.cwd();
119
+ const sessionDir = getDefaultSessionDir(cwd);
120
+ const manager = new SessionManager(cwd, sessionDir, undefined, true);
121
+ const sessionId = manager.getSessionId();
122
+ this._sessionManagers.set(sessionId, manager);
123
+
174
124
  return {
175
125
  success: true,
176
- data: { sessionId: session.id, createdAt: session.createdAt }
177
- }
126
+ data: {
127
+ sessionId,
128
+ sessionFile: manager.getSessionFile(),
129
+ createdAt: new Date().toISOString()
130
+ }
131
+ };
178
132
  }
179
- })
133
+ });
180
134
 
181
135
  framework.registerTool({
182
136
  name: 'session_get',
183
137
  description: '获取会话信息',
184
138
  inputSchema: z.object({
185
- sessionId: z.string().describe('会话 ID')
139
+ sessionId: z.string().describe('会话 ID'),
140
+ cwd: z.string().optional().describe('工作目录')
186
141
  }),
187
142
  execute: async (args) => {
188
- const session = this.getSession(args.sessionId)
189
- if (!session) {
190
- return { success: false, error: 'Session not found' }
191
- }
143
+ const cwd = args.cwd || process.cwd();
144
+ const manager = this._getSessionManager(args.sessionId, cwd);
145
+ const entries = manager.getEntries();
146
+
192
147
  return {
193
148
  success: true,
194
149
  data: {
195
- sessionId: session.id,
196
- messageCount: session.messages.length,
197
- variables: Object.keys(session.variables),
198
- createdAt: session.createdAt,
199
- lastActive: session.lastActive
150
+ sessionId: manager.getSessionId(),
151
+ messageCount: entries.length,
152
+ leafId: manager.getLeafId(),
153
+ sessionFile: manager.getSessionFile()
200
154
  }
201
- }
155
+ };
202
156
  }
203
- })
157
+ });
204
158
 
205
159
  framework.registerTool({
206
160
  name: 'session_list',
207
161
  description: '列出所有会话',
208
- inputSchema: z.object({}),
209
- execute: async () => {
210
- const sessions = this.listSessions()
162
+ inputSchema: z.object({
163
+ cwd: z.string().optional().describe('工作目录')
164
+ }),
165
+ execute: async (args) => {
166
+ const cwd = args.cwd || process.cwd();
167
+ const sessions = await this.listSessions(cwd);
211
168
  return {
212
169
  success: true,
213
170
  data: sessions,
214
171
  metadata: { total: sessions.length }
215
- }
172
+ };
216
173
  }
217
- })
174
+ });
218
175
 
219
176
  framework.registerTool({
220
177
  name: 'session_delete',
221
178
  description: '删除会话',
222
179
  inputSchema: z.object({
223
- sessionId: z.string().describe('会话 ID')
180
+ sessionId: z.string().describe('会话 ID'),
181
+ cwd: z.string().optional().describe('工作目录')
224
182
  }),
225
183
  execute: async (args) => {
226
- const deleted = this.deleteSession(args.sessionId)
227
- return { success: deleted, data: `会话 ${args.sessionId} 已${deleted ? '删除' : '删除失败'}` }
184
+ const cwd = args.cwd || process.cwd();
185
+ const manager = this._getSessionManager(args.sessionId, cwd);
186
+
187
+ if (manager.getSessionFile() && require('fs').existsSync(manager.getSessionFile())) {
188
+ require('fs').unlinkSync(manager.getSessionFile());
189
+ }
190
+
191
+ this._sessionManagers.delete(args.sessionId);
192
+
193
+ return { success: true, data: `Session ${args.sessionId} deleted` };
228
194
  }
229
- })
195
+ });
230
196
 
231
197
  framework.registerTool({
232
198
  name: 'session_history',
233
199
  description: '获取会话历史消息',
234
200
  inputSchema: z.object({
235
201
  sessionId: z.string().describe('会话 ID'),
202
+ cwd: z.string().optional().describe('工作目录'),
236
203
  limit: z.number().optional().describe('返回消息数量限制')
237
204
  }),
238
205
  execute: async (args) => {
239
- const history = this.getHistory(args.sessionId, args.limit)
206
+ const cwd = args.cwd || process.cwd();
207
+ const manager = this._getSessionManager(args.sessionId, cwd);
208
+ const context = manager.buildSessionContext();
209
+ let messages = context.messages;
210
+
211
+ if (args.limit) {
212
+ messages = messages.slice(-args.limit);
213
+ }
214
+
240
215
  return {
241
216
  success: true,
242
- data: history,
243
- metadata: { sessionId: args.sessionId, count: history.length }
217
+ data: messages,
218
+ metadata: {
219
+ sessionId: args.sessionId,
220
+ count: messages.length,
221
+ thinkingLevel: context.thinkingLevel
222
+ }
223
+ };
224
+ }
225
+ });
226
+
227
+ framework.registerTool({
228
+ name: 'session_branch',
229
+ description: '创建新分支',
230
+ inputSchema: z.object({
231
+ sessionId: z.string().describe('会话 ID'),
232
+ entryId: z.string().describe('分支点 entry ID'),
233
+ cwd: z.string().optional().describe('工作目录')
234
+ }),
235
+ execute: async (args) => {
236
+ const cwd = args.cwd || process.cwd();
237
+ const manager = this._getSessionManager(args.sessionId, cwd);
238
+
239
+ try {
240
+ manager.branch(args.entryId);
241
+ return {
242
+ success: true,
243
+ data: {
244
+ sessionId: args.sessionId,
245
+ newLeafId: args.entryId,
246
+ message: `Branch created from entry ${args.entryId}`
247
+ }
248
+ };
249
+ } catch (err) {
250
+ return { success: false, error: err.message };
251
+ }
252
+ }
253
+ });
254
+
255
+ framework.registerTool({
256
+ name: 'session_branch_with_summary',
257
+ description: '创建带摘要的分支',
258
+ inputSchema: z.object({
259
+ sessionId: z.string().describe('会话 ID'),
260
+ entryId: z.string().optional().describe('分支点 entry ID'),
261
+ summary: z.string().describe('分支摘要'),
262
+ cwd: z.string().optional().describe('工作目录')
263
+ }),
264
+ execute: async (args) => {
265
+ const cwd = args.cwd || process.cwd();
266
+ const manager = this._getSessionManager(args.sessionId, cwd);
267
+
268
+ try {
269
+ const branchId = manager.branchWithSummary(args.entryId || null, args.summary);
270
+ return {
271
+ success: true,
272
+ data: {
273
+ sessionId: args.sessionId,
274
+ branchId,
275
+ message: `Branch created with summary`
276
+ }
277
+ };
278
+ } catch (err) {
279
+ return { success: false, error: err.message };
244
280
  }
245
281
  }
246
- })
282
+ });
247
283
 
248
284
  framework.registerTool({
249
285
  name: 'session_stats',
@@ -252,321 +288,201 @@ class SessionPlugin extends Plugin {
252
288
  execute: async () => {
253
289
  return {
254
290
  success: true,
255
- data: this.getMemoryStats()
256
- }
291
+ data: {
292
+ activeSessions: this._sessionManagers.size,
293
+ maxSessions: this.config.maxSessions
294
+ }
295
+ };
257
296
  }
258
- })
297
+ });
259
298
 
260
- // 启动自动清理
299
+ // Start auto cleanup if enabled
261
300
  if (this.config.autoCleanup) {
262
- this._startAutoCleanup()
301
+ this._startAutoCleanup();
263
302
  }
264
303
 
265
- return this
304
+ return this;
266
305
  }
267
306
 
268
307
  /**
269
- * 创建新会话
308
+ * Add message to session
270
309
  */
271
- createSession(sessionId, options = {}) {
272
- const id = sessionId || `session_${generateId()}`
273
-
274
- if (this._sessions.has(id)) {
275
- return this._sessions.get(id)
276
- }
277
-
278
- // 如果会话数超过限制,清理最老的
279
- // if (this._sessions.size >= this.config.maxSessions) {
280
- // this._cleanupLRU()
281
- // }
282
-
283
- const session = {
284
- id,
285
- messages: options.initialMessages || [],
286
- variables: options.variables || {},
287
- metadata: options.metadata || {},
288
- createdAt: new Date(),
289
- lastActive: new Date()
290
- }
291
-
292
- this._sessions.set(id, session)
293
- this._saveToStorage(session)
294
-
295
- // 触发 session:created 事件
296
- this._events.emit('session:created', session)
310
+ addMessage(sessionId, message, cwd = process.cwd()) {
311
+ const manager = this._getSessionManager(sessionId, cwd);
312
+ const entryId = manager.appendMessage(message);
297
313
 
298
- // 调用 onSessionCreated 钩子
299
- if (this.config.onSessionCreated) {
300
- this._callHook(this.config.onSessionCreated, session)
314
+ // Emit session event
315
+ if (manager.getEntries().length === 1) {
316
+ this._events.emit('session:created', { sessionId, manager });
301
317
  }
302
318
 
303
- return session
319
+ return entryId;
304
320
  }
305
321
 
306
322
  /**
307
- * 获取会话
323
+ * Get session context for LLM
308
324
  */
309
- getSession(sessionId) {
310
- let session = this._sessions.get(sessionId)
311
- if (session) {
312
- session.lastActive = new Date()
313
- return session
314
- }
315
-
316
- // 尝试从 storage 加载
317
- const storageData = this._loadFromStorage(sessionId)
318
- if (storageData) {
319
- session = {
320
- id: sessionId,
321
- messages: storageData.messages || [],
322
- variables: storageData.variables || {},
323
- metadata: storageData.metadata || {},
324
- createdAt: storageData.createdAt ? new Date(storageData.createdAt) : new Date(),
325
- lastActive: new Date()
326
- }
327
- this._sessions.set(sessionId, session)
328
- return session
329
- }
330
-
331
- return null
325
+ getSessionContext(sessionId, cwd = process.cwd()) {
326
+ const manager = this._getSessionManager(sessionId, cwd);
327
+ return manager.buildSessionContext();
332
328
  }
333
329
 
334
330
  /**
335
- * 获取或创建会话
331
+ * Get or create session (alias for getSessionContext)
336
332
  */
337
- getOrCreateSession(sessionId, options = {}) {
338
- let session = this.getSession(sessionId)
339
- if (!session) {
340
- session = this.createSession(sessionId, options)
341
- }
342
- return session
333
+ getOrCreateSession(sessionId, options = {}, cwd = process.cwd()) {
334
+ return this.getSessionContext(sessionId, cwd);
343
335
  }
344
336
 
345
337
  /**
346
- * 添加消息到会话
338
+ * Get specific entry
347
339
  */
348
- addMessage(sessionId, message) {
349
- const session = this.getSession(sessionId)
350
- if (session) {
351
- // 基础消息验证
352
- if (!message || !message.role || !message.content) {
353
- log.warn('Invalid message format, skipping:', { sessionId, message });
354
- return session;
355
- }
356
-
357
- // 触发 session:started 事件(首次添加消息时)
358
- if (session.messages.length === 0) {
359
- this._events.emit('session:started', session)
360
- if (this.config.onSessionStart) {
361
- this._callHook(this.config.onSessionStart, session)
362
- }
363
- }
364
-
365
- session.messages.push(message)
366
- session.lastActive = new Date()
367
-
368
- // 限制历史长度
369
- if (session.messages.length > this.config.maxHistoryLength) {
370
- session.messages = session.messages.slice(-this.config.maxHistoryLength)
371
- }
372
-
373
- // 持久化到 storage
374
- this._saveToStorage(session)
375
- }
376
- return session
377
- }
378
-
379
- /**
380
- * 整体替换会话消息(用于压缩后同步)
381
- * @param {string} sessionId - 会话 ID
382
- * @param {Array} messages - 新的消息数组
383
- */
384
- replaceMessages(sessionId, messages) {
385
- const session = this.getSession(sessionId)
386
- if (session) {
387
- session.messages = messages || []
388
- session.lastActive = new Date()
389
- // 持久化到 storage
390
- this._saveToStorage(session)
391
- }
392
- return session
340
+ getEntry(sessionId, entryId, cwd = process.cwd()) {
341
+ const manager = this._getSessionManager(sessionId, cwd);
342
+ return manager.getEntry(entryId);
393
343
  }
394
344
 
395
345
  /**
396
- * 获取会话历史
346
+ * Get branch (path from root to leaf)
397
347
  */
398
- getHistory(sessionId, limit = null) {
399
- const session = this.getSession(sessionId)
400
- if (!session) return []
401
-
402
- const messages = session.messages
403
- if (limit) {
404
- return messages.slice(-limit)
405
- }
406
- return messages
348
+ getBranch(sessionId, fromId, cwd = process.cwd()) {
349
+ const manager = this._getSessionManager(sessionId, cwd);
350
+ return manager.getBranch(fromId);
407
351
  }
408
352
 
409
353
  /**
410
- * 删除会话
354
+ * Append compaction entry
411
355
  */
412
- deleteSession(sessionId) {
413
- const deleted = this._sessions.delete(sessionId)
414
- if (deleted) {
415
- this._deleteFromStorage(sessionId)
416
- this._events.emit('session:deleted', sessionId)
417
- }
418
- return deleted
356
+ appendCompaction(sessionId, summary, firstKeptEntryId, tokensBefore, details, cwd = process.cwd()) {
357
+ const manager = this._getSessionManager(sessionId, cwd);
358
+ return manager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, false);
419
359
  }
420
360
 
421
361
  /**
422
- * 列出所有会话
362
+ * Append thinking level change
423
363
  */
424
- listSessions() {
425
- return Array.from(this._sessions.values()).map(s => ({
426
- id: s.id,
427
- messageCount: s.messages.length,
428
- createdAt: s.createdAt,
429
- lastActive: s.lastActive,
430
- metadata: s.metadata
431
- }))
364
+ appendThinkingLevelChange(sessionId, thinkingLevel, cwd = process.cwd()) {
365
+ const manager = this._getSessionManager(sessionId, cwd);
366
+ return manager.appendThinkingLevelChange(thinkingLevel);
432
367
  }
433
368
 
434
369
  /**
435
- * 清理过期会话
370
+ * Append model change
436
371
  */
437
- cleanup() {
438
- const now = Date.now()
439
- let cleanedCount = 0
440
-
441
- for (const [sessionId, session] of this._sessions) {
442
- if (now - session.lastActive.getTime() > this.config.sessionTTL) {
443
- this._sessions.delete(sessionId)
444
- cleanedCount++
445
- }
446
- }
447
-
448
- return cleanedCount
372
+ appendModelChange(sessionId, provider, modelId, cwd = process.cwd()) {
373
+ const manager = this._getSessionManager(sessionId, cwd);
374
+ return manager.appendModelChange(provider, modelId);
449
375
  }
450
376
 
451
377
  /**
452
- * LRU 清理 - 删除最老的 20% 会话
378
+ * Set session label
453
379
  */
454
- _cleanupLRU() {
455
- const sessions = Array.from(this._sessions.values())
456
- if (sessions.length === 0) return
457
-
458
- // 按最后活跃时间排序
459
- sessions.sort((a, b) => a.lastActive.getTime() - b.lastActive.getTime())
460
-
461
- // 删除最老的会话
462
- const toDelete = sessions.slice(0, Math.ceil(sessions.length * 0.2))
463
- for (const session of toDelete) {
464
- this._sessions.delete(session.id)
465
- }
380
+ setLabel(sessionId, entryId, label, cwd = process.cwd()) {
381
+ const manager = this._getSessionManager(sessionId, cwd);
382
+ return manager.appendLabelChange(entryId, label);
466
383
  }
467
384
 
468
385
  /**
469
- * 获取内存统计
386
+ * Get session info (compatible with external callers)
470
387
  */
471
- getMemoryStats() {
472
- const sessionCount = this._sessions.size
473
- const totalMessages = Array.from(this._sessions.values()).reduce(
474
- (sum, s) => sum + s.messages.length, 0
475
- )
476
-
388
+ getSession(sessionId, cwd = process.cwd()) {
389
+ const manager = this._getSessionManager(sessionId, cwd);
390
+ const entries = manager.getEntries();
477
391
  return {
478
- sessionCount,
479
- maxSessions: this.config.maxSessions,
480
- totalMessages,
481
- ttl: this.config.sessionTTL
482
- }
392
+ id: sessionId,
393
+ messages: entries.filter(e => e.type === 'message'),
394
+ lastActive: manager.getHeader()?.timestamp ? new Date(manager.getHeader().timestamp) : null,
395
+ };
483
396
  }
484
397
 
485
398
  /**
486
- * 清理所有会话
399
+ * Start auto cleanup timer
487
400
  */
488
- clear() {
489
- const sessionIds = Array.from(this._sessions.keys())
490
- this._sessions.clear()
491
- sessionIds.forEach(id => this._events.emit('session:deleted', id))
492
- }
493
-
494
- /**
495
- * 监听会话事件
496
- */
497
- on(event, listener) {
498
- this._events.on(event, listener)
401
+ _startAutoCleanup() {
402
+ if (this._cleanupTimer) {
403
+ clearInterval(this._cleanupTimer);
404
+ }
405
+ this._cleanupTimer = setInterval(async () => {
406
+ await this._doCleanup();
407
+ }, this.config.cleanupInterval);
408
+ log.info(`Auto cleanup started (interval: ${this.config.cleanupInterval}ms)`);
499
409
  }
500
410
 
501
411
  /**
502
- * 移除会话事件监听
412
+ * Perform cleanup of old sessions and message history
503
413
  */
504
- off(event, listener) {
505
- this._events.off(event, listener)
506
- }
414
+ async _doCleanup() {
415
+ try {
416
+ const fs = require('fs');
417
+ const sessionDir = getDefaultSessionDir(process.cwd());
418
+
419
+ if (!fs.existsSync(sessionDir)) {
420
+ return;
421
+ }
507
422
 
508
- /**
509
- * 调用会话钩子
510
- * @param {Function} hook - 钩子函数
511
- * @param {Object} session - 会话对象
512
- * @private
513
- */
514
- _callHook(hook, session) {
515
- if (typeof hook === 'function') {
516
- try {
517
- const result = hook(session)
518
- if (result && typeof result.catch === 'function') {
519
- result.catch(err => log.warn('Session hook error:', err.message))
423
+ const files = fs.readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'));
424
+ let cleanedCount = 0;
425
+
426
+ for (const file of files) {
427
+ const filePath = path.join(sessionDir, file);
428
+ try {
429
+ const manager = SessionManager.open(filePath, sessionDir, process.cwd());
430
+ const entries = manager.getEntries();
431
+
432
+ // Clean up if exceeds maxHistoryLength
433
+ if (entries.length > this.config.maxHistoryLength) {
434
+ const toRemove = entries.length - this.config.maxHistoryLength;
435
+ // Keep session header, remove oldest entries
436
+ const entriesToKeep = manager.fileEntries.filter(e => e.type === 'session');
437
+ const messagesToKeep = manager.fileEntries
438
+ .filter(e => e.type !== 'session')
439
+ .slice(-this.config.maxHistoryLength);
440
+
441
+ manager.fileEntries = [...entriesToKeep, ...messagesToKeep];
442
+ manager._rewriteFile();
443
+ cleanedCount++;
444
+ log.debug(`Cleaned ${file}: removed ${toRemove} entries, kept ${this.config.maxHistoryLength}`);
445
+ }
446
+ } catch (err) {
447
+ log.warn(`Failed to cleanup session ${file}: ${err.message}`);
520
448
  }
521
- } catch (err) {
522
- log.warn('Session hook error:', err.message)
523
449
  }
450
+
451
+ if (cleanedCount > 0) {
452
+ log.info(`Auto cleanup completed: cleaned ${cleanedCount} sessions`);
453
+ }
454
+ } catch (err) {
455
+ log.error(`Auto cleanup error: ${err.message}`);
524
456
  }
525
457
  }
526
458
 
527
459
  /**
528
- * 结束会话(触发 session:ended 事件)
529
- * 当会话完成处理时调用
530
- * @param {string} sessionId - 会话 ID
460
+ * On session event listener
531
461
  */
532
- endSession(sessionId) {
533
- const session = this.getSession(sessionId)
534
- if (session) {
535
- this._events.emit('session:ended', session)
536
- if (this.config.onSessionEnd) {
537
- this._callHook(this.config.onSessionEnd, session)
538
- }
539
- }
462
+ on(event, listener) {
463
+ this._events.on(event, listener);
540
464
  }
541
465
 
542
466
  /**
543
- * 启动自动清理定时器
467
+ * Off session event listener
544
468
  */
545
- _startAutoCleanup() {
546
- if (this._cleanupTimer) {
547
- clearInterval(this._cleanupTimer)
548
- }
549
- this._cleanupTimer = setInterval(() => {
550
- const cleaned = this.cleanup()
551
- if (cleaned > 0) {
552
- //log.info(` Cleaned up ${cleaned} expired sessions`)
553
- }
554
- }, this.config.cleanupInterval)
469
+ off(event, listener) {
470
+ this._events.off(event, listener);
555
471
  }
556
472
 
557
473
  reload(framework) {
558
- this._framework = framework
474
+ this._framework = framework;
559
475
  }
560
476
 
561
477
  uninstall(framework) {
562
478
  if (this._cleanupTimer) {
563
- clearInterval(this._cleanupTimer)
564
- this._cleanupTimer = null
479
+ clearInterval(this._cleanupTimer);
480
+ this._cleanupTimer = null;
565
481
  }
566
- this._events.removeAllListeners()
567
- this._sessions.clear()
568
- this._framework = null
482
+ this._events.removeAllListeners();
483
+ this._sessionManagers.clear();
484
+ this._framework = null;
569
485
  }
570
486
  }
571
487
 
572
- module.exports = SessionPlugin
488
+ module.exports = { SessionPlugin };