@wu529778790/open-im 1.11.1-beta.5 → 1.11.1-beta.6

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.
package/README.md CHANGED
@@ -48,10 +48,10 @@ After `start`, the CLI prints the dashboard URL (default **`http://127.0.0.1:392
48
48
  | --- | --- |
49
49
  | `/help` | Help |
50
50
  | `/new` | New AI session |
51
- | `/sessions` | Session history |
52
- | `/resume <N>` | Resume by list number |
51
+ | `/sessions` | Session history with preview |
52
+ | `/resume [N]` | Resume session (no arg = most recent) |
53
53
  | `/status` | AI + session info |
54
- | `/cd` / `/pwd` | Working directory |
54
+ | `/cd` / `/pwd` | Switch work dir (auto-resumes that dir's session) |
55
55
  | `/allow` / `/y`, `/deny` / `/n` | Permission prompts |
56
56
 
57
57
  ## Session continuity
package/README.zh-CN.md CHANGED
@@ -48,10 +48,10 @@ npx @wu529778790/open-im start
48
48
  | --- | --- |
49
49
  | `/help` | 帮助 |
50
50
  | `/new` | 新 AI 会话 |
51
- | `/sessions` | 历史会话 |
52
- | `/resume <序号>` | 按列表序号恢复 |
51
+ | `/sessions` | 历史会话(含摘要预览) |
52
+ | `/resume [序号]` | 恢复会话(无参数恢复最近一条) |
53
53
  | `/status` | AI 与会话信息 |
54
- | `/cd` / `/pwd` | 工作目录 |
54
+ | `/cd` / `/pwd` | 切换工作目录(自动恢复该目录的历史会话) |
55
55
  | `/allow` / `/y`、`/deny` / `/n` | 权限确认 |
56
56
 
57
57
  ## 会话接力
@@ -3,6 +3,29 @@ import { escapePathForMarkdown } from '../shared/utils.js';
3
3
  import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
4
4
  import { createLogger } from '../logger.js';
5
5
  const log = createLogger('Commands');
6
+ function formatRelativeTime(ts) {
7
+ const sec = Math.floor((Date.now() - ts) / 1000);
8
+ if (sec < 60)
9
+ return '刚刚';
10
+ const min = Math.floor(sec / 60);
11
+ if (min < 60)
12
+ return `${min}分钟前`;
13
+ const hr = Math.floor(min / 60);
14
+ if (hr < 24)
15
+ return `${hr}小时前`;
16
+ const day = Math.floor(hr / 24);
17
+ if (day === 1)
18
+ return '昨天';
19
+ if (day < 30)
20
+ return `${day}天前`;
21
+ return new Date(ts).toLocaleDateString('zh-CN');
22
+ }
23
+ function truncatePreview(msg, maxLen = 30) {
24
+ if (!msg)
25
+ return '新会话';
26
+ const firstLine = msg.split('\n')[0].trim();
27
+ return firstLine.length > maxLen ? firstLine.slice(0, maxLen) + '...' : firstLine;
28
+ }
6
29
  import { AsyncLocalStorage } from 'node:async_hooks';
7
30
  import { execFile } from 'node:child_process';
8
31
  import { readdirSync } from 'node:fs';
@@ -84,7 +107,7 @@ export class CommandHandler {
84
107
  '/help - 显示帮助',
85
108
  '/new - 开始新会话(AI 上下文重置)',
86
109
  '/sessions - 查看历史会话',
87
- '/resume <序号> - 恢复历史会话',
110
+ '/resume [序号] - 恢复历史会话(无参数恢复最近一条)',
88
111
  '/status - 显示状态',
89
112
  '/cd <路径> - 切换工作目录',
90
113
  '/pwd - 当前工作目录',
@@ -101,23 +124,44 @@ export class CommandHandler {
101
124
  }
102
125
  const lines = ['📋 会话列表:', ''];
103
126
  history.forEach((entry, i) => {
104
- lines.push(`${i + 1}. ${entry.convId} · ${entry.totalTurns}轮`);
127
+ const preview = truncatePreview(entry.firstMessage);
128
+ const time = entry.createdAt ? ` · ${formatRelativeTime(entry.createdAt)}` : '';
129
+ lines.push(`${i + 1}. ${preview} · ${entry.totalTurns}轮${time}`);
105
130
  });
106
131
  if (active) {
107
132
  const num = history.length + 1;
108
- lines.push(`▸ ${num}. ${active.convId} · ${active.totalTurns}轮(当前)`);
133
+ const preview = truncatePreview(active.firstMessage);
134
+ lines.push(`▸ ${num}. ${preview} · ${active.totalTurns}轮(当前)`);
109
135
  }
110
- lines.push('', '使用 /resume <序号> 恢复历史会话');
136
+ lines.push('', '使用 /resume <序号> 恢复,或 /resume 恢复最近一条');
111
137
  await this.replySender().sendTextReply(chatId, lines.join('\n'));
112
138
  return true;
113
139
  }
114
140
  async handleResume(chatId, userId, arg, _platform) {
141
+ const history = this.deps.sessionManager.listConvHistory(userId);
142
+ // /resume (no arg) — resume the most recent session
143
+ if (!arg) {
144
+ if (history.length === 0) {
145
+ await this.replySender().sendTextReply(chatId, '没有可恢复的历史会话。');
146
+ return true;
147
+ }
148
+ const entry = history[history.length - 1];
149
+ this.deps.requestQueue.cancelUser(userId);
150
+ const ok = this.deps.sessionManager.resumeConv(userId, entry.convId);
151
+ if (ok) {
152
+ const preview = truncatePreview(entry.firstMessage);
153
+ await this.replySender().sendTextReply(chatId, `✅ 已恢复最近会话: ${preview}(${entry.totalTurns}轮)\n继续发消息即可。`);
154
+ }
155
+ else {
156
+ await this.replySender().sendTextReply(chatId, '❌ 恢复会话失败,请重试。');
157
+ }
158
+ return true;
159
+ }
115
160
  const index = parseInt(arg, 10);
116
161
  if (isNaN(index) || index < 1) {
117
- await this.replySender().sendTextReply(chatId, '用法: /resume <序号>\n\n使用 /sessions 查看会话列表。');
162
+ await this.replySender().sendTextReply(chatId, '用法: /resume [序号]\n\n不带序号则恢复最近一条会话。');
118
163
  return true;
119
164
  }
120
- const history = this.deps.sessionManager.listConvHistory(userId);
121
165
  if (index > history.length) {
122
166
  await this.replySender().sendTextReply(chatId, `序号 ${index} 无效,共 ${history.length} 个历史会话。`);
123
167
  return true;
@@ -126,7 +170,8 @@ export class CommandHandler {
126
170
  this.deps.requestQueue.cancelUser(userId);
127
171
  const ok = this.deps.sessionManager.resumeConv(userId, entry.convId);
128
172
  if (ok) {
129
- await this.replySender().sendTextReply(chatId, `✅ 已恢复会话 ${index} (${entry.convId}),共 ${entry.totalTurns}轮对话。\n继续发消息即可。`);
173
+ const preview = truncatePreview(entry.firstMessage);
174
+ await this.replySender().sendTextReply(chatId, `✅ 已恢复会话: ${preview}(${entry.totalTurns}轮)\n继续发消息即可。`);
130
175
  }
131
176
  else {
132
177
  await this.replySender().sendTextReply(chatId, '❌ 恢复会话失败,请重试。');
@@ -4,6 +4,7 @@ interface ConvHistoryEntry {
4
4
  totalTurns: number;
5
5
  createdAt: number;
6
6
  workDir?: string;
7
+ firstMessage?: string;
7
8
  }
8
9
  export declare function resolveWorkDirInput(baseDir: string, targetDir: string): string;
9
10
  export declare class SessionManager {
@@ -35,10 +36,13 @@ export declare class SessionManager {
35
36
  getActiveConvInfo(userId: string): {
36
37
  convId: string;
37
38
  totalTurns: number;
39
+ firstMessage?: string;
38
40
  } | undefined;
39
41
  resumeConv(userId: string, convId: string): boolean;
40
42
  addTurns(userId: string, turns: number): number;
41
43
  addTurnsForThread(userId: string, threadId: string, turns: number): number;
44
+ /** Save the first message preview for the current conversation (only on first turn). */
45
+ setConvPreview(userId: string, preview: string): void;
42
46
  getModel(userId: string, threadId?: string): string | undefined;
43
47
  setModel(userId: string, model: string | undefined, threadId?: string): void;
44
48
  private resolveAndValidate;
@@ -125,6 +125,7 @@ export class SessionManager {
125
125
  totalTurns: s.totalTurns ?? 0,
126
126
  createdAt: Date.now(),
127
127
  workDir: currentDir,
128
+ firstMessage: s.firstMessage,
128
129
  });
129
130
  if (s.convHistory.length > 10)
130
131
  s.convHistory = s.convHistory.slice(-10);
@@ -152,6 +153,7 @@ export class SessionManager {
152
153
  else {
153
154
  s.sessionIds = {};
154
155
  s.activeConvId = randomBytes(4).toString('hex');
156
+ s.firstMessage = undefined;
155
157
  }
156
158
  s.workDir = realPath;
157
159
  }
@@ -180,6 +182,7 @@ export class SessionManager {
180
182
  totalTurns: s.totalTurns ?? 0,
181
183
  createdAt: Date.now(),
182
184
  workDir: s.workDir,
185
+ firstMessage: s.firstMessage,
183
186
  });
184
187
  // Keep last 10 entries
185
188
  if (s.convHistory.length > 10)
@@ -188,6 +191,7 @@ export class SessionManager {
188
191
  s.sessionIds = {};
189
192
  s.activeConvId = randomBytes(4).toString('hex');
190
193
  s.totalTurns = 0;
194
+ s.firstMessage = undefined;
191
195
  this.flushSync();
192
196
  log.info(`New session for user ${userId}: oldConvId=${oldConvId}, oldSessionIds=${JSON.stringify(oldSessionIds)}, newConvId=${s.activeConvId}, sessionIds={}`);
193
197
  return true;
@@ -218,7 +222,7 @@ export class SessionManager {
218
222
  const s = this.sessions.get(userId);
219
223
  if (!s?.activeConvId)
220
224
  return undefined;
221
- return { convId: s.activeConvId, totalTurns: s.totalTurns ?? 0 };
225
+ return { convId: s.activeConvId, totalTurns: s.totalTurns ?? 0, firstMessage: s.firstMessage };
222
226
  }
223
227
  resumeConv(userId, convId) {
224
228
  const s = this.sessions.get(userId);
@@ -238,6 +242,7 @@ export class SessionManager {
238
242
  totalTurns: s.totalTurns ?? 0,
239
243
  createdAt: Date.now(),
240
244
  workDir: s.workDir,
245
+ firstMessage: s.firstMessage,
241
246
  });
242
247
  }
243
248
  // Remove target from history
@@ -245,6 +250,7 @@ export class SessionManager {
245
250
  // Restore target as active
246
251
  s.activeConvId = entry.convId;
247
252
  s.totalTurns = entry.totalTurns;
253
+ s.firstMessage = entry.firstMessage;
248
254
  s.sessionIds = {};
249
255
  // Restore sessionIds from convSessionMap
250
256
  for (const toolId of ['claude', 'codex', 'codebuddy']) {
@@ -280,6 +286,14 @@ export class SessionManager {
280
286
  this.save();
281
287
  return t.totalTurns;
282
288
  }
289
+ /** Save the first message preview for the current conversation (only on first turn). */
290
+ setConvPreview(userId, preview) {
291
+ const s = this.sessions.get(userId);
292
+ if (!s || (s.totalTurns ?? 0) > 0)
293
+ return;
294
+ s.firstMessage = preview;
295
+ this.save();
296
+ }
283
297
  getModel(userId, threadId) {
284
298
  const s = this.sessions.get(userId);
285
299
  if (threadId) {
@@ -153,6 +153,7 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
153
153
  const aiCommand = resolvePlatformAiCommand(config, ctx.platform);
154
154
  const startRun = () => {
155
155
  log.info(`[AITask] Starting: userId=${ctx.userId}, initialSessionId=${currentSessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
156
+ sessionManager.setConvPreview(ctx.userId, prompt.slice(0, 80));
156
157
  emitStructuredEvent('AITask', 'ai.task.start', {
157
158
  platform: ctx.platform,
158
159
  taskKey: hashUserId(ctx.taskKey),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.1-beta.5",
3
+ "version": "1.11.1-beta.6",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",