attocode 0.1.2 → 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 +33 -1
- package/README.md +124 -0
- package/dist/src/agent.d.ts +73 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +443 -26
- 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 +21 -1
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +44 -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 +6 -2
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +10 -2
- package/dist/src/integrations/index.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/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +131 -14
- package/dist/src/tui/app.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 +94 -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, createInteractivePlanner, createRecursiveContext, } 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
|
|
@@ -62,6 +62,11 @@ export class ProductionAgent {
|
|
|
62
62
|
pendingPlanManager;
|
|
63
63
|
interactivePlanner = null;
|
|
64
64
|
recursiveContext = null;
|
|
65
|
+
learningStore = null;
|
|
66
|
+
compactor = null;
|
|
67
|
+
autoCompactionManager = null;
|
|
68
|
+
fileChangeTracker = null;
|
|
69
|
+
capabilitiesRegistry = null;
|
|
65
70
|
toolResolver = null;
|
|
66
71
|
// Initialization tracking
|
|
67
72
|
initPromises = [];
|
|
@@ -410,6 +415,189 @@ export class ProductionAgent {
|
|
|
410
415
|
}
|
|
411
416
|
});
|
|
412
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
|
+
});
|
|
413
601
|
}
|
|
414
602
|
/**
|
|
415
603
|
* Ensure all async initialization is complete before running.
|
|
@@ -774,14 +962,21 @@ export class ProductionAgent {
|
|
|
774
962
|
// =====================================================================
|
|
775
963
|
// RESILIENT LLM CALL: Empty response retries + max_tokens continuation
|
|
776
964
|
// =====================================================================
|
|
777
|
-
|
|
778
|
-
const
|
|
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;
|
|
779
974
|
let response = await this.callLLM(messages);
|
|
780
975
|
let emptyRetries = 0;
|
|
781
976
|
let continuations = 0;
|
|
782
|
-
// Phase 1: Handle empty responses with retry
|
|
783
|
-
while (emptyRetries < MAX_EMPTY_RETRIES) {
|
|
784
|
-
const hasContent = response.content && response.content.length
|
|
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;
|
|
785
980
|
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
786
981
|
if (hasContent || hasToolCalls) {
|
|
787
982
|
// Valid response received
|
|
@@ -818,8 +1013,8 @@ export class ProductionAgent {
|
|
|
818
1013
|
this.state.messages.push(nudgeMessage);
|
|
819
1014
|
response = await this.callLLM(messages);
|
|
820
1015
|
}
|
|
821
|
-
// Phase 2: Handle max_tokens truncation with continuation
|
|
822
|
-
if (response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
1016
|
+
// Phase 2: Handle max_tokens truncation with continuation (if enabled)
|
|
1017
|
+
if (resilienceEnabled && AUTO_CONTINUE && response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
823
1018
|
let accumulatedContent = response.content || '';
|
|
824
1019
|
while (continuations < MAX_CONTINUATIONS && response.stopReason === 'max_tokens') {
|
|
825
1020
|
continuations++;
|
|
@@ -910,8 +1105,34 @@ export class ProductionAgent {
|
|
|
910
1105
|
const MAX_TOOL_OUTPUT_CHARS = 8000; // ~2000 tokens max per tool output
|
|
911
1106
|
// =======================================================================
|
|
912
1107
|
// PROACTIVE BUDGET CHECK - compact BEFORE we overflow, not after
|
|
1108
|
+
// Uses AutoCompactionManager if available for sophisticated compaction
|
|
913
1109
|
// =======================================================================
|
|
914
|
-
|
|
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
|
|
915
1136
|
const currentUsage = this.economics.getUsage();
|
|
916
1137
|
const budget = this.economics.getBudget();
|
|
917
1138
|
const percentUsed = (currentUsage.tokens / budget.maxTokens) * 100;
|
|
@@ -965,6 +1186,20 @@ export class ProductionAgent {
|
|
|
965
1186
|
messages.push(toolMessage);
|
|
966
1187
|
this.state.messages.push(toolMessage);
|
|
967
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
|
+
});
|
|
968
1203
|
}
|
|
969
1204
|
// =======================================================================
|
|
970
1205
|
// REFLECTION (Lesson 16)
|
|
@@ -1012,6 +1247,11 @@ export class ProductionAgent {
|
|
|
1012
1247
|
const rulesContent = this.rules?.getRulesContent() ?? '';
|
|
1013
1248
|
const skillsPrompt = this.skillManager?.getActiveSkillsPrompt() ?? '';
|
|
1014
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
|
+
}) ?? '';
|
|
1015
1255
|
// Budget-aware codebase context selection
|
|
1016
1256
|
let codebaseContextStr = '';
|
|
1017
1257
|
if (this.codebaseContext) {
|
|
@@ -1059,9 +1299,10 @@ export class ProductionAgent {
|
|
|
1059
1299
|
}
|
|
1060
1300
|
// Build system prompt using cache-aware builder if available (Trick P)
|
|
1061
1301
|
let systemPrompt;
|
|
1062
|
-
// Combine memory and codebase context
|
|
1302
|
+
// Combine memory, learnings, and codebase context
|
|
1063
1303
|
const combinedContext = [
|
|
1064
1304
|
...(memoryContext.length > 0 ? memoryContext : []),
|
|
1305
|
+
...(learningsContext ? [learningsContext] : []),
|
|
1065
1306
|
...(codebaseContextStr ? [`\n## Relevant Code\n${codebaseContextStr}`] : []),
|
|
1066
1307
|
].join('\n');
|
|
1067
1308
|
if (this.contextEngineering) {
|
|
@@ -1193,6 +1434,17 @@ export class ProductionAgent {
|
|
|
1193
1434
|
reason: actualModel !== model ? 'Routed based on complexity' : 'Default model',
|
|
1194
1435
|
complexity: complexity <= 0.3 ? 'low' : complexity <= 0.7 ? 'medium' : 'high',
|
|
1195
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
|
+
});
|
|
1196
1448
|
}
|
|
1197
1449
|
else {
|
|
1198
1450
|
response = await this.provider.chat(messages, {
|
|
@@ -1329,6 +1581,15 @@ export class ProductionAgent {
|
|
|
1329
1581
|
policy: evaluation.policy,
|
|
1330
1582
|
reason: evaluation.reason,
|
|
1331
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
|
+
});
|
|
1332
1593
|
// Handle forbidden policy - always block
|
|
1333
1594
|
if (evaluation.policy === 'forbidden') {
|
|
1334
1595
|
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
@@ -1628,6 +1889,67 @@ export class ProductionAgent {
|
|
|
1628
1889
|
getTraceCollector() {
|
|
1629
1890
|
return this.traceCollector;
|
|
1630
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
|
+
}
|
|
1631
1953
|
/**
|
|
1632
1954
|
* Subscribe to events.
|
|
1633
1955
|
*/
|
|
@@ -2288,13 +2610,27 @@ export class ProductionAgent {
|
|
|
2288
2610
|
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
2289
2611
|
? agentDef.model
|
|
2290
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;
|
|
2291
2623
|
// Create a sub-agent with the agent's config
|
|
2292
2624
|
const subAgent = new ProductionAgent({
|
|
2293
2625
|
provider: this.provider,
|
|
2294
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,
|
|
2295
2631
|
systemPrompt: agentDef.systemPrompt,
|
|
2296
2632
|
model: resolvedModel,
|
|
2297
|
-
maxIterations: agentDef.maxIterations ||
|
|
2633
|
+
maxIterations: agentDef.maxIterations || defaultMaxIterations,
|
|
2298
2634
|
// Inherit some features but keep subagent simpler
|
|
2299
2635
|
memory: false,
|
|
2300
2636
|
planning: false,
|
|
@@ -2317,21 +2653,62 @@ export class ProductionAgent {
|
|
|
2317
2653
|
const taggedEvent = { ...event, subagent: agentName };
|
|
2318
2654
|
this.emit(taggedEvent);
|
|
2319
2655
|
});
|
|
2320
|
-
//
|
|
2321
|
-
const
|
|
2322
|
-
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
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
|
+
}
|
|
2335
2712
|
}
|
|
2336
2713
|
catch (err) {
|
|
2337
2714
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -2814,6 +3191,18 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
2814
3191
|
// =========================================================================
|
|
2815
3192
|
// SKILLS METHODS
|
|
2816
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
|
+
}
|
|
2817
3206
|
/**
|
|
2818
3207
|
* Get all loaded skills.
|
|
2819
3208
|
*/
|
|
@@ -2860,6 +3249,34 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
2860
3249
|
findMatchingSkills(query) {
|
|
2861
3250
|
return this.skillManager?.findMatchingSkills(query) || [];
|
|
2862
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
|
+
}
|
|
2863
3280
|
/**
|
|
2864
3281
|
* Get formatted list of available skills.
|
|
2865
3282
|
*/
|