Merge branch 'main' into feature/support-agents-directory-skills

This commit is contained in:
LaZzyMan 2026-03-18 20:01:12 +08:00
commit 919265d7e8
161 changed files with 22520 additions and 2311 deletions

View file

@ -21,6 +21,8 @@ import type { ContentGeneratorConfigSources } from '../core/contentGenerator.js'
import type { MCPOAuthConfig } from '../mcp/oauth-provider.js';
import type { ShellExecutionConfig } from '../services/shellExecutionService.js';
import type { AnyToolInvocation } from '../tools/tools.js';
import type { ArenaManager } from '../agents/arena/ArenaManager.js';
import { ArenaAgentClient } from '../agents/arena/ArenaAgentClient.js';
// Core
import { BaseLlmClient } from '../core/baseLlmClient.js';
@ -284,6 +286,26 @@ export interface SandboxConfig {
image: string;
}
/**
* Settings shared across multi-agent collaboration features
* (Arena, Team, Swarm).
*/
export interface AgentsCollabSettings {
/** Display mode for multi-agent sessions ('in-process' | 'tmux' | 'iterm2') */
displayMode?: string;
/** Arena-specific settings */
arena?: {
/** Custom base directory for Arena worktrees (default: ~/.qwen/arena) */
worktreeBaseDir?: string;
/** Preserve worktrees and state files after session ends */
preserveArtifacts?: boolean;
/** Maximum rounds (turns) per agent. No limit if unset. */
maxRoundsPerAgent?: number;
/** Total timeout in seconds for the Arena session. No limit if unset. */
timeoutSeconds?: number;
};
}
export interface ConfigParameters {
sessionId?: string;
sessionData?: ResumedSessionData;
@ -381,6 +403,8 @@ export interface ConfigParameters {
channel?: string;
/** Model providers configuration grouped by authType */
modelProvidersConfig?: ModelProvidersConfig;
/** Multi-agent collaboration settings (Arena, Team, Swarm) */
agents?: AgentsCollabSettings;
/** Enable hook system for lifecycle events */
enableHooks?: boolean;
/** Hooks configuration from settings */
@ -516,6 +540,12 @@ export class Config {
private readonly shouldUseNodePtyShell: boolean;
private readonly skipNextSpeakerCheck: boolean;
private shellExecutionConfig: ShellExecutionConfig;
private arenaManager: ArenaManager | null = null;
private arenaManagerChangeCallback:
| ((manager: ArenaManager | null) => void)
| null = null;
private readonly arenaAgentClient: ArenaAgentClient | null;
private readonly agentsSettings: AgentsCollabSettings;
private readonly skipLoopDetection: boolean;
private readonly skipStartupContext: boolean;
private readonly warnings: string[];
@ -651,6 +681,8 @@ export class Config {
this.inputFormat = params.inputFormat ?? InputFormat.TEXT;
this.fileExclusions = new FileExclusions(this);
this.eventEmitter = params.eventEmitter;
this.arenaAgentClient = ArenaAgentClient.create();
this.agentsSettings = params.agents ?? {};
if (params.contextFileName) {
setGeminiMdFilename(params.contextFileName);
}
@ -1183,6 +1215,8 @@ export class Config {
if (this.toolRegistry) {
await this.toolRegistry.stop();
}
await this.cleanupArenaRuntime();
} catch (error) {
// Log but don't throw - cleanup should be best-effort
this.debugLogger.error('Error during Config shutdown:', error);
@ -1335,6 +1369,50 @@ export class Config {
this.geminiMdFileCount = count;
}
getArenaManager(): ArenaManager | null {
return this.arenaManager;
}
setArenaManager(manager: ArenaManager | null): void {
this.arenaManager = manager;
this.arenaManagerChangeCallback?.(manager);
}
/**
* Register a callback invoked whenever the arena manager changes.
* Pass `null` to unsubscribe. Only one subscriber is supported.
*/
onArenaManagerChange(
cb: ((manager: ArenaManager | null) => void) | null,
): void {
this.arenaManagerChangeCallback = cb;
}
getArenaAgentClient(): ArenaAgentClient | null {
return this.arenaAgentClient;
}
getAgentsSettings(): AgentsCollabSettings {
return this.agentsSettings;
}
/**
* Clean up Arena runtime. When `force` is true (e.g., /arena select --discard),
* always removes worktrees regardless of preserveArtifacts.
*/
async cleanupArenaRuntime(force?: boolean): Promise<void> {
const manager = this.arenaManager;
if (!manager) {
return;
}
if (!force && this.agentsSettings.arena?.preserveArtifacts) {
await manager.cleanupRuntime();
} else {
await manager.cleanup();
}
this.setArenaManager(null);
}
getApprovalMode(): ApprovalMode {
return this.approvalMode;
}
@ -1808,6 +1886,7 @@ export class Config {
async createToolRegistry(
sendSdkMcpMessage?: SendSdkMcpMessage,
options?: { skipDiscovery?: boolean },
): Promise<ToolRegistry> {
const registry = new ToolRegistry(
this,
@ -1897,7 +1976,9 @@ export class Config {
registerCoreTool(LspTool, this);
}
await registry.discoverAllTools();
if (!options?.skipDiscovery) {
await registry.discoverAllTools();
}
this.debugLogger.debug(
`ToolRegistry created: ${JSON.stringify(registry.getAllToolNames())} (${registry.getAllToolNames().length} tools)`,
);

View file

@ -18,6 +18,7 @@ const BIN_DIR_NAME = 'bin';
const PROJECT_DIR_NAME = 'projects';
const IDE_DIR_NAME = 'ide';
const DEBUG_DIR_NAME = 'debug';
const ARENA_DIR_NAME = 'arena';
export class Storage {
private readonly targetDir: string;
@ -78,6 +79,10 @@ export class Storage {
return path.join(Storage.getGlobalQwenDir(), BIN_DIR_NAME);
}
static getGlobalArenaDir(): string {
return path.join(Storage.getGlobalQwenDir(), ARENA_DIR_NAME);
}
getQwenDir(): string {
return path.join(this.targetDir, QWEN_DIR);
}