Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions src/claude/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,24 +438,20 @@ export class ClaudeExecutor {
? withKnowledge + input.memoryContext
: withKnowledge;
// System prompt 结构性哈希:仅包含 knowledge + base prompt(不含记忆和 historySummaries)
// 代码部署后 prompt 变化时自动使旧 session 失效,避免 resume 旧 session 丢失新工具描述
// 排除运行时动态值(process.pid 等)——仅反映代码/配置变更
// 用于诊断日志,追踪 prompt 变化。不再用于自动失效 session
// Agent SDK resume 时会使用新传入的 systemPrompt,旧 session 自然获得最新工具描述。
const hashInput = withKnowledge.replace(/\*\*当前 PID\*\*: `\d+`/, '**当前 PID**: `0`');
const systemPromptHash = createHash('sha256').update(hashInput).digest('hex').slice(0, 16);

// 自动失效:system prompt 变化后跳过 resume,起新 session(工具描述、persona 等会刷新)
// 注意:storedHash 为空(旧 session 迁移前创建)也视为不匹配,强制起新 session
let effectiveResumeId = resumeSessionId;
let resumeSkipped = false;
if (resumeSessionId && input.storedSystemPromptHash !== systemPromptHash) {
if (resumeSessionId && input.storedSystemPromptHash && input.storedSystemPromptHash !== systemPromptHash) {
logger.info(
{ sessionKey, storedHash: input.storedSystemPromptHash, currentHash: systemPromptHash },
'System prompt changed since last session — skipping resume, starting fresh',
'System prompt changed since last session — continuing with updated prompt (no longer invalidates session)',
);
effectiveResumeId = undefined;
resumeSkipped = true;
}

const effectiveResumeId = resumeSessionId;

const promptAppend = historySummaries
? withMemory + `\n\n## 历史会话摘要\n以下是该用户之前的会话记录,帮助你了解项目上下文:\n${historySummaries}`
: withMemory;
Expand Down Expand Up @@ -752,7 +748,6 @@ export class ClaudeExecutor {
durationMs,
needsRestart: workspaceChanged,
newWorkingDir,
resumeSkipped,
};
}

Expand Down Expand Up @@ -806,7 +801,6 @@ export class ClaudeExecutor {
numTurns: resultMessage.num_turns,
needsRestart: workspaceChanged,
newWorkingDir,
resumeSkipped,
};
} else {
// 错误结果
Expand All @@ -823,7 +817,6 @@ export class ClaudeExecutor {
numTurns: resultMessage.num_turns,
needsRestart: workspaceChanged,
newWorkingDir,
resumeSkipped,
};
}
}
Expand All @@ -837,7 +830,6 @@ export class ClaudeExecutor {
durationMs,
needsRestart: workspaceChanged,
newWorkingDir,
resumeSkipped,
};
}

Expand Down
4 changes: 1 addition & 3 deletions src/claude/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@ export interface ClaudeResult {
costUsd?: number;
/** 总轮数 */
numTurns?: number;
/** System prompt 结构性哈希(不含 historySummaries),用于自动失效旧 session */
/** System prompt 结构性哈希(不含 historySummaries),用于诊断日志追踪 prompt 变化 */
systemPromptHash?: string;
/** 是否需要重启 (workspace 变更后) */
needsRestart?: boolean;
/** 重启目标工作目录 */
newWorkingDir?: string;
/** resume 因 system prompt hash 不匹配被跳过 */
resumeSkipped?: boolean;
}

/** executor.execute() 的可选参数 */
Expand Down
8 changes: 0 additions & 8 deletions src/feishu/event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1311,7 +1311,6 @@ async function executeClaudeTask(

try {
// Resume 策略:activeConversationId/activeConversationCwd 已在上方提前计算
// 额外检查 systemPromptHash:代码部署后 prompt 变化时自动使旧 session 失效
const activePromptHash = threadId ? threadSession?.systemPromptHash : session.systemPromptHash;
const canResume = activeConversationId
&& (!activeConversationCwd || activeConversationCwd === workingDir);
Expand Down Expand Up @@ -1795,13 +1794,6 @@ async function executeDirectTask(
...(discussionMcp ? { additionalMcpServers: { 'discussion-tools': discussionMcp } } : {}),
});

// resume 被跳过时清除 _historyDedup,让下次 query 重新注入完整历史
// 否则新 session 只能看到 afterMsgId 之后的消息,丢失之前的上下文
if (result.resumeSkipped) {
_historyDedup.delete(sessionKey);
logger.info({ sessionKey }, 'Cleared _historyDedup due to resume skip (system prompt hash mismatch)');
}

// 保存 conversationId(下次消息可 resume)
if (result.sessionId) {
if (threadId) {
Expand Down
11 changes: 9 additions & 2 deletions src/feishu/tools/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function feishuTaskTool(getUserToken?: () => Promise<string | undefined>,
'创建和管理飞书任务 (Task v2)。',
'',
'Actions:',
'- create: 创建任务 (需要 summary, 可选 tasklists 指定归属清单)',
'- create: 创建任务 (需要 summary, 必须通过 tasklists 指定归属清单)',
'- get: 获取任务详情 (需要 task_guid)',
'- list: 查询任务列表 (支持 completed/page_size 过滤)',
'- list_tasklists: 列出可用的任务清单 (返回 guid + name)',
Expand All @@ -166,9 +166,16 @@ export function feishuTaskTool(getUserToken?: () => Promise<string | undefined>,
'- add_members: 添加任务成员 (需要 task_guid + members)',
'- remove_members: 移除任务成员 (需要 task_guid + members)',
'',
'⚠️ 创建任务时必须归入以下 4 个清单之一,根据任务内容选择最合适的清单,不确定时默认用 Bot:',
' - Bot (默认): c7f1907f-4d80-430f-837d-94ce300d2efc',
' - MCPTools: 57614ca5-c704-436a-820e-7b5b361d43de',
' - UrhoX: 1e358420-cba4-4054-9b83-d3ac24bd84bd',
' - Maker: f3d4c103-b2d7-4154-9f25-9a72e12a4bf3',
' 示例: tasklists=\'[{"tasklist_guid": "c7f1907f-4d80-430f-837d-94ce300d2efc"}]\'',
'',
'时间格式 (due/start): Unix 秒级时间戳 或 ISO 日期 (如 "2026-03-15" 或 "2026-03-15T10:00:00")',
'members 格式: JSON 数组 \'[{"id": "ou_xxx", "role": "assignee"}]\'',
'tasklists 格式: JSON 数组 \'[{"tasklist_guid": "xxx"}]\' (通过 list_tasklists 获取可用清单)',
'tasklists 格式: JSON 数组 \'[{"tasklist_guid": "xxx"}]\'',
'update_fields: 逗号分隔的字段名 (如 "summary,due,description,completed_at")',
' - completed_at: 设为当前时间表示完成任务,设为空字符串表示取消完成',
].join('\n'),
Expand Down
Loading