attocode 0.1.2 → 0.1.4
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 +51 -1
- package/README.md +180 -0
- package/dist/src/agent.d.ts +78 -1
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +639 -36
- package/dist/src/agent.js.map +1 -1
- package/dist/src/analysis/feedback-loop.d.ts +115 -0
- package/dist/src/analysis/feedback-loop.d.ts.map +1 -0
- package/dist/src/analysis/feedback-loop.js +226 -0
- package/dist/src/analysis/feedback-loop.js.map +1 -0
- package/dist/src/analysis/index.d.ts +9 -0
- package/dist/src/analysis/index.d.ts.map +1 -0
- package/dist/src/analysis/index.js +9 -0
- package/dist/src/analysis/index.js.map +1 -0
- package/dist/src/analysis/prompt-templates.d.ts +36 -0
- package/dist/src/analysis/prompt-templates.d.ts.map +1 -0
- package/dist/src/analysis/prompt-templates.js +198 -0
- package/dist/src/analysis/prompt-templates.js.map +1 -0
- package/dist/src/analysis/trace-summary.d.ts +56 -0
- package/dist/src/analysis/trace-summary.d.ts.map +1 -0
- package/dist/src/analysis/trace-summary.js +261 -0
- package/dist/src/analysis/trace-summary.js.map +1 -0
- 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 +329 -21
- 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/config.d.ts +3 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/defaults.d.ts +31 -2
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +69 -2
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/errors/index.d.ts +233 -0
- package/dist/src/errors/index.d.ts.map +1 -0
- package/dist/src/errors/index.js +427 -0
- package/dist/src/errors/index.js.map +1 -0
- 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/auto-compaction.d.ts +33 -0
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
- package/dist/src/integrations/auto-compaction.js +47 -3
- package/dist/src/integrations/auto-compaction.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/dead-letter-queue.d.ts +208 -0
- package/dist/src/integrations/dead-letter-queue.d.ts.map +1 -0
- package/dist/src/integrations/dead-letter-queue.js +458 -0
- package/dist/src/integrations/dead-letter-queue.js.map +1 -0
- package/dist/src/integrations/health-check.d.ts +218 -0
- package/dist/src/integrations/health-check.d.ts.map +1 -0
- package/dist/src/integrations/health-check.js +400 -0
- package/dist/src/integrations/health-check.js.map +1 -0
- package/dist/src/integrations/index.d.ts +11 -2
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +19 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/mcp-client.d.ts +9 -0
- package/dist/src/integrations/mcp-client.d.ts.map +1 -1
- package/dist/src/integrations/mcp-client.js +49 -7
- package/dist/src/integrations/mcp-client.js.map +1 -1
- package/dist/src/integrations/openrouter-pricing.d.ts +28 -3
- package/dist/src/integrations/openrouter-pricing.d.ts.map +1 -1
- package/dist/src/integrations/openrouter-pricing.js +57 -16
- package/dist/src/integrations/openrouter-pricing.js.map +1 -1
- package/dist/src/integrations/retry.d.ts +131 -0
- package/dist/src/integrations/retry.d.ts.map +1 -0
- package/dist/src/integrations/retry.js +233 -0
- package/dist/src/integrations/retry.js.map +1 -0
- 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/integrations/sqlite-store.d.ts +42 -0
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
- package/dist/src/integrations/sqlite-store.js +111 -0
- package/dist/src/integrations/sqlite-store.js.map +1 -1
- package/dist/src/main.js +88 -7
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +37 -1
- package/dist/src/modes/repl.js.map +1 -1
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +46 -5
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.d.ts.map +1 -1
- package/dist/src/modes.js +10 -3
- package/dist/src/modes.js.map +1 -1
- package/dist/src/persistence/schema.d.ts +4 -0
- package/dist/src/persistence/schema.d.ts.map +1 -1
- package/dist/src/persistence/schema.js +49 -0
- package/dist/src/persistence/schema.js.map +1 -1
- package/dist/src/providers/adapters/anthropic.d.ts +24 -2
- package/dist/src/providers/adapters/anthropic.d.ts.map +1 -1
- package/dist/src/providers/adapters/anthropic.js +184 -0
- package/dist/src/providers/adapters/anthropic.js.map +1 -1
- package/dist/src/tools/bash.d.ts.map +1 -1
- package/dist/src/tools/bash.js +7 -4
- package/dist/src/tools/bash.js.map +1 -1
- package/dist/src/tools/file.d.ts.map +1 -1
- package/dist/src/tools/file.js +31 -10
- package/dist/src/tools/file.js.map +1 -1
- package/dist/src/tools/permission.d.ts +12 -0
- package/dist/src/tools/permission.d.ts.map +1 -1
- package/dist/src/tools/permission.js +136 -0
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tools/registry.d.ts +23 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +77 -17
- package/dist/src/tools/registry.js.map +1 -1
- package/dist/src/tools/standard.d.ts.map +1 -1
- package/dist/src/tools/standard.js +8 -0
- package/dist/src/tools/standard.js.map +1 -1
- package/dist/src/tools/types.d.ts +20 -1
- package/dist/src/tools/types.d.ts.map +1 -1
- package/dist/src/tools/types.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +198 -2
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +315 -3
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +470 -2
- package/dist/src/tracing/types.d.ts.map +1 -1
- package/dist/src/tracing/types.js +25 -0
- package/dist/src/tracing/types.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +292 -18
- 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 +129 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +6 -3
- package/dist/src/hello.d.ts +0 -2
- package/dist/src/hello.d.ts.map +0 -1
- package/dist/src/hello.js +0 -4
- package/dist/src/hello.js.map +0 -1
- package/dist/src/test-sqlite.d.ts +0 -2
- package/dist/src/test-sqlite.d.ts.map +0 -1
- package/dist/src/test-sqlite.js +0 -114
- package/dist/src/test-sqlite.js.map +0 -1
package/dist/src/agent.js
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
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
|
+
// Model registry for context window limits
|
|
28
|
+
import { modelRegistry } from './costs/index.js';
|
|
29
|
+
import { getModelContextLength } from './integrations/openrouter-pricing.js';
|
|
27
30
|
// Spawn agent tool for LLM-driven subagent delegation
|
|
28
31
|
import { createBoundSpawnAgentTool } from './tools/agent.js';
|
|
29
32
|
// =============================================================================
|
|
@@ -62,6 +65,11 @@ export class ProductionAgent {
|
|
|
62
65
|
pendingPlanManager;
|
|
63
66
|
interactivePlanner = null;
|
|
64
67
|
recursiveContext = null;
|
|
68
|
+
learningStore = null;
|
|
69
|
+
compactor = null;
|
|
70
|
+
autoCompactionManager = null;
|
|
71
|
+
fileChangeTracker = null;
|
|
72
|
+
capabilitiesRegistry = null;
|
|
65
73
|
toolResolver = null;
|
|
66
74
|
// Initialization tracking
|
|
67
75
|
initPromises = [];
|
|
@@ -410,6 +418,224 @@ export class ProductionAgent {
|
|
|
410
418
|
}
|
|
411
419
|
});
|
|
412
420
|
}
|
|
421
|
+
// Learning Store (cross-session learning from failures)
|
|
422
|
+
// Connects to the failure tracker in contextEngineering for automatic learning extraction
|
|
423
|
+
if (isFeatureEnabled(this.config.learningStore)) {
|
|
424
|
+
const learningConfig = typeof this.config.learningStore === 'object'
|
|
425
|
+
? this.config.learningStore
|
|
426
|
+
: {};
|
|
427
|
+
this.learningStore = createLearningStore({
|
|
428
|
+
dbPath: learningConfig.dbPath ?? '.agent/learnings.db',
|
|
429
|
+
requireValidation: learningConfig.requireValidation ?? true,
|
|
430
|
+
autoValidateThreshold: learningConfig.autoValidateThreshold ?? 0.9,
|
|
431
|
+
maxLearnings: learningConfig.maxLearnings ?? 500,
|
|
432
|
+
});
|
|
433
|
+
// Connect to the failure tracker if available
|
|
434
|
+
if (this.contextEngineering) {
|
|
435
|
+
const failureTracker = this.contextEngineering.getFailureTracker();
|
|
436
|
+
if (failureTracker) {
|
|
437
|
+
this.learningStore.connectFailureTracker(failureTracker);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Forward learning events to observability
|
|
441
|
+
this.learningStore.on(event => {
|
|
442
|
+
switch (event.type) {
|
|
443
|
+
case 'learning.proposed':
|
|
444
|
+
this.observability?.logger?.info('Learning proposed', {
|
|
445
|
+
learningId: event.learning.id,
|
|
446
|
+
description: event.learning.description,
|
|
447
|
+
});
|
|
448
|
+
this.emit({
|
|
449
|
+
type: 'learning.proposed',
|
|
450
|
+
learningId: event.learning.id,
|
|
451
|
+
description: event.learning.description,
|
|
452
|
+
});
|
|
453
|
+
break;
|
|
454
|
+
case 'learning.validated':
|
|
455
|
+
this.observability?.logger?.info('Learning validated', {
|
|
456
|
+
learningId: event.learningId,
|
|
457
|
+
});
|
|
458
|
+
this.emit({ type: 'learning.validated', learningId: event.learningId });
|
|
459
|
+
break;
|
|
460
|
+
case 'learning.applied':
|
|
461
|
+
this.observability?.logger?.debug('Learning applied', {
|
|
462
|
+
learningId: event.learningId,
|
|
463
|
+
context: event.context,
|
|
464
|
+
});
|
|
465
|
+
this.emit({
|
|
466
|
+
type: 'learning.applied',
|
|
467
|
+
learningId: event.learningId,
|
|
468
|
+
context: event.context,
|
|
469
|
+
});
|
|
470
|
+
break;
|
|
471
|
+
case 'pattern.extracted':
|
|
472
|
+
this.observability?.logger?.info('Pattern extracted as learning', {
|
|
473
|
+
pattern: event.pattern.description,
|
|
474
|
+
learningId: event.learning.id,
|
|
475
|
+
});
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// Auto-Compaction Manager (sophisticated context compaction)
|
|
481
|
+
// Uses the Compactor for LLM-based summarization with threshold monitoring
|
|
482
|
+
if (isFeatureEnabled(this.config.compaction)) {
|
|
483
|
+
const compactionConfig = typeof this.config.compaction === 'object'
|
|
484
|
+
? this.config.compaction
|
|
485
|
+
: {};
|
|
486
|
+
// Create the compactor (requires provider for LLM summarization)
|
|
487
|
+
this.compactor = createCompactor(this.provider, {
|
|
488
|
+
enabled: true,
|
|
489
|
+
tokenThreshold: compactionConfig.tokenThreshold ?? 80000,
|
|
490
|
+
preserveRecentCount: compactionConfig.preserveRecentCount ?? 10,
|
|
491
|
+
preserveToolResults: compactionConfig.preserveToolResults ?? true,
|
|
492
|
+
summaryMaxTokens: compactionConfig.summaryMaxTokens ?? 2000,
|
|
493
|
+
summaryModel: compactionConfig.summaryModel,
|
|
494
|
+
});
|
|
495
|
+
// Create the auto-compaction manager with threshold monitoring
|
|
496
|
+
// Wire reversible compaction through contextEngineering when available
|
|
497
|
+
const compactHandler = this.contextEngineering
|
|
498
|
+
? async (messages) => {
|
|
499
|
+
// Use contextEngineering's reversible compaction to preserve references
|
|
500
|
+
const summarize = async (msgs) => {
|
|
501
|
+
// Use the basic compactor's summarization capability
|
|
502
|
+
const result = await this.compactor.compact(msgs);
|
|
503
|
+
return result.summary;
|
|
504
|
+
};
|
|
505
|
+
const contextMsgs = messages.map(m => ({
|
|
506
|
+
role: m.role,
|
|
507
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
|
|
508
|
+
}));
|
|
509
|
+
const result = await this.contextEngineering.compact(contextMsgs, summarize);
|
|
510
|
+
const tokensBefore = this.compactor.estimateTokens(messages);
|
|
511
|
+
const tokensAfter = this.compactor.estimateTokens([{ role: 'assistant', content: result.summary }]);
|
|
512
|
+
return {
|
|
513
|
+
summary: result.summary + (result.reconstructionPrompt ? `\n\n${result.reconstructionPrompt}` : ''),
|
|
514
|
+
tokensBefore,
|
|
515
|
+
tokensAfter,
|
|
516
|
+
preservedMessages: [{ role: 'assistant', content: result.summary }],
|
|
517
|
+
references: result.references,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
: undefined;
|
|
521
|
+
// Get model's actual context window - try OpenRouter first (real API data),
|
|
522
|
+
// then fall back to hardcoded ModelRegistry, then config, then default
|
|
523
|
+
const openRouterContext = getModelContextLength(this.config.model || '');
|
|
524
|
+
const registryInfo = modelRegistry.getModel(this.config.model || '');
|
|
525
|
+
const registryContext = registryInfo?.capabilities?.maxContextTokens;
|
|
526
|
+
const maxContextTokens = this.config.maxContextTokens
|
|
527
|
+
?? openRouterContext // From OpenRouter API (e.g., GLM-4.7 = 202752)
|
|
528
|
+
?? registryContext // From hardcoded registry (Claude, GPT-4o, etc.)
|
|
529
|
+
?? 200000; // Fallback to 200K
|
|
530
|
+
this.autoCompactionManager = createAutoCompactionManager(this.compactor, {
|
|
531
|
+
mode: compactionConfig.mode ?? 'auto',
|
|
532
|
+
warningThreshold: 0.70, // Warn at 70% of model's context
|
|
533
|
+
autoCompactThreshold: 0.80, // Compact at 80% (changed from 0.90)
|
|
534
|
+
hardLimitThreshold: 0.95, // Hard limit at 95%
|
|
535
|
+
preserveRecentUserMessages: Math.ceil((compactionConfig.preserveRecentCount ?? 10) / 2),
|
|
536
|
+
preserveRecentAssistantMessages: Math.ceil((compactionConfig.preserveRecentCount ?? 10) / 2),
|
|
537
|
+
cooldownMs: 60000, // 1 minute cooldown
|
|
538
|
+
maxContextTokens, // Dynamic from model registry or config
|
|
539
|
+
compactHandler, // Use reversible compaction when contextEngineering is available
|
|
540
|
+
});
|
|
541
|
+
// Forward compactor events to observability
|
|
542
|
+
this.compactor.on(event => {
|
|
543
|
+
switch (event.type) {
|
|
544
|
+
case 'compaction.start':
|
|
545
|
+
this.observability?.logger?.info('Compaction started', {
|
|
546
|
+
messageCount: event.messageCount,
|
|
547
|
+
});
|
|
548
|
+
break;
|
|
549
|
+
case 'compaction.complete':
|
|
550
|
+
this.observability?.logger?.info('Compaction complete', {
|
|
551
|
+
tokensBefore: event.result.tokensBefore,
|
|
552
|
+
tokensAfter: event.result.tokensAfter,
|
|
553
|
+
compactedCount: event.result.compactedCount,
|
|
554
|
+
});
|
|
555
|
+
break;
|
|
556
|
+
case 'compaction.error':
|
|
557
|
+
this.observability?.logger?.error('Compaction error', {
|
|
558
|
+
error: event.error,
|
|
559
|
+
});
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
// Forward auto-compaction events
|
|
564
|
+
this.autoCompactionManager.on((event) => {
|
|
565
|
+
switch (event.type) {
|
|
566
|
+
case 'autocompaction.warning':
|
|
567
|
+
this.observability?.logger?.warn('Context approaching limit', {
|
|
568
|
+
currentTokens: event.currentTokens,
|
|
569
|
+
ratio: event.ratio,
|
|
570
|
+
});
|
|
571
|
+
this.emit({
|
|
572
|
+
type: 'compaction.warning',
|
|
573
|
+
currentTokens: event.currentTokens,
|
|
574
|
+
threshold: Math.round(event.ratio * (this.config.maxContextTokens ?? 200000)),
|
|
575
|
+
});
|
|
576
|
+
break;
|
|
577
|
+
case 'autocompaction.triggered':
|
|
578
|
+
this.observability?.logger?.info('Auto-compaction triggered', {
|
|
579
|
+
mode: event.mode,
|
|
580
|
+
currentTokens: event.currentTokens,
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
case 'autocompaction.completed':
|
|
584
|
+
this.observability?.logger?.info('Auto-compaction completed', {
|
|
585
|
+
tokensBefore: event.tokensBefore,
|
|
586
|
+
tokensAfter: event.tokensAfter,
|
|
587
|
+
reduction: event.reduction,
|
|
588
|
+
});
|
|
589
|
+
this.emit({
|
|
590
|
+
type: 'compaction.auto',
|
|
591
|
+
tokensBefore: event.tokensBefore,
|
|
592
|
+
tokensAfter: event.tokensAfter,
|
|
593
|
+
messagesCompacted: event.tokensBefore - event.tokensAfter,
|
|
594
|
+
});
|
|
595
|
+
break;
|
|
596
|
+
case 'autocompaction.hard_limit':
|
|
597
|
+
this.observability?.logger?.error('Context hard limit reached', {
|
|
598
|
+
currentTokens: event.currentTokens,
|
|
599
|
+
ratio: event.ratio,
|
|
600
|
+
});
|
|
601
|
+
break;
|
|
602
|
+
case 'autocompaction.emergency_truncate':
|
|
603
|
+
this.observability?.logger?.warn('Emergency truncation performed', {
|
|
604
|
+
reason: event.reason,
|
|
605
|
+
messagesBefore: event.messagesBefore,
|
|
606
|
+
messagesAfter: event.messagesAfter,
|
|
607
|
+
});
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
// Note: FileChangeTracker requires a database instance which is not
|
|
613
|
+
// available at this point. Use initFileChangeTracker() to enable it
|
|
614
|
+
// after the agent is constructed with a database reference.
|
|
615
|
+
// This allows the feature to be optional and not require SQLite at all times.
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Initialize the file change tracker with a database instance.
|
|
619
|
+
* Call this if you want undo capability for file operations.
|
|
620
|
+
*
|
|
621
|
+
* @param db - SQLite database instance from better-sqlite3
|
|
622
|
+
* @param sessionId - Session ID for tracking changes
|
|
623
|
+
*/
|
|
624
|
+
initFileChangeTracker(db, sessionId) {
|
|
625
|
+
if (!isFeatureEnabled(this.config.fileChangeTracker)) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const trackerConfig = typeof this.config.fileChangeTracker === 'object'
|
|
629
|
+
? this.config.fileChangeTracker
|
|
630
|
+
: {};
|
|
631
|
+
this.fileChangeTracker = createFileChangeTracker(db, sessionId, {
|
|
632
|
+
enabled: true,
|
|
633
|
+
maxFullContentBytes: trackerConfig.maxFullContentBytes ?? 50 * 1024,
|
|
634
|
+
});
|
|
635
|
+
this.observability?.logger?.info('File change tracker initialized', {
|
|
636
|
+
sessionId,
|
|
637
|
+
maxFullContentBytes: trackerConfig.maxFullContentBytes ?? 50 * 1024,
|
|
638
|
+
});
|
|
413
639
|
}
|
|
414
640
|
/**
|
|
415
641
|
* Ensure all async initialization is complete before running.
|
|
@@ -438,9 +664,19 @@ export class ProductionAgent {
|
|
|
438
664
|
const traceId = this.observability?.tracer?.startTrace('agent.run') || `trace-${Date.now()}`;
|
|
439
665
|
this.emit({ type: 'start', task, traceId });
|
|
440
666
|
this.observability?.logger?.info('Agent started', { task });
|
|
441
|
-
// Lesson 26: Start trace capture
|
|
442
|
-
|
|
443
|
-
|
|
667
|
+
// Lesson 26: Start trace capture
|
|
668
|
+
// If session is already active (managed by REPL), start a task within it.
|
|
669
|
+
// Otherwise, start a new session for backward compatibility (single-task mode).
|
|
670
|
+
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
671
|
+
if (this.traceCollector?.isSessionActive()) {
|
|
672
|
+
// Session managed by REPL - just start a task
|
|
673
|
+
await this.traceCollector.startTask(taskId, task);
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
// Single-task mode (backward compatibility) - start session with task
|
|
677
|
+
const traceSessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
678
|
+
await this.traceCollector?.startSession(traceSessionId, task, this.config.model || 'default', {});
|
|
679
|
+
}
|
|
444
680
|
try {
|
|
445
681
|
// Check for cancellation before starting
|
|
446
682
|
cancellationToken?.throwIfCancellationRequested();
|
|
@@ -471,8 +707,14 @@ export class ProductionAgent {
|
|
|
471
707
|
};
|
|
472
708
|
this.emit({ type: 'complete', result });
|
|
473
709
|
this.observability?.logger?.info('Agent completed', { duration, success: true });
|
|
474
|
-
// Lesson 26: End trace capture
|
|
475
|
-
|
|
710
|
+
// Lesson 26: End trace capture
|
|
711
|
+
// If task is active (REPL mode), end the task. Otherwise end the session (single-task mode).
|
|
712
|
+
if (this.traceCollector?.isTaskActive()) {
|
|
713
|
+
await this.traceCollector.endTask({ success: true, output: response });
|
|
714
|
+
}
|
|
715
|
+
else if (this.traceCollector?.isSessionActive()) {
|
|
716
|
+
await this.traceCollector.endSession({ success: true, output: response });
|
|
717
|
+
}
|
|
476
718
|
return result;
|
|
477
719
|
}
|
|
478
720
|
catch (err) {
|
|
@@ -484,8 +726,13 @@ export class ProductionAgent {
|
|
|
484
726
|
const cleanupDuration = Date.now() - cleanupStart;
|
|
485
727
|
this.emit({ type: 'cancellation.completed', cleanupDuration });
|
|
486
728
|
this.observability?.logger?.info('Agent cancelled', { reason: error.message, cleanupDuration });
|
|
487
|
-
// Lesson 26: End trace capture
|
|
488
|
-
|
|
729
|
+
// Lesson 26: End trace capture on cancellation
|
|
730
|
+
if (this.traceCollector?.isTaskActive()) {
|
|
731
|
+
await this.traceCollector.endTask({ success: false, failureReason: `Cancelled: ${error.message}` });
|
|
732
|
+
}
|
|
733
|
+
else if (this.traceCollector?.isSessionActive()) {
|
|
734
|
+
await this.traceCollector.endSession({ success: false, failureReason: `Cancelled: ${error.message}` });
|
|
735
|
+
}
|
|
489
736
|
return {
|
|
490
737
|
success: false,
|
|
491
738
|
response: '',
|
|
@@ -499,8 +746,13 @@ export class ProductionAgent {
|
|
|
499
746
|
await this.observability?.tracer?.endTrace();
|
|
500
747
|
this.emit({ type: 'error', error: error.message });
|
|
501
748
|
this.observability?.logger?.error('Agent failed', { error: error.message });
|
|
502
|
-
// Lesson 26: End trace capture
|
|
503
|
-
|
|
749
|
+
// Lesson 26: End trace capture on error
|
|
750
|
+
if (this.traceCollector?.isTaskActive()) {
|
|
751
|
+
await this.traceCollector.endTask({ success: false, failureReason: error.message });
|
|
752
|
+
}
|
|
753
|
+
else if (this.traceCollector?.isSessionActive()) {
|
|
754
|
+
await this.traceCollector.endSession({ success: false, failureReason: error.message });
|
|
755
|
+
}
|
|
504
756
|
return {
|
|
505
757
|
success: false,
|
|
506
758
|
response: '',
|
|
@@ -573,6 +825,11 @@ export class ProductionAgent {
|
|
|
573
825
|
// Agent loop - now uses economics-based budget checking
|
|
574
826
|
while (true) {
|
|
575
827
|
this.state.iteration++;
|
|
828
|
+
// Record iteration start for tracing
|
|
829
|
+
this.traceCollector?.record({
|
|
830
|
+
type: 'iteration.start',
|
|
831
|
+
data: { iterationNumber: this.state.iteration },
|
|
832
|
+
});
|
|
576
833
|
// =======================================================================
|
|
577
834
|
// CANCELLATION CHECK
|
|
578
835
|
// =======================================================================
|
|
@@ -774,14 +1031,21 @@ export class ProductionAgent {
|
|
|
774
1031
|
// =====================================================================
|
|
775
1032
|
// RESILIENT LLM CALL: Empty response retries + max_tokens continuation
|
|
776
1033
|
// =====================================================================
|
|
777
|
-
|
|
778
|
-
const
|
|
1034
|
+
// Get resilience config
|
|
1035
|
+
const resilienceConfig = typeof this.config.resilience === 'object'
|
|
1036
|
+
? this.config.resilience
|
|
1037
|
+
: {};
|
|
1038
|
+
const resilienceEnabled = isFeatureEnabled(this.config.resilience);
|
|
1039
|
+
const MAX_EMPTY_RETRIES = resilienceConfig.maxEmptyRetries ?? 2;
|
|
1040
|
+
const MAX_CONTINUATIONS = resilienceConfig.maxContinuations ?? 3;
|
|
1041
|
+
const AUTO_CONTINUE = resilienceConfig.autoContinue ?? true;
|
|
1042
|
+
const MIN_CONTENT_LENGTH = resilienceConfig.minContentLength ?? 1;
|
|
779
1043
|
let response = await this.callLLM(messages);
|
|
780
1044
|
let emptyRetries = 0;
|
|
781
1045
|
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
|
|
1046
|
+
// Phase 1: Handle empty responses with retry (if resilience enabled)
|
|
1047
|
+
while (resilienceEnabled && emptyRetries < MAX_EMPTY_RETRIES) {
|
|
1048
|
+
const hasContent = response.content && response.content.length >= MIN_CONTENT_LENGTH;
|
|
785
1049
|
const hasToolCalls = response.toolCalls && response.toolCalls.length > 0;
|
|
786
1050
|
if (hasContent || hasToolCalls) {
|
|
787
1051
|
// Valid response received
|
|
@@ -818,8 +1082,8 @@ export class ProductionAgent {
|
|
|
818
1082
|
this.state.messages.push(nudgeMessage);
|
|
819
1083
|
response = await this.callLLM(messages);
|
|
820
1084
|
}
|
|
821
|
-
// Phase 2: Handle max_tokens truncation with continuation
|
|
822
|
-
if (response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
1085
|
+
// Phase 2: Handle max_tokens truncation with continuation (if enabled)
|
|
1086
|
+
if (resilienceEnabled && AUTO_CONTINUE && response.stopReason === 'max_tokens' && !response.toolCalls?.length) {
|
|
823
1087
|
let accumulatedContent = response.content || '';
|
|
824
1088
|
while (continuations < MAX_CONTINUATIONS && response.stopReason === 'max_tokens') {
|
|
825
1089
|
continuations++;
|
|
@@ -896,6 +1160,11 @@ export class ProductionAgent {
|
|
|
896
1160
|
continuations,
|
|
897
1161
|
});
|
|
898
1162
|
}
|
|
1163
|
+
// Record iteration end for tracing (no tool calls case)
|
|
1164
|
+
this.traceCollector?.record({
|
|
1165
|
+
type: 'iteration.end',
|
|
1166
|
+
data: { iterationNumber: this.state.iteration },
|
|
1167
|
+
});
|
|
899
1168
|
break;
|
|
900
1169
|
}
|
|
901
1170
|
// Execute tool calls
|
|
@@ -910,8 +1179,34 @@ export class ProductionAgent {
|
|
|
910
1179
|
const MAX_TOOL_OUTPUT_CHARS = 8000; // ~2000 tokens max per tool output
|
|
911
1180
|
// =======================================================================
|
|
912
1181
|
// PROACTIVE BUDGET CHECK - compact BEFORE we overflow, not after
|
|
1182
|
+
// Uses AutoCompactionManager if available for sophisticated compaction
|
|
913
1183
|
// =======================================================================
|
|
914
|
-
|
|
1184
|
+
const currentContextTokens = this.estimateContextTokens(messages);
|
|
1185
|
+
if (this.autoCompactionManager) {
|
|
1186
|
+
// Use the AutoCompactionManager for threshold-based compaction
|
|
1187
|
+
const compactionResult = await this.autoCompactionManager.checkAndMaybeCompact({
|
|
1188
|
+
currentTokens: currentContextTokens,
|
|
1189
|
+
messages: messages,
|
|
1190
|
+
});
|
|
1191
|
+
// Handle compaction result
|
|
1192
|
+
if (compactionResult.status === 'compacted' && compactionResult.compactedMessages) {
|
|
1193
|
+
// Replace messages with compacted version
|
|
1194
|
+
messages.length = 0;
|
|
1195
|
+
messages.push(...compactionResult.compactedMessages);
|
|
1196
|
+
this.state.messages.length = 0;
|
|
1197
|
+
this.state.messages.push(...compactionResult.compactedMessages);
|
|
1198
|
+
}
|
|
1199
|
+
else if (compactionResult.status === 'hard_limit') {
|
|
1200
|
+
// Hard limit reached - this is serious, emit error
|
|
1201
|
+
this.emit({
|
|
1202
|
+
type: 'error',
|
|
1203
|
+
error: `Context hard limit reached (${Math.round(compactionResult.ratio * 100)}% of max tokens)`,
|
|
1204
|
+
});
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
else if (this.economics) {
|
|
1209
|
+
// Fallback to simple compaction
|
|
915
1210
|
const currentUsage = this.economics.getUsage();
|
|
916
1211
|
const budget = this.economics.getBudget();
|
|
917
1212
|
const percentUsed = (currentUsage.tokens / budget.maxTokens) * 100;
|
|
@@ -965,6 +1260,25 @@ export class ProductionAgent {
|
|
|
965
1260
|
messages.push(toolMessage);
|
|
966
1261
|
this.state.messages.push(toolMessage);
|
|
967
1262
|
}
|
|
1263
|
+
// Emit context health after adding tool results
|
|
1264
|
+
const currentTokenEstimate = this.estimateContextTokens(messages);
|
|
1265
|
+
const contextLimit = this.getMaxContextTokens();
|
|
1266
|
+
const percentUsed = Math.round((currentTokenEstimate / contextLimit) * 100);
|
|
1267
|
+
const avgTokensPerExchange = currentTokenEstimate / Math.max(1, this.state.iteration);
|
|
1268
|
+
const remainingTokens = contextLimit - currentTokenEstimate;
|
|
1269
|
+
const estimatedExchanges = Math.floor(remainingTokens / Math.max(1, avgTokensPerExchange));
|
|
1270
|
+
this.emit({
|
|
1271
|
+
type: 'context.health',
|
|
1272
|
+
currentTokens: currentTokenEstimate,
|
|
1273
|
+
maxTokens: contextLimit,
|
|
1274
|
+
estimatedExchanges,
|
|
1275
|
+
percentUsed,
|
|
1276
|
+
});
|
|
1277
|
+
// Record iteration end for tracing (after tool execution)
|
|
1278
|
+
this.traceCollector?.record({
|
|
1279
|
+
type: 'iteration.end',
|
|
1280
|
+
data: { iterationNumber: this.state.iteration },
|
|
1281
|
+
});
|
|
968
1282
|
}
|
|
969
1283
|
// =======================================================================
|
|
970
1284
|
// REFLECTION (Lesson 16)
|
|
@@ -1012,6 +1326,11 @@ export class ProductionAgent {
|
|
|
1012
1326
|
const rulesContent = this.rules?.getRulesContent() ?? '';
|
|
1013
1327
|
const skillsPrompt = this.skillManager?.getActiveSkillsPrompt() ?? '';
|
|
1014
1328
|
const memoryContext = this.memory?.getContextStrings(task) ?? [];
|
|
1329
|
+
// Get relevant learnings from past sessions
|
|
1330
|
+
const learningsContext = this.learningStore?.getLearningContext({
|
|
1331
|
+
query: task,
|
|
1332
|
+
maxLearnings: 5,
|
|
1333
|
+
}) ?? '';
|
|
1015
1334
|
// Budget-aware codebase context selection
|
|
1016
1335
|
let codebaseContextStr = '';
|
|
1017
1336
|
if (this.codebaseContext) {
|
|
@@ -1059,9 +1378,10 @@ export class ProductionAgent {
|
|
|
1059
1378
|
}
|
|
1060
1379
|
// Build system prompt using cache-aware builder if available (Trick P)
|
|
1061
1380
|
let systemPrompt;
|
|
1062
|
-
// Combine memory and codebase context
|
|
1381
|
+
// Combine memory, learnings, and codebase context
|
|
1063
1382
|
const combinedContext = [
|
|
1064
1383
|
...(memoryContext.length > 0 ? memoryContext : []),
|
|
1384
|
+
...(learningsContext ? [learningsContext] : []),
|
|
1065
1385
|
...(codebaseContextStr ? [`\n## Relevant Code\n${codebaseContextStr}`] : []),
|
|
1066
1386
|
].join('\n');
|
|
1067
1387
|
if (this.contextEngineering) {
|
|
@@ -1118,7 +1438,7 @@ export class ProductionAgent {
|
|
|
1118
1438
|
return sum + Math.ceil(content.length / 3.5); // ~3.5 chars per token estimate
|
|
1119
1439
|
}, 0);
|
|
1120
1440
|
// Use context window size, not output token limit
|
|
1121
|
-
const contextLimit = this.
|
|
1441
|
+
const contextLimit = this.getMaxContextTokens();
|
|
1122
1442
|
this.emit({
|
|
1123
1443
|
type: 'insight.context',
|
|
1124
1444
|
currentTokens: estimatedTokens,
|
|
@@ -1193,6 +1513,38 @@ export class ProductionAgent {
|
|
|
1193
1513
|
reason: actualModel !== model ? 'Routed based on complexity' : 'Default model',
|
|
1194
1514
|
complexity: complexity <= 0.3 ? 'low' : complexity <= 0.7 ? 'medium' : 'high',
|
|
1195
1515
|
});
|
|
1516
|
+
// Emit decision transparency event
|
|
1517
|
+
this.emit({
|
|
1518
|
+
type: 'decision.routing',
|
|
1519
|
+
model: actualModel,
|
|
1520
|
+
reason: actualModel !== model
|
|
1521
|
+
? `Complexity ${(complexity * 100).toFixed(0)}% - using ${actualModel}`
|
|
1522
|
+
: 'Default model for current task',
|
|
1523
|
+
alternatives: actualModel !== model
|
|
1524
|
+
? [{ model, rejected: 'complexity threshold exceeded' }]
|
|
1525
|
+
: undefined,
|
|
1526
|
+
});
|
|
1527
|
+
// Enhanced tracing: Record routing decision
|
|
1528
|
+
this.traceCollector?.record({
|
|
1529
|
+
type: 'decision',
|
|
1530
|
+
data: {
|
|
1531
|
+
type: 'routing',
|
|
1532
|
+
decision: `Selected model: ${actualModel}`,
|
|
1533
|
+
outcome: 'allowed',
|
|
1534
|
+
reasoning: actualModel !== model
|
|
1535
|
+
? `Task complexity ${(complexity * 100).toFixed(0)}% exceeded threshold - routed to ${actualModel}`
|
|
1536
|
+
: `Default model ${model} suitable for task complexity ${(complexity * 100).toFixed(0)}%`,
|
|
1537
|
+
factors: [
|
|
1538
|
+
{ name: 'complexity', value: complexity, weight: 0.8 },
|
|
1539
|
+
{ name: 'hasTools', value: context.hasTools, weight: 0.1 },
|
|
1540
|
+
{ name: 'taskType', value: context.taskType, weight: 0.1 },
|
|
1541
|
+
],
|
|
1542
|
+
alternatives: actualModel !== model
|
|
1543
|
+
? [{ option: model, reason: 'complexity threshold exceeded', rejected: true }]
|
|
1544
|
+
: undefined,
|
|
1545
|
+
confidence: 0.9,
|
|
1546
|
+
},
|
|
1547
|
+
});
|
|
1196
1548
|
}
|
|
1197
1549
|
else {
|
|
1198
1550
|
response = await this.provider.chat(messages, {
|
|
@@ -1226,6 +1578,19 @@ export class ProductionAgent {
|
|
|
1226
1578
|
durationMs: duration,
|
|
1227
1579
|
},
|
|
1228
1580
|
});
|
|
1581
|
+
// Enhanced tracing: Record thinking/reasoning blocks if present
|
|
1582
|
+
if (response.thinking) {
|
|
1583
|
+
this.traceCollector?.record({
|
|
1584
|
+
type: 'llm.thinking',
|
|
1585
|
+
data: {
|
|
1586
|
+
requestId,
|
|
1587
|
+
content: response.thinking,
|
|
1588
|
+
summarized: response.thinking.length > 10000, // Summarize if very long
|
|
1589
|
+
originalLength: response.thinking.length,
|
|
1590
|
+
durationMs: duration,
|
|
1591
|
+
},
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1229
1594
|
// Record metrics
|
|
1230
1595
|
this.observability?.metrics?.recordLLMCall(response.usage?.inputTokens || 0, response.usage?.outputTokens || 0, duration, actualModel, response.usage?.cost // Actual cost from provider (e.g., OpenRouter)
|
|
1231
1596
|
);
|
|
@@ -1299,6 +1664,7 @@ export class ProductionAgent {
|
|
|
1299
1664
|
type: 'plan.change.queued',
|
|
1300
1665
|
tool: toolCall.name,
|
|
1301
1666
|
changeId: change?.id,
|
|
1667
|
+
summary: this.formatToolArgsForPlan(toolCall.name, toolCall.arguments),
|
|
1302
1668
|
});
|
|
1303
1669
|
// Return a message indicating the change was queued
|
|
1304
1670
|
const queueMessage = `[PLAN MODE] Change queued for approval:\n` +
|
|
@@ -1329,6 +1695,32 @@ export class ProductionAgent {
|
|
|
1329
1695
|
policy: evaluation.policy,
|
|
1330
1696
|
reason: evaluation.reason,
|
|
1331
1697
|
});
|
|
1698
|
+
// Emit decision transparency event
|
|
1699
|
+
this.emit({
|
|
1700
|
+
type: 'decision.tool',
|
|
1701
|
+
tool: toolCall.name,
|
|
1702
|
+
decision: evaluation.policy === 'forbidden' ? 'blocked'
|
|
1703
|
+
: evaluation.policy === 'prompt' ? 'prompted'
|
|
1704
|
+
: 'allowed',
|
|
1705
|
+
policyMatch: evaluation.reason,
|
|
1706
|
+
});
|
|
1707
|
+
// Enhanced tracing: Record policy decision
|
|
1708
|
+
this.traceCollector?.record({
|
|
1709
|
+
type: 'decision',
|
|
1710
|
+
data: {
|
|
1711
|
+
type: 'policy',
|
|
1712
|
+
decision: `Tool ${toolCall.name}: ${evaluation.policy}`,
|
|
1713
|
+
outcome: evaluation.policy === 'forbidden' ? 'blocked'
|
|
1714
|
+
: evaluation.policy === 'prompt' ? 'deferred'
|
|
1715
|
+
: 'allowed',
|
|
1716
|
+
reasoning: evaluation.reason,
|
|
1717
|
+
factors: [
|
|
1718
|
+
{ name: 'policy', value: evaluation.policy },
|
|
1719
|
+
{ name: 'requiresApproval', value: evaluation.requiresApproval ?? false },
|
|
1720
|
+
],
|
|
1721
|
+
confidence: evaluation.intent?.confidence ?? 0.8,
|
|
1722
|
+
},
|
|
1723
|
+
});
|
|
1332
1724
|
// Handle forbidden policy - always block
|
|
1333
1725
|
if (evaluation.policy === 'forbidden') {
|
|
1334
1726
|
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
@@ -1594,6 +1986,12 @@ export class ProductionAgent {
|
|
|
1594
1986
|
if (toolName === 'delete_file') {
|
|
1595
1987
|
return `Delete: ${args.path || args.file_path}`;
|
|
1596
1988
|
}
|
|
1989
|
+
if (toolName === 'spawn_agent' || toolName === 'researcher') {
|
|
1990
|
+
const task = String(args.task || args.prompt || args.goal || '');
|
|
1991
|
+
const model = args.model ? ` (${args.model})` : '';
|
|
1992
|
+
const firstLine = task.split('\n')[0].slice(0, 100);
|
|
1993
|
+
return `${firstLine}${task.length > 100 ? '...' : ''}${model}`;
|
|
1994
|
+
}
|
|
1597
1995
|
// Generic
|
|
1598
1996
|
return `Args: ${JSON.stringify(args).slice(0, 100)}...`;
|
|
1599
1997
|
}
|
|
@@ -1621,6 +2019,27 @@ export class ProductionAgent {
|
|
|
1621
2019
|
getState() {
|
|
1622
2020
|
return { ...this.state };
|
|
1623
2021
|
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Get the maximum context tokens for this agent's model.
|
|
2024
|
+
* Priority: user config > OpenRouter API > hardcoded ModelRegistry > 200K default
|
|
2025
|
+
*/
|
|
2026
|
+
getMaxContextTokens() {
|
|
2027
|
+
if (this.config.maxContextTokens) {
|
|
2028
|
+
return this.config.maxContextTokens;
|
|
2029
|
+
}
|
|
2030
|
+
// Try OpenRouter API cache (has real data for GLM-4.7, etc.)
|
|
2031
|
+
const openRouterContext = getModelContextLength(this.config.model || '');
|
|
2032
|
+
if (openRouterContext) {
|
|
2033
|
+
return openRouterContext;
|
|
2034
|
+
}
|
|
2035
|
+
// Fall back to hardcoded registry
|
|
2036
|
+
const registryInfo = modelRegistry.getModel(this.config.model || '');
|
|
2037
|
+
if (registryInfo?.capabilities?.maxContextTokens) {
|
|
2038
|
+
return registryInfo.capabilities.maxContextTokens;
|
|
2039
|
+
}
|
|
2040
|
+
// Default
|
|
2041
|
+
return 200000;
|
|
2042
|
+
}
|
|
1624
2043
|
/**
|
|
1625
2044
|
* Get the trace collector (Lesson 26).
|
|
1626
2045
|
* Returns null if trace capture is not enabled.
|
|
@@ -1628,6 +2047,67 @@ export class ProductionAgent {
|
|
|
1628
2047
|
getTraceCollector() {
|
|
1629
2048
|
return this.traceCollector;
|
|
1630
2049
|
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Get the learning store for cross-session learning.
|
|
2052
|
+
* Returns null if learning store is not enabled.
|
|
2053
|
+
*/
|
|
2054
|
+
getLearningStore() {
|
|
2055
|
+
return this.learningStore;
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Get the auto-compaction manager.
|
|
2059
|
+
* Returns null if compaction is not enabled.
|
|
2060
|
+
*/
|
|
2061
|
+
getAutoCompactionManager() {
|
|
2062
|
+
return this.autoCompactionManager;
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Get the file change tracker for undo capability.
|
|
2066
|
+
* Returns null if file change tracking is not enabled.
|
|
2067
|
+
*/
|
|
2068
|
+
getFileChangeTracker() {
|
|
2069
|
+
return this.fileChangeTracker;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Record a file change for potential undo.
|
|
2073
|
+
* No-op if file change tracking is not enabled.
|
|
2074
|
+
*
|
|
2075
|
+
* @param params - Change details
|
|
2076
|
+
* @returns Change ID if tracked, -1 otherwise
|
|
2077
|
+
*/
|
|
2078
|
+
async trackFileChange(params) {
|
|
2079
|
+
if (!this.fileChangeTracker) {
|
|
2080
|
+
return -1;
|
|
2081
|
+
}
|
|
2082
|
+
return this.fileChangeTracker.recordChange({
|
|
2083
|
+
filePath: params.filePath,
|
|
2084
|
+
operation: params.operation,
|
|
2085
|
+
contentBefore: params.contentBefore,
|
|
2086
|
+
contentAfter: params.contentAfter,
|
|
2087
|
+
turnNumber: this.state.iteration,
|
|
2088
|
+
toolCallId: params.toolCallId,
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Undo the last change to a specific file.
|
|
2093
|
+
* Returns null if file change tracking is not enabled.
|
|
2094
|
+
*/
|
|
2095
|
+
async undoLastFileChange(filePath) {
|
|
2096
|
+
if (!this.fileChangeTracker) {
|
|
2097
|
+
return null;
|
|
2098
|
+
}
|
|
2099
|
+
return this.fileChangeTracker.undoLastChange(filePath);
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Undo all changes in the current turn.
|
|
2103
|
+
* Returns null if file change tracking is not enabled.
|
|
2104
|
+
*/
|
|
2105
|
+
async undoCurrentTurn() {
|
|
2106
|
+
if (!this.fileChangeTracker) {
|
|
2107
|
+
return null;
|
|
2108
|
+
}
|
|
2109
|
+
return this.fileChangeTracker.undoTurn(this.state.iteration);
|
|
2110
|
+
}
|
|
1631
2111
|
/**
|
|
1632
2112
|
* Subscribe to events.
|
|
1633
2113
|
*/
|
|
@@ -2280,6 +2760,8 @@ export class ProductionAgent {
|
|
|
2280
2760
|
this.emit({ type: 'agent.spawn', agentId: `spawn-${Date.now()}`, name: agentName, task });
|
|
2281
2761
|
this.observability?.logger?.info('Spawning agent', { name: agentName, task });
|
|
2282
2762
|
const startTime = Date.now();
|
|
2763
|
+
const childSessionId = `subagent-${agentName}-${Date.now()}`;
|
|
2764
|
+
const childTraceId = `trace-${childSessionId}`;
|
|
2283
2765
|
try {
|
|
2284
2766
|
// Filter tools for this agent
|
|
2285
2767
|
const agentTools = filterToolsForAgent(agentDef, Array.from(this.tools.values()));
|
|
@@ -2288,13 +2770,27 @@ export class ProductionAgent {
|
|
|
2288
2770
|
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
2289
2771
|
? agentDef.model
|
|
2290
2772
|
: this.config.model;
|
|
2773
|
+
// Get subagent config with defaults
|
|
2774
|
+
// Note: subagent config is SubagentConfig | false from buildConfig
|
|
2775
|
+
const subagentConfig = this.config.subagent;
|
|
2776
|
+
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
2777
|
+
const defaultMaxIterations = hasSubagentConfig
|
|
2778
|
+
? subagentConfig.defaultMaxIterations ?? 10
|
|
2779
|
+
: 10;
|
|
2780
|
+
const subagentTimeout = hasSubagentConfig
|
|
2781
|
+
? subagentConfig.defaultTimeout ?? 120000
|
|
2782
|
+
: 120000;
|
|
2291
2783
|
// Create a sub-agent with the agent's config
|
|
2292
2784
|
const subAgent = new ProductionAgent({
|
|
2293
2785
|
provider: this.provider,
|
|
2294
2786
|
tools: agentTools,
|
|
2787
|
+
// Pass toolResolver so subagent can lazy-load MCP tools
|
|
2788
|
+
toolResolver: this.toolResolver || undefined,
|
|
2789
|
+
// Pass MCP tool summaries so subagent knows what tools are available
|
|
2790
|
+
mcpToolSummaries: this.config.mcpToolSummaries,
|
|
2295
2791
|
systemPrompt: agentDef.systemPrompt,
|
|
2296
2792
|
model: resolvedModel,
|
|
2297
|
-
maxIterations: agentDef.maxIterations ||
|
|
2793
|
+
maxIterations: agentDef.maxIterations || defaultMaxIterations,
|
|
2298
2794
|
// Inherit some features but keep subagent simpler
|
|
2299
2795
|
memory: false,
|
|
2300
2796
|
planning: false,
|
|
@@ -2317,21 +2813,88 @@ export class ProductionAgent {
|
|
|
2317
2813
|
const taggedEvent = { ...event, subagent: agentName };
|
|
2318
2814
|
this.emit(taggedEvent);
|
|
2319
2815
|
});
|
|
2320
|
-
//
|
|
2321
|
-
const
|
|
2322
|
-
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2816
|
+
// Create timeout token for subagent execution
|
|
2817
|
+
const timeoutSource = createTimeoutToken(subagentTimeout);
|
|
2818
|
+
// Link parent's cancellation with subagent timeout so ESC propagates to subagents
|
|
2819
|
+
const parentSource = this.cancellation?.getSource();
|
|
2820
|
+
const effectiveSource = parentSource
|
|
2821
|
+
? createLinkedToken(parentSource, timeoutSource)
|
|
2822
|
+
: timeoutSource;
|
|
2823
|
+
try {
|
|
2824
|
+
// Run the task with cancellation propagation from parent
|
|
2825
|
+
const result = await race(subAgent.run(task), effectiveSource.token);
|
|
2826
|
+
const duration = Date.now() - startTime;
|
|
2827
|
+
const spawnResult = {
|
|
2828
|
+
success: result.success,
|
|
2829
|
+
output: result.response || result.error || '',
|
|
2830
|
+
metrics: {
|
|
2831
|
+
tokens: result.metrics.totalTokens,
|
|
2832
|
+
duration,
|
|
2833
|
+
toolCalls: result.metrics.toolCalls,
|
|
2834
|
+
},
|
|
2835
|
+
};
|
|
2836
|
+
this.emit({ type: 'agent.complete', agentId: agentName, success: result.success });
|
|
2837
|
+
// Enhanced tracing: Record subagent completion
|
|
2838
|
+
this.traceCollector?.record({
|
|
2839
|
+
type: 'subagent.link',
|
|
2840
|
+
data: {
|
|
2841
|
+
parentSessionId: this.traceCollector.getSessionId() || 'unknown',
|
|
2842
|
+
childSessionId,
|
|
2843
|
+
childTraceId,
|
|
2844
|
+
childConfig: {
|
|
2845
|
+
agentType: agentName,
|
|
2846
|
+
model: resolvedModel || 'default',
|
|
2847
|
+
task,
|
|
2848
|
+
tools: agentTools.map(t => t.name),
|
|
2849
|
+
},
|
|
2850
|
+
spawnContext: {
|
|
2851
|
+
reason: `Delegated task: ${task.slice(0, 100)}`,
|
|
2852
|
+
expectedOutcome: agentDef.description,
|
|
2853
|
+
parentIteration: this.state.iteration,
|
|
2854
|
+
},
|
|
2855
|
+
result: {
|
|
2856
|
+
success: result.success,
|
|
2857
|
+
summary: (result.response || result.error || '').slice(0, 500),
|
|
2858
|
+
tokensUsed: result.metrics.totalTokens,
|
|
2859
|
+
durationMs: duration,
|
|
2860
|
+
},
|
|
2861
|
+
},
|
|
2862
|
+
});
|
|
2863
|
+
await subAgent.cleanup();
|
|
2864
|
+
return spawnResult;
|
|
2865
|
+
}
|
|
2866
|
+
catch (err) {
|
|
2867
|
+
// Handle cancellation (user ESC or timeout) for cleaner error messages
|
|
2868
|
+
if (isCancellationError(err)) {
|
|
2869
|
+
const duration = Date.now() - startTime;
|
|
2870
|
+
const isUserCancellation = parentSource?.isCancellationRequested;
|
|
2871
|
+
const reason = isUserCancellation
|
|
2872
|
+
? 'User cancelled'
|
|
2873
|
+
: `Timed out after ${subagentTimeout}ms`;
|
|
2874
|
+
this.emit({ type: 'agent.error', agentId: agentName, error: reason });
|
|
2875
|
+
// Try to cleanup the subagent gracefully
|
|
2876
|
+
try {
|
|
2877
|
+
await subAgent.cleanup();
|
|
2878
|
+
}
|
|
2879
|
+
catch {
|
|
2880
|
+
// Ignore cleanup errors on cancellation
|
|
2881
|
+
}
|
|
2882
|
+
const output = isUserCancellation
|
|
2883
|
+
? `Subagent '${agentName}' was cancelled by user.`
|
|
2884
|
+
: `Subagent '${agentName}' timed out after ${Math.round(subagentTimeout / 1000)}s. The task may be too complex or the model may be slow.`;
|
|
2885
|
+
return {
|
|
2886
|
+
success: false,
|
|
2887
|
+
output,
|
|
2888
|
+
metrics: { tokens: 0, duration, toolCalls: 0 },
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
throw err; // Re-throw non-cancellation errors
|
|
2892
|
+
}
|
|
2893
|
+
finally {
|
|
2894
|
+
// Dispose both sources (linked source disposes its internal state, timeout source handles its timer)
|
|
2895
|
+
effectiveSource.dispose();
|
|
2896
|
+
timeoutSource.dispose();
|
|
2897
|
+
}
|
|
2335
2898
|
}
|
|
2336
2899
|
catch (err) {
|
|
2337
2900
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -2814,6 +3377,18 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
2814
3377
|
// =========================================================================
|
|
2815
3378
|
// SKILLS METHODS
|
|
2816
3379
|
// =========================================================================
|
|
3380
|
+
/**
|
|
3381
|
+
* Get the skill manager instance for advanced operations.
|
|
3382
|
+
*/
|
|
3383
|
+
getSkillManager() {
|
|
3384
|
+
return this.skillManager;
|
|
3385
|
+
}
|
|
3386
|
+
/**
|
|
3387
|
+
* Get the agent registry instance for advanced operations.
|
|
3388
|
+
*/
|
|
3389
|
+
getAgentRegistry() {
|
|
3390
|
+
return this.agentRegistry;
|
|
3391
|
+
}
|
|
2817
3392
|
/**
|
|
2818
3393
|
* Get all loaded skills.
|
|
2819
3394
|
*/
|
|
@@ -2860,6 +3435,34 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
2860
3435
|
findMatchingSkills(query) {
|
|
2861
3436
|
return this.skillManager?.findMatchingSkills(query) || [];
|
|
2862
3437
|
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Get the capabilities registry for unified discovery.
|
|
3440
|
+
* Lazily creates and populates the registry on first access.
|
|
3441
|
+
*/
|
|
3442
|
+
getCapabilitiesRegistry() {
|
|
3443
|
+
if (!this.capabilitiesRegistry) {
|
|
3444
|
+
this.capabilitiesRegistry = createCapabilitiesRegistry();
|
|
3445
|
+
// Register sources
|
|
3446
|
+
this.capabilitiesRegistry.registerToolRegistry({
|
|
3447
|
+
getTools: () => this.getTools(),
|
|
3448
|
+
});
|
|
3449
|
+
if (this.skillManager) {
|
|
3450
|
+
this.capabilitiesRegistry.registerSkillManager(this.skillManager);
|
|
3451
|
+
}
|
|
3452
|
+
if (this.agentRegistry) {
|
|
3453
|
+
this.capabilitiesRegistry.registerAgentRegistry(this.agentRegistry);
|
|
3454
|
+
}
|
|
3455
|
+
// MCP client is registered externally if available
|
|
3456
|
+
}
|
|
3457
|
+
return this.capabilitiesRegistry;
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Register an MCP client with the capabilities registry.
|
|
3461
|
+
*/
|
|
3462
|
+
registerMCPClient(client) {
|
|
3463
|
+
const registry = this.getCapabilitiesRegistry();
|
|
3464
|
+
registry.registerMCPClient(client);
|
|
3465
|
+
}
|
|
2863
3466
|
/**
|
|
2864
3467
|
* Get formatted list of available skills.
|
|
2865
3468
|
*/
|