mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
feat: 添加服务层增强与零散改进
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
59
src/assistant/__tests__/index.test.ts
Normal file
59
src/assistant/__tests__/index.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
||||
import { readFile, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import {
|
||||
resetStateForTests,
|
||||
setCwdState,
|
||||
setOriginalCwd,
|
||||
} from '../../bootstrap/state'
|
||||
import { getTaskListId } from '../../utils/tasks'
|
||||
import { getTeamFilePath } from '../../utils/swarm/teamHelpers'
|
||||
import { initializeAssistantTeam } from '../index'
|
||||
|
||||
let tempDir = ''
|
||||
let previousConfigDir: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
previousConfigDir = process.env.CLAUDE_CONFIG_DIR
|
||||
tempDir = join(
|
||||
tmpdir(),
|
||||
`assistant-team-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
)
|
||||
process.env.CLAUDE_CONFIG_DIR = join(tempDir, 'config')
|
||||
resetStateForTests()
|
||||
setOriginalCwd(tempDir)
|
||||
setCwdState(tempDir)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
resetStateForTests()
|
||||
if (previousConfigDir === undefined) {
|
||||
delete process.env.CLAUDE_CONFIG_DIR
|
||||
} else {
|
||||
process.env.CLAUDE_CONFIG_DIR = previousConfigDir
|
||||
}
|
||||
await rm(tempDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('initializeAssistantTeam', () => {
|
||||
test('creates a session-scoped in-process team context and task list', async () => {
|
||||
const context = await initializeAssistantTeam()
|
||||
expect(context).toBeDefined()
|
||||
const teamContext = context!
|
||||
|
||||
expect(teamContext.teamName).toStartWith('assistant-')
|
||||
expect(teamContext.isLeader).toBe(true)
|
||||
expect(teamContext.selfAgentName).toBe('team-lead')
|
||||
expect(
|
||||
teamContext.teammates[teamContext.leadAgentId]?.tmuxSessionName,
|
||||
).toBe('in-process')
|
||||
expect(getTaskListId()).toBe(teamContext.teamName)
|
||||
|
||||
const raw = await readFile(getTeamFilePath(teamContext.teamName), 'utf-8')
|
||||
const teamFile = JSON.parse(raw)
|
||||
expect(teamFile.leadAgentId).toBe(teamContext.leadAgentId)
|
||||
expect(teamFile.members[0].backendType).toBe('in-process')
|
||||
expect(teamFile.members[0].agentType).toBe('assistant')
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,24 @@
|
||||
import { readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { getKairosActive } from '../bootstrap/state.js'
|
||||
import { getKairosActive, getSessionId } from '../bootstrap/state.js'
|
||||
import type { AppState } from '../state/AppState.js'
|
||||
import { formatAgentId } from '../utils/agentId.js'
|
||||
import { getCwd } from '../utils/cwd.js'
|
||||
import { getClaudeConfigHomeDir } from '../utils/envUtils.js'
|
||||
import { TEAM_LEAD_NAME } from '../utils/swarm/constants.js'
|
||||
import {
|
||||
getTeamFilePath,
|
||||
registerTeamForSessionCleanup,
|
||||
sanitizeName,
|
||||
writeTeamFileAsync,
|
||||
type TeamFile,
|
||||
} from '../utils/swarm/teamHelpers.js'
|
||||
import { assignTeammateColor } from '../utils/swarm/teammateLayoutManager.js'
|
||||
import {
|
||||
ensureTasksDir,
|
||||
resetTaskList,
|
||||
setLeaderTeamName,
|
||||
} from '../utils/tasks.js'
|
||||
|
||||
let _assistantForced = false
|
||||
|
||||
@@ -29,13 +46,67 @@ export function isAssistantForced(): boolean {
|
||||
* Pre-create an in-process team so Agent(name) can spawn teammates
|
||||
* without TeamCreate.
|
||||
*
|
||||
* Phase 1: returns undefined so main.tsx's `assistantTeamContext ?? computeInitialTeamContext()`
|
||||
* correctly falls back. Returning {} would bypass the ?? operator since {} is truthy.
|
||||
*
|
||||
* Phase 2: should return a full team context object matching AppState.teamContext shape.
|
||||
* Creates a session-scoped assistant team file and returns a full team
|
||||
* context object matching AppState.teamContext.
|
||||
*/
|
||||
export async function initializeAssistantTeam(): Promise<undefined> {
|
||||
return undefined
|
||||
export async function initializeAssistantTeam(): Promise<
|
||||
AppState['teamContext']
|
||||
> {
|
||||
const sessionId = getSessionId()
|
||||
const teamName = sanitizeName(`assistant-${sessionId.slice(0, 8)}`)
|
||||
const leadAgentId = formatAgentId(TEAM_LEAD_NAME, teamName)
|
||||
const teamFilePath = getTeamFilePath(teamName)
|
||||
const now = Date.now()
|
||||
const cwd = getCwd()
|
||||
const color = assignTeammateColor(leadAgentId)
|
||||
|
||||
const teamFile: TeamFile = {
|
||||
name: teamName,
|
||||
description: 'Assistant mode in-process team',
|
||||
createdAt: now,
|
||||
leadAgentId,
|
||||
leadSessionId: sessionId,
|
||||
members: [
|
||||
{
|
||||
agentId: leadAgentId,
|
||||
name: TEAM_LEAD_NAME,
|
||||
agentType: 'assistant',
|
||||
color,
|
||||
joinedAt: now,
|
||||
tmuxPaneId: '',
|
||||
cwd,
|
||||
subscriptions: [],
|
||||
backendType: 'in-process',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await writeTeamFileAsync(teamName, teamFile)
|
||||
registerTeamForSessionCleanup(teamName)
|
||||
await resetTaskList(teamName)
|
||||
await ensureTasksDir(teamName)
|
||||
setLeaderTeamName(teamName)
|
||||
|
||||
return {
|
||||
teamName,
|
||||
teamFilePath,
|
||||
leadAgentId,
|
||||
selfAgentId: leadAgentId,
|
||||
selfAgentName: TEAM_LEAD_NAME,
|
||||
isLeader: true,
|
||||
selfAgentColor: color,
|
||||
teammates: {
|
||||
[leadAgentId]: {
|
||||
name: TEAM_LEAD_NAME,
|
||||
agentType: 'assistant',
|
||||
color,
|
||||
tmuxSessionName: 'in-process',
|
||||
tmuxPaneId: 'leader',
|
||||
cwd,
|
||||
spawnedAt: now,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user