chatccc 0.2.41 → 0.2.43
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/config.sample.json +30 -29
- package/demo/ilink_echo_probe.ts +222 -222
- package/package.json +59 -59
- package/src/__tests__/cards.test.ts +22 -6
- package/src/__tests__/crash-logging.test.ts +62 -62
- package/src/__tests__/session.test.ts +124 -11
- package/src/cards.ts +5 -3
- package/src/config.ts +742 -736
- package/src/index.ts +126 -13
- package/src/session.ts +136 -7
- package/src/shared.ts +63 -63
|
@@ -311,7 +311,7 @@ describe("buildSessionsCard", () => {
|
|
|
311
311
|
|
|
312
312
|
it("returns valid JSON with session listing", () => {
|
|
313
313
|
const card = buildSessionsCard([
|
|
314
|
-
{ sessionId: "abc123", active: true, turnCount: 5, elapsedSeconds: 120, model: "Claude Opus 4.7", tool: "claude" },
|
|
314
|
+
{ sessionId: "abc123", chatName: "test-group", active: true, turnCount: 5, elapsedSeconds: 120, model: "Claude Opus 4.7", tool: "claude" },
|
|
315
315
|
]);
|
|
316
316
|
const parsed = JSON.parse(card);
|
|
317
317
|
expect(parsed.elements[0].text.content).toContain("共 **1** 个会话");
|
|
@@ -322,7 +322,7 @@ describe("buildSessionsCard", () => {
|
|
|
322
322
|
|
|
323
323
|
it("shows idle status for inactive sessions", () => {
|
|
324
324
|
const card = buildSessionsCard([
|
|
325
|
-
{ sessionId: "xyz", active: false, turnCount: 0, elapsedSeconds: null, model: "Claude Sonnet 4.6", tool: "claude" },
|
|
325
|
+
{ sessionId: "xyz", chatName: "", active: false, turnCount: 0, elapsedSeconds: null, model: "Claude Sonnet 4.6", tool: "claude" },
|
|
326
326
|
]);
|
|
327
327
|
const parsed = JSON.parse(card);
|
|
328
328
|
expect(parsed.elements[0].text.content).toContain("⚪ 空闲");
|
|
@@ -330,7 +330,7 @@ describe("buildSessionsCard", () => {
|
|
|
330
330
|
|
|
331
331
|
it("shows elapsed time for active sessions", () => {
|
|
332
332
|
const card = buildSessionsCard([
|
|
333
|
-
{ sessionId: "active123", active: true, turnCount: 3, elapsedSeconds: 95, model: "Claude Opus 4.7", tool: "claude" },
|
|
333
|
+
{ sessionId: "active123", chatName: "", active: true, turnCount: 3, elapsedSeconds: 95, model: "Claude Opus 4.7", tool: "claude" },
|
|
334
334
|
]);
|
|
335
335
|
const parsed = JSON.parse(card);
|
|
336
336
|
expect(parsed.elements[0].text.content).toContain("1分35秒");
|
|
@@ -338,8 +338,8 @@ describe("buildSessionsCard", () => {
|
|
|
338
338
|
|
|
339
339
|
it("separates Claude Code and Cursor sessions", () => {
|
|
340
340
|
const card = buildSessionsCard([
|
|
341
|
-
{ sessionId: "c1", active: false, turnCount: 1, elapsedSeconds: null, model: "(留空)", tool: "claude" },
|
|
342
|
-
{ sessionId: "c2", active: false, turnCount: 2, elapsedSeconds: null, model: "claude-opus-4-7-max", tool: "cursor" },
|
|
341
|
+
{ sessionId: "c1", chatName: "", active: false, turnCount: 1, elapsedSeconds: null, model: "(留空)", tool: "claude" },
|
|
342
|
+
{ sessionId: "c2", chatName: "", active: false, turnCount: 2, elapsedSeconds: null, model: "claude-opus-4-7-max", tool: "cursor" },
|
|
343
343
|
]);
|
|
344
344
|
const parsed = JSON.parse(card);
|
|
345
345
|
const content: string = parsed.elements[0].text.content;
|
|
@@ -349,7 +349,7 @@ describe("buildSessionsCard", () => {
|
|
|
349
349
|
|
|
350
350
|
it("omits Cursor section when no Cursor sessions", () => {
|
|
351
351
|
const card = buildSessionsCard([
|
|
352
|
-
{ sessionId: "c1", active: false, turnCount: 1, elapsedSeconds: null, model: "(留空)", tool: "claude" },
|
|
352
|
+
{ sessionId: "c1", chatName: "", active: false, turnCount: 1, elapsedSeconds: null, model: "(留空)", tool: "claude" },
|
|
353
353
|
]);
|
|
354
354
|
const parsed = JSON.parse(card);
|
|
355
355
|
const content: string = parsed.elements[0].text.content;
|
|
@@ -357,6 +357,22 @@ describe("buildSessionsCard", () => {
|
|
|
357
357
|
expect(content).not.toContain("Cursor 会话");
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
+
it("displays chatName when provided", () => {
|
|
361
|
+
const card = buildSessionsCard([
|
|
362
|
+
{ sessionId: "abc123", chatName: "帮我写代码-src", active: false, turnCount: 2, elapsedSeconds: null, model: "Claude Opus 4.7", tool: "claude" },
|
|
363
|
+
]);
|
|
364
|
+
const parsed = JSON.parse(card);
|
|
365
|
+
expect(parsed.elements[0].text.content).toContain("帮我写代码-src");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("includes /session help text in non-empty card", () => {
|
|
369
|
+
const card = buildSessionsCard([
|
|
370
|
+
{ sessionId: "abc123", chatName: "", active: false, turnCount: 2, elapsedSeconds: null, model: "Claude Opus 4.7", tool: "claude" },
|
|
371
|
+
]);
|
|
372
|
+
const parsed = JSON.parse(card);
|
|
373
|
+
expect(parsed.elements[2].text.content).toContain("/session 数字");
|
|
374
|
+
});
|
|
375
|
+
|
|
360
376
|
it("includes close button", () => {
|
|
361
377
|
const card = buildSessionsCard([]);
|
|
362
378
|
const parsed = JSON.parse(card);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { describe, it, expect, vi } from "vitest";
|
|
5
|
-
|
|
6
|
-
import { buildCrashLoggingHandlers, installCrashLogging, setupFileLogging } from "../shared.ts";
|
|
1
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, it, expect, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { buildCrashLoggingHandlers, installCrashLogging, setupFileLogging } from "../shared.ts";
|
|
7
7
|
|
|
8
8
|
describe("buildCrashLoggingHandlers", () => {
|
|
9
9
|
it("uncaughtException: 写诊断、刷新日志、调用 onFatal", () => {
|
|
@@ -144,7 +144,7 @@ describe("buildCrashLoggingHandlers", () => {
|
|
|
144
144
|
});
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
describe("installCrashLogging", () => {
|
|
147
|
+
describe("installCrashLogging", () => {
|
|
148
148
|
it("注册所有相关事件监听器", () => {
|
|
149
149
|
const before = {
|
|
150
150
|
uncaught: process.listenerCount("uncaughtException"),
|
|
@@ -209,58 +209,58 @@ describe("installCrashLogging", () => {
|
|
|
209
209
|
} finally {
|
|
210
210
|
cleanup();
|
|
211
211
|
}
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe("setupFileLogging", () => {
|
|
216
|
-
it("flush 后继续写日志不会触发 write after end,且日志已落盘", async () => {
|
|
217
|
-
const originalLog = console.log;
|
|
218
|
-
const originalError = console.error;
|
|
219
|
-
console.log = vi.fn() as never;
|
|
220
|
-
console.error = vi.fn() as never;
|
|
221
|
-
const dir = await mkdtemp(join(tmpdir(), "chatccc-log-"));
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
const fileLog = setupFileLogging(dir, "index");
|
|
225
|
-
|
|
226
|
-
console.log("before flush");
|
|
227
|
-
fileLog.flush();
|
|
228
|
-
|
|
229
|
-
expect(() => console.error("after flush")).not.toThrow();
|
|
230
|
-
fileLog.flush();
|
|
231
|
-
|
|
232
|
-
const content = await readFile(fileLog.logPath, "utf8");
|
|
233
|
-
expect(content).toContain("[LOG] before flush");
|
|
234
|
-
expect(content).toContain("[ERR] after flush");
|
|
235
|
-
} finally {
|
|
236
|
-
console.log = originalLog;
|
|
237
|
-
console.error = originalError;
|
|
238
|
-
await rm(dir, { recursive: true, force: true });
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("日志参数无法 JSON 序列化时也不会让 console 调用抛错", async () => {
|
|
243
|
-
const originalLog = console.log;
|
|
244
|
-
const originalError = console.error;
|
|
245
|
-
console.log = vi.fn() as never;
|
|
246
|
-
console.error = vi.fn() as never;
|
|
247
|
-
const dir = await mkdtemp(join(tmpdir(), "chatccc-log-"));
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
const fileLog = setupFileLogging(dir, "index");
|
|
251
|
-
const circular: Record<string, unknown> = { name: "root" };
|
|
252
|
-
circular.self = circular;
|
|
253
|
-
|
|
254
|
-
expect(() => console.log("circular", circular)).not.toThrow();
|
|
255
|
-
fileLog.flush();
|
|
256
|
-
|
|
257
|
-
const content = await readFile(fileLog.logPath, "utf8");
|
|
258
|
-
expect(content).toContain("[LOG] circular");
|
|
259
|
-
expect(content).toContain("self");
|
|
260
|
-
} finally {
|
|
261
|
-
console.log = originalLog;
|
|
262
|
-
console.error = originalError;
|
|
263
|
-
await rm(dir, { recursive: true, force: true });
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("setupFileLogging", () => {
|
|
216
|
+
it("flush 后继续写日志不会触发 write after end,且日志已落盘", async () => {
|
|
217
|
+
const originalLog = console.log;
|
|
218
|
+
const originalError = console.error;
|
|
219
|
+
console.log = vi.fn() as never;
|
|
220
|
+
console.error = vi.fn() as never;
|
|
221
|
+
const dir = await mkdtemp(join(tmpdir(), "chatccc-log-"));
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const fileLog = setupFileLogging(dir, "index");
|
|
225
|
+
|
|
226
|
+
console.log("before flush");
|
|
227
|
+
fileLog.flush();
|
|
228
|
+
|
|
229
|
+
expect(() => console.error("after flush")).not.toThrow();
|
|
230
|
+
fileLog.flush();
|
|
231
|
+
|
|
232
|
+
const content = await readFile(fileLog.logPath, "utf8");
|
|
233
|
+
expect(content).toContain("[LOG] before flush");
|
|
234
|
+
expect(content).toContain("[ERR] after flush");
|
|
235
|
+
} finally {
|
|
236
|
+
console.log = originalLog;
|
|
237
|
+
console.error = originalError;
|
|
238
|
+
await rm(dir, { recursive: true, force: true });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("日志参数无法 JSON 序列化时也不会让 console 调用抛错", async () => {
|
|
243
|
+
const originalLog = console.log;
|
|
244
|
+
const originalError = console.error;
|
|
245
|
+
console.log = vi.fn() as never;
|
|
246
|
+
console.error = vi.fn() as never;
|
|
247
|
+
const dir = await mkdtemp(join(tmpdir(), "chatccc-log-"));
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const fileLog = setupFileLogging(dir, "index");
|
|
251
|
+
const circular: Record<string, unknown> = { name: "root" };
|
|
252
|
+
circular.self = circular;
|
|
253
|
+
|
|
254
|
+
expect(() => console.log("circular", circular)).not.toThrow();
|
|
255
|
+
fileLog.flush();
|
|
256
|
+
|
|
257
|
+
const content = await readFile(fileLog.logPath, "utf8");
|
|
258
|
+
expect(content).toContain("[LOG] circular");
|
|
259
|
+
expect(content).toContain("self");
|
|
260
|
+
} finally {
|
|
261
|
+
console.log = originalLog;
|
|
262
|
+
console.error = originalError;
|
|
263
|
+
await rm(dir, { recursive: true, force: true });
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
2
5
|
import {
|
|
3
6
|
chatSessionMap,
|
|
4
7
|
sessionInfoMap,
|
|
@@ -7,9 +10,12 @@ import {
|
|
|
7
10
|
resetState,
|
|
8
11
|
getSessionStatus,
|
|
9
12
|
getAllSessionsStatus,
|
|
13
|
+
recordSessionRegistry,
|
|
10
14
|
accumulateBlockContent,
|
|
11
15
|
pickFinalReply,
|
|
12
16
|
UNKNOWN_MODEL_PLACEHOLDER,
|
|
17
|
+
_setSessionRegistryFileForTest,
|
|
18
|
+
_resetSessionRegistryFileForTest,
|
|
13
19
|
_setAdapterForToolForTest,
|
|
14
20
|
_clearAdapterCacheForTest,
|
|
15
21
|
} from "../session.ts";
|
|
@@ -196,39 +202,146 @@ describe("getSessionStatus", () => {
|
|
|
196
202
|
});
|
|
197
203
|
|
|
198
204
|
describe("getAllSessionsStatus", () => {
|
|
199
|
-
|
|
205
|
+
let registryFile = "";
|
|
206
|
+
|
|
207
|
+
beforeEach(async () => {
|
|
200
208
|
chatSessionMap.clear();
|
|
201
209
|
sessionInfoMap.clear();
|
|
210
|
+
const dir = await mkdtemp(join(tmpdir(), "chatccc-session-registry-"));
|
|
211
|
+
registryFile = join(dir, "session-registry.json");
|
|
212
|
+
_setSessionRegistryFileForTest(registryFile);
|
|
202
213
|
});
|
|
203
214
|
|
|
204
|
-
afterEach(() => {
|
|
215
|
+
afterEach(async () => {
|
|
205
216
|
_clearAdapterCacheForTest();
|
|
217
|
+
_resetSessionRegistryFileForTest();
|
|
218
|
+
if (registryFile) {
|
|
219
|
+
await rm(dirname(registryFile), { recursive: true, force: true });
|
|
220
|
+
}
|
|
206
221
|
});
|
|
207
222
|
|
|
208
223
|
it("returns empty array when no sessions", async () => {
|
|
209
224
|
await expect(getAllSessionsStatus()).resolves.toEqual([]);
|
|
210
225
|
});
|
|
211
226
|
|
|
212
|
-
it("
|
|
227
|
+
it("does not read memory-only sessions", async () => {
|
|
213
228
|
mockSessionInfo("chat1", { sessionId: "s1" });
|
|
214
229
|
mockSessionInfo("chat2", { sessionId: "s2" });
|
|
215
230
|
mockActiveSession("chat1");
|
|
231
|
+
|
|
232
|
+
const result = await getAllSessionsStatus();
|
|
233
|
+
expect(result).toEqual([]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("returns statuses from disk registry", async () => {
|
|
237
|
+
await recordSessionRegistry({
|
|
238
|
+
chatId: "chat1",
|
|
239
|
+
sessionId: "s1",
|
|
240
|
+
tool: "claude",
|
|
241
|
+
chatName: "test-chat-1",
|
|
242
|
+
turnCount: 2,
|
|
243
|
+
startTime: 1000,
|
|
244
|
+
updatedAt: 2000,
|
|
245
|
+
running: true,
|
|
246
|
+
});
|
|
247
|
+
await recordSessionRegistry({
|
|
248
|
+
chatId: "chat2",
|
|
249
|
+
sessionId: "s2",
|
|
250
|
+
tool: "claude",
|
|
251
|
+
chatName: "test-chat-2",
|
|
252
|
+
turnCount: 0,
|
|
253
|
+
startTime: 900,
|
|
254
|
+
updatedAt: 1900,
|
|
255
|
+
running: false,
|
|
256
|
+
});
|
|
257
|
+
|
|
216
258
|
const result = await getAllSessionsStatus();
|
|
217
259
|
expect(result).toHaveLength(2);
|
|
260
|
+
expect(result[0].chatId).toBe("chat1");
|
|
261
|
+
expect(result[0].active).toBe(true);
|
|
262
|
+
expect(result[0].turnCount).toBe(2);
|
|
263
|
+
expect(result[0].chatName).toBe("test-chat-1");
|
|
264
|
+
expect(result[1].chatId).toBe("chat2");
|
|
265
|
+
expect(result[1].active).toBe(false);
|
|
266
|
+
expect(result[1].chatName).toBe("test-chat-2");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("returns recent disk sessions by updatedAt desc, limited to 20", async () => {
|
|
270
|
+
for (let i = 0; i < 25; i++) {
|
|
271
|
+
await recordSessionRegistry({
|
|
272
|
+
chatId: `chat-${i}`,
|
|
273
|
+
sessionId: `sid-${i}`,
|
|
274
|
+
tool: "claude",
|
|
275
|
+
startTime: i,
|
|
276
|
+
updatedAt: 1000 + i,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const result = await getAllSessionsStatus();
|
|
281
|
+
expect(result).toHaveLength(20);
|
|
282
|
+
expect(result[0].chatId).toBe("chat-24");
|
|
283
|
+
expect(result[19].chatId).toBe("chat-5");
|
|
284
|
+
expect(result.some((r) => r.chatId === "chat-4")).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("marks disk-running sessions as active without checking chatSessionMap", async () => {
|
|
288
|
+
await recordSessionRegistry({
|
|
289
|
+
chatId: "chat1",
|
|
290
|
+
sessionId: "s1",
|
|
291
|
+
tool: "claude",
|
|
292
|
+
running: true,
|
|
293
|
+
updatedAt: 1000,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const result = await getAllSessionsStatus();
|
|
218
297
|
expect(result.find(r => r.chatId === "chat1")!.active).toBe(true);
|
|
219
|
-
expect(result.find(r => r.chatId === "chat2")!.active).toBe(false);
|
|
220
298
|
});
|
|
221
299
|
|
|
222
|
-
it("
|
|
223
|
-
|
|
224
|
-
|
|
300
|
+
it("persists chatName across updates and defaults to empty string when not set", async () => {
|
|
301
|
+
await recordSessionRegistry({
|
|
302
|
+
chatId: "chat-a",
|
|
303
|
+
sessionId: "sa",
|
|
304
|
+
tool: "claude",
|
|
305
|
+
chatName: "My Chat",
|
|
306
|
+
updatedAt: 100,
|
|
307
|
+
});
|
|
308
|
+
// Update without chatName — should keep existing
|
|
309
|
+
await recordSessionRegistry({
|
|
310
|
+
chatId: "chat-a",
|
|
311
|
+
sessionId: "sa",
|
|
312
|
+
tool: "claude",
|
|
313
|
+
updatedAt: 200,
|
|
314
|
+
});
|
|
315
|
+
const result = await getAllSessionsStatus();
|
|
316
|
+
expect(result.find(r => r.chatId === "chat-a")!.chatName).toBe("My Chat");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("chatName defaults to empty string for sessions without it", async () => {
|
|
320
|
+
await recordSessionRegistry({
|
|
321
|
+
chatId: "chat-b",
|
|
322
|
+
sessionId: "sb",
|
|
323
|
+
tool: "claude",
|
|
324
|
+
updatedAt: 100,
|
|
325
|
+
});
|
|
225
326
|
const result = await getAllSessionsStatus();
|
|
226
|
-
expect(result
|
|
327
|
+
expect(result.find(r => r.chatId === "chat-b")!.chatName).toBe("");
|
|
227
328
|
});
|
|
228
329
|
|
|
229
330
|
it("混合 claude + cursor 会话:各自取自己来源的 model/effort", async () => {
|
|
230
|
-
|
|
231
|
-
|
|
331
|
+
await recordSessionRegistry({
|
|
332
|
+
chatId: "chat-c",
|
|
333
|
+
sessionId: "sid-c",
|
|
334
|
+
tool: "claude",
|
|
335
|
+
chatName: "claude-chat",
|
|
336
|
+
updatedAt: 100,
|
|
337
|
+
});
|
|
338
|
+
await recordSessionRegistry({
|
|
339
|
+
chatId: "chat-x",
|
|
340
|
+
sessionId: "sid-x",
|
|
341
|
+
tool: "cursor",
|
|
342
|
+
chatName: "cursor-chat",
|
|
343
|
+
updatedAt: 200,
|
|
344
|
+
});
|
|
232
345
|
_setAdapterForToolForTest(
|
|
233
346
|
"cursor",
|
|
234
347
|
mockAdapter((sid) =>
|
|
@@ -473,4 +586,4 @@ describe("pickFinalReply", () => {
|
|
|
473
586
|
}),
|
|
474
587
|
).toBe("");
|
|
475
588
|
});
|
|
476
|
-
});
|
|
589
|
+
});
|
package/src/cards.ts
CHANGED
|
@@ -254,6 +254,7 @@ export function buildCdCard(
|
|
|
254
254
|
// 所有会话列表卡片(Claude Code 优先,然后 Cursor)
|
|
255
255
|
export function buildSessionsCard(sessions: Array<{
|
|
256
256
|
sessionId: string;
|
|
257
|
+
chatName: string;
|
|
257
258
|
active: boolean;
|
|
258
259
|
turnCount: number;
|
|
259
260
|
elapsedSeconds: number | null;
|
|
@@ -274,7 +275,7 @@ export function buildSessionsCard(sessions: Array<{
|
|
|
274
275
|
config: { wide_screen_mode: true },
|
|
275
276
|
header: { template: "blue", title: { content: "所有会话", tag: "plain_text" } },
|
|
276
277
|
elements: [
|
|
277
|
-
{ tag: "div", text: { tag: "lark_md", content: `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor** 或 **/new codex**
|
|
278
|
+
{ tag: "div", text: { tag: "lark_md", content: `当前没有会话记录。\n\n使用 **/new**(默认 ${defaultToolLabel})、**/new claude**、**/new cursor** 或 **/new codex** 创建新会话。\n创建后可在任意会话群内发送 **/sessions** 查看列表,用 **/session 数字** 切换会话。` } },
|
|
278
279
|
{ tag: "hr" },
|
|
279
280
|
{ tag: "action", actions: [{ tag: "button", text: { tag: "plain_text", content: "收起" }, type: "default", value: { action: "close" } }] },
|
|
280
281
|
],
|
|
@@ -291,7 +292,8 @@ export function buildSessionsCard(sessions: Array<{
|
|
|
291
292
|
extra = ` | 本轮: ${mins}分${secs}秒`;
|
|
292
293
|
}
|
|
293
294
|
const toolLabel = s.tool === "cursor" ? "Cursor" : s.tool === "codex" ? "Codex" : "Claude Code";
|
|
294
|
-
|
|
295
|
+
const namePart = s.chatName ? `**${s.chatName}** ` : "";
|
|
296
|
+
return `**${i + 1}.** ${namePart}\`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
|
|
295
297
|
};
|
|
296
298
|
|
|
297
299
|
const lines: string[] = [`共 **${sessions.length}** 个会话:`, ""];
|
|
@@ -326,7 +328,7 @@ export function buildSessionsCard(sessions: Array<{
|
|
|
326
328
|
elements: [
|
|
327
329
|
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
328
330
|
{ tag: "hr" },
|
|
329
|
-
{ tag: "div", text: { tag: "lark_md", content: "在会话群内发送 **/forget** 可重置当前会话(创建新 Session
|
|
331
|
+
{ tag: "div", text: { tag: "lark_md", content: "在会话群内发送 **/forget** 可重置当前会话(创建新 Session,保留工作目录和群聊)。\n发送 **/session 数字**(如 `/session 1`)可将当前群聊切换到列表中对应编号的会话。" } },
|
|
330
332
|
{ tag: "hr" },
|
|
331
333
|
{
|
|
332
334
|
tag: "action",
|