chatccc 0.2.5 → 0.2.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 +39 -14
- package/package.json +1 -1
- package/src/__tests__/cards.test.ts +122 -5
- package/src/__tests__/claude-adapter.test.ts +2 -2
- package/src/__tests__/cursor-adapter.test.ts +250 -0
- package/src/__tests__/session.test.ts +2 -1
- package/src/adapters/claude-adapter.ts +3 -2
- package/src/adapters/cursor-adapter.ts +229 -0
- package/src/cards.ts +121 -22
- package/src/config.ts +62 -4
- package/src/feishu-api.ts +22 -8
- package/src/index.ts +107 -32
- package/src/session.ts +81 -18
package/src/index.ts
CHANGED
|
@@ -48,13 +48,17 @@ import {
|
|
|
48
48
|
getDefaultCwd,
|
|
49
49
|
maskAppId,
|
|
50
50
|
setDefaultCwd,
|
|
51
|
+
getRecentDirs,
|
|
52
|
+
addRecentDir,
|
|
53
|
+
sessionPrefixForTool,
|
|
54
|
+
toolDisplayName,
|
|
51
55
|
ts,
|
|
52
56
|
} from "./config.ts";
|
|
53
57
|
import { printServiceDidNotStart, printServiceRunningHint } from "./exit-banner.ts";
|
|
54
58
|
import {
|
|
55
59
|
addReaction,
|
|
56
60
|
createGroupChat,
|
|
57
|
-
|
|
61
|
+
extractSessionInfo,
|
|
58
62
|
getChatInfo,
|
|
59
63
|
getTenantAccessToken,
|
|
60
64
|
recallMessage,
|
|
@@ -66,7 +70,7 @@ import {
|
|
|
66
70
|
verifyAllPermissions,
|
|
67
71
|
reportPermissionResults,
|
|
68
72
|
} from "./feishu-api.ts";
|
|
69
|
-
import { buildHelpCard, buildStatusCard, buildProgressCard, buildCdContent, buildSessionsCard } from "./cards.ts";
|
|
73
|
+
import { buildHelpCard, buildStatusCard, buildProgressCard, buildCdContent, buildCdCard, buildSessionsCard } from "./cards.ts";
|
|
70
74
|
import { updateCardKitCard } from "./cardkit.ts";
|
|
71
75
|
import {
|
|
72
76
|
MAX_PROCESSED,
|
|
@@ -78,8 +82,7 @@ import {
|
|
|
78
82
|
resetState,
|
|
79
83
|
resumeAndPrompt,
|
|
80
84
|
sessionInfoMap,
|
|
81
|
-
|
|
82
|
-
getAdapter,
|
|
85
|
+
getAdapterForTool,
|
|
83
86
|
} from "./session.ts";
|
|
84
87
|
|
|
85
88
|
// ---------------------------------------------------------------------------
|
|
@@ -97,7 +100,11 @@ function getInnerEvent(data: Evt): InnerEvent {
|
|
|
97
100
|
return (data.event ?? data) as InnerEvent;
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
/**
|
|
104
|
+
* 将飞书消息的原始 content JSON 结构转成可读文本,保留代码块等结构信息。
|
|
105
|
+
* 未知类型直接返回 JSON 原文,让 AI 自行理解。
|
|
106
|
+
*/
|
|
107
|
+
function formatMessageContent(message: { message_type?: string; content?: string }): string {
|
|
101
108
|
const contentStr = message.content ?? "{}";
|
|
102
109
|
let content: Record<string, unknown>;
|
|
103
110
|
try { content = JSON.parse(contentStr); } catch { return ""; }
|
|
@@ -109,7 +116,36 @@ function extractText(message: { message_type?: string; content?: string }): stri
|
|
|
109
116
|
text = text.replace(/ /gi, " ");
|
|
110
117
|
return text.trim();
|
|
111
118
|
}
|
|
112
|
-
|
|
119
|
+
|
|
120
|
+
if (message.message_type === "post") {
|
|
121
|
+
return formatPostContent(content);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 其他类型(image, file, audio, media, sticker)直接给原始 JSON
|
|
125
|
+
return contentStr;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatPostContent(content: Record<string, unknown>): string {
|
|
129
|
+
const paragraphs = content.content as unknown[][];
|
|
130
|
+
if (!Array.isArray(paragraphs)) return "";
|
|
131
|
+
|
|
132
|
+
const parts: string[] = [];
|
|
133
|
+
for (const line of paragraphs) {
|
|
134
|
+
if (!Array.isArray(line)) continue;
|
|
135
|
+
for (const elem of line) {
|
|
136
|
+
const el = elem as Record<string, unknown>;
|
|
137
|
+
if (!el || typeof el !== "object") continue;
|
|
138
|
+
const t = typeof el.text === "string" ? el.text : "";
|
|
139
|
+
|
|
140
|
+
if (el.tag === "code_block") {
|
|
141
|
+
const lang = typeof el.language === "string" ? el.language : "";
|
|
142
|
+
parts.push("```" + lang + "\n" + t + "\n```");
|
|
143
|
+
} else if (el.tag === "p" || el.tag === "text") {
|
|
144
|
+
if (t) parts.push(t);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return parts.join("\n").trim();
|
|
113
149
|
}
|
|
114
150
|
|
|
115
151
|
// ---------------------------------------------------------------------------
|
|
@@ -139,8 +175,12 @@ function parseCardAction(data: unknown): CardActionResult | null {
|
|
|
139
175
|
}
|
|
140
176
|
if (!cmd) return null;
|
|
141
177
|
|
|
142
|
-
const CMD_MAP: Record<string, string> = { stop: "/stop", new: "/new", restart: "/restart", status: "/status", cd: "/cd", sessions: "/sessions" };
|
|
143
|
-
|
|
178
|
+
const CMD_MAP: Record<string, string> = { stop: "/stop", new: "/new", "new cursor": "/new cursor", restart: "/restart", status: "/status", cd: "/cd", sessions: "/sessions" };
|
|
179
|
+
let text = CMD_MAP[cmd] ?? "";
|
|
180
|
+
if (cmd === "cd" && typeof action.value === "object" && action.value !== null) {
|
|
181
|
+
const path = (action.value as Record<string, string>).path;
|
|
182
|
+
if (path) text = `/cd ${path}`;
|
|
183
|
+
}
|
|
144
184
|
if (!text) return null;
|
|
145
185
|
|
|
146
186
|
const chatId =
|
|
@@ -189,9 +229,10 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
189
229
|
let sessionCwd: string | undefined;
|
|
190
230
|
try {
|
|
191
231
|
const chatInfo = await getChatInfo(cdToken, chatId);
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
const
|
|
232
|
+
const sessionInfoResult = extractSessionInfo(chatInfo.description);
|
|
233
|
+
if (sessionInfoResult) {
|
|
234
|
+
const adapter = getAdapterForTool(sessionInfoResult.tool);
|
|
235
|
+
const info = await adapter.getSessionInfo(sessionInfoResult.sessionId);
|
|
195
236
|
sessionCwd = info?.cwd;
|
|
196
237
|
}
|
|
197
238
|
} catch { /* 非会话群或获取失败,不显示 */ }
|
|
@@ -224,6 +265,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
224
265
|
const isUpdate = !!arg && targetDir !== currentDir;
|
|
225
266
|
if (isUpdate) {
|
|
226
267
|
await setDefaultCwd(targetDir);
|
|
268
|
+
await addRecentDir(targetDir);
|
|
227
269
|
}
|
|
228
270
|
|
|
229
271
|
// Read directory entries
|
|
@@ -248,12 +290,39 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
248
290
|
return a.name.localeCompare(b.name);
|
|
249
291
|
});
|
|
250
292
|
|
|
251
|
-
|
|
252
|
-
|
|
293
|
+
if (!arg) {
|
|
294
|
+
// /cd 无参数:展示卡片(含最近使用路径按钮)
|
|
295
|
+
const recentDirs = await getRecentDirs();
|
|
296
|
+
const card = buildCdCard(targetDir, withStats, recentDirs, sessionCwd);
|
|
297
|
+
const resp = await fetch(`${BASE_URL}/im/v1/messages?receive_id_type=chat_id`, {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers: {
|
|
300
|
+
Authorization: `Bearer ${cdToken}`,
|
|
301
|
+
"Content-Type": "application/json",
|
|
302
|
+
},
|
|
303
|
+
body: JSON.stringify({ receive_id: chatId, msg_type: "interactive", content: card }),
|
|
304
|
+
});
|
|
305
|
+
const respData: Record<string, any> = await resp.json().catch(() => ({}));
|
|
306
|
+
console.log(`[${ts()}] [CD] card sent, code=${respData.code}, msgId=${respData.data?.message_id ?? "N/A"}, recentDirs=${recentDirs.length}`);
|
|
307
|
+
} else {
|
|
308
|
+
// /cd <path>:切换目录,发送文本卡片
|
|
309
|
+
const content = buildCdContent(targetDir, withStats, isUpdate, sessionCwd);
|
|
310
|
+
await sendCardReply(cdToken, chatId, "新会话工作路径", content, "blue");
|
|
311
|
+
}
|
|
253
312
|
return;
|
|
254
313
|
}
|
|
255
314
|
|
|
256
|
-
if (text === "/new") {
|
|
315
|
+
if (text === "/new" || text.startsWith("/new ")) {
|
|
316
|
+
const toolArg = text.slice(5).trim();
|
|
317
|
+
const tool = toolArg || "claude";
|
|
318
|
+
const validTools = ["claude", "cursor"];
|
|
319
|
+
if (!validTools.includes(tool)) {
|
|
320
|
+
const warnToken = await getTenantAccessToken();
|
|
321
|
+
await sendCardReply(warnToken, chatId, "Error", `未知的工具类型: "${toolArg}"。支持: claude (Claude Code), cursor (Cursor)。`, "red");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const toolLabel = toolDisplayName(tool);
|
|
325
|
+
|
|
257
326
|
if (!openId) {
|
|
258
327
|
console.log(`[${ts()}] [WARN] Cannot get sender open_id`);
|
|
259
328
|
const warnToken = await getTenantAccessToken();
|
|
@@ -265,13 +334,13 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
265
334
|
|
|
266
335
|
let sessionId: string;
|
|
267
336
|
try {
|
|
268
|
-
sessionId = await initClaudeSession();
|
|
269
|
-
console.log(`[${ts()}] [STEP 1/4]
|
|
337
|
+
sessionId = await initClaudeSession(tool);
|
|
338
|
+
console.log(`[${ts()}] [STEP 1/4] ${toolLabel} session created: ${sessionId} → OK`);
|
|
270
339
|
} catch (err) {
|
|
271
340
|
console.error(`[${ts()}] [STEP 1/4] FAIL: ${(err as Error).message}`);
|
|
272
341
|
await sendCardReply(
|
|
273
342
|
freshToken, chatId, "Error",
|
|
274
|
-
`Failed to initialize
|
|
343
|
+
`Failed to initialize ${toolLabel} session:\n${(err as Error).message}`,
|
|
275
344
|
"red"
|
|
276
345
|
);
|
|
277
346
|
return;
|
|
@@ -289,8 +358,9 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
289
358
|
|
|
290
359
|
try {
|
|
291
360
|
const initialName = `新会话-${sessionId}`;
|
|
292
|
-
|
|
293
|
-
|
|
361
|
+
const descPrefix = sessionPrefixForTool(tool);
|
|
362
|
+
await updateChatInfo(freshToken, newChatId, initialName, `${descPrefix} ${sessionId}`);
|
|
363
|
+
console.log(`[${ts()}] [STEP 3/4] Renamed group → name="${initialName}" (${toolLabel}) → OK`);
|
|
294
364
|
} catch (err) {
|
|
295
365
|
console.error(`[${ts()}] [STEP 3/4] FAIL: ${(err as Error).message}`);
|
|
296
366
|
await sendCardReply(freshToken, chatId, "Error", `Group created but rename failed:\n${(err as Error).message}`, "yellow");
|
|
@@ -298,9 +368,10 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
298
368
|
}
|
|
299
369
|
|
|
300
370
|
const cwd = await getDefaultCwd();
|
|
371
|
+
const adapter = getAdapterForTool(tool);
|
|
301
372
|
await sendCardReply(
|
|
302
|
-
freshToken, newChatId,
|
|
303
|
-
`群聊已创建,这是你的
|
|
373
|
+
freshToken, newChatId, `${toolLabel} Session Ready`,
|
|
374
|
+
`群聊已创建,这是你的 **${toolLabel}** 会话群。\n\n**Session ID:** ${sessionId}\n**工作目录:** \`${cwd}\`\n\n直接在这里发消息即可与 ${toolLabel} 对话。\n\n发送 **/sessions** 查看所有会话状态。`,
|
|
304
375
|
"green"
|
|
305
376
|
);
|
|
306
377
|
|
|
@@ -313,10 +384,13 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
313
384
|
const token = await getTenantAccessToken();
|
|
314
385
|
const chatInfo = await getChatInfo(token, chatId);
|
|
315
386
|
const description = chatInfo.description;
|
|
316
|
-
const
|
|
387
|
+
const sessionInfo = extractSessionInfo(description);
|
|
317
388
|
|
|
318
|
-
if (
|
|
319
|
-
|
|
389
|
+
if (sessionInfo) {
|
|
390
|
+
const sessionId = sessionInfo.sessionId;
|
|
391
|
+
const descriptionTool = sessionInfo.tool;
|
|
392
|
+
const toolLabel = toolDisplayName(descriptionTool);
|
|
393
|
+
console.log(`[${ts()}] [RESUME] ${toolLabel} session group detected, session=${sessionId} tool=${descriptionTool}`);
|
|
320
394
|
|
|
321
395
|
const freshToken = await getTenantAccessToken();
|
|
322
396
|
|
|
@@ -352,6 +426,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
352
426
|
const isActive = running && !running.stopped;
|
|
353
427
|
const statusText = [
|
|
354
428
|
`**Session ID:** \`${status?.sessionId ?? sessionId}\``,
|
|
429
|
+
`**工具:** ${toolLabel}`,
|
|
355
430
|
`**状态:** ${isActive ? "🟢 运行中" : "⚪ 空闲"}`,
|
|
356
431
|
`**已对话轮数:** ${status?.turnCount ?? 0}`,
|
|
357
432
|
`**模型:** ${status?.model ?? anthropicConfigDisplay(CLAUDE_MODEL)}`,
|
|
@@ -390,6 +465,7 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
390
465
|
turnCount: s.turnCount,
|
|
391
466
|
elapsedSeconds: s.active ? Math.floor((now - s.startTime) / 1000) : null,
|
|
392
467
|
model: s.model,
|
|
468
|
+
tool: s.tool,
|
|
393
469
|
}));
|
|
394
470
|
const card = buildSessionsCard(cardData);
|
|
395
471
|
const sessionsResp = await fetch(`${BASE_URL}/im/v1/messages?receive_id_type=chat_id`, {
|
|
@@ -434,13 +510,14 @@ async function handleCommand(text: string, chatId: string, openId: string, msgTi
|
|
|
434
510
|
}
|
|
435
511
|
|
|
436
512
|
try {
|
|
437
|
-
await resumeAndPrompt(sessionId, text, freshToken, chatId, msgTimestamp);
|
|
513
|
+
await resumeAndPrompt(sessionId, text, freshToken, chatId, msgTimestamp, descriptionTool);
|
|
438
514
|
console.log(`[${ts()}] [RESUME] Session ${sessionId} done`);
|
|
439
515
|
} catch (err) {
|
|
440
516
|
console.error(`[${ts()}] [RESUME] FAIL: ${(err as Error).message}`);
|
|
517
|
+
fileLog.flush();
|
|
441
518
|
await sendCardReply(
|
|
442
519
|
freshToken, chatId, "Error",
|
|
443
|
-
`Failed to resume
|
|
520
|
+
`Failed to resume ${toolLabel} session:\n${(err as Error).message}`,
|
|
444
521
|
"red"
|
|
445
522
|
);
|
|
446
523
|
}
|
|
@@ -580,7 +657,7 @@ async function main(): Promise<void> {
|
|
|
580
657
|
}
|
|
581
658
|
}
|
|
582
659
|
|
|
583
|
-
const text =
|
|
660
|
+
const text = formatMessageContent(message);
|
|
584
661
|
const sender = event.sender;
|
|
585
662
|
const openId = sender?.sender_id?.open_id ?? "";
|
|
586
663
|
const chatId = message.chat_id ?? "";
|
|
@@ -649,7 +726,6 @@ async function main(): Promise<void> {
|
|
|
649
726
|
});
|
|
650
727
|
|
|
651
728
|
if (USE_LOCAL) {
|
|
652
|
-
initAdapter();
|
|
653
729
|
console.log(`\n[启动 6/7] 本地区 relay 模式:正在连接 ${LOCAL_RELAY_URL} …`);
|
|
654
730
|
console.log(" 若失败:请先在 SDK 模式下启动主进程,或确认本机中继已在该地址监听。");
|
|
655
731
|
let localRelayOpened = false;
|
|
@@ -673,7 +749,7 @@ async function main(): Promise<void> {
|
|
|
673
749
|
const event = getInnerEvent(data);
|
|
674
750
|
const message = event.message;
|
|
675
751
|
if (!message) return;
|
|
676
|
-
const text =
|
|
752
|
+
const text = formatMessageContent(message);
|
|
677
753
|
const openId = event.sender?.sender_id?.open_id ?? "";
|
|
678
754
|
const chatId = message.chat_id ?? "";
|
|
679
755
|
appendChatLog(chatId, openId, text);
|
|
@@ -695,13 +771,12 @@ async function main(): Promise<void> {
|
|
|
695
771
|
});
|
|
696
772
|
} else {
|
|
697
773
|
resetState();
|
|
698
|
-
initAdapter();
|
|
699
774
|
|
|
700
775
|
const wsClient = new WSClient({
|
|
701
776
|
appId: APP_ID,
|
|
702
777
|
appSecret: APP_SECRET,
|
|
703
|
-
onReady: () => { resetState();
|
|
704
|
-
onReconnected: () => { resetState();
|
|
778
|
+
onReady: () => { resetState(); },
|
|
779
|
+
onReconnected: () => { resetState(); },
|
|
705
780
|
});
|
|
706
781
|
|
|
707
782
|
console.log(`\n[启动 6/7] 飞书长连接:正在通过 SDK 建立 WebSocket …`);
|
package/src/session.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
CLAUDE_EFFORT,
|
|
3
6
|
CLAUDE_MODEL,
|
|
7
|
+
SESSIONS_FILE,
|
|
8
|
+
addRecentDir,
|
|
4
9
|
anthropicConfigDisplay,
|
|
5
10
|
fileLog,
|
|
6
11
|
getDefaultCwd,
|
|
7
12
|
isSdkAnthropicDefault,
|
|
13
|
+
toolDisplayName,
|
|
8
14
|
ts,
|
|
9
15
|
} from "./config.ts";
|
|
10
16
|
import { buildProgressCard, getToolEmoji, truncateContent } from "./cards.ts";
|
|
@@ -17,6 +23,7 @@ import { sendTextReply } from "./feishu-api.ts";
|
|
|
17
23
|
import type { UnifiedBlock } from "./adapters/adapter-interface.ts";
|
|
18
24
|
import type { ToolAdapter } from "./adapters/adapter-interface.ts";
|
|
19
25
|
import { createClaudeAdapter } from "./adapters/claude-adapter.ts";
|
|
26
|
+
import { createCursorAdapter } from "./adapters/cursor-adapter.ts";
|
|
20
27
|
|
|
21
28
|
// ---------------------------------------------------------------------------
|
|
22
29
|
// Shared state (imported by index.ts)
|
|
@@ -46,6 +53,7 @@ export const sessionInfoMap = new Map<string, {
|
|
|
46
53
|
startTime: number;
|
|
47
54
|
model: string;
|
|
48
55
|
effort: string;
|
|
56
|
+
tool: string;
|
|
49
57
|
}>();
|
|
50
58
|
|
|
51
59
|
export function resetState(): void {
|
|
@@ -60,23 +68,67 @@ export function resetState(): void {
|
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
// ---------------------------------------------------------------------------
|
|
63
|
-
// Adapter
|
|
71
|
+
// Adapter: 按 tool 创建并缓存
|
|
64
72
|
// ---------------------------------------------------------------------------
|
|
65
73
|
|
|
66
|
-
|
|
74
|
+
const adapterCache = new Map<string, ToolAdapter>();
|
|
67
75
|
|
|
68
|
-
export function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
export function getAdapterForTool(tool: string): ToolAdapter {
|
|
77
|
+
const cached = adapterCache.get(tool);
|
|
78
|
+
if (cached) return cached;
|
|
79
|
+
|
|
80
|
+
let adapter: ToolAdapter;
|
|
81
|
+
if (tool === "cursor") {
|
|
82
|
+
adapter = createCursorAdapter();
|
|
83
|
+
} else {
|
|
84
|
+
adapter = createClaudeAdapter({
|
|
85
|
+
model: CLAUDE_MODEL,
|
|
86
|
+
effort: CLAUDE_EFFORT,
|
|
87
|
+
isDefault: isSdkAnthropicDefault,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
adapterCache.set(tool, adapter);
|
|
74
91
|
return adapter;
|
|
75
92
|
}
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Session tool persistence (.claude/sessions.json)
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
interface SessionToolRecord {
|
|
99
|
+
tool: string;
|
|
100
|
+
createdAt: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function loadSessionTools(): Promise<Record<string, SessionToolRecord>> {
|
|
104
|
+
try {
|
|
105
|
+
const raw = await readFile(SESSIONS_FILE, "utf-8");
|
|
106
|
+
return JSON.parse(raw);
|
|
107
|
+
} catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function saveSessionTools(data: Record<string, SessionToolRecord>): Promise<void> {
|
|
113
|
+
try {
|
|
114
|
+
await mkdir(dirname(SESSIONS_FILE), { recursive: true });
|
|
115
|
+
await writeFile(SESSIONS_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(`[${ts()}] Failed to save sessions.json: ${(err as Error).message}`);
|
|
118
|
+
fileLog.flush();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function saveSessionTool(sessionId: string, tool: string): Promise<void> {
|
|
123
|
+
const data = await loadSessionTools();
|
|
124
|
+
data[sessionId] = { tool, createdAt: Date.now() };
|
|
125
|
+
await saveSessionTools(data);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function getSessionTool(sessionId: string): Promise<string | null> {
|
|
129
|
+
const data = await loadSessionTools();
|
|
130
|
+
const record = data[sessionId];
|
|
131
|
+
return record?.tool ?? null;
|
|
80
132
|
}
|
|
81
133
|
|
|
82
134
|
// ---------------------------------------------------------------------------
|
|
@@ -153,16 +205,21 @@ export function accumulateBlockContent(
|
|
|
153
205
|
// Claude session management
|
|
154
206
|
// ---------------------------------------------------------------------------
|
|
155
207
|
|
|
156
|
-
export async function initClaudeSession(): Promise<string> {
|
|
208
|
+
export async function initClaudeSession(tool: string): Promise<string> {
|
|
157
209
|
const cwd = await getDefaultCwd();
|
|
210
|
+
const adapter = getAdapterForTool(tool);
|
|
158
211
|
console.log(
|
|
159
|
-
`[${ts()}] [STEP 1/5] Creating ${
|
|
212
|
+
`[${ts()}] [STEP 1/5] Creating ${adapter.displayName} session (model=${anthropicConfigDisplay(CLAUDE_MODEL)}, effort=${anthropicConfigDisplay(CLAUDE_EFFORT)}, cwd=${cwd})`
|
|
160
213
|
);
|
|
161
214
|
|
|
162
|
-
const result = await
|
|
215
|
+
const result = await adapter.createSession(cwd);
|
|
163
216
|
const sessionId = result.sessionId;
|
|
164
217
|
console.log(`[${ts()}] → sessionId: ${sessionId}`);
|
|
165
218
|
|
|
219
|
+
await saveSessionTool(sessionId, tool);
|
|
220
|
+
|
|
221
|
+
await addRecentDir(cwd);
|
|
222
|
+
|
|
166
223
|
return sessionId;
|
|
167
224
|
}
|
|
168
225
|
|
|
@@ -171,12 +228,14 @@ export async function resumeAndPrompt(
|
|
|
171
228
|
userText: string,
|
|
172
229
|
token: string,
|
|
173
230
|
chatId: string,
|
|
174
|
-
msgTimestamp: number
|
|
231
|
+
msgTimestamp: number,
|
|
232
|
+
tool: string,
|
|
175
233
|
): Promise<void> {
|
|
176
|
-
const
|
|
234
|
+
const adapter = getAdapterForTool(tool);
|
|
235
|
+
const info = await adapter.getSessionInfo(sessionId);
|
|
177
236
|
const cwd = info?.cwd ?? (await getDefaultCwd());
|
|
178
237
|
console.log(
|
|
179
|
-
`[${ts()}] Resuming ${
|
|
238
|
+
`[${ts()}] Resuming ${adapter.displayName} session: ${sessionId} (model=${anthropicConfigDisplay(CLAUDE_MODEL)}, effort=${anthropicConfigDisplay(CLAUDE_EFFORT)}, cwd=${cwd})`
|
|
180
239
|
);
|
|
181
240
|
|
|
182
241
|
const controller = new AbortController();
|
|
@@ -204,6 +263,7 @@ export async function resumeAndPrompt(
|
|
|
204
263
|
startTime: now,
|
|
205
264
|
model: anthropicConfigDisplay(CLAUDE_MODEL),
|
|
206
265
|
effort: anthropicConfigDisplay(CLAUDE_EFFORT),
|
|
266
|
+
tool,
|
|
207
267
|
});
|
|
208
268
|
|
|
209
269
|
let cardId: string | null = null;
|
|
@@ -304,7 +364,7 @@ export async function resumeAndPrompt(
|
|
|
304
364
|
}
|
|
305
365
|
|
|
306
366
|
try {
|
|
307
|
-
for await (const unifiedMsg of
|
|
367
|
+
for await (const unifiedMsg of adapter.prompt(sessionId, userText, cwd, controller.signal)) {
|
|
308
368
|
for (const block of unifiedMsg.blocks) {
|
|
309
369
|
accumulateBlockContent(block, state);
|
|
310
370
|
|
|
@@ -425,6 +485,7 @@ export function getAllSessionsStatus(): Array<{
|
|
|
425
485
|
startTime: number;
|
|
426
486
|
model: string;
|
|
427
487
|
effort: string;
|
|
488
|
+
tool: string;
|
|
428
489
|
}> {
|
|
429
490
|
const result: Array<{
|
|
430
491
|
chatId: string;
|
|
@@ -434,6 +495,7 @@ export function getAllSessionsStatus(): Array<{
|
|
|
434
495
|
startTime: number;
|
|
435
496
|
model: string;
|
|
436
497
|
effort: string;
|
|
498
|
+
tool: string;
|
|
437
499
|
}> = [];
|
|
438
500
|
for (const [chatId, info] of sessionInfoMap) {
|
|
439
501
|
const active = chatSessionMap.get(chatId);
|
|
@@ -445,6 +507,7 @@ export function getAllSessionsStatus(): Array<{
|
|
|
445
507
|
startTime: info.startTime,
|
|
446
508
|
model: info.model,
|
|
447
509
|
effort: info.effort,
|
|
510
|
+
tool: info.tool,
|
|
448
511
|
});
|
|
449
512
|
}
|
|
450
513
|
return result;
|