attocode 0.1.6 → 0.1.7
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 +10 -1
- package/README.md +47 -0
- package/dist/src/adapters.d.ts +21 -1
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +29 -1
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +266 -34
- package/dist/src/agent.js.map +1 -1
- package/dist/src/commands/agents-commands.d.ts.map +1 -1
- package/dist/src/commands/agents-commands.js +18 -4
- package/dist/src/commands/agents-commands.js.map +1 -1
- package/dist/src/commands/handler.d.ts.map +1 -1
- package/dist/src/commands/handler.js +120 -5
- package/dist/src/commands/handler.js.map +1 -1
- package/dist/src/defaults.d.ts +30 -0
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +52 -2
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.js +4 -4
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/index.d.ts +1 -0
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +2 -0
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/sqlite-store.d.ts.map +1 -1
- package/dist/src/integrations/sqlite-store.js +17 -1
- package/dist/src/integrations/sqlite-store.js.map +1 -1
- package/dist/src/integrations/task-manager.d.ts +132 -0
- package/dist/src/integrations/task-manager.d.ts.map +1 -0
- package/dist/src/integrations/task-manager.js +309 -0
- package/dist/src/integrations/task-manager.js.map +1 -0
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +9 -0
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.d.ts +23 -0
- package/dist/src/modes.d.ts.map +1 -1
- package/dist/src/modes.js +61 -0
- package/dist/src/modes.js.map +1 -1
- package/dist/src/providers/adapters/openai.d.ts +46 -2
- package/dist/src/providers/adapters/openai.d.ts.map +1 -1
- package/dist/src/providers/adapters/openai.js +221 -21
- package/dist/src/providers/adapters/openai.js.map +1 -1
- package/dist/src/tools/agent.d.ts +18 -1
- package/dist/src/tools/agent.d.ts.map +1 -1
- package/dist/src/tools/agent.js +38 -2
- package/dist/src/tools/agent.js.map +1 -1
- package/dist/src/tools/tasks.d.ts +32 -0
- package/dist/src/tools/tasks.d.ts.map +1 -0
- package/dist/src/tools/tasks.js +334 -0
- package/dist/src/tools/tasks.js.map +1 -0
- package/dist/src/tracing/trace-collector.d.ts +81 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +216 -4
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +8 -0
- package/dist/src/tracing/types.d.ts.map +1 -1
- 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 +264 -29
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/components/ActiveAgentsPanel.d.ts +45 -0
- package/dist/src/tui/components/ActiveAgentsPanel.d.ts.map +1 -0
- package/dist/src/tui/components/ActiveAgentsPanel.js +121 -0
- package/dist/src/tui/components/ActiveAgentsPanel.js.map +1 -0
- package/dist/src/tui/components/ErrorBoundary.d.ts +63 -0
- package/dist/src/tui/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/src/tui/components/ErrorBoundary.js +88 -0
- package/dist/src/tui/components/ErrorBoundary.js.map +1 -0
- package/dist/src/tui/components/TasksPanel.d.ts +25 -0
- package/dist/src/tui/components/TasksPanel.d.ts.map +1 -0
- package/dist/src/tui/components/TasksPanel.js +101 -0
- package/dist/src/tui/components/TasksPanel.js.map +1 -0
- package/dist/src/tui/components/index.d.ts +3 -0
- package/dist/src/tui/components/index.d.ts.map +1 -1
- package/dist/src/tui/components/index.js +6 -0
- package/dist/src/tui/components/index.js.map +1 -1
- package/dist/src/tui/hooks/index.d.ts +7 -0
- package/dist/src/tui/hooks/index.d.ts.map +1 -0
- package/dist/src/tui/hooks/index.js +7 -0
- package/dist/src/tui/hooks/index.js.map +1 -0
- package/dist/src/tui/hooks/useMessagePruning.d.ts +114 -0
- package/dist/src/tui/hooks/useMessagePruning.d.ts.map +1 -0
- package/dist/src/tui/hooks/useMessagePruning.js +127 -0
- package/dist/src/tui/hooks/useMessagePruning.js.map +1 -0
- package/dist/src/tui/index.d.ts +3 -0
- package/dist/src/tui/index.d.ts.map +1 -1
- package/dist/src/tui/index.js +9 -0
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/utils/index.d.ts +7 -0
- package/dist/src/tui/utils/index.d.ts.map +1 -0
- package/dist/src/tui/utils/index.js +7 -0
- package/dist/src/tui/utils/index.js.map +1 -0
- package/dist/src/tui/utils/keyboard.d.ts +123 -0
- package/dist/src/tui/utils/keyboard.d.ts.map +1 -0
- package/dist/src/tui/utils/keyboard.js +185 -0
- package/dist/src/tui/utils/keyboard.js.map +1 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/src/agent.js
CHANGED
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* - Execution Policies (Lesson 23)
|
|
19
19
|
* - Thread Management (Lesson 24)
|
|
20
20
|
*/
|
|
21
|
-
import { buildConfig, isFeatureEnabled, getEnabledFeatures, } from './defaults.js';
|
|
22
|
-
import { createModeManager, formatModeList, parseMode, } from './modes.js';
|
|
21
|
+
import { buildConfig, isFeatureEnabled, getEnabledFeatures, getSubagentTimeout, getSubagentMaxIterations, } from './defaults.js';
|
|
22
|
+
import { createModeManager, formatModeList, parseMode, calculateTaskSimilarity, SUBAGENT_PLAN_MODE_ADDITION, } 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, createTimeoutToken, createLinkedToken, race, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, createPendingPlanManager, createInteractivePlanner, createRecursiveContext, createLearningStore, createCompactor, createAutoCompactionManager, createFileChangeTracker, createCapabilitiesRegistry, createSharedBlackboard, } 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, createSharedBlackboard, createTaskManager, } from './integrations/index.js';
|
|
25
25
|
// Lesson 26: Tracing & Evaluation integration
|
|
26
26
|
import { createTraceCollector } from './tracing/trace-collector.js';
|
|
27
27
|
// Model registry for context window limits
|
|
@@ -29,6 +29,8 @@ import { modelRegistry } from './costs/index.js';
|
|
|
29
29
|
import { getModelContextLength } from './integrations/openrouter-pricing.js';
|
|
30
30
|
// Spawn agent tools for LLM-driven subagent delegation
|
|
31
31
|
import { createBoundSpawnAgentTool, createBoundSpawnAgentsParallelTool, } from './tools/agent.js';
|
|
32
|
+
// Task tools for Claude Code-style task management
|
|
33
|
+
import { createTaskTools, } from './tools/tasks.js';
|
|
32
34
|
// =============================================================================
|
|
33
35
|
// PRODUCTION AGENT
|
|
34
36
|
// =============================================================================
|
|
@@ -72,12 +74,16 @@ export class ProductionAgent {
|
|
|
72
74
|
capabilitiesRegistry = null;
|
|
73
75
|
toolResolver = null;
|
|
74
76
|
blackboard = null;
|
|
77
|
+
taskManager = null;
|
|
75
78
|
// Duplicate spawn prevention - tracks recently spawned tasks to prevent doom loops
|
|
76
79
|
// Map<taskKey, { timestamp: number; result: string; queuedChanges: number }>
|
|
77
80
|
spawnedTasks = new Map();
|
|
78
81
|
static SPAWN_DEDUP_WINDOW_MS = 60000; // 60 seconds
|
|
79
82
|
// Parent iteration tracking for total budget calculation
|
|
80
83
|
parentIterations = 0;
|
|
84
|
+
// External cancellation token (for subagent timeout propagation)
|
|
85
|
+
// When set, the agent will check this token in addition to its own cancellation manager
|
|
86
|
+
externalCancellationToken = null;
|
|
81
87
|
// Initialization tracking
|
|
82
88
|
initPromises = [];
|
|
83
89
|
initComplete = false;
|
|
@@ -234,11 +240,25 @@ export class ProductionAgent {
|
|
|
234
240
|
console.warn('[ProductionAgent] Failed to load user agents:', err);
|
|
235
241
|
}));
|
|
236
242
|
// Register spawn_agent tool so LLM can delegate to subagents
|
|
237
|
-
const boundSpawnTool = createBoundSpawnAgentTool((name, task) => this.spawnAgent(name, task));
|
|
243
|
+
const boundSpawnTool = createBoundSpawnAgentTool((name, task, constraints) => this.spawnAgent(name, task, constraints));
|
|
238
244
|
this.tools.set(boundSpawnTool.name, boundSpawnTool);
|
|
239
245
|
// Register spawn_agents_parallel tool for parallel subagent execution
|
|
240
246
|
const boundParallelSpawnTool = createBoundSpawnAgentsParallelTool((tasks) => this.spawnAgentsParallel(tasks));
|
|
241
247
|
this.tools.set(boundParallelSpawnTool.name, boundParallelSpawnTool);
|
|
248
|
+
// Task Manager - Claude Code-style task system for coordination
|
|
249
|
+
this.taskManager = createTaskManager();
|
|
250
|
+
// Forward task events
|
|
251
|
+
this.taskManager.on('task.created', (data) => {
|
|
252
|
+
this.emit({ type: 'task.created', task: data.task });
|
|
253
|
+
});
|
|
254
|
+
this.taskManager.on('task.updated', (data) => {
|
|
255
|
+
this.emit({ type: 'task.updated', task: data.task });
|
|
256
|
+
});
|
|
257
|
+
// Register task tools
|
|
258
|
+
const taskTools = createTaskTools(this.taskManager);
|
|
259
|
+
for (const tool of taskTools) {
|
|
260
|
+
this.tools.set(tool.name, tool);
|
|
261
|
+
}
|
|
242
262
|
// Cancellation Support
|
|
243
263
|
if (isFeatureEnabled(this.config.cancellation)) {
|
|
244
264
|
this.cancellation = createCancellationManager();
|
|
@@ -863,10 +883,16 @@ export class ProductionAgent {
|
|
|
863
883
|
});
|
|
864
884
|
// =======================================================================
|
|
865
885
|
// CANCELLATION CHECK
|
|
886
|
+
// Checks both internal cancellation (ESC key) and external cancellation
|
|
887
|
+
// (parent timeout when this agent is a subagent)
|
|
866
888
|
// =======================================================================
|
|
867
889
|
if (this.cancellation?.isCancelled) {
|
|
868
890
|
this.cancellation.token.throwIfCancellationRequested();
|
|
869
891
|
}
|
|
892
|
+
// Also check external cancellation token (from parent when spawned as subagent)
|
|
893
|
+
if (this.externalCancellationToken?.isCancellationRequested) {
|
|
894
|
+
this.externalCancellationToken.throwIfCancellationRequested();
|
|
895
|
+
}
|
|
870
896
|
// =======================================================================
|
|
871
897
|
// RESOURCE CHECK - system resource limits
|
|
872
898
|
// =======================================================================
|
|
@@ -1900,17 +1926,21 @@ export class ProductionAgent {
|
|
|
1900
1926
|
// Execute tool (with sandbox if available)
|
|
1901
1927
|
let result;
|
|
1902
1928
|
if (this.safety?.sandbox) {
|
|
1903
|
-
// CRITICAL: spawn_agent
|
|
1929
|
+
// CRITICAL: spawn_agent and spawn_agents_parallel need MUCH longer timeouts
|
|
1904
1930
|
// The default 60s sandbox timeout would kill subagents prematurely
|
|
1905
1931
|
// Subagents may run for minutes (per their own timeout config)
|
|
1906
1932
|
const isSpawnAgent = toolCall.name === 'spawn_agent';
|
|
1933
|
+
const isSpawnParallel = toolCall.name === 'spawn_agents_parallel';
|
|
1934
|
+
const isSubagentTool = isSpawnAgent || isSpawnParallel;
|
|
1907
1935
|
const subagentConfig = this.config.subagent;
|
|
1908
1936
|
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
1909
1937
|
const subagentTimeout = hasSubagentConfig
|
|
1910
1938
|
? subagentConfig.defaultTimeout ?? 600000 // 10 min default
|
|
1911
1939
|
: 600000;
|
|
1912
|
-
// Use subagent timeout + buffer for
|
|
1913
|
-
|
|
1940
|
+
// Use subagent timeout + buffer for spawn tools, default for others
|
|
1941
|
+
// For spawn_agents_parallel, multiply by number of agents (they run in parallel,
|
|
1942
|
+
// but the total wall-clock time should still allow the slowest agent to complete)
|
|
1943
|
+
const toolTimeout = isSubagentTool ? subagentTimeout + 30000 : undefined;
|
|
1914
1944
|
result = await this.safety.sandbox.executeWithLimits(() => tool.execute(toolCall.arguments), toolTimeout);
|
|
1915
1945
|
}
|
|
1916
1946
|
else {
|
|
@@ -2294,6 +2324,13 @@ export class ProductionAgent {
|
|
|
2294
2324
|
getTraceCollector() {
|
|
2295
2325
|
return this.traceCollector;
|
|
2296
2326
|
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Set a trace collector for this agent.
|
|
2329
|
+
* Used for subagents to share the parent's trace collector (with subagent context).
|
|
2330
|
+
*/
|
|
2331
|
+
setTraceCollector(collector) {
|
|
2332
|
+
this.traceCollector = collector;
|
|
2333
|
+
}
|
|
2297
2334
|
/**
|
|
2298
2335
|
* Get the learning store for cross-session learning.
|
|
2299
2336
|
* Returns null if learning store is not enabled.
|
|
@@ -2813,6 +2850,11 @@ export class ProductionAgent {
|
|
|
2813
2850
|
if (!this.threadManager) {
|
|
2814
2851
|
throw new Error('Thread management not enabled. Enable it in config to use createCheckpoint()');
|
|
2815
2852
|
}
|
|
2853
|
+
// CRITICAL: Sync current state.messages to threadManager before checkpoint
|
|
2854
|
+
// The run() method adds messages directly to this.state.messages but doesn't sync
|
|
2855
|
+
// to threadManager, so thread.messages would be empty without this sync
|
|
2856
|
+
const thread = this.threadManager.getActiveThread();
|
|
2857
|
+
thread.messages = [...this.state.messages];
|
|
2816
2858
|
const checkpoint = this.threadManager.createCheckpoint({
|
|
2817
2859
|
label,
|
|
2818
2860
|
agentState: this.state,
|
|
@@ -2987,8 +3029,12 @@ export class ProductionAgent {
|
|
|
2987
3029
|
/**
|
|
2988
3030
|
* Spawn an agent to execute a task.
|
|
2989
3031
|
* Returns the result when the agent completes.
|
|
3032
|
+
*
|
|
3033
|
+
* @param agentName - Name of the agent to spawn (researcher, coder, etc.)
|
|
3034
|
+
* @param task - The task description for the agent
|
|
3035
|
+
* @param constraints - Optional constraints to keep the subagent focused
|
|
2990
3036
|
*/
|
|
2991
|
-
async spawnAgent(agentName, task) {
|
|
3037
|
+
async spawnAgent(agentName, task, constraints) {
|
|
2992
3038
|
if (!this.agentRegistry) {
|
|
2993
3039
|
return {
|
|
2994
3040
|
success: false,
|
|
@@ -3004,10 +3050,10 @@ export class ProductionAgent {
|
|
|
3004
3050
|
metrics: { tokens: 0, duration: 0, toolCalls: 0 },
|
|
3005
3051
|
};
|
|
3006
3052
|
}
|
|
3007
|
-
// DUPLICATE SPAWN PREVENTION
|
|
3008
|
-
//
|
|
3053
|
+
// DUPLICATE SPAWN PREVENTION with SEMANTIC SIMILARITY
|
|
3054
|
+
// First try exact string match, then check semantic similarity for similar tasks
|
|
3055
|
+
const SEMANTIC_SIMILARITY_THRESHOLD = 0.75; // 75% similarity = duplicate
|
|
3009
3056
|
const taskKey = `${agentName}:${task.slice(0, 150).toLowerCase().replace(/\s+/g, ' ').trim()}`;
|
|
3010
|
-
const existing = this.spawnedTasks.get(taskKey);
|
|
3011
3057
|
const now = Date.now();
|
|
3012
3058
|
// Clean up old entries (older than dedup window)
|
|
3013
3059
|
for (const [key, entry] of this.spawnedTasks.entries()) {
|
|
@@ -3015,21 +3061,48 @@ export class ProductionAgent {
|
|
|
3015
3061
|
this.spawnedTasks.delete(key);
|
|
3016
3062
|
}
|
|
3017
3063
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3064
|
+
// Check for exact match first
|
|
3065
|
+
let existingMatch = this.spawnedTasks.get(taskKey);
|
|
3066
|
+
let matchType = 'exact';
|
|
3067
|
+
// If no exact match, check for semantic similarity among same agent's tasks
|
|
3068
|
+
if (!existingMatch) {
|
|
3069
|
+
for (const [key, entry] of this.spawnedTasks.entries()) {
|
|
3070
|
+
// Only compare tasks from the same agent type
|
|
3071
|
+
if (!key.startsWith(`${agentName}:`))
|
|
3072
|
+
continue;
|
|
3073
|
+
if (now - entry.timestamp >= ProductionAgent.SPAWN_DEDUP_WINDOW_MS)
|
|
3074
|
+
continue;
|
|
3075
|
+
// Extract the task portion from the key
|
|
3076
|
+
const existingTask = key.slice(agentName.length + 1);
|
|
3077
|
+
const similarity = calculateTaskSimilarity(task, existingTask);
|
|
3078
|
+
if (similarity >= SEMANTIC_SIMILARITY_THRESHOLD) {
|
|
3079
|
+
existingMatch = entry;
|
|
3080
|
+
matchType = 'semantic';
|
|
3081
|
+
this.observability?.logger?.debug('Semantic duplicate detected', {
|
|
3082
|
+
agent: agentName,
|
|
3083
|
+
newTask: task.slice(0, 80),
|
|
3084
|
+
existingTask: existingTask.slice(0, 80),
|
|
3085
|
+
similarity: (similarity * 100).toFixed(1) + '%',
|
|
3086
|
+
});
|
|
3087
|
+
break;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
if (existingMatch && now - existingMatch.timestamp < ProductionAgent.SPAWN_DEDUP_WINDOW_MS) {
|
|
3092
|
+
// Same or semantically similar task spawned within the dedup window
|
|
3021
3093
|
this.observability?.logger?.warn('Duplicate spawn prevented', {
|
|
3022
3094
|
agent: agentName,
|
|
3023
3095
|
task: task.slice(0, 100),
|
|
3024
|
-
|
|
3025
|
-
|
|
3096
|
+
matchType,
|
|
3097
|
+
originalTimestamp: existingMatch.timestamp,
|
|
3098
|
+
elapsedMs: now - existingMatch.timestamp,
|
|
3026
3099
|
});
|
|
3027
|
-
const duplicateMessage = `[DUPLICATE SPAWN PREVENTED]\n` +
|
|
3028
|
-
`This task was already spawned ${Math.round((now -
|
|
3029
|
-
`${
|
|
3030
|
-
? `The previous spawn queued ${
|
|
3100
|
+
const duplicateMessage = `[DUPLICATE SPAWN PREVENTED${matchType === 'semantic' ? ' - SEMANTIC MATCH' : ''}]\n` +
|
|
3101
|
+
`This task was already spawned ${Math.round((now - existingMatch.timestamp) / 1000)}s ago.\n` +
|
|
3102
|
+
`${existingMatch.queuedChanges > 0
|
|
3103
|
+
? `The previous spawn queued ${existingMatch.queuedChanges} change(s) to the pending plan.\n` +
|
|
3031
3104
|
`These changes are already in your plan - do NOT spawn again.\n`
|
|
3032
|
-
: ''}Previous result summary:\n${
|
|
3105
|
+
: ''}Previous result summary:\n${existingMatch.result.slice(0, 500)}`;
|
|
3033
3106
|
return {
|
|
3034
3107
|
success: true, // Mark as success since original task completed
|
|
3035
3108
|
output: duplicateMessage,
|
|
@@ -3049,16 +3122,88 @@ export class ProductionAgent {
|
|
|
3049
3122
|
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
3050
3123
|
? agentDef.model
|
|
3051
3124
|
: this.config.model;
|
|
3052
|
-
// Get subagent config with
|
|
3053
|
-
//
|
|
3125
|
+
// Get subagent config with agent-type-specific timeouts and iteration limits
|
|
3126
|
+
// Uses dynamic configuration based on agent type (researcher needs more time than reviewer)
|
|
3054
3127
|
const subagentConfig = this.config.subagent;
|
|
3055
3128
|
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3129
|
+
// Agent-type-specific timeout: researchers get 5min, reviewers get 2min, etc.
|
|
3130
|
+
const agentTypeTimeout = getSubagentTimeout(agentName);
|
|
3131
|
+
const configTimeout = hasSubagentConfig
|
|
3132
|
+
? subagentConfig.defaultTimeout
|
|
3133
|
+
: undefined;
|
|
3134
|
+
const subagentTimeout = configTimeout ?? agentTypeTimeout;
|
|
3135
|
+
// Agent-type-specific iteration limit: researchers get 25, documenters get 10, etc.
|
|
3136
|
+
const agentTypeMaxIter = getSubagentMaxIterations(agentName);
|
|
3137
|
+
const configMaxIter = hasSubagentConfig
|
|
3138
|
+
? subagentConfig.defaultMaxIterations
|
|
3139
|
+
: undefined;
|
|
3140
|
+
const defaultMaxIterations = agentDef.maxIterations ?? configMaxIter ?? agentTypeMaxIter;
|
|
3141
|
+
// BLACKBOARD CONTEXT INJECTION
|
|
3142
|
+
// Gather relevant context from the blackboard for the subagent
|
|
3143
|
+
let blackboardContext = '';
|
|
3144
|
+
const parentAgentId = `parent-${Date.now()}`;
|
|
3145
|
+
if (this.blackboard) {
|
|
3146
|
+
// Post parent's exploration context before spawning
|
|
3147
|
+
this.blackboard.post(parentAgentId, {
|
|
3148
|
+
topic: 'spawn.parent_context',
|
|
3149
|
+
content: `Parent spawning ${agentName} for task: ${task.slice(0, 200)}`,
|
|
3150
|
+
type: 'progress',
|
|
3151
|
+
confidence: 1,
|
|
3152
|
+
metadata: { agentName, taskPreview: task.slice(0, 100) },
|
|
3153
|
+
});
|
|
3154
|
+
// Gather recent findings that might help the subagent
|
|
3155
|
+
const recentFindings = this.blackboard.query({
|
|
3156
|
+
limit: 5,
|
|
3157
|
+
types: ['discovery', 'analysis', 'progress'],
|
|
3158
|
+
minConfidence: 0.7,
|
|
3159
|
+
});
|
|
3160
|
+
if (recentFindings.length > 0) {
|
|
3161
|
+
const findingsSummary = recentFindings
|
|
3162
|
+
.map(f => `- [${f.agentId}] ${f.topic}: ${f.content.slice(0, 150)}${f.content.length > 150 ? '...' : ''}`)
|
|
3163
|
+
.join('\n');
|
|
3164
|
+
blackboardContext = `\n\n**BLACKBOARD CONTEXT (from parent/sibling agents):**\n${findingsSummary}\n`;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
// Check for files already being modified in parent's pending plan
|
|
3168
|
+
const currentPlan = this.pendingPlanManager.getPendingPlan();
|
|
3169
|
+
if (currentPlan && currentPlan.proposedChanges.length > 0) {
|
|
3170
|
+
const pendingFiles = currentPlan.proposedChanges
|
|
3171
|
+
.filter((c) => c.tool === 'write_file' || c.tool === 'edit_file')
|
|
3172
|
+
.map((c) => c.args.path || c.args.file_path)
|
|
3173
|
+
.filter(Boolean);
|
|
3174
|
+
if (pendingFiles.length > 0) {
|
|
3175
|
+
blackboardContext += `\n**FILES ALREADY IN PENDING PLAN (do not duplicate):**\n${pendingFiles.slice(0, 10).join('\n')}\n`;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
// CONSTRAINT INJECTION
|
|
3179
|
+
// Add constraints to the subagent's context if provided
|
|
3180
|
+
let constraintContext = '';
|
|
3181
|
+
if (constraints) {
|
|
3182
|
+
const constraintParts = [];
|
|
3183
|
+
if (constraints.focusAreas && constraints.focusAreas.length > 0) {
|
|
3184
|
+
constraintParts.push(`**FOCUS AREAS (limit exploration to these paths):**\n${constraints.focusAreas.map(a => ` - ${a}`).join('\n')}`);
|
|
3185
|
+
}
|
|
3186
|
+
if (constraints.excludeAreas && constraints.excludeAreas.length > 0) {
|
|
3187
|
+
constraintParts.push(`**EXCLUDED AREAS (do NOT explore these):**\n${constraints.excludeAreas.map(a => ` - ${a}`).join('\n')}`);
|
|
3188
|
+
}
|
|
3189
|
+
if (constraints.requiredDeliverables && constraints.requiredDeliverables.length > 0) {
|
|
3190
|
+
constraintParts.push(`**REQUIRED DELIVERABLES (you must produce these):**\n${constraints.requiredDeliverables.map(d => ` - ${d}`).join('\n')}`);
|
|
3191
|
+
}
|
|
3192
|
+
if (constraints.maxTokens) {
|
|
3193
|
+
constraintParts.push(`**TOKEN BUDGET:** ${constraints.maxTokens} tokens maximum`);
|
|
3194
|
+
}
|
|
3195
|
+
if (constraints.timeboxMinutes) {
|
|
3196
|
+
constraintParts.push(`**TIME LIMIT:** ${constraints.timeboxMinutes} minutes (soft limit - wrap up if approaching)`);
|
|
3197
|
+
}
|
|
3198
|
+
if (constraintParts.length > 0) {
|
|
3199
|
+
constraintContext = `\n\n**EXECUTION CONSTRAINTS:**\n${constraintParts.join('\n\n')}\n`;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
// Build subagent system prompt with subagent-specific plan mode addition
|
|
3203
|
+
const parentMode = this.getMode();
|
|
3204
|
+
const subagentSystemPrompt = parentMode === 'plan'
|
|
3205
|
+
? `${agentDef.systemPrompt}\n\n${SUBAGENT_PLAN_MODE_ADDITION}${blackboardContext}${constraintContext}`
|
|
3206
|
+
: `${agentDef.systemPrompt}${blackboardContext}${constraintContext}`;
|
|
3062
3207
|
// Create a sub-agent with the agent's config
|
|
3063
3208
|
const subAgent = new ProductionAgent({
|
|
3064
3209
|
provider: this.provider,
|
|
@@ -3067,7 +3212,7 @@ export class ProductionAgent {
|
|
|
3067
3212
|
toolResolver: this.toolResolver || undefined,
|
|
3068
3213
|
// Pass MCP tool summaries so subagent knows what tools are available
|
|
3069
3214
|
mcpToolSummaries: this.config.mcpToolSummaries,
|
|
3070
|
-
systemPrompt:
|
|
3215
|
+
systemPrompt: subagentSystemPrompt,
|
|
3071
3216
|
model: resolvedModel,
|
|
3072
3217
|
maxIterations: agentDef.maxIterations || defaultMaxIterations,
|
|
3073
3218
|
// Inherit some features but keep subagent simpler
|
|
@@ -3093,13 +3238,23 @@ export class ProductionAgent {
|
|
|
3093
3238
|
// - Subagent's read operations execute immediately (visible exploration)
|
|
3094
3239
|
// - Subagent's write operations get queued in the subagent's pending plan
|
|
3095
3240
|
// - User maintains control over what actually gets written
|
|
3096
|
-
const parentMode = this.getMode();
|
|
3097
3241
|
if (parentMode !== 'build') {
|
|
3098
3242
|
subAgent.setMode(parentMode);
|
|
3099
3243
|
}
|
|
3100
3244
|
// Pass parent's iteration count to subagent for accurate budget tracking
|
|
3101
3245
|
// This prevents subagents from consuming excessive iterations when parent already used many
|
|
3102
3246
|
subAgent.setParentIterations(this.getTotalIterations());
|
|
3247
|
+
// UNIFIED TRACING: Share parent's trace collector with subagent context
|
|
3248
|
+
// This ensures all subagent events are written to the same trace file as the parent,
|
|
3249
|
+
// tagged with subagent context for proper aggregation in /trace output
|
|
3250
|
+
if (this.traceCollector) {
|
|
3251
|
+
const subagentTraceView = this.traceCollector.createSubagentView({
|
|
3252
|
+
parentSessionId: this.traceCollector.getSessionId() || 'unknown',
|
|
3253
|
+
agentType: agentName,
|
|
3254
|
+
spawnedAtIteration: this.state.iteration,
|
|
3255
|
+
});
|
|
3256
|
+
subAgent.setTraceCollector(subagentTraceView);
|
|
3257
|
+
}
|
|
3103
3258
|
// Forward events from subagent with context
|
|
3104
3259
|
subAgent.subscribe(event => {
|
|
3105
3260
|
// Tag event with subagent source so TUI can display it properly
|
|
@@ -3113,6 +3268,10 @@ export class ProductionAgent {
|
|
|
3113
3268
|
const effectiveSource = parentSource
|
|
3114
3269
|
? createLinkedToken(parentSource, timeoutSource)
|
|
3115
3270
|
: timeoutSource;
|
|
3271
|
+
// CRITICAL: Pass the cancellation token to the subagent so it can check and stop
|
|
3272
|
+
// gracefully when timeout fires. Without this, the subagent continues running as
|
|
3273
|
+
// a "zombie" even after race() returns with a timeout error.
|
|
3274
|
+
subAgent.setExternalCancellation(effectiveSource.token);
|
|
3116
3275
|
try {
|
|
3117
3276
|
// Run the task with cancellation propagation from parent
|
|
3118
3277
|
const result = await race(subAgent.run(task), effectiveSource.token);
|
|
@@ -3225,6 +3384,20 @@ export class ProductionAgent {
|
|
|
3225
3384
|
? 'User cancelled'
|
|
3226
3385
|
: `Timed out after ${subagentTimeout}ms`;
|
|
3227
3386
|
this.emit({ type: 'agent.error', agentId: agentName, error: reason });
|
|
3387
|
+
// =======================================================================
|
|
3388
|
+
// PRESERVE PARTIAL RESULTS
|
|
3389
|
+
// Instead of discarding all work, capture whatever the subagent produced
|
|
3390
|
+
// before timeout. This prevents the "zombie agent" problem where tokens
|
|
3391
|
+
// are consumed but results are lost.
|
|
3392
|
+
// =======================================================================
|
|
3393
|
+
const subagentState = subAgent.getState();
|
|
3394
|
+
const subagentMetrics = subAgent.getMetrics();
|
|
3395
|
+
// Extract partial response from the last assistant message
|
|
3396
|
+
const assistantMessages = subagentState.messages.filter(m => m.role === 'assistant');
|
|
3397
|
+
const lastAssistantMsg = assistantMessages[assistantMessages.length - 1];
|
|
3398
|
+
const partialResponse = typeof lastAssistantMsg?.content === 'string'
|
|
3399
|
+
? lastAssistantMsg.content
|
|
3400
|
+
: '';
|
|
3228
3401
|
// Extract pending plan before cleanup (even on cancellation, preserve any queued work)
|
|
3229
3402
|
let cancelledQueuedSummary = '';
|
|
3230
3403
|
if (subAgent.hasPendingPlan()) {
|
|
@@ -3267,13 +3440,50 @@ export class ProductionAgent {
|
|
|
3267
3440
|
catch {
|
|
3268
3441
|
// Ignore cleanup errors on cancellation
|
|
3269
3442
|
}
|
|
3443
|
+
// Build output message with partial results
|
|
3270
3444
|
const baseOutput = isUserCancellation
|
|
3271
3445
|
? `Subagent '${agentName}' was cancelled by user.`
|
|
3272
|
-
: `Subagent '${agentName}' timed out after ${Math.round(subagentTimeout / 1000)}s
|
|
3446
|
+
: `Subagent '${agentName}' timed out after ${Math.round(subagentTimeout / 1000)}s.`;
|
|
3447
|
+
// Include partial response if we have one
|
|
3448
|
+
const partialResultSection = partialResponse
|
|
3449
|
+
? `\n\n[PARTIAL RESULTS BEFORE TIMEOUT]\n${partialResponse.slice(0, 2000)}${partialResponse.length > 2000 ? '...(truncated)' : ''}`
|
|
3450
|
+
: '';
|
|
3451
|
+
// Enhanced tracing: Record subagent timeout with partial results
|
|
3452
|
+
this.traceCollector?.record({
|
|
3453
|
+
type: 'subagent.link',
|
|
3454
|
+
data: {
|
|
3455
|
+
parentSessionId: this.traceCollector.getSessionId() || 'unknown',
|
|
3456
|
+
childSessionId,
|
|
3457
|
+
childTraceId,
|
|
3458
|
+
childConfig: {
|
|
3459
|
+
agentType: agentName,
|
|
3460
|
+
model: resolvedModel || 'default',
|
|
3461
|
+
task,
|
|
3462
|
+
tools: agentTools.map(t => t.name),
|
|
3463
|
+
},
|
|
3464
|
+
spawnContext: {
|
|
3465
|
+
reason: `Delegated task: ${task.slice(0, 100)}`,
|
|
3466
|
+
expectedOutcome: agentDef.description,
|
|
3467
|
+
parentIteration: this.state.iteration,
|
|
3468
|
+
},
|
|
3469
|
+
result: {
|
|
3470
|
+
success: false,
|
|
3471
|
+
summary: `[TIMEOUT] ${baseOutput}\n${partialResponse.slice(0, 200)}`,
|
|
3472
|
+
tokensUsed: subagentMetrics.totalTokens,
|
|
3473
|
+
durationMs: duration,
|
|
3474
|
+
},
|
|
3475
|
+
},
|
|
3476
|
+
});
|
|
3273
3477
|
return {
|
|
3274
3478
|
success: false,
|
|
3275
|
-
output: baseOutput + cancelledQueuedSummary,
|
|
3276
|
-
|
|
3479
|
+
output: baseOutput + partialResultSection + cancelledQueuedSummary,
|
|
3480
|
+
// IMPORTANT: Use actual metrics instead of zeros
|
|
3481
|
+
// This ensures accurate token tracking in /trace output
|
|
3482
|
+
metrics: {
|
|
3483
|
+
tokens: subagentMetrics.totalTokens,
|
|
3484
|
+
duration,
|
|
3485
|
+
toolCalls: subagentMetrics.toolCalls,
|
|
3486
|
+
},
|
|
3277
3487
|
};
|
|
3278
3488
|
}
|
|
3279
3489
|
throw err; // Re-throw non-cancellation errors
|
|
@@ -3663,6 +3873,22 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
3663
3873
|
setParentIterations(count) {
|
|
3664
3874
|
this.parentIterations = count;
|
|
3665
3875
|
}
|
|
3876
|
+
/**
|
|
3877
|
+
* Set an external cancellation token for this agent.
|
|
3878
|
+
* Used when spawning subagents to propagate parent timeout/cancellation.
|
|
3879
|
+
* The agent will check this token in its main loop and stop gracefully
|
|
3880
|
+
* when cancellation is requested, preserving partial results.
|
|
3881
|
+
*/
|
|
3882
|
+
setExternalCancellation(token) {
|
|
3883
|
+
this.externalCancellationToken = token;
|
|
3884
|
+
}
|
|
3885
|
+
/**
|
|
3886
|
+
* Check if external cancellation has been requested.
|
|
3887
|
+
* Returns true if the external token signals cancellation.
|
|
3888
|
+
*/
|
|
3889
|
+
isExternallyCancelled() {
|
|
3890
|
+
return this.externalCancellationToken?.isCancellationRequested ?? false;
|
|
3891
|
+
}
|
|
3666
3892
|
/**
|
|
3667
3893
|
* Get total iterations (this agent + parent).
|
|
3668
3894
|
* Used for accurate budget tracking across subagent hierarchies.
|
|
@@ -3844,6 +4070,12 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
3844
4070
|
getAgentRegistry() {
|
|
3845
4071
|
return this.agentRegistry;
|
|
3846
4072
|
}
|
|
4073
|
+
/**
|
|
4074
|
+
* Get the task manager instance for task tracking.
|
|
4075
|
+
*/
|
|
4076
|
+
getTaskManager() {
|
|
4077
|
+
return this.taskManager;
|
|
4078
|
+
}
|
|
3847
4079
|
/**
|
|
3848
4080
|
* Get all loaded skills.
|
|
3849
4081
|
*/
|