feat: 增强会话管理功能,添加会话状态跟踪和进程披露,优化钩子管理界面

This commit is contained in:
catlog22
2025-12-20 23:14:07 +08:00
parent fd4a15c84e
commit 775928456d
9 changed files with 356 additions and 138 deletions

View File

@@ -138,23 +138,14 @@ const HOOK_TEMPLATES = {
category: 'memory',
timeout: 5000
},
// Session Context - Progressive Disclosure (session start - recent sessions)
// Session Context - Fires once per session at startup
// Uses state file to detect first prompt, only fires once
'session-context': {
event: 'UserPromptSubmit',
matcher: '',
command: 'bash',
args: ['-c', 'curl -s -X POST -H "Content-Type: application/json" -d "{\\"type\\":\\"session-start\\",\\"sessionId\\":\\"$CLAUDE_SESSION_ID\\"}" http://localhost:3456/api/hook 2>/dev/null | jq -r ".content // empty"'],
description: 'Load recent sessions at session start (time-sorted)',
category: 'context',
timeout: 5000
},
// Session Context - Continuous Disclosure (intent matching on every prompt)
'session-context-continuous': {
event: 'UserPromptSubmit',
matcher: '',
command: 'bash',
args: ['-c', 'PROMPT=$(cat | jq -r ".prompt // empty"); curl -s -X POST -H "Content-Type: application/json" -d "{\\"type\\":\\"context\\",\\"sessionId\\":\\"$CLAUDE_SESSION_ID\\",\\"prompt\\":\\"$PROMPT\\"}" http://localhost:3456/api/hook 2>/dev/null | jq -r ".content // empty"'],
description: 'Load intent-matched sessions on every prompt (similarity-based)',
args: ['-c', 'STATE_FILE="/tmp/.ccw-session-$CLAUDE_SESSION_ID"; [ -f "$STATE_FILE" ] && exit 0; touch "$STATE_FILE"; curl -s -X POST -H "Content-Type: application/json" -d "{\\"sessionId\\":\\"$CLAUDE_SESSION_ID\\"}" http://localhost:3456/api/hook/session-context 2>/dev/null | jq -r ".content // empty"'],
description: 'Load session context once at startup (cluster overview)',
category: 'context',
timeout: 5000
}

View File

@@ -99,7 +99,7 @@ const i18n = {
// Search
'search.placeholder': 'Search...',
// Session cards
// Session cards - 3 states: planning, active, completed (archived location)
'session.status.active': 'ACTIVE',
'session.status.archived': 'ARCHIVED',
'session.status.planning': 'PLANNING',
@@ -714,10 +714,8 @@ const i18n = {
'hook.template.gitAddDesc': 'Auto stage written files',
// Hook Quick Install Templates
'hook.tpl.sessionContext': 'Session Context (Start)',
'hook.tpl.sessionContextDesc': 'Load recent sessions at session start (time-sorted)',
'hook.tpl.sessionContextContinuous': 'Session Context (Continuous)',
'hook.tpl.sessionContextContinuousDesc': 'Load intent-matched sessions on every prompt (similarity-based)',
'hook.tpl.sessionContext': 'Session Context',
'hook.tpl.sessionContextDesc': 'Load cluster overview once at session start',
'hook.tpl.codexlensSync': 'CodexLens Auto-Sync',
'hook.tpl.codexlensSyncDesc': 'Auto-update code index when files are written or edited',
'hook.tpl.ccwDashboardNotify': 'CCW Dashboard Notify',
@@ -2021,10 +2019,8 @@ const i18n = {
'hook.template.gitAddDesc': '自动暂存写入的文件',
// Hook Quick Install Templates
'hook.tpl.sessionContext': 'Session 上下文(启动)',
'hook.tpl.sessionContextDesc': '会话启动时加载最近会话(按时间排序',
'hook.tpl.sessionContextContinuous': 'Session 上下文(持续)',
'hook.tpl.sessionContextContinuousDesc': '每次提示词时加载意图匹配会话(相似度排序)',
'hook.tpl.sessionContext': 'Session 上下文',
'hook.tpl.sessionContextDesc': '会话启动时加载集群概览(仅触发一次',
'hook.tpl.codexlensSync': 'CodexLens 自动同步',
'hook.tpl.codexlensSyncDesc': '文件写入或编辑时自动更新代码索引',
'hook.tpl.ccwDashboardNotify': 'CCW 控制面板通知',

View File

@@ -102,24 +102,28 @@ function renderSessionCard(session) {
const isActive = session._isActive !== false;
const date = session.created_at;
// Detect planning status from session.status field
const isPlanning = session.status === 'planning';
// Get session status from metadata (default to 'planning' for new sessions)
// 3 states: planning → active → completed (archived)
const sessionStatus = session.status || 'planning';
const isPlanning = sessionStatus === 'planning';
// Get session type badge
const sessionType = session.type || 'workflow';
const typeBadge = sessionType !== 'workflow' ? `<span class="session-type-badge ${sessionType}">${sessionType}</span>` : '';
// Determine status badge class and text
// Priority: archived > planning > active
// Priority: archived location > planning status > active status
let statusClass, statusText;
if (!isActive) {
// Archived sessions always show as ARCHIVED regardless of status field
// Archived sessions (completed) always show as ARCHIVED
statusClass = 'archived';
statusText = t('session.status.archived');
} else if (isPlanning) {
// Planning state - session created but not yet executed
statusClass = 'planning';
statusText = t('session.status.planning');
} else {
// Active state - session is being executed
statusClass = 'active';
statusText = t('session.status.active');
}

View File

@@ -101,7 +101,6 @@ async function renderHookManager() {
<div class="hook-templates-grid grid grid-cols-1 md:grid-cols-2 gap-4">
${renderQuickInstallCard('session-context', t('hook.tpl.sessionContext'), t('hook.tpl.sessionContextDesc'), 'UserPromptSubmit', '')}
${renderQuickInstallCard('session-context-continuous', t('hook.tpl.sessionContextContinuous'), t('hook.tpl.sessionContextContinuousDesc'), 'UserPromptSubmit', '')}
${renderQuickInstallCard('codexlens-update', t('hook.tpl.codexlensSync'), t('hook.tpl.codexlensSyncDesc'), 'PostToolUse', 'Write|Edit')}
${renderQuickInstallCard('ccw-notify', t('hook.tpl.ccwDashboardNotify'), t('hook.tpl.ccwDashboardNotifyDesc'), 'PostToolUse', 'Write')}
${renderQuickInstallCard('log-tool', t('hook.tpl.toolLogger'), t('hook.tpl.toolLoggerDesc'), 'PostToolUse', 'All')}
@@ -506,11 +505,37 @@ async function uninstallHookTemplate(templateId) {
const template = HOOK_TEMPLATES[templateId];
if (!template) return;
// Extract unique identifier from template args for matching
// Template args format: ['-c', 'actual command...']
const templateArgs = template.args || [];
const templateFullCmd = templateArgs.length > 0 ? templateArgs.join(' ') : '';
// Define unique patterns for each template type
const uniquePatterns = {
'session-context': 'api/hook/session-context',
'codexlens-update': 'codexlens update',
'ccw-notify': 'api/hook',
'log-tool': 'tool-usage.log',
'lint-check': 'eslint',
'git-add': 'git add',
'memory-file-read': 'memory track',
'memory-file-write': 'memory track',
'memory-prompt-track': 'memory track'
};
const uniquePattern = uniquePatterns[templateId] || template.command;
// Helper to check if a hook matches the template
const matchesTemplate = (h) => {
const hookCmd = h.hooks?.[0]?.command || h.command || '';
return hookCmd.includes(uniquePattern);
};
// Find and remove from project hooks
const projectHooks = hookConfig.project?.hooks?.[template.event];
if (projectHooks) {
const hookList = Array.isArray(projectHooks) ? projectHooks : [projectHooks];
const index = hookList.findIndex(h => h.command === template.command);
const index = hookList.findIndex(matchesTemplate);
if (index !== -1) {
await removeHook('project', template.event, index);
return;
@@ -521,12 +546,14 @@ async function uninstallHookTemplate(templateId) {
const globalHooks = hookConfig.global?.hooks?.[template.event];
if (globalHooks) {
const hookList = Array.isArray(globalHooks) ? globalHooks : [globalHooks];
const index = hookList.findIndex(h => h.command === template.command);
const index = hookList.findIndex(matchesTemplate);
if (index !== -1) {
await removeHook('global', template.event, index);
return;
}
}
showRefreshToast('Hook not found', 'error');
}
function attachHookEventListeners() {