Files
AirCoding/packages/runtime/src/agents/main/MainAgent.ts
AirCoding 06a07689f8 fix(lint): resolve noUnusedLocals regression in MainAgent.classify_via_llm
R4 introduced classify_via_llm() which built classification_prompt but
fell through to regex without using it, tripping noUnusedLocals (TS6133).
Use 'void classification_prompt' to preserve the GA prompt structure as
documentation while satisfying strict lint. Removed console.warn (no
@types/node / dom lib in ES2022 target).

Regression scope: third-round verification.
- 169/169 tests pass
- IPC files (B14) bun-build clean (EXIT=0)
- MainAgent transpile clean (EXIT=0)
- Confirmed remaining tsc errors are environmental (missing @types/node:
  fs/path/crypto/Buffer) or pre-existing (config write-only field,
  EventIngestor value-as-type), NOT R4 regressions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:21:52 +08:00

209 lines
5.6 KiB
TypeScript
Executable File

/**
* MainAgent - Primary user-facing agent
*
* Implements DD §14.1 + state machine §20.1.
* INV-1: no direct status writes (works via Scheduler/events).
* INV-3: side effects only via ToolRegistry+PermissionEngine.
*
* @module packages/runtime/src/agents/main/MainAgent
*/
import type { SessionID, ProjectID } from '@aircoding/contracts'
export type MainAgentState =
| 'IDLE'
| 'CLASSIFYING'
| 'ANSWERING'
| 'DELEGATING'
| 'DIRECT_MODE'
| 'SCHEDULING'
| 'AWAITING'
| 'ARCHITECTURE_DESIGNING'
| 'CONFIRMING'
| 'EXECUTING'
| 'INTERRUPTING'
| 'ARCHITECTURE_REVISING'
| 'SUMMARIZING'
| 'ERROR'
| 'TERMINATED'
export type ClassifyMode = 'regex' | 'llm'
export interface MainAgentConfig {
session_id: SessionID
project_id: ProjectID
classify_mode?: ClassifyMode // Alpha default: 'regex'; GA target: 'llm'
provider_manager?: any // ProviderManager for LLM-based classify (GA)
}
export class MainAgent {
private config: MainAgentConfig
private classify_mode: ClassifyMode
private provider_manager?: any
state: MainAgentState = 'IDLE'
constructor(config: MainAgentConfig) {
this.config = config
this.classify_mode = config.classify_mode || 'regex'
this.provider_manager = config.provider_manager
}
/**
* Handle incoming user message.
* Classifies intent → routing decision.
*/
async handle_user_message(message: string): Promise<{
action: 'answer' | 'delegate' | 'direct'
tasks?: string[]
response?: string
}> {
// Classify intent (passes through a Promise.resolve for regex mode)
this.state = 'CLASSIFYING'
const classification = await Promise.resolve(this.classify(message))
switch (classification) {
case 'simple_question':
case 'clarification':
this.state = 'ANSWERING'
return { action: 'answer', response: 'Processing your question...' }
case 'implementation_request':
case 'task_request':
this.state = 'DELEGATING'
return { action: 'delegate', tasks: ['task-1'] }
case 'direct_command':
this.state = 'DIRECT_MODE'
return { action: 'direct' }
default:
this.state = 'ANSWERING'
return { action: 'answer', response: 'How can I help?' }
}
}
/**
* Classify user message intent.
* - regex mode: pattern matching (Alpha scope, deterministic, synchronous)
* - llm mode: calls ProviderManager for LLM-based classification (GA target, async)
*/
classify(message: string): string | Promise<string> {
if (this.classify_mode === 'llm' && this.provider_manager) {
return this.classify_via_llm(message)
}
return this.classify_regex(message)
}
/**
* Regex-based intent classification (Alpha scope, 5 patterns).
*/
private classify_regex(message: string): string {
const lower = message.toLowerCase()
if (/^(what|how|why|when|where|who|can you|could you|explain)/.test(lower)) {
return 'simple_question'
}
if (/^(\/direct|\/done|implement|create|build|write|add|fix|change|update|remove|delete|refactor)/.test(lower)) {
return 'implementation_request'
}
if (/^(run|execute|test|debug|check|inspect)/.test(lower)) {
return 'direct_command'
}
return 'simple_question'
}
/**
* LLM-based intent classification (GA target).
* Calls ProviderManager→Adapter→LLM to classify intent into the state machine route.
* TODO(GA): Implement by sending a classification prompt to the configured model.
*/
private async classify_via_llm(message: string): Promise<string> {
const classification_prompt = [
'Classify this user message into one of:',
' simple_question | implementation_request | direct_command',
'',
`Message: "${message}"`,
'',
'Respond with ONLY the classification string, no other text.',
].join('\n')
try {
// GA: const result = await this.provider_manager.complete(classification_prompt, ...)
// GA: return parse_classification(result.content)
// Alpha: prompt is built but not yet sent; fall through to regex as a safety net.
void classification_prompt
return this.classify_regex(message)
} catch {
return this.classify_regex(message)
}
}
/**
* Handle confirmation from user.
*/
async handle_confirmation(confirmed: boolean): Promise<void> {
if (this.state !== 'CONFIRMING') return
if (confirmed) {
this.state = 'DELEGATING'
} else {
this.state = 'IDLE'
}
}
/**
* Transition to idle after summarization.
*/
summarize(): void {
this.state = 'SUMMARIZING'
// After summarization completes
this.state = 'IDLE'
}
/**
* Handle an interruption at the specified change level.
* 'execution' → state EXECUTING
* 'design' → state ARCHITECTURE_REVISING
* 'full' → state ARCHITECTURE_DESIGNING
*/
handle_interruption(change_level: 'execution' | 'design' | 'full'): void {
this.state = 'INTERRUPTING'
switch (change_level) {
case 'execution':
this.state = 'EXECUTING'
break
case 'design':
this.state = 'ARCHITECTURE_REVISING'
break
case 'full':
this.state = 'ARCHITECTURE_DESIGNING'
break
}
}
/**
* Transition to CONFIRMING state (awaiting user confirmation).
*/
transition_to_confirming(): void {
this.state = 'CONFIRMING'
}
/**
* Transition to EXECUTING state.
*/
transition_to_executing(): void {
this.state = 'EXECUTING'
}
/**
* Transition to INTERRUPTING state.
*/
transition_to_interrupting(): void {
this.state = 'INTERRUPTING'
}
}