mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 11:53:10 +08:00
feat(im): resume project session history (#1286)
Add shared project and session selection across IM adapters, preserve bindings on failed restoration, and synchronize permissions with desktop clients. Fixes #1286
This commit is contained in:
@@ -16,6 +16,8 @@ import {
|
||||
parsePermissionCommand,
|
||||
} from '../common/permission.js'
|
||||
import { SessionStore } from '../common/session-store.js'
|
||||
import { syncImPermissionState } from '../common/permission-sync.js'
|
||||
import { SessionSelectionController } from '../common/session-selection.js'
|
||||
import { createAdapterClient } from '../common/adapter-client.js'
|
||||
import { restoreStoredSessionBinding } from '../common/session-recovery.js'
|
||||
import { isAllowedUser, tryPair } from '../common/pairing.js'
|
||||
@@ -66,6 +68,21 @@ attachmentStore.gc().catch((err) => {
|
||||
console.warn('[WeChat] AttachmentStore.gc failed:', err instanceof Error ? err.message : err)
|
||||
})
|
||||
|
||||
const sessionSelectionController = new SessionSelectionController({
|
||||
httpClient,
|
||||
bridge,
|
||||
sessionStore,
|
||||
sendNotice: async (chatId, text) => { await sendText(chatId, text) },
|
||||
onServerMessage: handleServerMessage,
|
||||
clearTransientState: clearTransientChatState,
|
||||
clearProjectSelection: (chatId) => { pendingProjectSelection.delete(chatId) },
|
||||
isBusy: (chatId) => {
|
||||
const runtime = getRuntimeState(chatId)
|
||||
return runtime.state !== 'idle' || runtime.pendingPermissionCount > 0
|
||||
|| (pendingPermissions.get(chatId)?.size ?? 0) > 0
|
||||
},
|
||||
})
|
||||
|
||||
type ChatRuntimeState = {
|
||||
state: 'idle' | 'thinking' | 'streaming' | 'tool_executing' | 'permission_pending'
|
||||
verb?: string
|
||||
@@ -277,6 +294,7 @@ async function ensureSession(chatId: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
async function createSessionForChat(chatId: string, workDir: string): Promise<boolean> {
|
||||
sessionSelectionController.clear(chatId)
|
||||
try {
|
||||
bridge.resetSession(chatId)
|
||||
clearTransientChatState(chatId)
|
||||
@@ -297,6 +315,7 @@ async function createSessionForChat(chatId: string, workDir: string): Promise<bo
|
||||
}
|
||||
|
||||
async function showProjectPicker(chatId: string): Promise<void> {
|
||||
sessionSelectionController.clear(chatId)
|
||||
try {
|
||||
const projects = await httpClient.listRecentProjects()
|
||||
if (projects.length === 0) {
|
||||
@@ -315,6 +334,7 @@ async function showProjectPicker(chatId: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function startNewSession(chatId: string, query?: string): Promise<void> {
|
||||
sessionSelectionController.clear(chatId)
|
||||
bridge.resetSession(chatId)
|
||||
sessionStore.delete(chatId)
|
||||
clearTransientChatState(chatId)
|
||||
@@ -351,10 +371,12 @@ async function startNewSession(chatId: string, query?: string): Promise<void> {
|
||||
|
||||
async function handleServerMessage(chatId: string, msg: ServerMessage): Promise<void> {
|
||||
const runtime = getRuntimeState(chatId)
|
||||
if (syncImPermissionState(chatId, msg, runtime, pendingPermissions)) return
|
||||
|
||||
switch (msg.type) {
|
||||
case 'connected':
|
||||
break
|
||||
|
||||
case 'status':
|
||||
runtime.state = msg.state
|
||||
runtime.verb = typeof msg.verb === 'string' ? msg.verb : undefined
|
||||
@@ -491,6 +513,7 @@ async function routeUserMessage(message: WechatMessage): Promise<void> {
|
||||
}
|
||||
clearTransientChatState(chatId)
|
||||
const sent = bridge.sendUserMessage(chatId, '/clear')
|
||||
if (sent) getRuntimeState(chatId).state = 'thinking'
|
||||
await sendText(chatId, sent ? '已清空当前会话上下文。' : '无法发送 /clear,请先发送 /new 重新连接会话。')
|
||||
return
|
||||
}
|
||||
@@ -511,6 +534,8 @@ async function routeUserMessage(message: WechatMessage): Promise<void> {
|
||||
await sendText(chatId, sent ? `${formatPermissionDecisionStatus(permissionDecision)}。` : '权限响应发送失败,请检查会话状态。')
|
||||
return
|
||||
}
|
||||
if (!hasAttachments && await sessionSelectionController.handleInput(chatId, text)) return
|
||||
|
||||
if (!hasAttachments && pendingProjectSelection.has(chatId)) {
|
||||
await startNewSession(chatId, text)
|
||||
return
|
||||
@@ -523,6 +548,7 @@ async function routeUserMessage(message: WechatMessage): Promise<void> {
|
||||
if (!effectiveText && attachments.length === 0) return
|
||||
typingController.start(chatId)
|
||||
const sent = bridge.sendUserMessage(chatId, effectiveText, attachments.length ? attachments : undefined)
|
||||
if (sent) getRuntimeState(chatId).state = 'thinking'
|
||||
if (!sent) await sendText(chatId, '消息发送失败,连接可能已断开。请发送 /new 重新开始。')
|
||||
})
|
||||
}
|
||||
@@ -612,9 +638,9 @@ function redactChatId(chatId: string): string {
|
||||
|
||||
console.log('[WeChat] Starting adapter...')
|
||||
console.log(`[WeChat] Account: ${accountId}`)
|
||||
void pollLoop()
|
||||
if (import.meta.main || process.argv.includes('--wechat')) void pollLoop()
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
if (import.meta.main || process.argv.includes('--wechat')) process.on('SIGINT', () => {
|
||||
console.log('[WeChat] Shutting down...')
|
||||
stopped = true
|
||||
typingController.destroy()
|
||||
@@ -622,3 +648,5 @@ process.on('SIGINT', () => {
|
||||
dedup.destroy()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
export { bridge, dedup, sessionStore, sessionSelectionController, handleServerMessage, getRuntimeState, clearTransientChatState, createSessionForChat, showProjectPicker, routeUserMessage, startNewSession, typingController }
|
||||
|
||||
Reference in New Issue
Block a user