mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 23:42:03 +00:00
feat(core,cli)!: Implement in-process agent backend for arenas
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Add InProcessBackend to run subagents in-process rather than via subprocess, enabling faster initialization and better resource management for agent collaboration arenas. Key changes: - Add InProcessBackend with sandboxed in-process agent execution - Refactor agent runtime into headless vs interactive modes - Add AsyncMessageQueue utility for agent message passing - Update ArenaManager with backend selection (in-process vs subprocess) - Refactor subagent types/exports; consolidate in subagents/types - Remove deprecated agent-hooks.ts (functionality merged into runtime) - Update task tool to support new agent lifecycle Breaking: Subagent type exports restructured; import from subagents/types
This commit is contained in:
parent
e968483a8a
commit
d4cfb18f79
39 changed files with 2951 additions and 502 deletions
|
|
@ -7,7 +7,7 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import {
|
||||
type ArenaManager,
|
||||
ArenaAgentStatus,
|
||||
AgentStatus,
|
||||
ArenaSessionStatus,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { arenaCommand } from './arenaCommand.js';
|
||||
|
|
@ -242,7 +242,7 @@ describe('arenaCommand select subcommand', () => {
|
|||
getAgentStates: vi.fn(() => [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: ArenaAgentStatus.TERMINATED,
|
||||
status: AgentStatus.FAILED,
|
||||
model: { modelId: 'model-1' },
|
||||
},
|
||||
]),
|
||||
|
|
@ -267,12 +267,12 @@ describe('arenaCommand select subcommand', () => {
|
|||
getAgentStates: vi.fn(() => [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'model-1' },
|
||||
},
|
||||
{
|
||||
agentId: 'agent-2',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'model-2' },
|
||||
},
|
||||
]),
|
||||
|
|
@ -294,12 +294,12 @@ describe('arenaCommand select subcommand', () => {
|
|||
getAgentStates: vi.fn(() => [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'gpt-4o', displayName: 'gpt-4o' },
|
||||
},
|
||||
{
|
||||
agentId: 'agent-2',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'claude-sonnet', displayName: 'claude-sonnet' },
|
||||
},
|
||||
]),
|
||||
|
|
@ -327,7 +327,7 @@ describe('arenaCommand select subcommand', () => {
|
|||
getAgentStates: vi.fn(() => [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'gpt-4o', displayName: 'gpt-4o' },
|
||||
},
|
||||
]),
|
||||
|
|
@ -350,7 +350,7 @@ describe('arenaCommand select subcommand', () => {
|
|||
getAgentStates: vi.fn(() => [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'gpt-4o' },
|
||||
},
|
||||
]),
|
||||
|
|
@ -373,7 +373,7 @@ describe('arenaCommand select subcommand', () => {
|
|||
getAgentStates: vi.fn(() => [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: ArenaAgentStatus.COMPLETED,
|
||||
status: AgentStatus.COMPLETED,
|
||||
model: { modelId: 'gpt-4o' },
|
||||
},
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import { CommandKind } from './types.js';
|
|||
import {
|
||||
ArenaManager,
|
||||
ArenaEventType,
|
||||
ArenaAgentStatus,
|
||||
AgentStatus,
|
||||
isTerminalStatus,
|
||||
ArenaSessionStatus,
|
||||
AuthType,
|
||||
createDebugLogger,
|
||||
|
|
@ -246,41 +247,23 @@ function executeArenaCommand(
|
|||
|
||||
const buildAgentCardData = (
|
||||
result: ArenaAgentCompleteEvent['result'],
|
||||
): ArenaAgentCardData => {
|
||||
let status: ArenaAgentCardData['status'];
|
||||
switch (result.status) {
|
||||
case ArenaAgentStatus.COMPLETED:
|
||||
status = 'completed';
|
||||
break;
|
||||
case ArenaAgentStatus.CANCELLED:
|
||||
status = 'cancelled';
|
||||
break;
|
||||
default:
|
||||
status = 'terminated';
|
||||
break;
|
||||
}
|
||||
return {
|
||||
label: result.model.displayName || result.model.modelId,
|
||||
status,
|
||||
durationMs: result.stats.durationMs,
|
||||
totalTokens: result.stats.totalTokens,
|
||||
inputTokens: result.stats.inputTokens,
|
||||
outputTokens: result.stats.outputTokens,
|
||||
toolCalls: result.stats.toolCalls,
|
||||
successfulToolCalls: result.stats.successfulToolCalls,
|
||||
failedToolCalls: result.stats.failedToolCalls,
|
||||
rounds: result.stats.rounds,
|
||||
error: result.error,
|
||||
diff: result.diff,
|
||||
};
|
||||
};
|
||||
): ArenaAgentCardData => ({
|
||||
label: result.model.displayName || result.model.modelId,
|
||||
status: result.status,
|
||||
durationMs: result.stats.durationMs,
|
||||
totalTokens: result.stats.totalTokens,
|
||||
inputTokens: result.stats.inputTokens,
|
||||
outputTokens: result.stats.outputTokens,
|
||||
toolCalls: result.stats.toolCalls,
|
||||
successfulToolCalls: result.stats.successfulToolCalls,
|
||||
failedToolCalls: result.stats.failedToolCalls,
|
||||
rounds: result.stats.rounds,
|
||||
error: result.error,
|
||||
diff: result.diff,
|
||||
});
|
||||
|
||||
const handleAgentComplete = (event: ArenaAgentCompleteEvent) => {
|
||||
if (
|
||||
event.result.status !== ArenaAgentStatus.COMPLETED &&
|
||||
event.result.status !== ArenaAgentStatus.CANCELLED &&
|
||||
event.result.status !== ArenaAgentStatus.TERMINATED
|
||||
) {
|
||||
if (!isTerminalStatus(event.result.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -598,7 +581,7 @@ export const arenaCommand: SlashCommand = {
|
|||
|
||||
const agents = manager.getAgentStates();
|
||||
const hasSuccessful = agents.some(
|
||||
(a) => a.status === ArenaAgentStatus.COMPLETED,
|
||||
(a) => a.status === AgentStatus.COMPLETED,
|
||||
);
|
||||
|
||||
if (!hasSuccessful) {
|
||||
|
|
@ -616,7 +599,7 @@ export const arenaCommand: SlashCommand = {
|
|||
const matchingAgent = agents.find((a) => {
|
||||
const label = a.model.displayName || a.model.modelId;
|
||||
return (
|
||||
a.status === ArenaAgentStatus.COMPLETED &&
|
||||
a.status === AgentStatus.COMPLETED &&
|
||||
(label.toLowerCase() === trimmedArgs.toLowerCase() ||
|
||||
a.model.modelId.toLowerCase() === trimmedArgs.toLowerCase())
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useCallback, useMemo } from 'react';
|
|||
import { Box, Text } from 'ink';
|
||||
import {
|
||||
type ArenaManager,
|
||||
ArenaAgentStatus,
|
||||
AgentStatus,
|
||||
type Config,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
|
@ -138,7 +138,7 @@ export function ArenaSelectDialog({
|
|||
// Build diff summary from cached result if available
|
||||
let diffAdditions = 0;
|
||||
let diffDeletions = 0;
|
||||
if (agent.status === ArenaAgentStatus.COMPLETED && result) {
|
||||
if (agent.status === AgentStatus.COMPLETED && result) {
|
||||
const agentResult = result.agents.find(
|
||||
(a) => a.agentId === agent.agentId,
|
||||
);
|
||||
|
|
@ -182,7 +182,7 @@ export function ArenaSelectDialog({
|
|||
value: agent.agentId,
|
||||
title,
|
||||
description,
|
||||
disabled: agent.status !== ArenaAgentStatus.COMPLETED,
|
||||
disabled: agent.status !== AgentStatus.COMPLETED,
|
||||
};
|
||||
}),
|
||||
[agents, result],
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Box, Text } from 'ink';
|
|||
import {
|
||||
type ArenaManager,
|
||||
type ArenaAgentState,
|
||||
ArenaAgentStatus,
|
||||
isTerminalStatus,
|
||||
ArenaSessionStatus,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
|
@ -42,11 +42,7 @@ function pad(
|
|||
}
|
||||
|
||||
function getElapsedMs(agent: ArenaAgentState): number {
|
||||
if (
|
||||
agent.status === ArenaAgentStatus.COMPLETED ||
|
||||
agent.status === ArenaAgentStatus.TERMINATED ||
|
||||
agent.status === ArenaAgentStatus.CANCELLED
|
||||
) {
|
||||
if (isTerminalStatus(agent.status)) {
|
||||
return agent.stats.durationMs;
|
||||
}
|
||||
return Date.now() - agent.startedAt;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import type {
|
|||
ToolCallConfirmationDetails,
|
||||
ToolConfirmationOutcome,
|
||||
ToolResultDisplay,
|
||||
AgentStatus,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { PartListUnion } from '@google/genai';
|
||||
import { type ReactNode } from 'react';
|
||||
|
|
@ -266,7 +267,7 @@ export type HistoryItemMcpStatus = HistoryItemBase & {
|
|||
*/
|
||||
export interface ArenaAgentCardData {
|
||||
label: string;
|
||||
status: 'completed' | 'cancelled' | 'terminated';
|
||||
status: AgentStatus;
|
||||
durationMs: number;
|
||||
totalTokens: number;
|
||||
inputTokens: number;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { ArenaAgentStatus } from '@qwen-code/qwen-code-core';
|
||||
import { AgentStatus } from '@qwen-code/qwen-code-core';
|
||||
|
||||
// --- Status Labels ---
|
||||
|
||||
|
|
@ -15,24 +15,17 @@ export interface StatusLabel {
|
|||
color: string;
|
||||
}
|
||||
|
||||
export function getArenaStatusLabel(
|
||||
status: ArenaAgentStatus | string,
|
||||
): StatusLabel {
|
||||
export function getArenaStatusLabel(status: AgentStatus): StatusLabel {
|
||||
switch (status) {
|
||||
case ArenaAgentStatus.COMPLETED:
|
||||
case 'completed':
|
||||
case AgentStatus.COMPLETED:
|
||||
return { icon: '✓', text: 'Done', color: theme.status.success };
|
||||
case ArenaAgentStatus.CANCELLED:
|
||||
case 'cancelled':
|
||||
case AgentStatus.CANCELLED:
|
||||
return { icon: '⊘', text: 'Cancelled', color: theme.status.warning };
|
||||
case ArenaAgentStatus.TERMINATED:
|
||||
case 'terminated':
|
||||
return { icon: '✗', text: 'Terminated', color: theme.status.error };
|
||||
case ArenaAgentStatus.RUNNING:
|
||||
case 'running':
|
||||
case AgentStatus.FAILED:
|
||||
return { icon: '✗', text: 'Failed', color: theme.status.error };
|
||||
case AgentStatus.RUNNING:
|
||||
return { icon: '○', text: 'Running', color: theme.text.secondary };
|
||||
case ArenaAgentStatus.INITIALIZING:
|
||||
case 'initializing':
|
||||
case AgentStatus.INITIALIZING:
|
||||
return { icon: '○', text: 'Initializing', color: theme.text.secondary };
|
||||
default:
|
||||
return { icon: '○', text: status, color: theme.text.secondary };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue