attocode 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -1
- package/README.md +138 -10
- package/dist/src/agent.d.ts +75 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +700 -25
- package/dist/src/agent.js.map +1 -1
- package/dist/src/commands/agents-commands.d.ts +24 -0
- package/dist/src/commands/agents-commands.d.ts.map +1 -0
- package/dist/src/commands/agents-commands.js +284 -0
- package/dist/src/commands/agents-commands.js.map +1 -0
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +135 -19
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/commands/init-commands.d.ts +35 -0
- package/dist/src/commands/init-commands.d.ts.map +1 -0
- package/dist/src/commands/init-commands.js +187 -0
- package/dist/src/commands/init-commands.js.map +1 -0
- package/dist/src/commands/skills-commands.d.ts +26 -0
- package/dist/src/commands/skills-commands.d.ts.map +1 -0
- package/dist/src/commands/skills-commands.js +309 -0
- package/dist/src/commands/skills-commands.js.map +1 -0
- package/dist/src/commands/types.d.ts +13 -2
- package/dist/src/commands/types.d.ts.map +1 -1
- package/dist/src/defaults.d.ts +29 -1
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +66 -0
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.d.ts +68 -2
- package/dist/src/integrations/agent-registry.d.ts.map +1 -1
- package/dist/src/integrations/agent-registry.js +230 -23
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/cancellation.d.ts +5 -0
- package/dist/src/integrations/cancellation.d.ts.map +1 -1
- package/dist/src/integrations/cancellation.js +7 -0
- package/dist/src/integrations/cancellation.js.map +1 -1
- package/dist/src/integrations/capabilities.d.ts +160 -0
- package/dist/src/integrations/capabilities.d.ts.map +1 -0
- package/dist/src/integrations/capabilities.js +426 -0
- package/dist/src/integrations/capabilities.js.map +1 -0
- package/dist/src/integrations/context-engineering.d.ts +6 -1
- package/dist/src/integrations/context-engineering.d.ts.map +1 -1
- package/dist/src/integrations/context-engineering.js +7 -0
- package/dist/src/integrations/context-engineering.js.map +1 -1
- package/dist/src/integrations/index.d.ts +12 -2
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +22 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/interactive-planning.d.ts +322 -0
- package/dist/src/integrations/interactive-planning.d.ts.map +1 -0
- package/dist/src/integrations/interactive-planning.js +655 -0
- package/dist/src/integrations/interactive-planning.js.map +1 -0
- package/dist/src/integrations/learning-store.d.ts +291 -0
- package/dist/src/integrations/learning-store.d.ts.map +1 -0
- package/dist/src/integrations/learning-store.js +640 -0
- package/dist/src/integrations/learning-store.js.map +1 -0
- package/dist/src/integrations/pending-plan.d.ts.map +1 -1
- package/dist/src/integrations/pending-plan.js +69 -10
- package/dist/src/integrations/pending-plan.js.map +1 -1
- package/dist/src/integrations/skill-executor.d.ts +113 -0
- package/dist/src/integrations/skill-executor.d.ts.map +1 -0
- package/dist/src/integrations/skill-executor.js +270 -0
- package/dist/src/integrations/skill-executor.js.map +1 -0
- package/dist/src/integrations/skills.d.ts +98 -7
- package/dist/src/integrations/skills.d.ts.map +1 -1
- package/dist/src/integrations/skills.js +210 -11
- package/dist/src/integrations/skills.js.map +1 -1
- package/dist/src/providers/circuit-breaker.d.ts +180 -0
- package/dist/src/providers/circuit-breaker.d.ts.map +1 -0
- package/dist/src/providers/circuit-breaker.js +349 -0
- package/dist/src/providers/circuit-breaker.js.map +1 -0
- package/dist/src/providers/fallback-chain.d.ts +194 -0
- package/dist/src/providers/fallback-chain.d.ts.map +1 -0
- package/dist/src/providers/fallback-chain.js +363 -0
- package/dist/src/providers/fallback-chain.js.map +1 -0
- package/dist/src/providers/llm-resilience.d.ts +126 -0
- package/dist/src/providers/llm-resilience.d.ts.map +1 -0
- package/dist/src/providers/llm-resilience.js +261 -0
- package/dist/src/providers/llm-resilience.js.map +1 -0
- package/dist/src/providers/resilient-provider.d.ts +124 -0
- package/dist/src/providers/resilient-provider.d.ts.map +1 -0
- package/dist/src/providers/resilient-provider.js +242 -0
- package/dist/src/providers/resilient-provider.js.map +1 -0
- package/dist/src/tricks/recursive-context.d.ts +296 -0
- package/dist/src/tricks/recursive-context.d.ts.map +1 -0
- package/dist/src/tricks/recursive-context.js +518 -0
- package/dist/src/tricks/recursive-context.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +226 -29
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ApprovalDialog.d.ts.map +1 -1
- package/dist/src/tui/components/ApprovalDialog.js +1 -1
- package/dist/src/tui/components/ApprovalDialog.js.map +1 -1
- package/dist/src/tui/index.d.ts +1 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +2 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/transparency-aggregator.d.ts +100 -0
- package/dist/src/tui/transparency-aggregator.d.ts.map +1 -0
- package/dist/src/tui/transparency-aggregator.js +234 -0
- package/dist/src/tui/transparency-aggregator.js.map +1 -0
- package/dist/src/types.d.ts +155 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/src/agent.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { buildConfig, isFeatureEnabled, getEnabledFeatures, } from './defaults.js';
|
|
22
22
|
import { createModeManager, formatModeList, parseMode, } from './modes.js';
|
|
23
23
|
import { createLSPFileTools, } from './agent-tools/index.js';
|
|
24
|
-
import { HookManager, MemoryManager, PlanningManager, ObservabilityManager, SafetyManager, RoutingManager, MultiAgentManager, ReActManager, ExecutionPolicyManager, ThreadManager, RulesManager, DEFAULT_RULE_SOURCES, ExecutionEconomicsManager, STANDARD_BUDGET, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, createPendingPlanManager, } from './integrations/index.js';
|
|
24
|
+
import { HookManager, MemoryManager, PlanningManager, ObservabilityManager, SafetyManager, RoutingManager, MultiAgentManager, ReActManager, ExecutionPolicyManager, ThreadManager, RulesManager, DEFAULT_RULE_SOURCES, ExecutionEconomicsManager, STANDARD_BUDGET, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createTimeoutToken, createLinkedToken, race, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, createPendingPlanManager, createInteractivePlanner, createRecursiveContext, createLearningStore, createCompactor, createAutoCompactionManager, createFileChangeTracker, createCapabilitiesRegistry, } from './integrations/index.js';
|
|
25
25
|
// Lesson 26: Tracing & Evaluation integration
|
|
26
26
|
import { createTraceCollector } from './tracing/trace-collector.js';
|
|
27
27
|
// Spawn agent tool for LLM-driven subagent delegation
|
|
@@ -60,6 +60,13 @@ export class ProductionAgent {
|
|
|
60
60
|
traceCollector = null;
|
|
61
61
|
modeManager;
|
|
62
62
|
pendingPlanManager;
|
|
63
|
+
interactivePlanner = null;
|
|
64
|
+
recursiveContext = null;
|
|
65
|
+
learningStore = null;
|
|
66
|
+
compactor = null;
|
|
67
|
+
autoCompactionManager = null;
|
|
68
|
+
fileChangeTracker = null;
|
|
69
|
+
capabilitiesRegistry = null;
|
|
63
70
|
toolResolver = null;
|
|
64
71
|
// Initialization tracking
|
|
65
72
|
initPromises = [];
|
|
@@ -327,6 +334,270 @@ export class ProductionAgent {
|
|
|
327
334
|
break;
|
|
328
335
|
}
|
|
329
336
|
});
|
|
337
|
+
// Interactive Planning (conversational + editable planning)
|
|
338
|
+
if (isFeatureEnabled(this.config.interactivePlanning)) {
|
|
339
|
+
const interactiveConfig = typeof this.config.interactivePlanning === 'object'
|
|
340
|
+
? this.config.interactivePlanning
|
|
341
|
+
: {};
|
|
342
|
+
this.interactivePlanner = createInteractivePlanner({
|
|
343
|
+
autoCheckpoint: interactiveConfig.enableCheckpoints ?? true,
|
|
344
|
+
confirmBeforeExecute: interactiveConfig.requireApproval ?? true,
|
|
345
|
+
maxCheckpoints: 20,
|
|
346
|
+
autoPauseAtDecisions: true,
|
|
347
|
+
});
|
|
348
|
+
// Forward planner events to observability
|
|
349
|
+
this.interactivePlanner.on(event => {
|
|
350
|
+
switch (event.type) {
|
|
351
|
+
case 'plan.created':
|
|
352
|
+
this.observability?.logger?.info('Interactive plan created', {
|
|
353
|
+
planId: event.plan.id,
|
|
354
|
+
stepCount: event.plan.steps.length,
|
|
355
|
+
});
|
|
356
|
+
break;
|
|
357
|
+
case 'step.completed':
|
|
358
|
+
this.observability?.logger?.debug('Plan step completed', {
|
|
359
|
+
stepId: event.step.id,
|
|
360
|
+
status: event.step.status,
|
|
361
|
+
});
|
|
362
|
+
break;
|
|
363
|
+
case 'plan.cancelled':
|
|
364
|
+
this.observability?.logger?.info('Plan cancelled', { reason: event.reason });
|
|
365
|
+
break;
|
|
366
|
+
case 'checkpoint.created':
|
|
367
|
+
this.observability?.logger?.debug('Plan checkpoint created', {
|
|
368
|
+
checkpointId: event.checkpoint.id,
|
|
369
|
+
});
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Recursive Context (RLM - Recursive Language Models)
|
|
375
|
+
// Enables on-demand context exploration for large codebases
|
|
376
|
+
if (isFeatureEnabled(this.config.recursiveContext)) {
|
|
377
|
+
const recursiveConfig = typeof this.config.recursiveContext === 'object'
|
|
378
|
+
? this.config.recursiveContext
|
|
379
|
+
: {};
|
|
380
|
+
this.recursiveContext = createRecursiveContext({
|
|
381
|
+
maxDepth: recursiveConfig.maxRecursionDepth ?? 5,
|
|
382
|
+
snippetTokens: recursiveConfig.maxSnippetTokens ?? 2000,
|
|
383
|
+
synthesisTokens: 1000,
|
|
384
|
+
totalBudget: 50000,
|
|
385
|
+
cacheResults: recursiveConfig.cacheNavigationResults ?? true,
|
|
386
|
+
});
|
|
387
|
+
// Note: File system source should be registered when needed with proper glob/readFile functions
|
|
388
|
+
// This is deferred to allow flexible configuration
|
|
389
|
+
// Forward RLM events
|
|
390
|
+
this.recursiveContext.on(event => {
|
|
391
|
+
switch (event.type) {
|
|
392
|
+
case 'process.started':
|
|
393
|
+
this.observability?.logger?.debug('RLM process started', {
|
|
394
|
+
query: event.query,
|
|
395
|
+
depth: event.depth,
|
|
396
|
+
});
|
|
397
|
+
break;
|
|
398
|
+
case 'navigation.command':
|
|
399
|
+
this.observability?.logger?.debug('RLM navigation command', {
|
|
400
|
+
command: event.command,
|
|
401
|
+
depth: event.depth,
|
|
402
|
+
});
|
|
403
|
+
break;
|
|
404
|
+
case 'process.completed':
|
|
405
|
+
this.observability?.logger?.debug('RLM process completed', {
|
|
406
|
+
stats: event.stats,
|
|
407
|
+
});
|
|
408
|
+
break;
|
|
409
|
+
case 'budget.warning':
|
|
410
|
+
this.observability?.logger?.warn('RLM budget warning', {
|
|
411
|
+
remaining: event.remaining,
|
|
412
|
+
total: event.total,
|
|
413
|
+
});
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
// Learning Store (cross-session learning from failures)
|
|
419
|
+
// Connects to the failure tracker in contextEngineering for automatic learning extraction
|
|
420
|
+
if (isFeatureEnabled(this.config.learningStore)) {
|
|
421
|
+
const learningConfig = typeof this.config.learningStore === 'object'
|
|
422
|
+
? this.config.learningStore
|
|
423
|
+
: {};
|
|
424
|
+
this.learningStore = createLearningStore({
|
|
425
|
+
dbPath: learningConfig.dbPath ?? '.agent/learnings.db',
|
|
426
|
+
requireValidation: learningConfig.requireValidation ?? true,
|
|
427
|
+
autoValidateThreshold: learningConfig.autoValidateThreshold ?? 0.9,
|
|
428
|
+
maxLearnings: learningConfig.maxLearnings ?? 500,
|
|
429
|
+
});
|
|
430
|
+
// Connect to the failure tracker if available
|
|
431
|
+
if (this.contextEngineering) {
|
|
432
|
+
const failureTracker = this.contextEngineering.getFailureTracker();
|
|
433
|
+
if (failureTracker) {
|
|
434
|
+
this.learningStore.connectFailureTracker(failureTracker);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// Forward learning events to observability
|
|
438
|
+
this.learningStore.on(event => {
|
|
439
|
+
switch (event.type) {
|
|
440
|
+
case 'learning.proposed':
|
|
441
|
+
this.observability?.logger?.info('Learning proposed', {
|
|
442
|
+
learningId: event.learning.id,
|
|
443
|
+
description: event.learning.description,
|
|
444
|
+
});
|
|
445
|
+
this.emit({
|
|
446
|
+
type: 'learning.proposed',
|
|
447
|
+
learningId: event.learning.id,
|
|
448
|
+
description: event.learning.description,
|
|
449
|
+
});
|
|
450
|
+
break;
|
|
451
|
+
case 'learning.validated':
|
|
452
|
+
this.observability?.logger?.info('Learning validated', {
|
|
453
|
+
learningId: event.learningId,
|
|
454
|
+
});
|
|
455
|
+
this.emit({ type: 'learning.validated', learningId: event.learningId });
|
|
456
|
+
break;
|
|
457
|
+
case 'learning.applied':
|
|
458
|
+
this.observability?.logger?.debug('Learning applied', {
|
|
459
|
+
learningId: event.learningId,
|
|
460
|
+
context: event.context,
|
|
461
|
+
});
|
|
462
|
+
this.emit({
|
|
463
|
+
type: 'learning.applied',
|
|
464
|
+
learningId: event.learningId,
|
|
465
|
+
context: event.context,
|
|
466
|
+
});
|
|
467
|
+
break;
|
|
468
|
+
case 'pattern.extracted':
|
|
469
|
+
this.observability?.logger?.info('Pattern extracted as learning', {
|
|
470
|
+
pattern: event.pattern.description,
|
|
471
|
+
learningId: event.learning.id,
|
|
472
|
+
});
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// Auto-Compaction Manager (sophisticated context compaction)
|
|
478
|
+
// Uses the Compactor for LLM-based summarization with threshold monitoring
|
|
479
|
+
if (isFeatureEnabled(this.config.compaction)) {
|
|
480
|
+
const compactionConfig = typeof this.config.compaction === 'object'
|
|
481
|
+
? this.config.compaction
|
|
482
|
+
: {};
|
|
483
|
+
// Create the compactor (requires provider for LLM summarization)
|
|
484
|
+
this.compactor = createCompactor(this.provider, {
|
|
485
|
+
enabled: true,
|
|
486
|
+
tokenThreshold: compactionConfig.tokenThreshold ?? 80000,
|
|
487
|
+
preserveRecentCount: compactionConfig.preserveRecentCount ?? 10,
|
|
488
|
+
preserveToolResults: compactionConfig.preserveToolResults ?? true,
|
|
489
|
+
summaryMaxTokens: compactionConfig.summaryMaxTokens ?? 2000,
|
|
490
|
+
summaryModel: compactionConfig.summaryModel,
|
|
491
|
+
});
|
|
492
|
+
// Create the auto-compaction manager with threshold monitoring
|
|
493
|
+
this.autoCompactionManager = createAutoCompactionManager(this.compactor, {
|
|
494
|
+
mode: compactionConfig.mode ?? 'auto',
|
|
495
|
+
warningThreshold: 0.80,
|
|
496
|
+
autoCompactThreshold: 0.90,
|
|
497
|
+
hardLimitThreshold: 0.98,
|
|
498
|
+
preserveRecentUserMessages: Math.ceil((compactionConfig.preserveRecentCount ?? 10) / 2),
|
|
499
|
+
preserveRecentAssistantMessages: Math.ceil((compactionConfig.preserveRecentCount ?? 10) / 2),
|
|
500
|
+
cooldownMs: 60000, // 1 minute cooldown
|
|
501
|
+
maxContextTokens: this.config.maxContextTokens ?? 200000,
|
|
502
|
+
});
|
|
503
|
+
// Forward compactor events to observability
|
|
504
|
+
this.compactor.on(event => {
|
|
505
|
+
switch (event.type) {
|
|
506
|
+
case 'compaction.start':
|
|
507
|
+
this.observability?.logger?.info('Compaction started', {
|
|
508
|
+
messageCount: event.messageCount,
|
|
509
|
+
});
|
|
510
|
+
break;
|
|
511
|
+
case 'compaction.complete':
|
|
512
|
+
this.observability?.logger?.info('Compaction complete', {
|
|
513
|
+
tokensBefore: event.result.tokensBefore,
|
|
514
|
+
tokensAfter: event.result.tokensAfter,
|
|
515
|
+
compactedCount: event.result.compactedCount,
|
|
516
|
+
});
|
|
517
|
+
break;
|
|
518
|
+
case 'compaction.error':
|
|
519
|
+
this.observability?.logger?.error('Compaction error', {
|
|
520
|
+
error: event.error,
|
|
521
|
+
});
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
// Forward auto-compaction events
|
|
526
|
+
this.autoCompactionManager.on((event) => {
|
|
527
|
+
switch (event.type) {
|
|
528
|
+
case 'autocompaction.warning':
|
|
529
|
+
this.observability?.logger?.warn('Context approaching limit', {
|
|
530
|
+
currentTokens: event.currentTokens,
|
|
531
|
+
ratio: event.ratio,
|
|
532
|
+
});
|
|
533
|
+
this.emit({
|
|
534
|
+
type: 'compaction.warning',
|
|
535
|
+
currentTokens: event.currentTokens,
|
|
536
|
+
threshold: Math.round(event.ratio * (this.config.maxContextTokens ?? 200000)),
|
|
537
|
+
});
|
|
538
|
+
break;
|
|
539
|
+
case 'autocompaction.triggered':
|
|
540
|
+
this.observability?.logger?.info('Auto-compaction triggered', {
|
|
541
|
+
mode: event.mode,
|
|
542
|
+
currentTokens: event.currentTokens,
|
|
543
|
+
});
|
|
544
|
+
break;
|
|
545
|
+
case 'autocompaction.completed':
|
|
546
|
+
this.observability?.logger?.info('Auto-compaction completed', {
|
|
547
|
+
tokensBefore: event.tokensBefore,
|
|
548
|
+
tokensAfter: event.tokensAfter,
|
|
549
|
+
reduction: event.reduction,
|
|
550
|
+
});
|
|
551
|
+
this.emit({
|
|
552
|
+
type: 'compaction.auto',
|
|
553
|
+
tokensBefore: event.tokensBefore,
|
|
554
|
+
tokensAfter: event.tokensAfter,
|
|
555
|
+
messagesCompacted: event.tokensBefore - event.tokensAfter,
|
|
556
|
+
});
|
|
557
|
+
break;
|
|
558
|
+
case 'autocompaction.hard_limit':
|
|
559
|
+
this.observability?.logger?.error('Context hard limit reached', {
|
|
560
|
+
currentTokens: event.currentTokens,
|
|
561
|
+
ratio: event.ratio,
|
|
562
|
+
});
|
|
563
|
+
break;
|
|
564
|
+
case 'autocompaction.emergency_truncate':
|
|
565
|
+
this.observability?.logger?.warn('Emergency truncation performed', {
|
|
566
|
+
reason: event.reason,
|
|
567
|
+
messagesBefore: event.messagesBefore,
|
|
568
|
+
messagesAfter: event.messagesAfter,
|
|
569
|
+
});
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
// Note: FileChangeTracker requires a database instance which is not
|
|
575
|
+
// available at this point. Use initFileChangeTracker() to enable it
|
|
576
|
+
// after the agent is constructed with a database reference.
|
|
577
|
+
// This allows the feature to be optional and not require SQLite at all times.
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Initialize the file change tracker with a database instance.
|
|
581
|
+
* Call this if you want undo capability for file operations.
|
|
582
|
+
*
|
|
583
|
+
* @param db - SQLite database instance from better-sqlite3
|
|
584
|
+
* @param sessionId - Session ID for tracking changes
|
|
585
|
+
*/
|
|
586
|
+
initFileChangeTracker(db, sessionId) {
|
|
587
|
+
if (!isFeatureEnabled(this.config.fileChangeTracker)) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const trackerConfig = typeof this.config.fileChangeTracker === 'object'
|
|
591
|
+
? this.config.fileChangeTracker
|
|
592
|
+
: {};
|
|
593
|
+
this.fileChangeTracker = createFileChangeTracker(db, sessionId, {
|
|
594
|
+
enabled: true,
|
|
595
|
+
maxFullContentBytes: trackerConfig.maxFullContentBytes ?? 50 * 1024,
|
|
596
|
+
});
|
|
597
|
+
this.observability?.logger?.info('File change tracker initialized', {
|
|
598
|
+
sessionId,
|
|
599
|
+
maxFullContentBytes: trackerConfig.maxFullContentBytes ?? 50 * 1024,
|
|
600
|
+
});
|
|
330
601
|
}
|
|
331
602
|
/**
|
|
332
603
|
* Ensure all async initialization is complete before running.
|
|
@@ -518,11 +789,82 @@ export class ProductionAgent {
|
|
|
518
789
|
}
|
|
519
790
|
// =======================================================================
|
|
520
791
|
// ECONOMICS CHECK (Token Budget) - replaces hard iteration limit
|
|
792
|
+
// With recovery: try compaction before giving up on token limits
|
|
521
793
|
// =======================================================================
|
|
522
794
|
if (this.economics) {
|
|
523
795
|
const budgetCheck = this.economics.checkBudget();
|
|
524
796
|
if (!budgetCheck.canContinue) {
|
|
525
|
-
//
|
|
797
|
+
// ===================================================================
|
|
798
|
+
// RECOVERY ATTEMPT: Try emergency context reduction before giving up
|
|
799
|
+
// Only for token-based limits, not iteration limits
|
|
800
|
+
// ===================================================================
|
|
801
|
+
const isTokenLimit = budgetCheck.budgetType === 'tokens' || budgetCheck.budgetType === 'cost';
|
|
802
|
+
const alreadyTriedRecovery = this.state._recoveryAttempted === true;
|
|
803
|
+
if (isTokenLimit && !alreadyTriedRecovery) {
|
|
804
|
+
this.observability?.logger?.info('Budget limit reached, attempting recovery via context reduction', {
|
|
805
|
+
reason: budgetCheck.reason,
|
|
806
|
+
percentUsed: budgetCheck.percentUsed,
|
|
807
|
+
});
|
|
808
|
+
this.emit({
|
|
809
|
+
type: 'resilience.retry',
|
|
810
|
+
reason: 'budget_limit_compaction',
|
|
811
|
+
attempt: 1,
|
|
812
|
+
maxAttempts: 1,
|
|
813
|
+
});
|
|
814
|
+
// Mark that we've attempted recovery to prevent infinite loops
|
|
815
|
+
this.state._recoveryAttempted = true;
|
|
816
|
+
const tokensBefore = this.estimateContextTokens(messages);
|
|
817
|
+
// Step 1: Compact tool outputs aggressively
|
|
818
|
+
this.compactToolOutputs();
|
|
819
|
+
// Step 2: Emergency truncation - keep system + last N messages
|
|
820
|
+
const PRESERVE_RECENT = 10;
|
|
821
|
+
if (messages.length > PRESERVE_RECENT + 2) {
|
|
822
|
+
const systemMessage = messages.find(m => m.role === 'system');
|
|
823
|
+
const recentMessages = messages.slice(-(PRESERVE_RECENT));
|
|
824
|
+
// Rebuild message array
|
|
825
|
+
messages.length = 0;
|
|
826
|
+
if (systemMessage) {
|
|
827
|
+
messages.push(systemMessage);
|
|
828
|
+
}
|
|
829
|
+
messages.push({
|
|
830
|
+
role: 'system',
|
|
831
|
+
content: `[CONTEXT REDUCED: Earlier messages were removed to stay within budget. Conversation continues from recent context.]`,
|
|
832
|
+
});
|
|
833
|
+
messages.push(...recentMessages);
|
|
834
|
+
// Update state messages too
|
|
835
|
+
this.state.messages.length = 0;
|
|
836
|
+
this.state.messages.push(...messages);
|
|
837
|
+
}
|
|
838
|
+
const tokensAfter = this.estimateContextTokens(messages);
|
|
839
|
+
const reduction = Math.round((1 - tokensAfter / tokensBefore) * 100);
|
|
840
|
+
if (tokensAfter < tokensBefore * 0.8) {
|
|
841
|
+
// Significant reduction achieved
|
|
842
|
+
this.observability?.logger?.info('Context reduction successful, continuing execution', {
|
|
843
|
+
tokensBefore,
|
|
844
|
+
tokensAfter,
|
|
845
|
+
reduction,
|
|
846
|
+
});
|
|
847
|
+
this.emit({
|
|
848
|
+
type: 'resilience.recovered',
|
|
849
|
+
reason: 'budget_limit_compaction',
|
|
850
|
+
attempts: 1,
|
|
851
|
+
});
|
|
852
|
+
this.emit({
|
|
853
|
+
type: 'compaction.auto',
|
|
854
|
+
tokensBefore,
|
|
855
|
+
tokensAfter,
|
|
856
|
+
messagesCompacted: tokensBefore - tokensAfter,
|
|
857
|
+
});
|
|
858
|
+
// Continue execution instead of breaking
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
this.observability?.logger?.warn('Context reduction insufficient', {
|
|
862
|
+
tokensBefore,
|
|
863
|
+
tokensAfter,
|
|
864
|
+
reduction,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
// Hard limit reached and recovery failed (or not applicable)
|
|
526
868
|
this.observability?.logger?.warn('Budget limit reached', {
|
|
527
869
|
reason: budgetCheck.reason,
|
|
528
870
|
budgetType: budgetCheck.budgetType,
|
|
@@ -617,8 +959,104 @@ export class ProductionAgent {
|
|
|
617
959
|
}
|
|
618
960
|
}
|
|
619
961
|
}
|
|
620
|
-
//
|
|
621
|
-
|
|
962
|
+
// =====================================================================
|
|
963
|
+
// RESILIENT LLM CALL: Empty response retries + max_tokens continuation
|
|
964
|
+
// =====================================================================
|
|
965
|
+
// Get resilience config
|
|
966
|
+
const resilienceConfig = typeof this.config.resilience === 'object'
|
|
967
|
+
? this.config.resilience
|
|
968
|
+
: {};
|
|
969
|
+
const resilienceEnabled = isFeatureEnabled(this.config.resilience);
|
|
970
|
+
const MAX_EMPTY_RETRIES = resilienceConfig.maxEmptyRetries ?? 2;
|
|
971
|
+
const MAX_CONTINUATIONS = resilienceConfig.maxContinuations ?? 3;
|
|
972
|
+
const AUTO_CONTINUE = resilienceConfig.autoContinue ?? true;
|
|
973
|
+
const MIN_CONTENT_LENGTH = resilienceConfig.minContentLength ?? 1;
|
|
974
|
+
let response = await this.callLLM(messages);
|
|
975
|
+
let emptyRetries = 0;
|
|
976
|
+
let continuations = 0;
|
|
977
|
+
// Phase 1: Handle empty responses with retry (if resilience enabled)
|
|
978
|
+
while (resilienceEnabled && emptyRetries < MAX_EMPTY_RETRIES) {
|
|
979
|
+
const hasContent = response.content && response.content.length >= MIN_CONTENT_LENGTH;
|
|
980
|
+
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
981
|
+
if (hasContent || hasToolCalls) {
|
|
982
|
+
// Valid response received
|
|
983
|
+
if (emptyRetries > 0) {
|
|
984
|
+
this.emit({
|
|
985
|
+
type: 'resilience.recovered',
|
|
986
|
+
reason: 'empty_response',
|
|
987
|
+
attempts: emptyRetries,
|
|
988
|
+
});
|
|
989
|
+
this.observability?.logger?.info('Recovered from empty response', {
|
|
990
|
+
retries: emptyRetries,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
// Empty response - retry with nudge
|
|
996
|
+
emptyRetries++;
|
|
997
|
+
this.emit({
|
|
998
|
+
type: 'resilience.retry',
|
|
999
|
+
reason: 'empty_response',
|
|
1000
|
+
attempt: emptyRetries,
|
|
1001
|
+
maxAttempts: MAX_EMPTY_RETRIES,
|
|
1002
|
+
});
|
|
1003
|
+
this.observability?.logger?.warn('Empty LLM response, retrying', {
|
|
1004
|
+
attempt: emptyRetries,
|
|
1005
|
+
maxAttempts: MAX_EMPTY_RETRIES,
|
|
1006
|
+
});
|
|
1007
|
+
// Add gentle nudge and retry
|
|
1008
|
+
const nudgeMessage = {
|
|
1009
|
+
role: 'user',
|
|
1010
|
+
content: '[System: Your previous response was empty. Please provide a response or use a tool.]',
|
|
1011
|
+
};
|
|
1012
|
+
messages.push(nudgeMessage);
|
|
1013
|
+
this.state.messages.push(nudgeMessage);
|
|
1014
|
+
response = await this.callLLM(messages);
|
|
1015
|
+
}
|
|
1016
|
+
// Phase 2: Handle max_tokens truncation with continuation (if enabled)
|
|
1017
|
+
if (resilienceEnabled && AUTO_CONTINUE && response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
1018
|
+
let accumulatedContent = response.content || '';
|
|
1019
|
+
while (continuations < MAX_CONTINUATIONS && response.stopReason === 'max_tokens') {
|
|
1020
|
+
continuations++;
|
|
1021
|
+
this.emit({
|
|
1022
|
+
type: 'resilience.continue',
|
|
1023
|
+
reason: 'max_tokens',
|
|
1024
|
+
continuation: continuations,
|
|
1025
|
+
maxContinuations: MAX_CONTINUATIONS,
|
|
1026
|
+
accumulatedLength: accumulatedContent.length,
|
|
1027
|
+
});
|
|
1028
|
+
this.observability?.logger?.info('Response truncated at max_tokens, continuing', {
|
|
1029
|
+
continuation: continuations,
|
|
1030
|
+
accumulatedLength: accumulatedContent.length,
|
|
1031
|
+
});
|
|
1032
|
+
// Add continuation request
|
|
1033
|
+
const continuationMessage = {
|
|
1034
|
+
role: 'assistant',
|
|
1035
|
+
content: accumulatedContent,
|
|
1036
|
+
};
|
|
1037
|
+
const continueRequest = {
|
|
1038
|
+
role: 'user',
|
|
1039
|
+
content: '[System: Please continue from where you left off. Do not repeat what you already said.]',
|
|
1040
|
+
};
|
|
1041
|
+
messages.push(continuationMessage, continueRequest);
|
|
1042
|
+
this.state.messages.push(continuationMessage, continueRequest);
|
|
1043
|
+
response = await this.callLLM(messages);
|
|
1044
|
+
// Accumulate content
|
|
1045
|
+
if (response.content) {
|
|
1046
|
+
accumulatedContent += response.content;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
// Update response with accumulated content
|
|
1050
|
+
if (continuations > 0) {
|
|
1051
|
+
response = { ...response, content: accumulatedContent };
|
|
1052
|
+
this.emit({
|
|
1053
|
+
type: 'resilience.completed',
|
|
1054
|
+
reason: 'max_tokens_continuation',
|
|
1055
|
+
continuations,
|
|
1056
|
+
finalLength: accumulatedContent.length,
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
622
1060
|
// Record LLM usage for economics
|
|
623
1061
|
if (this.economics && response.usage) {
|
|
624
1062
|
this.economics.recordLLMUsage(response.usage.inputTokens, response.usage.outputTokens, this.config.model, response.usage.cost // Use actual cost from provider when available
|
|
@@ -639,6 +1077,20 @@ export class ProductionAgent {
|
|
|
639
1077
|
// The model has "consumed" the tool outputs and produced a response,
|
|
640
1078
|
// so we can replace verbose outputs with compact summaries
|
|
641
1079
|
this.compactToolOutputs();
|
|
1080
|
+
// Final validation: warn if response is still empty after all retries
|
|
1081
|
+
if (!response.content || response.content.length === 0) {
|
|
1082
|
+
this.observability?.logger?.error('Agent finished with empty response after all retries', {
|
|
1083
|
+
emptyRetries,
|
|
1084
|
+
continuations,
|
|
1085
|
+
iteration: this.state.iteration,
|
|
1086
|
+
});
|
|
1087
|
+
this.emit({
|
|
1088
|
+
type: 'resilience.failed',
|
|
1089
|
+
reason: 'empty_final_response',
|
|
1090
|
+
emptyRetries,
|
|
1091
|
+
continuations,
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
642
1094
|
break;
|
|
643
1095
|
}
|
|
644
1096
|
// Execute tool calls
|
|
@@ -653,8 +1105,34 @@ export class ProductionAgent {
|
|
|
653
1105
|
const MAX_TOOL_OUTPUT_CHARS = 8000; // ~2000 tokens max per tool output
|
|
654
1106
|
// =======================================================================
|
|
655
1107
|
// PROACTIVE BUDGET CHECK - compact BEFORE we overflow, not after
|
|
1108
|
+
// Uses AutoCompactionManager if available for sophisticated compaction
|
|
656
1109
|
// =======================================================================
|
|
657
|
-
|
|
1110
|
+
const currentContextTokens = this.estimateContextTokens(messages);
|
|
1111
|
+
if (this.autoCompactionManager) {
|
|
1112
|
+
// Use the AutoCompactionManager for threshold-based compaction
|
|
1113
|
+
const compactionResult = await this.autoCompactionManager.checkAndMaybeCompact({
|
|
1114
|
+
currentTokens: currentContextTokens,
|
|
1115
|
+
messages: messages,
|
|
1116
|
+
});
|
|
1117
|
+
// Handle compaction result
|
|
1118
|
+
if (compactionResult.status === 'compacted' && compactionResult.compactedMessages) {
|
|
1119
|
+
// Replace messages with compacted version
|
|
1120
|
+
messages.length = 0;
|
|
1121
|
+
messages.push(...compactionResult.compactedMessages);
|
|
1122
|
+
this.state.messages.length = 0;
|
|
1123
|
+
this.state.messages.push(...compactionResult.compactedMessages);
|
|
1124
|
+
}
|
|
1125
|
+
else if (compactionResult.status === 'hard_limit') {
|
|
1126
|
+
// Hard limit reached - this is serious, emit error
|
|
1127
|
+
this.emit({
|
|
1128
|
+
type: 'error',
|
|
1129
|
+
error: `Context hard limit reached (${Math.round(compactionResult.ratio * 100)}% of max tokens)`,
|
|
1130
|
+
});
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
else if (this.economics) {
|
|
1135
|
+
// Fallback to simple compaction
|
|
658
1136
|
const currentUsage = this.economics.getUsage();
|
|
659
1137
|
const budget = this.economics.getBudget();
|
|
660
1138
|
const percentUsed = (currentUsage.tokens / budget.maxTokens) * 100;
|
|
@@ -708,6 +1186,20 @@ export class ProductionAgent {
|
|
|
708
1186
|
messages.push(toolMessage);
|
|
709
1187
|
this.state.messages.push(toolMessage);
|
|
710
1188
|
}
|
|
1189
|
+
// Emit context health after adding tool results
|
|
1190
|
+
const currentTokenEstimate = this.estimateContextTokens(messages);
|
|
1191
|
+
const contextLimit = this.config.maxContextTokens || 100000;
|
|
1192
|
+
const percentUsed = Math.round((currentTokenEstimate / contextLimit) * 100);
|
|
1193
|
+
const avgTokensPerExchange = currentTokenEstimate / Math.max(1, this.state.iteration);
|
|
1194
|
+
const remainingTokens = contextLimit - currentTokenEstimate;
|
|
1195
|
+
const estimatedExchanges = Math.floor(remainingTokens / Math.max(1, avgTokensPerExchange));
|
|
1196
|
+
this.emit({
|
|
1197
|
+
type: 'context.health',
|
|
1198
|
+
currentTokens: currentTokenEstimate,
|
|
1199
|
+
maxTokens: contextLimit,
|
|
1200
|
+
estimatedExchanges,
|
|
1201
|
+
percentUsed,
|
|
1202
|
+
});
|
|
711
1203
|
}
|
|
712
1204
|
// =======================================================================
|
|
713
1205
|
// REFLECTION (Lesson 16)
|
|
@@ -755,6 +1247,11 @@ export class ProductionAgent {
|
|
|
755
1247
|
const rulesContent = this.rules?.getRulesContent() ?? '';
|
|
756
1248
|
const skillsPrompt = this.skillManager?.getActiveSkillsPrompt() ?? '';
|
|
757
1249
|
const memoryContext = this.memory?.getContextStrings(task) ?? [];
|
|
1250
|
+
// Get relevant learnings from past sessions
|
|
1251
|
+
const learningsContext = this.learningStore?.getLearningContext({
|
|
1252
|
+
query: task,
|
|
1253
|
+
maxLearnings: 5,
|
|
1254
|
+
}) ?? '';
|
|
758
1255
|
// Budget-aware codebase context selection
|
|
759
1256
|
let codebaseContextStr = '';
|
|
760
1257
|
if (this.codebaseContext) {
|
|
@@ -802,9 +1299,10 @@ export class ProductionAgent {
|
|
|
802
1299
|
}
|
|
803
1300
|
// Build system prompt using cache-aware builder if available (Trick P)
|
|
804
1301
|
let systemPrompt;
|
|
805
|
-
// Combine memory and codebase context
|
|
1302
|
+
// Combine memory, learnings, and codebase context
|
|
806
1303
|
const combinedContext = [
|
|
807
1304
|
...(memoryContext.length > 0 ? memoryContext : []),
|
|
1305
|
+
...(learningsContext ? [learningsContext] : []),
|
|
808
1306
|
...(codebaseContextStr ? [`\n## Relevant Code\n${codebaseContextStr}`] : []),
|
|
809
1307
|
].join('\n');
|
|
810
1308
|
if (this.contextEngineering) {
|
|
@@ -936,6 +1434,17 @@ export class ProductionAgent {
|
|
|
936
1434
|
reason: actualModel !== model ? 'Routed based on complexity' : 'Default model',
|
|
937
1435
|
complexity: complexity <= 0.3 ? 'low' : complexity <= 0.7 ? 'medium' : 'high',
|
|
938
1436
|
});
|
|
1437
|
+
// Emit decision transparency event
|
|
1438
|
+
this.emit({
|
|
1439
|
+
type: 'decision.routing',
|
|
1440
|
+
model: actualModel,
|
|
1441
|
+
reason: actualModel !== model
|
|
1442
|
+
? `Complexity ${(complexity * 100).toFixed(0)}% - using ${actualModel}`
|
|
1443
|
+
: 'Default model for current task',
|
|
1444
|
+
alternatives: actualModel !== model
|
|
1445
|
+
? [{ model, rejected: 'complexity threshold exceeded' }]
|
|
1446
|
+
: undefined,
|
|
1447
|
+
});
|
|
939
1448
|
}
|
|
940
1449
|
else {
|
|
941
1450
|
response = await this.provider.chat(messages, {
|
|
@@ -1072,6 +1581,15 @@ export class ProductionAgent {
|
|
|
1072
1581
|
policy: evaluation.policy,
|
|
1073
1582
|
reason: evaluation.reason,
|
|
1074
1583
|
});
|
|
1584
|
+
// Emit decision transparency event
|
|
1585
|
+
this.emit({
|
|
1586
|
+
type: 'decision.tool',
|
|
1587
|
+
tool: toolCall.name,
|
|
1588
|
+
decision: evaluation.policy === 'forbidden' ? 'blocked'
|
|
1589
|
+
: evaluation.policy === 'prompt' ? 'prompted'
|
|
1590
|
+
: 'allowed',
|
|
1591
|
+
policyMatch: evaluation.reason,
|
|
1592
|
+
});
|
|
1075
1593
|
// Handle forbidden policy - always block
|
|
1076
1594
|
if (evaluation.policy === 'forbidden') {
|
|
1077
1595
|
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
@@ -1371,6 +1889,67 @@ export class ProductionAgent {
|
|
|
1371
1889
|
getTraceCollector() {
|
|
1372
1890
|
return this.traceCollector;
|
|
1373
1891
|
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Get the learning store for cross-session learning.
|
|
1894
|
+
* Returns null if learning store is not enabled.
|
|
1895
|
+
*/
|
|
1896
|
+
getLearningStore() {
|
|
1897
|
+
return this.learningStore;
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Get the auto-compaction manager.
|
|
1901
|
+
* Returns null if compaction is not enabled.
|
|
1902
|
+
*/
|
|
1903
|
+
getAutoCompactionManager() {
|
|
1904
|
+
return this.autoCompactionManager;
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Get the file change tracker for undo capability.
|
|
1908
|
+
* Returns null if file change tracking is not enabled.
|
|
1909
|
+
*/
|
|
1910
|
+
getFileChangeTracker() {
|
|
1911
|
+
return this.fileChangeTracker;
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Record a file change for potential undo.
|
|
1915
|
+
* No-op if file change tracking is not enabled.
|
|
1916
|
+
*
|
|
1917
|
+
* @param params - Change details
|
|
1918
|
+
* @returns Change ID if tracked, -1 otherwise
|
|
1919
|
+
*/
|
|
1920
|
+
async trackFileChange(params) {
|
|
1921
|
+
if (!this.fileChangeTracker) {
|
|
1922
|
+
return -1;
|
|
1923
|
+
}
|
|
1924
|
+
return this.fileChangeTracker.recordChange({
|
|
1925
|
+
filePath: params.filePath,
|
|
1926
|
+
operation: params.operation,
|
|
1927
|
+
contentBefore: params.contentBefore,
|
|
1928
|
+
contentAfter: params.contentAfter,
|
|
1929
|
+
turnNumber: this.state.iteration,
|
|
1930
|
+
toolCallId: params.toolCallId,
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Undo the last change to a specific file.
|
|
1935
|
+
* Returns null if file change tracking is not enabled.
|
|
1936
|
+
*/
|
|
1937
|
+
async undoLastFileChange(filePath) {
|
|
1938
|
+
if (!this.fileChangeTracker) {
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
return this.fileChangeTracker.undoLastChange(filePath);
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Undo all changes in the current turn.
|
|
1945
|
+
* Returns null if file change tracking is not enabled.
|
|
1946
|
+
*/
|
|
1947
|
+
async undoCurrentTurn() {
|
|
1948
|
+
if (!this.fileChangeTracker) {
|
|
1949
|
+
return null;
|
|
1950
|
+
}
|
|
1951
|
+
return this.fileChangeTracker.undoTurn(this.state.iteration);
|
|
1952
|
+
}
|
|
1374
1953
|
/**
|
|
1375
1954
|
* Subscribe to events.
|
|
1376
1955
|
*/
|
|
@@ -2031,13 +2610,27 @@ export class ProductionAgent {
|
|
|
2031
2610
|
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
2032
2611
|
? agentDef.model
|
|
2033
2612
|
: this.config.model;
|
|
2613
|
+
// Get subagent config with defaults
|
|
2614
|
+
// Note: subagent config is SubagentConfig | false from buildConfig
|
|
2615
|
+
const subagentConfig = this.config.subagent;
|
|
2616
|
+
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
2617
|
+
const defaultMaxIterations = hasSubagentConfig
|
|
2618
|
+
? subagentConfig.defaultMaxIterations ?? 10
|
|
2619
|
+
: 10;
|
|
2620
|
+
const subagentTimeout = hasSubagentConfig
|
|
2621
|
+
? subagentConfig.defaultTimeout ?? 120000
|
|
2622
|
+
: 120000;
|
|
2034
2623
|
// Create a sub-agent with the agent's config
|
|
2035
2624
|
const subAgent = new ProductionAgent({
|
|
2036
2625
|
provider: this.provider,
|
|
2037
2626
|
tools: agentTools,
|
|
2627
|
+
// Pass toolResolver so subagent can lazy-load MCP tools
|
|
2628
|
+
toolResolver: this.toolResolver || undefined,
|
|
2629
|
+
// Pass MCP tool summaries so subagent knows what tools are available
|
|
2630
|
+
mcpToolSummaries: this.config.mcpToolSummaries,
|
|
2038
2631
|
systemPrompt: agentDef.systemPrompt,
|
|
2039
2632
|
model: resolvedModel,
|
|
2040
|
-
maxIterations: agentDef.maxIterations ||
|
|
2633
|
+
maxIterations: agentDef.maxIterations || defaultMaxIterations,
|
|
2041
2634
|
// Inherit some features but keep subagent simpler
|
|
2042
2635
|
memory: false,
|
|
2043
2636
|
planning: false,
|
|
@@ -2054,26 +2647,68 @@ export class ProductionAgent {
|
|
|
2054
2647
|
custom: [],
|
|
2055
2648
|
},
|
|
2056
2649
|
});
|
|
2057
|
-
// Forward events from subagent
|
|
2650
|
+
// Forward events from subagent with context
|
|
2058
2651
|
subAgent.subscribe(event => {
|
|
2059
|
-
//
|
|
2060
|
-
|
|
2652
|
+
// Tag event with subagent source so TUI can display it properly
|
|
2653
|
+
const taggedEvent = { ...event, subagent: agentName };
|
|
2654
|
+
this.emit(taggedEvent);
|
|
2061
2655
|
});
|
|
2062
|
-
//
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2656
|
+
// Create timeout token for subagent execution
|
|
2657
|
+
const timeoutSource = createTimeoutToken(subagentTimeout);
|
|
2658
|
+
// Link parent's cancellation with subagent timeout so ESC propagates to subagents
|
|
2659
|
+
const parentSource = this.cancellation?.getSource();
|
|
2660
|
+
const effectiveSource = parentSource
|
|
2661
|
+
? createLinkedToken(parentSource, timeoutSource)
|
|
2662
|
+
: timeoutSource;
|
|
2663
|
+
try {
|
|
2664
|
+
// Run the task with cancellation propagation from parent
|
|
2665
|
+
const result = await race(subAgent.run(task), effectiveSource.token);
|
|
2666
|
+
const duration = Date.now() - startTime;
|
|
2667
|
+
const spawnResult = {
|
|
2668
|
+
success: result.success,
|
|
2669
|
+
output: result.response || result.error || '',
|
|
2670
|
+
metrics: {
|
|
2671
|
+
tokens: result.metrics.totalTokens,
|
|
2672
|
+
duration,
|
|
2673
|
+
toolCalls: result.metrics.toolCalls,
|
|
2674
|
+
},
|
|
2675
|
+
};
|
|
2676
|
+
this.emit({ type: 'agent.complete', agentId: agentName, success: result.success });
|
|
2677
|
+
await subAgent.cleanup();
|
|
2678
|
+
return spawnResult;
|
|
2679
|
+
}
|
|
2680
|
+
catch (err) {
|
|
2681
|
+
// Handle cancellation (user ESC or timeout) for cleaner error messages
|
|
2682
|
+
if (isCancellationError(err)) {
|
|
2683
|
+
const duration = Date.now() - startTime;
|
|
2684
|
+
const isUserCancellation = parentSource?.isCancellationRequested;
|
|
2685
|
+
const reason = isUserCancellation
|
|
2686
|
+
? 'User cancelled'
|
|
2687
|
+
: `Timed out after ${subagentTimeout}ms`;
|
|
2688
|
+
this.emit({ type: 'agent.error', agentId: agentName, error: reason });
|
|
2689
|
+
// Try to cleanup the subagent gracefully
|
|
2690
|
+
try {
|
|
2691
|
+
await subAgent.cleanup();
|
|
2692
|
+
}
|
|
2693
|
+
catch {
|
|
2694
|
+
// Ignore cleanup errors on cancellation
|
|
2695
|
+
}
|
|
2696
|
+
const output = isUserCancellation
|
|
2697
|
+
? `Subagent '${agentName}' was cancelled by user.`
|
|
2698
|
+
: `Subagent '${agentName}' timed out after ${Math.round(subagentTimeout / 1000)}s. The task may be too complex or the model may be slow.`;
|
|
2699
|
+
return {
|
|
2700
|
+
success: false,
|
|
2701
|
+
output,
|
|
2702
|
+
metrics: { tokens: 0, duration, toolCalls: 0 },
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
throw err; // Re-throw non-cancellation errors
|
|
2706
|
+
}
|
|
2707
|
+
finally {
|
|
2708
|
+
// Dispose both sources (linked source disposes its internal state, timeout source handles its timer)
|
|
2709
|
+
effectiveSource.dispose();
|
|
2710
|
+
timeoutSource.dispose();
|
|
2711
|
+
}
|
|
2077
2712
|
}
|
|
2078
2713
|
catch (err) {
|
|
2079
2714
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -2556,6 +3191,18 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
2556
3191
|
// =========================================================================
|
|
2557
3192
|
// SKILLS METHODS
|
|
2558
3193
|
// =========================================================================
|
|
3194
|
+
/**
|
|
3195
|
+
* Get the skill manager instance for advanced operations.
|
|
3196
|
+
*/
|
|
3197
|
+
getSkillManager() {
|
|
3198
|
+
return this.skillManager;
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Get the agent registry instance for advanced operations.
|
|
3202
|
+
*/
|
|
3203
|
+
getAgentRegistry() {
|
|
3204
|
+
return this.agentRegistry;
|
|
3205
|
+
}
|
|
2559
3206
|
/**
|
|
2560
3207
|
* Get all loaded skills.
|
|
2561
3208
|
*/
|
|
@@ -2602,6 +3249,34 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
2602
3249
|
findMatchingSkills(query) {
|
|
2603
3250
|
return this.skillManager?.findMatchingSkills(query) || [];
|
|
2604
3251
|
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Get the capabilities registry for unified discovery.
|
|
3254
|
+
* Lazily creates and populates the registry on first access.
|
|
3255
|
+
*/
|
|
3256
|
+
getCapabilitiesRegistry() {
|
|
3257
|
+
if (!this.capabilitiesRegistry) {
|
|
3258
|
+
this.capabilitiesRegistry = createCapabilitiesRegistry();
|
|
3259
|
+
// Register sources
|
|
3260
|
+
this.capabilitiesRegistry.registerToolRegistry({
|
|
3261
|
+
getTools: () => this.getTools(),
|
|
3262
|
+
});
|
|
3263
|
+
if (this.skillManager) {
|
|
3264
|
+
this.capabilitiesRegistry.registerSkillManager(this.skillManager);
|
|
3265
|
+
}
|
|
3266
|
+
if (this.agentRegistry) {
|
|
3267
|
+
this.capabilitiesRegistry.registerAgentRegistry(this.agentRegistry);
|
|
3268
|
+
}
|
|
3269
|
+
// MCP client is registered externally if available
|
|
3270
|
+
}
|
|
3271
|
+
return this.capabilitiesRegistry;
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Register an MCP client with the capabilities registry.
|
|
3275
|
+
*/
|
|
3276
|
+
registerMCPClient(client) {
|
|
3277
|
+
const registry = this.getCapabilitiesRegistry();
|
|
3278
|
+
registry.registerMCPClient(client);
|
|
3279
|
+
}
|
|
2605
3280
|
/**
|
|
2606
3281
|
* Get formatted list of available skills.
|
|
2607
3282
|
*/
|