attocode 0.2.2 → 0.2.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 +103 -3
- package/dist/src/agent.d.ts +6 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +504 -49
- package/dist/src/agent.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +23 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/core/protocol/types.d.ts +8 -8
- package/dist/src/defaults.d.ts +6 -1
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +36 -2
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.d.ts +11 -0
- package/dist/src/integrations/agent-registry.d.ts.map +1 -1
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
- package/dist/src/integrations/auto-compaction.js +5 -1
- package/dist/src/integrations/auto-compaction.js.map +1 -1
- package/dist/src/integrations/bash-policy.d.ts +33 -0
- package/dist/src/integrations/bash-policy.d.ts.map +1 -0
- package/dist/src/integrations/bash-policy.js +142 -0
- package/dist/src/integrations/bash-policy.js.map +1 -0
- package/dist/src/integrations/codebase-context.d.ts +5 -0
- package/dist/src/integrations/codebase-context.d.ts.map +1 -1
- package/dist/src/integrations/codebase-context.js +33 -0
- package/dist/src/integrations/codebase-context.js.map +1 -1
- package/dist/src/integrations/delegation-protocol.js +2 -2
- package/dist/src/integrations/delegation-protocol.js.map +1 -1
- package/dist/src/integrations/economics.d.ts +42 -0
- package/dist/src/integrations/economics.d.ts.map +1 -1
- package/dist/src/integrations/economics.js +130 -14
- package/dist/src/integrations/economics.js.map +1 -1
- package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
- package/dist/src/integrations/hierarchical-config.js +17 -0
- package/dist/src/integrations/hierarchical-config.js.map +1 -1
- package/dist/src/integrations/index.d.ts +3 -1
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +3 -1
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/policy-engine.d.ts +55 -0
- package/dist/src/integrations/policy-engine.d.ts.map +1 -0
- package/dist/src/integrations/policy-engine.js +247 -0
- package/dist/src/integrations/policy-engine.js.map +1 -0
- package/dist/src/integrations/safety.d.ts +5 -4
- package/dist/src/integrations/safety.d.ts.map +1 -1
- package/dist/src/integrations/safety.js +32 -7
- package/dist/src/integrations/safety.js.map +1 -1
- package/dist/src/integrations/sandbox/basic.d.ts +7 -0
- package/dist/src/integrations/sandbox/basic.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/basic.js +27 -2
- package/dist/src/integrations/sandbox/basic.js.map +1 -1
- package/dist/src/integrations/sandbox/index.d.ts +6 -0
- package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/index.js +3 -0
- package/dist/src/integrations/sandbox/index.js.map +1 -1
- package/dist/src/integrations/sandbox/landlock.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/landlock.js +3 -0
- package/dist/src/integrations/sandbox/landlock.js.map +1 -1
- package/dist/src/integrations/self-improvement.d.ts.map +1 -1
- package/dist/src/integrations/self-improvement.js +12 -0
- package/dist/src/integrations/self-improvement.js.map +1 -1
- package/dist/src/integrations/smart-decomposer.d.ts +18 -1
- package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
- package/dist/src/integrations/smart-decomposer.js +72 -0
- package/dist/src/integrations/smart-decomposer.js.map +1 -1
- package/dist/src/integrations/swarm/index.d.ts +1 -1
- package/dist/src/integrations/swarm/index.d.ts.map +1 -1
- package/dist/src/integrations/swarm/index.js.map +1 -1
- package/dist/src/integrations/swarm/model-selector.d.ts +15 -0
- package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
- package/dist/src/integrations/swarm/model-selector.js +99 -20
- package/dist/src/integrations/swarm/model-selector.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.d.ts +4 -0
- package/dist/src/integrations/swarm/swarm-budget.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.js +6 -0
- package/dist/src/integrations/swarm/swarm-budget.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.js +154 -7
- package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +12 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.js +170 -23
- package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts +55 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.js +22 -5
- package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +124 -8
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.js +1668 -96
- package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.d.ts +83 -2
- package/dist/src/integrations/swarm/swarm-quality-gate.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.js +278 -19
- package/dist/src/integrations/swarm/swarm-quality-gate.js.map +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts +44 -0
- package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
- package/dist/src/integrations/swarm/task-queue.js +274 -11
- package/dist/src/integrations/swarm/task-queue.js.map +1 -1
- package/dist/src/integrations/swarm/types.d.ts +210 -13
- package/dist/src/integrations/swarm/types.d.ts.map +1 -1
- package/dist/src/integrations/swarm/types.js +61 -8
- package/dist/src/integrations/swarm/types.js.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts +11 -4
- package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.js +173 -43
- package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
- package/dist/src/integrations/tool-recommendation.d.ts +7 -4
- package/dist/src/integrations/tool-recommendation.d.ts.map +1 -1
- package/dist/src/integrations/tool-recommendation.js +58 -5
- package/dist/src/integrations/tool-recommendation.js.map +1 -1
- package/dist/src/integrations/work-log.js +4 -4
- package/dist/src/integrations/work-log.js.map +1 -1
- package/dist/src/main.js +26 -1
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +10 -4
- 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 +5 -0
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.d.ts.map +1 -1
- package/dist/src/modes.js +4 -27
- package/dist/src/modes.js.map +1 -1
- package/dist/src/tools/agent.d.ts.map +1 -1
- package/dist/src/tools/agent.js +11 -2
- package/dist/src/tools/agent.js.map +1 -1
- package/dist/src/tools/bash.d.ts +3 -3
- package/dist/src/tools/coercion.d.ts +6 -0
- package/dist/src/tools/coercion.d.ts.map +1 -1
- package/dist/src/tools/coercion.js +13 -0
- package/dist/src/tools/coercion.js.map +1 -1
- package/dist/src/tools/file.d.ts +2 -2
- package/dist/src/tools/file.js +2 -2
- package/dist/src/tools/file.js.map +1 -1
- package/dist/src/tools/permission.d.ts.map +1 -1
- package/dist/src/tools/permission.js +4 -111
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +167 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +137 -0
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +105 -1
- 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 +34 -5
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/types.d.ts +71 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/src/agent.js
CHANGED
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
import { buildConfig, isFeatureEnabled, getEnabledFeatures, getSubagentTimeout, getSubagentMaxIterations, } from './defaults.js';
|
|
22
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, SUBAGENT_BUDGET, TIMEOUT_WRAPUP_PROMPT, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createLinkedToken, createGracefulTimeout, race, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, createSharedFileCache, createBudgetPool, createDynamicBudgetPool, createPendingPlanManager, createInteractivePlanner, createRecursiveContext, createLearningStore, createCompactor, createAutoCompactionManager, createFileChangeTracker, createCapabilitiesRegistry, createSharedBlackboard, createTaskManager, createSwarmOrchestrator, createThrottledProvider, FREE_TIER_THROTTLE, PAID_TIER_THROTTLE, createWorkLog, createVerificationGate,
|
|
24
|
+
import { HookManager, MemoryManager, PlanningManager, ObservabilityManager, SafetyManager, RoutingManager, MultiAgentManager, ReActManager, ExecutionPolicyManager, ThreadManager, RulesManager, DEFAULT_RULE_SOURCES, ExecutionEconomicsManager, STANDARD_BUDGET, SUBAGENT_BUDGET, TIMEOUT_WRAPUP_PROMPT, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createLinkedToken, createGracefulTimeout, race, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, generateLightweightRepoMap, createSharedFileCache, createBudgetPool, createDynamicBudgetPool, createPendingPlanManager, createInteractivePlanner, createRecursiveContext, createLearningStore, createCompactor, createAutoCompactionManager, createFileChangeTracker, createCapabilitiesRegistry, createSharedBlackboard, createTaskManager, createSwarmOrchestrator, createThrottledProvider, FREE_TIER_THROTTLE, PAID_TIER_THROTTLE, createWorkLog, createVerificationGate,
|
|
25
25
|
// Phase 2: Orchestration
|
|
26
26
|
classifyComplexity, getScalingGuidance, buildDelegationPrompt, createMinimalDelegationSpec, getSubagentQualityPrompt, ToolRecommendationEngine, createToolRecommendationEngine, createInjectionBudgetManager,
|
|
27
27
|
// Phase 3: Advanced
|
|
28
28
|
getThinkingSystemPrompt, createSelfImprovementProtocol, createSubagentOutputStore, createSerperSearchTool, getEnvironmentFacts, formatFactsBlock, createAutoCheckpointManager, createSubagentSupervisor, createSubagentHandle, } from './integrations/index.js';
|
|
29
|
+
import { mergeApprovalScopeWithProfile, resolvePolicyProfile, } from './integrations/policy-engine.js';
|
|
29
30
|
// Lesson 26: Tracing & Evaluation integration
|
|
30
31
|
import { createTraceCollector } from './tracing/trace-collector.js';
|
|
31
32
|
// Model registry for context window limits
|
|
@@ -179,6 +180,7 @@ export class ProductionAgent {
|
|
|
179
180
|
skillManager = null;
|
|
180
181
|
contextEngineering = null;
|
|
181
182
|
codebaseContext = null;
|
|
183
|
+
codebaseAnalysisTriggered = false;
|
|
182
184
|
traceCollector = null;
|
|
183
185
|
modeManager;
|
|
184
186
|
pendingPlanManager;
|
|
@@ -221,6 +223,9 @@ export class ProductionAgent {
|
|
|
221
223
|
// Cacheable system prompt blocks for prompt caching (Improvement P1)
|
|
222
224
|
// When set, callLLM() will inject these as structured content with cache_control markers
|
|
223
225
|
cacheableSystemBlocks = null;
|
|
226
|
+
// Pre-compaction agentic turn: when true, the agent gets one more LLM turn
|
|
227
|
+
// to summarize its state before compaction clears the context.
|
|
228
|
+
compactionPending = false;
|
|
224
229
|
// Initialization tracking
|
|
225
230
|
initPromises = [];
|
|
226
231
|
initComplete = false;
|
|
@@ -339,7 +344,29 @@ export class ProductionAgent {
|
|
|
339
344
|
}
|
|
340
345
|
// Safety (Sandbox + Human-in-Loop)
|
|
341
346
|
if (isFeatureEnabled(this.config.sandbox) || isFeatureEnabled(this.config.humanInLoop)) {
|
|
342
|
-
this.safety = new SafetyManager(isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : false, isFeatureEnabled(this.config.humanInLoop) ? this.config.humanInLoop : false);
|
|
347
|
+
this.safety = new SafetyManager(isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : false, isFeatureEnabled(this.config.humanInLoop) ? this.config.humanInLoop : false, isFeatureEnabled(this.config.policyEngine) ? this.config.policyEngine : false);
|
|
348
|
+
}
|
|
349
|
+
if (isFeatureEnabled(this.config.policyEngine)) {
|
|
350
|
+
const rootPolicy = resolvePolicyProfile({
|
|
351
|
+
policyEngine: this.config.policyEngine,
|
|
352
|
+
sandboxConfig: isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : undefined,
|
|
353
|
+
});
|
|
354
|
+
this.emit({
|
|
355
|
+
type: 'policy.profile.resolved',
|
|
356
|
+
profile: rootPolicy.profileName,
|
|
357
|
+
context: 'root',
|
|
358
|
+
selectionSource: rootPolicy.metadata.selectionSource,
|
|
359
|
+
usedLegacyMappings: rootPolicy.metadata.usedLegacyMappings,
|
|
360
|
+
legacySources: rootPolicy.metadata.legacyMappingSources,
|
|
361
|
+
});
|
|
362
|
+
if (rootPolicy.metadata.usedLegacyMappings) {
|
|
363
|
+
this.emit({
|
|
364
|
+
type: 'policy.legacy.fallback.used',
|
|
365
|
+
profile: rootPolicy.profileName,
|
|
366
|
+
sources: rootPolicy.metadata.legacyMappingSources,
|
|
367
|
+
warnings: rootPolicy.metadata.warnings,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
343
370
|
}
|
|
344
371
|
// Routing
|
|
345
372
|
if (isFeatureEnabled(this.config.routing)) {
|
|
@@ -943,7 +970,11 @@ export class ProductionAgent {
|
|
|
943
970
|
else {
|
|
944
971
|
// Single-task mode (backward compatibility) - start session with task
|
|
945
972
|
const traceSessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
946
|
-
|
|
973
|
+
const sessionMetadata = {};
|
|
974
|
+
if (this.swarmOrchestrator) {
|
|
975
|
+
sessionMetadata.swarm = true;
|
|
976
|
+
}
|
|
977
|
+
await this.traceCollector?.startSession(traceSessionId, task, this.config.model || 'default', sessionMetadata);
|
|
947
978
|
}
|
|
948
979
|
try {
|
|
949
980
|
// Check for cancellation before starting
|
|
@@ -1102,6 +1133,161 @@ export class ProductionAgent {
|
|
|
1102
1133
|
const { SwarmEventBridge } = await import('./integrations/swarm/swarm-event-bridge.js');
|
|
1103
1134
|
const bridge = new SwarmEventBridge({ outputDir: '.agent/swarm-live' });
|
|
1104
1135
|
const unsubBridge = bridge.attach(this.swarmOrchestrator);
|
|
1136
|
+
// Bridge swarm events into JSONL trace pipeline
|
|
1137
|
+
const traceCollector = this.traceCollector;
|
|
1138
|
+
let unsubTrace;
|
|
1139
|
+
if (traceCollector) {
|
|
1140
|
+
unsubTrace = this.swarmOrchestrator.subscribe(event => {
|
|
1141
|
+
switch (event.type) {
|
|
1142
|
+
case 'swarm.start':
|
|
1143
|
+
traceCollector.record({
|
|
1144
|
+
type: 'swarm.start',
|
|
1145
|
+
data: { taskCount: event.taskCount, config: event.config },
|
|
1146
|
+
});
|
|
1147
|
+
break;
|
|
1148
|
+
case 'swarm.tasks.loaded':
|
|
1149
|
+
traceCollector.record({
|
|
1150
|
+
type: 'swarm.decomposition',
|
|
1151
|
+
data: {
|
|
1152
|
+
tasks: event.tasks.map(t => ({
|
|
1153
|
+
id: t.id,
|
|
1154
|
+
description: t.description.slice(0, 200),
|
|
1155
|
+
type: t.type,
|
|
1156
|
+
wave: t.wave,
|
|
1157
|
+
deps: t.dependencies,
|
|
1158
|
+
})),
|
|
1159
|
+
totalWaves: Math.max(...event.tasks.map(t => t.wave), 0) + 1,
|
|
1160
|
+
},
|
|
1161
|
+
});
|
|
1162
|
+
break;
|
|
1163
|
+
case 'swarm.wave.start':
|
|
1164
|
+
traceCollector.record({
|
|
1165
|
+
type: 'swarm.wave',
|
|
1166
|
+
data: { phase: 'start', wave: event.wave, taskCount: event.taskCount },
|
|
1167
|
+
});
|
|
1168
|
+
break;
|
|
1169
|
+
case 'swarm.wave.complete':
|
|
1170
|
+
traceCollector.record({
|
|
1171
|
+
type: 'swarm.wave',
|
|
1172
|
+
data: {
|
|
1173
|
+
phase: 'complete',
|
|
1174
|
+
wave: event.wave,
|
|
1175
|
+
taskCount: event.completed + event.failed + (event.skipped ?? 0),
|
|
1176
|
+
completed: event.completed,
|
|
1177
|
+
failed: event.failed,
|
|
1178
|
+
},
|
|
1179
|
+
});
|
|
1180
|
+
break;
|
|
1181
|
+
case 'swarm.task.dispatched':
|
|
1182
|
+
traceCollector.record({
|
|
1183
|
+
type: 'swarm.task',
|
|
1184
|
+
data: { phase: 'dispatched', taskId: event.taskId, model: event.model },
|
|
1185
|
+
});
|
|
1186
|
+
break;
|
|
1187
|
+
case 'swarm.task.completed':
|
|
1188
|
+
traceCollector.record({
|
|
1189
|
+
type: 'swarm.task',
|
|
1190
|
+
data: {
|
|
1191
|
+
phase: 'completed',
|
|
1192
|
+
taskId: event.taskId,
|
|
1193
|
+
tokensUsed: event.tokensUsed,
|
|
1194
|
+
costUsed: event.costUsed,
|
|
1195
|
+
qualityScore: event.qualityScore,
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
break;
|
|
1199
|
+
case 'swarm.task.failed':
|
|
1200
|
+
traceCollector.record({
|
|
1201
|
+
type: 'swarm.task',
|
|
1202
|
+
data: { phase: 'failed', taskId: event.taskId, error: event.error },
|
|
1203
|
+
});
|
|
1204
|
+
break;
|
|
1205
|
+
case 'swarm.task.skipped':
|
|
1206
|
+
traceCollector.record({
|
|
1207
|
+
type: 'swarm.task',
|
|
1208
|
+
data: { phase: 'skipped', taskId: event.taskId, reason: event.reason },
|
|
1209
|
+
});
|
|
1210
|
+
break;
|
|
1211
|
+
case 'swarm.quality.rejected':
|
|
1212
|
+
traceCollector.record({
|
|
1213
|
+
type: 'swarm.quality',
|
|
1214
|
+
data: { taskId: event.taskId, score: event.score, feedback: event.feedback },
|
|
1215
|
+
});
|
|
1216
|
+
break;
|
|
1217
|
+
case 'swarm.budget.update':
|
|
1218
|
+
traceCollector.record({
|
|
1219
|
+
type: 'swarm.budget',
|
|
1220
|
+
data: {
|
|
1221
|
+
tokensUsed: event.tokensUsed,
|
|
1222
|
+
tokensTotal: event.tokensTotal,
|
|
1223
|
+
costUsed: event.costUsed,
|
|
1224
|
+
costTotal: event.costTotal,
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
break;
|
|
1228
|
+
case 'swarm.verify.start':
|
|
1229
|
+
traceCollector.record({
|
|
1230
|
+
type: 'swarm.verification',
|
|
1231
|
+
data: { phase: 'start', description: `${event.stepCount} verification steps` },
|
|
1232
|
+
});
|
|
1233
|
+
break;
|
|
1234
|
+
case 'swarm.verify.step':
|
|
1235
|
+
traceCollector.record({
|
|
1236
|
+
type: 'swarm.verification',
|
|
1237
|
+
data: {
|
|
1238
|
+
phase: 'step',
|
|
1239
|
+
stepIndex: event.stepIndex,
|
|
1240
|
+
description: event.description,
|
|
1241
|
+
passed: event.passed,
|
|
1242
|
+
},
|
|
1243
|
+
});
|
|
1244
|
+
break;
|
|
1245
|
+
case 'swarm.verify.complete':
|
|
1246
|
+
traceCollector.record({
|
|
1247
|
+
type: 'swarm.verification',
|
|
1248
|
+
data: {
|
|
1249
|
+
phase: 'complete',
|
|
1250
|
+
passed: event.result.passed,
|
|
1251
|
+
summary: event.result.summary,
|
|
1252
|
+
},
|
|
1253
|
+
});
|
|
1254
|
+
break;
|
|
1255
|
+
case 'swarm.orchestrator.llm':
|
|
1256
|
+
traceCollector.record({
|
|
1257
|
+
type: 'swarm.orchestrator.llm',
|
|
1258
|
+
data: { model: event.model, purpose: event.purpose, tokens: event.tokens, cost: event.cost },
|
|
1259
|
+
});
|
|
1260
|
+
break;
|
|
1261
|
+
case 'swarm.wave.allFailed':
|
|
1262
|
+
traceCollector.record({
|
|
1263
|
+
type: 'swarm.wave.allFailed',
|
|
1264
|
+
data: { wave: event.wave },
|
|
1265
|
+
});
|
|
1266
|
+
break;
|
|
1267
|
+
case 'swarm.phase.progress':
|
|
1268
|
+
traceCollector.record({
|
|
1269
|
+
type: 'swarm.phase.progress',
|
|
1270
|
+
data: { phase: event.phase, message: event.message },
|
|
1271
|
+
});
|
|
1272
|
+
break;
|
|
1273
|
+
case 'swarm.complete':
|
|
1274
|
+
traceCollector.record({
|
|
1275
|
+
type: 'swarm.complete',
|
|
1276
|
+
data: {
|
|
1277
|
+
stats: {
|
|
1278
|
+
totalTasks: event.stats.totalTasks,
|
|
1279
|
+
completedTasks: event.stats.completedTasks,
|
|
1280
|
+
failedTasks: event.stats.failedTasks,
|
|
1281
|
+
totalTokens: event.stats.totalTokens,
|
|
1282
|
+
totalCost: event.stats.totalCost,
|
|
1283
|
+
totalDuration: event.stats.totalDurationMs,
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
});
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1105
1291
|
try {
|
|
1106
1292
|
const result = await this.swarmOrchestrator.execute(task);
|
|
1107
1293
|
// Populate task DAG for dashboard after execution
|
|
@@ -1116,6 +1302,7 @@ export class ProductionAgent {
|
|
|
1116
1302
|
return result;
|
|
1117
1303
|
}
|
|
1118
1304
|
finally {
|
|
1305
|
+
unsubTrace?.();
|
|
1119
1306
|
unsubBridge();
|
|
1120
1307
|
bridge.close();
|
|
1121
1308
|
unsubSwarm();
|
|
@@ -1843,19 +2030,99 @@ export class ProductionAgent {
|
|
|
1843
2030
|
});
|
|
1844
2031
|
// Handle compaction result
|
|
1845
2032
|
if (compactionResult.status === 'compacted' && compactionResult.compactedMessages) {
|
|
1846
|
-
//
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
const
|
|
2033
|
+
// ─── Pre-compaction agentic turn ───────────────────────────────
|
|
2034
|
+
// Give the agent one LLM turn to summarize critical state before
|
|
2035
|
+
// compaction clears the context. On the first trigger we inject a
|
|
2036
|
+
// system message and skip compaction; on the next trigger (the
|
|
2037
|
+
// agent has already responded) we proceed with actual compaction.
|
|
2038
|
+
if (!this.compactionPending) {
|
|
2039
|
+
this.compactionPending = true;
|
|
2040
|
+
const preCompactionMsg = {
|
|
1854
2041
|
role: 'user',
|
|
1855
|
-
content:
|
|
2042
|
+
content: '[SYSTEM] Context compaction is imminent. Summarize your current progress, key findings, and next steps into a single concise message. This will be preserved after compaction.',
|
|
2043
|
+
};
|
|
2044
|
+
messages.push(preCompactionMsg);
|
|
2045
|
+
this.state.messages.push(preCompactionMsg);
|
|
2046
|
+
this.observability?.logger?.info('Pre-compaction agentic turn: injected summary request');
|
|
2047
|
+
// Skip compaction this iteration — let the agent respond first
|
|
2048
|
+
// (continue to tool result processing below)
|
|
2049
|
+
}
|
|
2050
|
+
else {
|
|
2051
|
+
// Agent has had its chance to summarize — now compact for real
|
|
2052
|
+
this.compactionPending = false;
|
|
2053
|
+
// Pre-compaction checkpoint: save full state before discarding
|
|
2054
|
+
try {
|
|
2055
|
+
this.autoCheckpoint(true); // force=true bypasses frequency check
|
|
2056
|
+
}
|
|
2057
|
+
catch {
|
|
2058
|
+
// Non-critical — don't block compaction
|
|
2059
|
+
}
|
|
2060
|
+
// Replace messages with compacted version
|
|
2061
|
+
messages.length = 0;
|
|
2062
|
+
messages.push(...compactionResult.compactedMessages);
|
|
2063
|
+
this.state.messages.length = 0;
|
|
2064
|
+
this.state.messages.push(...compactionResult.compactedMessages);
|
|
2065
|
+
// Inject work log after compaction to prevent amnesia
|
|
2066
|
+
if (this.workLog?.hasContent()) {
|
|
2067
|
+
const workLogMessage = {
|
|
2068
|
+
role: 'user',
|
|
2069
|
+
content: this.workLog.toCompactString(),
|
|
2070
|
+
};
|
|
2071
|
+
messages.push(workLogMessage);
|
|
2072
|
+
this.state.messages.push(workLogMessage);
|
|
2073
|
+
}
|
|
2074
|
+
// Context recovery: re-inject critical state after compaction
|
|
2075
|
+
const recoveryParts = [];
|
|
2076
|
+
// Goals
|
|
2077
|
+
if (this.store) {
|
|
2078
|
+
const goalsSummary = this.store.getGoalsSummary();
|
|
2079
|
+
if (goalsSummary && goalsSummary !== 'No active goals.' && goalsSummary !== 'Goals feature not available.') {
|
|
2080
|
+
recoveryParts.push(goalsSummary);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
// Junctures (last 5 key moments)
|
|
2084
|
+
if (this.store) {
|
|
2085
|
+
const juncturesSummary = this.store.getJuncturesSummary(undefined, 5);
|
|
2086
|
+
if (juncturesSummary) {
|
|
2087
|
+
recoveryParts.push(juncturesSummary);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
// Learnings from past patterns
|
|
2091
|
+
if (this.learningStore) {
|
|
2092
|
+
const learnings = this.learningStore.getLearningContext({ maxLearnings: 3 });
|
|
2093
|
+
if (learnings) {
|
|
2094
|
+
recoveryParts.push(learnings);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
if (recoveryParts.length > 0) {
|
|
2098
|
+
const recoveryMessage = {
|
|
2099
|
+
role: 'user',
|
|
2100
|
+
content: `[CONTEXT RECOVERY — Re-injected after compaction]\n\n${recoveryParts.join('\n\n')}`,
|
|
2101
|
+
};
|
|
2102
|
+
messages.push(recoveryMessage);
|
|
2103
|
+
this.state.messages.push(recoveryMessage);
|
|
2104
|
+
}
|
|
2105
|
+
// Emit compaction event for observability
|
|
2106
|
+
const compactionTokensAfter = this.estimateContextTokens(messages);
|
|
2107
|
+
const compactionRecoveryInjected = recoveryParts.length > 0;
|
|
2108
|
+
const compactionEvent = {
|
|
2109
|
+
type: 'context.compacted',
|
|
2110
|
+
tokensBefore: currentContextTokens,
|
|
2111
|
+
tokensAfter: compactionTokensAfter,
|
|
2112
|
+
recoveryInjected: compactionRecoveryInjected,
|
|
1856
2113
|
};
|
|
1857
|
-
|
|
1858
|
-
|
|
2114
|
+
this.emit(compactionEvent);
|
|
2115
|
+
// Record to trace collector for JSONL output
|
|
2116
|
+
if (this.traceCollector) {
|
|
2117
|
+
this.traceCollector.record({
|
|
2118
|
+
type: 'context.compacted',
|
|
2119
|
+
data: {
|
|
2120
|
+
tokensBefore: currentContextTokens,
|
|
2121
|
+
tokensAfter: compactionTokensAfter,
|
|
2122
|
+
recoveryInjected: compactionRecoveryInjected,
|
|
2123
|
+
},
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
1859
2126
|
}
|
|
1860
2127
|
}
|
|
1861
2128
|
else if (compactionResult.status === 'hard_limit') {
|
|
@@ -1879,6 +2146,13 @@ export class ProductionAgent {
|
|
|
1879
2146
|
currentTokens: currentUsage.tokens,
|
|
1880
2147
|
maxTokens: budget.maxTokens,
|
|
1881
2148
|
});
|
|
2149
|
+
// Also checkpoint before fallback compaction
|
|
2150
|
+
try {
|
|
2151
|
+
this.autoCheckpoint(true);
|
|
2152
|
+
}
|
|
2153
|
+
catch {
|
|
2154
|
+
// Non-critical
|
|
2155
|
+
}
|
|
1882
2156
|
this.compactToolOutputs();
|
|
1883
2157
|
}
|
|
1884
2158
|
}
|
|
@@ -1888,8 +2162,10 @@ export class ProductionAgent {
|
|
|
1888
2162
|
const sourceToolName = toolCallNameById.get(result.callId);
|
|
1889
2163
|
const isExpensiveResult = sourceToolName === 'spawn_agent' || sourceToolName === 'spawn_agents_parallel';
|
|
1890
2164
|
// Truncate long outputs to save context
|
|
1891
|
-
|
|
1892
|
-
|
|
2165
|
+
// Use larger limit for subagent results to preserve critical context
|
|
2166
|
+
const effectiveMaxChars = isExpensiveResult ? MAX_TOOL_OUTPUT_CHARS * 2 : MAX_TOOL_OUTPUT_CHARS;
|
|
2167
|
+
if (content.length > effectiveMaxChars) {
|
|
2168
|
+
content = content.slice(0, effectiveMaxChars) + `\n\n... [truncated ${content.length - effectiveMaxChars} chars]`;
|
|
1893
2169
|
}
|
|
1894
2170
|
// =======================================================================
|
|
1895
2171
|
// ESTIMATE if adding this result would exceed budget
|
|
@@ -2013,10 +2289,14 @@ export class ProductionAgent {
|
|
|
2013
2289
|
const reservedTokens = 10500;
|
|
2014
2290
|
const maxContextTokens = (this.config.maxContextTokens ?? 80000) - reservedTokens;
|
|
2015
2291
|
const codebaseBudget = Math.min(maxContextTokens * 0.3, 15000); // Up to 30% or 15K tokens
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2292
|
+
const repoMap = this.codebaseContext.getRepoMap();
|
|
2293
|
+
// Lazy: trigger analysis on first system prompt build, ready by next turn
|
|
2294
|
+
if (!repoMap && !this.codebaseAnalysisTriggered) {
|
|
2295
|
+
this.codebaseAnalysisTriggered = true;
|
|
2296
|
+
this.codebaseContext.analyze().catch(() => { });
|
|
2297
|
+
}
|
|
2298
|
+
if (repoMap) {
|
|
2299
|
+
try {
|
|
2020
2300
|
const selection = this.selectRelevantCodeSync(task, codebaseBudget);
|
|
2021
2301
|
if (selection.chunks.length > 0) {
|
|
2022
2302
|
codebaseContextStr = buildContextFromChunks(selection.chunks, {
|
|
@@ -2025,10 +2305,14 @@ export class ProductionAgent {
|
|
|
2025
2305
|
maxTotalTokens: codebaseBudget,
|
|
2026
2306
|
});
|
|
2027
2307
|
}
|
|
2308
|
+
else {
|
|
2309
|
+
// Fallback: lightweight repo map when task-specific selection finds nothing
|
|
2310
|
+
codebaseContextStr = generateLightweightRepoMap(repoMap, codebaseBudget);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch {
|
|
2314
|
+
// Selection error — skip
|
|
2028
2315
|
}
|
|
2029
|
-
}
|
|
2030
|
-
catch {
|
|
2031
|
-
// Codebase analysis not ready yet - skip for this call
|
|
2032
2316
|
}
|
|
2033
2317
|
}
|
|
2034
2318
|
// Build tool descriptions
|
|
@@ -2506,6 +2790,12 @@ export class ProductionAgent {
|
|
|
2506
2790
|
});
|
|
2507
2791
|
// Handle forbidden policy - always block
|
|
2508
2792
|
if (evaluation.policy === 'forbidden') {
|
|
2793
|
+
this.emit({
|
|
2794
|
+
type: 'policy.tool.blocked',
|
|
2795
|
+
tool: toolCall.name,
|
|
2796
|
+
phase: 'enforced',
|
|
2797
|
+
reason: `Forbidden by execution policy: ${evaluation.reason}`,
|
|
2798
|
+
});
|
|
2509
2799
|
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
2510
2800
|
}
|
|
2511
2801
|
// Handle prompt policy - requires approval
|
|
@@ -2548,6 +2838,21 @@ export class ProductionAgent {
|
|
|
2548
2838
|
const safety = this.safety;
|
|
2549
2839
|
const validation = await this.withPausedDuration(() => safety.validateAndApprove(toolCall, `Executing tool: ${toolCall.name}`, { skipHumanApproval: policyApprovedByUser }));
|
|
2550
2840
|
if (!validation.allowed) {
|
|
2841
|
+
this.emit({
|
|
2842
|
+
type: 'policy.tool.blocked',
|
|
2843
|
+
tool: toolCall.name,
|
|
2844
|
+
phase: 'enforced',
|
|
2845
|
+
reason: validation.reason || 'Blocked by safety manager',
|
|
2846
|
+
});
|
|
2847
|
+
if (toolCall.name === 'bash') {
|
|
2848
|
+
const args = toolCall.arguments;
|
|
2849
|
+
this.emit({
|
|
2850
|
+
type: 'policy.bash.blocked',
|
|
2851
|
+
phase: 'enforced',
|
|
2852
|
+
command: String(args.command || args.cmd || ''),
|
|
2853
|
+
reason: validation.reason || 'Blocked by safety manager',
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2551
2856
|
throw new Error(`Tool call blocked: ${validation.reason}`);
|
|
2552
2857
|
}
|
|
2553
2858
|
}
|
|
@@ -3608,6 +3913,12 @@ export class ProductionAgent {
|
|
|
3608
3913
|
return null;
|
|
3609
3914
|
return this.economics.getProgress();
|
|
3610
3915
|
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Get actual file paths modified during this agent's session.
|
|
3918
|
+
*/
|
|
3919
|
+
getModifiedFilePaths() {
|
|
3920
|
+
return this.economics?.getModifiedFilePaths() ?? [];
|
|
3921
|
+
}
|
|
3611
3922
|
/**
|
|
3612
3923
|
* Extend the budget limits.
|
|
3613
3924
|
*/
|
|
@@ -3916,6 +4227,43 @@ export class ProductionAgent {
|
|
|
3916
4227
|
try {
|
|
3917
4228
|
// Filter tools for this agent
|
|
3918
4229
|
let agentTools = filterToolsForAgent(agentDef, Array.from(this.tools.values()));
|
|
4230
|
+
// Resolve policy profile FIRST so we know which tools the policy allows.
|
|
4231
|
+
// This must happen before the recommendation filter so policy-allowed tools
|
|
4232
|
+
// are preserved through the recommendation pruning step.
|
|
4233
|
+
const inferredTaskType = agentDef.taskType ?? ToolRecommendationEngine.inferTaskType(agentName);
|
|
4234
|
+
const policyResolution = resolvePolicyProfile({
|
|
4235
|
+
policyEngine: this.config.policyEngine,
|
|
4236
|
+
requestedProfile: agentDef.policyProfile,
|
|
4237
|
+
swarmConfig: isSwarmWorker && this.config.swarm && typeof this.config.swarm === 'object'
|
|
4238
|
+
? this.config.swarm
|
|
4239
|
+
: undefined,
|
|
4240
|
+
taskType: inferredTaskType,
|
|
4241
|
+
isSwarmWorker,
|
|
4242
|
+
sandboxConfig: this.config.sandbox && typeof this.config.sandbox === 'object'
|
|
4243
|
+
? this.config.sandbox
|
|
4244
|
+
: undefined,
|
|
4245
|
+
});
|
|
4246
|
+
this.emit({
|
|
4247
|
+
type: 'policy.profile.resolved',
|
|
4248
|
+
profile: policyResolution.profileName,
|
|
4249
|
+
context: isSwarmWorker ? 'swarm' : 'subagent',
|
|
4250
|
+
selectionSource: policyResolution.metadata.selectionSource,
|
|
4251
|
+
usedLegacyMappings: policyResolution.metadata.usedLegacyMappings,
|
|
4252
|
+
legacySources: policyResolution.metadata.legacyMappingSources,
|
|
4253
|
+
});
|
|
4254
|
+
if (policyResolution.metadata.usedLegacyMappings) {
|
|
4255
|
+
this.emit({
|
|
4256
|
+
type: 'policy.legacy.fallback.used',
|
|
4257
|
+
profile: policyResolution.profileName,
|
|
4258
|
+
sources: policyResolution.metadata.legacyMappingSources,
|
|
4259
|
+
warnings: policyResolution.metadata.warnings,
|
|
4260
|
+
});
|
|
4261
|
+
this.observability?.logger?.warn('Policy legacy mappings used', {
|
|
4262
|
+
agent: agentName,
|
|
4263
|
+
profile: policyResolution.profileName,
|
|
4264
|
+
sources: policyResolution.metadata.legacyMappingSources,
|
|
4265
|
+
});
|
|
4266
|
+
}
|
|
3919
4267
|
// Apply tool recommendations to improve subagent focus (only for large tool sets)
|
|
3920
4268
|
if (this.toolRecommendation && agentTools.length > 15) {
|
|
3921
4269
|
const taskType = ToolRecommendationEngine.inferTaskType(agentName);
|
|
@@ -3924,9 +4272,29 @@ export class ProductionAgent {
|
|
|
3924
4272
|
const recommendedNames = new Set(recommendations.map(r => r.toolName));
|
|
3925
4273
|
// Always keep spawn tools even if not recommended
|
|
3926
4274
|
const alwaysKeep = new Set(['spawn_agent', 'spawn_agents_parallel']);
|
|
4275
|
+
// Also keep tools that the resolved policy profile explicitly allows.
|
|
4276
|
+
// This prevents the recommendation engine from stripping tools that the
|
|
4277
|
+
// security policy says the worker should have.
|
|
4278
|
+
if (policyResolution.profile.allowedTools) {
|
|
4279
|
+
for (const t of policyResolution.profile.allowedTools)
|
|
4280
|
+
alwaysKeep.add(t);
|
|
4281
|
+
}
|
|
3927
4282
|
agentTools = agentTools.filter(t => recommendedNames.has(t.name) || alwaysKeep.has(t.name));
|
|
3928
4283
|
}
|
|
3929
4284
|
}
|
|
4285
|
+
// Enforce unified tool policy at spawn-time so denied tools are never exposed.
|
|
4286
|
+
if (policyResolution.profile.toolAccessMode === 'whitelist' && policyResolution.profile.allowedTools) {
|
|
4287
|
+
const allowed = new Set(policyResolution.profile.allowedTools);
|
|
4288
|
+
agentTools = agentTools.filter(t => allowed.has(t.name));
|
|
4289
|
+
}
|
|
4290
|
+
else if (policyResolution.profile.deniedTools && policyResolution.profile.deniedTools.length > 0) {
|
|
4291
|
+
const denied = new Set(policyResolution.profile.deniedTools);
|
|
4292
|
+
agentTools = agentTools.filter(t => !denied.has(t.name));
|
|
4293
|
+
}
|
|
4294
|
+
// Fail fast if tool filtering resulted in zero tools — the worker can't do anything
|
|
4295
|
+
if (agentTools.length === 0) {
|
|
4296
|
+
throw new Error(`Worker '${agentName}' has zero available tools after filtering. Check toolAccessMode and policy profile '${policyResolution.profileName}'.`);
|
|
4297
|
+
}
|
|
3930
4298
|
// Resolve model - abstract tiers (fast/balanced/quality) should use parent's model
|
|
3931
4299
|
// Only use agentDef.model if it's an actual model ID (contains '/')
|
|
3932
4300
|
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
@@ -3949,7 +4317,8 @@ export class ProductionAgent {
|
|
|
3949
4317
|
// Precedence: per-type config > per-type default > global config > hardcoded fallback
|
|
3950
4318
|
const subagentConfig = this.config.subagent;
|
|
3951
4319
|
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
3952
|
-
// Timeout precedence: per-type config
|
|
4320
|
+
// Timeout precedence: agentDef.timeout > per-type config > agent-type default > global config default
|
|
4321
|
+
// agentDef.timeout is set by worker-pool for swarm workers, giving them precise timeout control
|
|
3953
4322
|
const agentTypeTimeout = getSubagentTimeout(agentName);
|
|
3954
4323
|
const rawPerTypeTimeout = hasSubagentConfig
|
|
3955
4324
|
? subagentConfig.timeouts?.[agentName]
|
|
@@ -3959,9 +4328,10 @@ export class ProductionAgent {
|
|
|
3959
4328
|
: undefined;
|
|
3960
4329
|
// Validate: reject negative, NaN, or non-finite timeout values
|
|
3961
4330
|
const isValidTimeout = (v) => v !== undefined && Number.isFinite(v) && v > 0;
|
|
4331
|
+
const agentDefTimeout = isValidTimeout(agentDef.timeout) ? agentDef.timeout : undefined;
|
|
3962
4332
|
const perTypeConfigTimeout = isValidTimeout(rawPerTypeTimeout) ? rawPerTypeTimeout : undefined;
|
|
3963
4333
|
const globalConfigTimeout = isValidTimeout(rawGlobalTimeout) ? rawGlobalTimeout : undefined;
|
|
3964
|
-
const subagentTimeout = perTypeConfigTimeout ?? agentTypeTimeout ?? globalConfigTimeout ?? 300000;
|
|
4334
|
+
const subagentTimeout = agentDefTimeout ?? perTypeConfigTimeout ?? agentTypeTimeout ?? globalConfigTimeout ?? 300000;
|
|
3965
4335
|
// Iteration precedence: per-type config override > agent-type default > global config default
|
|
3966
4336
|
const agentTypeMaxIter = getSubagentMaxIterations(agentName);
|
|
3967
4337
|
const rawPerTypeMaxIter = hasSubagentConfig
|
|
@@ -4019,17 +4389,16 @@ export class ProductionAgent {
|
|
|
4019
4389
|
const subagentBudgetTokens = constraints?.maxTokens ?? SUBAGENT_BUDGET.maxTokens ?? 100000;
|
|
4020
4390
|
const subagentBudgetMinutes = Math.round((SUBAGENT_BUDGET.maxDuration ?? 240000) / 60000);
|
|
4021
4391
|
if (isSwarmWorker) {
|
|
4022
|
-
//
|
|
4023
|
-
//
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
`-
|
|
4028
|
-
`-
|
|
4029
|
-
`-
|
|
4030
|
-
|
|
4031
|
-
`-
|
|
4032
|
-
` {"findings":[...], "actionsTaken":[...], "failures":[...], "remainingWork":[...], "suggestedNextSteps":[...]}`);
|
|
4392
|
+
// V8: Minimal resource awareness for swarm workers — removes budget/time
|
|
4393
|
+
// messaging entirely to prevent cheap models from bail-out anxiety.
|
|
4394
|
+
// The economics system handles budget warnings via system messages when needed.
|
|
4395
|
+
// Wrapup JSON format is ONLY injected when requestWrapup() is called.
|
|
4396
|
+
constraintParts.push(`**Execution Mode:** You are a focused worker agent.\n` +
|
|
4397
|
+
`- Complete your assigned task using tool calls.\n` +
|
|
4398
|
+
`- Your FIRST action must be a tool call (read_file, write_file, edit_file, grep, glob, etc.).\n` +
|
|
4399
|
+
`- To create files use write_file. To modify files use edit_file. Do NOT use bash for file operations.\n` +
|
|
4400
|
+
`- You will receive a system message if you need to wrap up. Until then, work normally.\n` +
|
|
4401
|
+
`- Do NOT produce summaries or reports — produce CODE and FILE CHANGES.`);
|
|
4033
4402
|
}
|
|
4034
4403
|
else {
|
|
4035
4404
|
// Original RESOURCE AWARENESS text for regular subagents
|
|
@@ -4073,6 +4442,20 @@ export class ProductionAgent {
|
|
|
4073
4442
|
// Allocate budget from pool (or use default) — track allocation ID for release later
|
|
4074
4443
|
const pooledBudget = this.getSubagentBudget(agentName, constraints);
|
|
4075
4444
|
const poolAllocationId = pooledBudget.allocationId;
|
|
4445
|
+
const deniedByProfile = new Set(policyResolution.profile.deniedTools ?? []);
|
|
4446
|
+
const policyToolPolicies = {};
|
|
4447
|
+
for (const toolName of deniedByProfile) {
|
|
4448
|
+
policyToolPolicies[toolName] = {
|
|
4449
|
+
policy: 'forbidden',
|
|
4450
|
+
reason: `Denied by policy profile '${policyResolution.profileName}'`,
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
if ((policyResolution.profile.bashMode ?? 'full') === 'disabled') {
|
|
4454
|
+
policyToolPolicies.bash = {
|
|
4455
|
+
policy: 'forbidden',
|
|
4456
|
+
reason: `Bash is disabled by policy profile '${policyResolution.profileName}'`,
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4076
4459
|
// Create a sub-agent with the agent's config
|
|
4077
4460
|
// Use SUBAGENT_BUDGET to constrain resource usage (prevents runaway token consumption)
|
|
4078
4461
|
const subAgent = new ProductionAgent({
|
|
@@ -4104,14 +4487,56 @@ export class ProductionAgent {
|
|
|
4104
4487
|
// Lower context window for subagents so percentage-based compaction triggers earlier
|
|
4105
4488
|
maxContextTokens: 80000,
|
|
4106
4489
|
observability: this.config.observability,
|
|
4107
|
-
sandbox:
|
|
4490
|
+
sandbox: (() => {
|
|
4491
|
+
const swarm = this.config.swarm;
|
|
4492
|
+
const extraCmds = swarm && typeof swarm === 'object' && swarm.permissions?.additionalAllowedCommands;
|
|
4493
|
+
const baseSbx = this.config.sandbox;
|
|
4494
|
+
if (baseSbx && typeof baseSbx === 'object') {
|
|
4495
|
+
const sbx = baseSbx;
|
|
4496
|
+
const allowedCommands = extraCmds
|
|
4497
|
+
? [...(sbx.allowedCommands || []), ...extraCmds]
|
|
4498
|
+
: sbx.allowedCommands;
|
|
4499
|
+
return {
|
|
4500
|
+
...sbx,
|
|
4501
|
+
allowedCommands,
|
|
4502
|
+
bashMode: policyResolution.profile.bashMode ?? sbx.bashMode,
|
|
4503
|
+
bashWriteProtection: policyResolution.profile.bashWriteProtection ?? sbx.bashWriteProtection,
|
|
4504
|
+
blockFileCreationViaBash: (policyResolution.profile.bashWriteProtection ?? 'off') === 'block_file_mutation'
|
|
4505
|
+
? true
|
|
4506
|
+
: sbx.blockFileCreationViaBash,
|
|
4507
|
+
};
|
|
4508
|
+
}
|
|
4509
|
+
return baseSbx;
|
|
4510
|
+
})(),
|
|
4108
4511
|
humanInLoop: this.config.humanInLoop,
|
|
4109
4512
|
// Subagents get 'allow' as default policy since they're already
|
|
4110
4513
|
// constrained to their registered tool set. The parent's 'prompt'
|
|
4111
4514
|
// policy can't work without humanInLoop.
|
|
4112
|
-
executionPolicy:
|
|
4113
|
-
|
|
4114
|
-
|
|
4515
|
+
executionPolicy: (() => {
|
|
4516
|
+
const hasPolicyOverrides = Object.keys(policyToolPolicies).length > 0;
|
|
4517
|
+
if (this.config.executionPolicy) {
|
|
4518
|
+
return {
|
|
4519
|
+
...this.config.executionPolicy,
|
|
4520
|
+
defaultPolicy: 'allow',
|
|
4521
|
+
toolPolicies: {
|
|
4522
|
+
...(this.config.executionPolicy.toolPolicies ?? {}),
|
|
4523
|
+
...policyToolPolicies,
|
|
4524
|
+
},
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4527
|
+
if (hasPolicyOverrides) {
|
|
4528
|
+
return {
|
|
4529
|
+
enabled: true,
|
|
4530
|
+
defaultPolicy: 'allow',
|
|
4531
|
+
toolPolicies: policyToolPolicies,
|
|
4532
|
+
intentAware: false,
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
return this.config.executionPolicy;
|
|
4536
|
+
})(),
|
|
4537
|
+
policyEngine: this.config.policyEngine
|
|
4538
|
+
? { ...this.config.policyEngine, defaultProfile: policyResolution.profileName }
|
|
4539
|
+
: this.config.policyEngine,
|
|
4115
4540
|
threads: false,
|
|
4116
4541
|
// Disable hooks console output in subagents - parent handles event display
|
|
4117
4542
|
hooks: this.config.hooks === false ? false : {
|
|
@@ -4127,7 +4552,10 @@ export class ProductionAgent {
|
|
|
4127
4552
|
fileCache: this.fileCache || undefined,
|
|
4128
4553
|
// CONSTRAINED BUDGET: Use pooled budget when available, falling back to SUBAGENT_BUDGET
|
|
4129
4554
|
// Pooled budget ensures total tree cost stays bounded by parent's budget
|
|
4130
|
-
|
|
4555
|
+
// Merge economicsTuning from agent definition so swarm workers get custom thresholds
|
|
4556
|
+
budget: agentDef.economicsTuning
|
|
4557
|
+
? { ...pooledBudget.budget, tuning: agentDef.economicsTuning }
|
|
4558
|
+
: pooledBudget.budget,
|
|
4131
4559
|
});
|
|
4132
4560
|
// CRITICAL: Subagent inherits parent's mode
|
|
4133
4561
|
// This ensures that if parent is in plan mode:
|
|
@@ -4140,14 +4568,35 @@ export class ProductionAgent {
|
|
|
4140
4568
|
// APPROVAL BATCHING (Improvement P6): Set approval scope for subagents
|
|
4141
4569
|
// Read-only tools are auto-approved; write tools get scoped approval
|
|
4142
4570
|
// This reduces interruptions from ~8 per session to ~1-2
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4571
|
+
// Swarm permissions from config override defaults when present
|
|
4572
|
+
const swarmPerms = this.config.swarm && typeof this.config.swarm === 'object'
|
|
4573
|
+
? this.config.swarm.permissions : undefined;
|
|
4574
|
+
const baseAutoApprove = ['read_file', 'list_files', 'glob', 'grep', 'show_file_history', 'show_session_changes'];
|
|
4575
|
+
const baseScopedApprove = isSwarmWorker
|
|
4576
|
+
? {
|
|
4146
4577
|
write_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4147
4578
|
edit_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4579
|
+
bash: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4580
|
+
}
|
|
4581
|
+
: {
|
|
4582
|
+
write_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4583
|
+
edit_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4584
|
+
};
|
|
4585
|
+
const baseRequireApproval = isSwarmWorker ? ['delete_file'] : ['bash', 'delete_file'];
|
|
4586
|
+
const mergedScope = mergeApprovalScopeWithProfile({
|
|
4587
|
+
autoApprove: swarmPerms?.autoApprove
|
|
4588
|
+
? [...new Set([...baseAutoApprove, ...swarmPerms.autoApprove])]
|
|
4589
|
+
: baseAutoApprove,
|
|
4590
|
+
scopedApprove: swarmPerms?.scopedApprove
|
|
4591
|
+
? { ...baseScopedApprove, ...swarmPerms.scopedApprove }
|
|
4592
|
+
: baseScopedApprove,
|
|
4593
|
+
// requireApproval: full replacement (not merge) — user may want to REMOVE
|
|
4594
|
+
// tools like 'bash' to let workers run freely
|
|
4595
|
+
requireApproval: swarmPerms?.requireApproval
|
|
4596
|
+
? swarmPerms.requireApproval
|
|
4597
|
+
: baseRequireApproval,
|
|
4598
|
+
}, policyResolution.profile);
|
|
4599
|
+
subAgent.setApprovalScope(mergedScope);
|
|
4151
4600
|
// Pass parent's iteration count to subagent for accurate budget tracking
|
|
4152
4601
|
// This prevents subagents from consuming excessive iterations when parent already used many
|
|
4153
4602
|
subAgent.setParentIterations(this.getTotalIterations());
|
|
@@ -4168,7 +4617,7 @@ export class ProductionAgent {
|
|
|
4168
4617
|
// 1. Normal operation: progress extends idle timer
|
|
4169
4618
|
// 2. Wrapup phase: 30s before hard kill, wrapup callback fires → forceTextOnly
|
|
4170
4619
|
// 3. Hard kill: race() throws CancellationError after wrapup window
|
|
4171
|
-
const IDLE_TIMEOUT = 120000; //
|
|
4620
|
+
const IDLE_TIMEOUT = agentDef.idleTimeout ?? 120000; // Configurable idle timeout (default: 2 min)
|
|
4172
4621
|
let WRAPUP_WINDOW = 30000;
|
|
4173
4622
|
let IDLE_CHECK_INTERVAL = 5000;
|
|
4174
4623
|
if (this.config.subagent) {
|
|
@@ -4271,6 +4720,8 @@ export class ProductionAgent {
|
|
|
4271
4720
|
: (result.response || result.error || '');
|
|
4272
4721
|
// Parse structured closure report from agent's response (if it produced one)
|
|
4273
4722
|
const structured = parseStructuredClosureReport(result.response || '', 'completed');
|
|
4723
|
+
// Extract real file paths from subagent's economics tracker (before cleanup)
|
|
4724
|
+
const subagentFilePaths = subAgent.getModifiedFilePaths();
|
|
4274
4725
|
const spawnResultFinal = {
|
|
4275
4726
|
success: result.success,
|
|
4276
4727
|
output: finalOutput,
|
|
@@ -4280,6 +4731,7 @@ export class ProductionAgent {
|
|
|
4280
4731
|
toolCalls: result.metrics.toolCalls,
|
|
4281
4732
|
},
|
|
4282
4733
|
structured,
|
|
4734
|
+
filesModified: subagentFilePaths,
|
|
4283
4735
|
};
|
|
4284
4736
|
// Save full output to subagent output store (avoids telephone problem)
|
|
4285
4737
|
if (this.subagentOutputStore) {
|
|
@@ -4290,7 +4742,7 @@ export class ProductionAgent {
|
|
|
4290
4742
|
task,
|
|
4291
4743
|
fullOutput: finalOutput,
|
|
4292
4744
|
structured,
|
|
4293
|
-
filesModified:
|
|
4745
|
+
filesModified: subagentFilePaths,
|
|
4294
4746
|
filesCreated: [],
|
|
4295
4747
|
timestamp: new Date(),
|
|
4296
4748
|
tokensUsed: result.metrics.totalTokens,
|
|
@@ -4440,6 +4892,8 @@ export class ProductionAgent {
|
|
|
4440
4892
|
this.pendingPlanManager.appendExplorationFinding(`[${agentName}] ${subPlan.explorationSummary}`);
|
|
4441
4893
|
}
|
|
4442
4894
|
}
|
|
4895
|
+
// Extract real file paths from subagent's economics tracker (before cleanup)
|
|
4896
|
+
const subagentFilePaths = subAgent.getModifiedFilePaths();
|
|
4443
4897
|
// Unsubscribe from subagent events and cleanup gracefully
|
|
4444
4898
|
unsubSubAgent();
|
|
4445
4899
|
try {
|
|
@@ -4507,6 +4961,7 @@ export class ProductionAgent {
|
|
|
4507
4961
|
toolCalls: subagentMetrics.toolCalls,
|
|
4508
4962
|
},
|
|
4509
4963
|
structured,
|
|
4964
|
+
filesModified: subagentFilePaths,
|
|
4510
4965
|
};
|
|
4511
4966
|
}
|
|
4512
4967
|
throw err; // Re-throw non-cancellation errors
|