fix: restore project handoff records
This commit is contained in:
@@ -254,12 +254,14 @@ export function filterRuntimeByProject(runtime = normalizeRuntime(), projectPath
|
||||
...threadIds,
|
||||
...runtime.threads.filter((thread) => threadBelongsToProject(thread, targetPath)).map((thread) => thread.id),
|
||||
])
|
||||
const projectMainThreadIds = new Set(
|
||||
runtime.threads.filter((thread) => projectThreadIds.has(thread.id) && isMainThread(thread)).map((thread) => thread.id),
|
||||
)
|
||||
const agentIdsWithProjectMainEdge = buildAgentIdsWithProjectMainEdge(runtime.edges, threadIds, projectMainThreadIds)
|
||||
const threads = runtime.threads.filter((thread) => threadIds.has(thread.id))
|
||||
const goals = runtime.goals.filter((goal) => threadIds.has(goal.threadId) || projectThreadIds.has(goal.threadId))
|
||||
const edges = runtime.edges.filter((edge) =>
|
||||
(threadIds.has(edge.fromThreadId) || threadIds.has(edge.toThreadId)) &&
|
||||
projectThreadIds.has(edge.fromThreadId) &&
|
||||
projectThreadIds.has(edge.toThreadId),
|
||||
edgeBelongsToProjectFlow(edge, { threadIds, projectThreadIds, threadByID, targetPath, agentIdsWithProjectMainEdge }),
|
||||
)
|
||||
const handoffs = edges.map((edge) => normalizeProjectHandoff(edge, { agentByID, threadByID, source: runtime.source }))
|
||||
const phaseGroups = buildPhaseGroups(agents)
|
||||
@@ -286,6 +288,55 @@ export function filterRuntimeByProject(runtime = normalizeRuntime(), projectPath
|
||||
}
|
||||
}
|
||||
|
||||
function buildAgentIdsWithProjectMainEdge(edges, threadIds, projectMainThreadIds) {
|
||||
const agentIds = new Set()
|
||||
for (const edge of edges) {
|
||||
if (projectMainThreadIds.has(edge.fromThreadId) && threadIds.has(edge.toThreadId)) {
|
||||
agentIds.add(edge.toThreadId)
|
||||
}
|
||||
if (projectMainThreadIds.has(edge.toThreadId) && threadIds.has(edge.fromThreadId)) {
|
||||
agentIds.add(edge.fromThreadId)
|
||||
}
|
||||
}
|
||||
return agentIds
|
||||
}
|
||||
|
||||
function edgeBelongsToProjectFlow(edge, { threadIds, projectThreadIds, threadByID, targetPath, agentIdsWithProjectMainEdge }) {
|
||||
const fromIsAgent = threadIds.has(edge.fromThreadId)
|
||||
const toIsAgent = threadIds.has(edge.toThreadId)
|
||||
const fromIsProjectThread = projectThreadIds.has(edge.fromThreadId)
|
||||
const toIsProjectThread = projectThreadIds.has(edge.toThreadId)
|
||||
const fromThread = threadByID.get(edge.fromThreadId)
|
||||
const toThread = threadByID.get(edge.toThreadId)
|
||||
const fromIsMain = mainThreadCanJoinProjectFlow(fromThread, targetPath, agentIdsWithProjectMainEdge, edge.toThreadId)
|
||||
const toIsMain = mainThreadCanJoinProjectFlow(toThread, targetPath, agentIdsWithProjectMainEdge, edge.fromThreadId)
|
||||
|
||||
if (fromIsAgent && toIsAgent) {
|
||||
return true
|
||||
}
|
||||
if (fromIsAgent && (toIsProjectThread || toIsMain)) {
|
||||
return true
|
||||
}
|
||||
if (toIsAgent && (fromIsProjectThread || fromIsMain)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function mainThreadCanJoinProjectFlow(thread, targetPath, agentIdsWithProjectMainEdge, agentThreadId) {
|
||||
if (!isMainThread(thread)) {
|
||||
return false
|
||||
}
|
||||
if (threadBelongsToProject(thread, targetPath)) {
|
||||
return true
|
||||
}
|
||||
if (agentIdsWithProjectMainEdge.has(agentThreadId)) {
|
||||
return false
|
||||
}
|
||||
const cwd = normalizePath(thread?.cwd)
|
||||
return Boolean(cwd && targetPath.startsWith(`${cwd}/`))
|
||||
}
|
||||
|
||||
function normalizeProjectHandoff(edge, context) {
|
||||
const source = normalizeSource(edge.source, context.source)
|
||||
const status = normalizeSpawnStatus(edge.reason || edge.status) || 'unknown'
|
||||
@@ -528,11 +579,11 @@ function enrichProjectWorkflow(agent, targetPath) {
|
||||
function extractWorkflowBatchName(values) {
|
||||
for (const value of values) {
|
||||
const text = String(value || '')
|
||||
const explicit = text.match(/工作流批次\s*[::]\s*([^\]\n;]+)/)
|
||||
const explicit = text.match(/工作流批次\s*[::]\s*([^\]`\n;]+)/)
|
||||
if (explicit) {
|
||||
return explicit[1].trim()
|
||||
return cleanExplicitWorkflowBatchName(explicit[1])
|
||||
}
|
||||
const version = text.match(/\b(v[0-9]+(?:\.[0-9]+)+)\s*([^\]\n;,,]*)/i)
|
||||
const version = text.match(/\b(v[0-9]+(?:\.[0-9]+)+)\s*([^\]`\n;,,。::/、]*)/i)
|
||||
if (version && /重构|升级|优化|修复|初始|搭建|发布/.test(version[2] || '')) {
|
||||
return `${version[1]} ${version[2].trim()}`.trim()
|
||||
}
|
||||
@@ -540,6 +591,12 @@ function extractWorkflowBatchName(values) {
|
||||
return ''
|
||||
}
|
||||
|
||||
function cleanExplicitWorkflowBatchName(value) {
|
||||
return String(value || '')
|
||||
.replace(/\s*[\/、]\s*(?:优化阶段|阶段)\s*[0-9一二三四五六七八九十]+.*$/, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function normalizeWorkflowBatchName(value) {
|
||||
return String(value || '默认工作流').replace(/^(v[0-9]+(?:\.[0-9]+)+)([^\s].*)$/, '$1 $2').trim()
|
||||
}
|
||||
|
||||
@@ -320,6 +320,94 @@ test('bare subagent threads are not treated as main supervision threads', () =>
|
||||
assert.equal(projectRuntime.handoffs[0].directionLabel, '子智能体交接')
|
||||
})
|
||||
|
||||
test('keeps main thread handoffs to project agents even when the main thread has only ancestor cwd', () => {
|
||||
const runtime = normalizeRuntime({
|
||||
items: [
|
||||
{
|
||||
id: 'main',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '我想要一个图形页面去管理 code 文件夹下的项目',
|
||||
threadSource: 'user',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'reviewer',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '请审查 `/Users/yoilun/Code/codex-agent-manager` 当前未提交改动。背景:用户要求新增批次切换。',
|
||||
agentNickname: 'Mencius',
|
||||
agentRole: '代码审查员',
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ fromThreadId: 'main', toThreadId: 'reviewer', reason: 'closed' },
|
||||
],
|
||||
source: { kind: 'sqlite_readonly', confidence: 'high' },
|
||||
})
|
||||
|
||||
const projectRuntime = filterRuntimeByProject(runtime, '/Users/yoilun/Code/codex-agent-manager')
|
||||
|
||||
assert.equal(projectRuntime.agents.length, 1)
|
||||
assert.equal(projectRuntime.handoffs.length, 1)
|
||||
assert.equal(projectRuntime.handoffs[0].directionLabel, '主线程派发')
|
||||
assert.equal(projectRuntime.workflowBatches[0].handoffCount, 1)
|
||||
assert.deepEqual(projectRuntime.workflowBatches[0].phases[0].handoffs.map((handoff) => handoff.to), [
|
||||
'Mencius / 代码审查员',
|
||||
])
|
||||
})
|
||||
|
||||
test('keeps parent main handoff when another project main is unrelated to that agent', () => {
|
||||
const runtime = normalizeRuntime({
|
||||
items: [
|
||||
{
|
||||
id: 'project-main',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '另一个明确提到 /Users/yoilun/Code/codex-agent-manager 的用户线程,但没有派发这个智能体',
|
||||
threadSource: 'user',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'parent-main',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '继续整个项目',
|
||||
threadSource: 'user',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'reviewer',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '请审查 `/Users/yoilun/Code/codex-agent-manager` 当前 bugfix。',
|
||||
agentNickname: 'Hubble',
|
||||
agentRole: '代码审查员',
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'other-agent',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '阶段 2:另一个 /Users/yoilun/Code/codex-agent-manager 智能体',
|
||||
agentNickname: 'Noether',
|
||||
agentRole: '后端架构师',
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ fromThreadId: 'parent-main', toThreadId: 'reviewer', reason: 'open' },
|
||||
{ fromThreadId: 'project-main', toThreadId: 'other-agent', reason: 'closed' },
|
||||
],
|
||||
source: { kind: 'sqlite_readonly', confidence: 'high' },
|
||||
})
|
||||
|
||||
const projectRuntime = filterRuntimeByProject(runtime, '/Users/yoilun/Code/codex-agent-manager')
|
||||
|
||||
assert.deepEqual(projectRuntime.handoffs.map((handoff) => `${handoff.from} -> ${handoff.to}`), [
|
||||
'主线程 / 主智能体监管 -> Hubble / 代码审查员',
|
||||
'主线程 / 主智能体监管 -> Noether / 后端架构师',
|
||||
])
|
||||
})
|
||||
|
||||
test('groups project flow by workflow batch and classifies current optimizations', () => {
|
||||
const runtime = normalizeRuntime({
|
||||
items: [
|
||||
@@ -357,6 +445,33 @@ test('groups project flow by workflow batch and classifies current optimizations
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'review-prompt',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '请审查 /Users/yoilun/Code/codex-agent-manager。背景:将优化改造归类到 `v1.1 优化修复 / 优化阶段 1`。当前实现会给项目内 agent/handoff 增加工作流批次。',
|
||||
agentNickname: 'Peirce',
|
||||
agentRole: '代码审查员',
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'explicit-slash-batch',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '[项目: /Users/yoilun/Code/codex-agent-manager] [工作流批次: v1.2 CI/CD 发布] [阶段: 阶段 9 发布验证]',
|
||||
agentNickname: 'Curie',
|
||||
agentRole: '发布经理',
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
{
|
||||
id: 'explicit-phase-tail',
|
||||
cwd: '/Users/yoilun',
|
||||
title: '[项目: /Users/yoilun/Code/codex-agent-manager] [工作流批次: v1.1 优化修复 / 优化阶段 1] [阶段: 优化阶段 1]',
|
||||
agentNickname: 'Turing',
|
||||
agentRole: '前端开发者',
|
||||
threadSource: 'subagent',
|
||||
source: { kind: 'sqlite_table', confidence: 'high' },
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{ fromThreadId: 'main', toThreadId: 'initial', reason: 'closed' },
|
||||
@@ -373,9 +488,13 @@ test('groups project flow by workflow batch and classifies current optimizations
|
||||
assert.equal(projectRuntime.agents.find((agent) => agent.id === 'optimization').phaseName, '优化阶段 1')
|
||||
assert.equal(projectRuntime.agents.find((agent) => agent.id === 'v2').workflowBatch, 'v2.0 重构升级')
|
||||
assert.equal(projectRuntime.agents.find((agent) => agent.id === 'v2').phaseName, '阶段 3')
|
||||
assert.equal(projectRuntime.agents.find((agent) => agent.id === 'review-prompt').workflowBatch, 'v1.1 优化修复')
|
||||
assert.equal(projectRuntime.agents.find((agent) => agent.id === 'explicit-slash-batch').workflowBatch, 'v1.2 CI/CD 发布')
|
||||
assert.equal(projectRuntime.agents.find((agent) => agent.id === 'explicit-phase-tail').workflowBatch, 'v1.1 优化修复')
|
||||
assert.deepEqual(projectRuntime.workflowBatches.map((batch) => batch.name), [
|
||||
'v1.0 初始搭建',
|
||||
'v1.1 优化修复',
|
||||
'v1.2 CI/CD 发布',
|
||||
'v2.0 重构升级',
|
||||
])
|
||||
assert.deepEqual(projectRuntime.workflowBatches[1].phases.map((phase) => phase.name), ['优化阶段 1'])
|
||||
|
||||
Reference in New Issue
Block a user