salmon-loop 0.3.2 → 0.5.0
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/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/ast/parser.js +18 -9
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +12 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +5 -4
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +90 -7
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- package/package.json +2 -1
|
@@ -3,16 +3,19 @@ import { text } from '../../../locales/index.js';
|
|
|
3
3
|
import { createFileSystemAdapter } from '../../adapters/fs/index.js';
|
|
4
4
|
import * as fs from '../../adapters/fs/node-fs.js';
|
|
5
5
|
import { GitAdapter } from '../../adapters/git/git-adapter.js';
|
|
6
|
+
import { createTaskEventBus } from '../../interaction/events/bus.js';
|
|
6
7
|
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
7
8
|
import { getLogger } from '../../observability/logger.js';
|
|
8
9
|
import { FileStateResolver } from '../../strata/layers/file-state-resolver.js';
|
|
9
10
|
import { RuntimeEnvironment } from '../../strata/runtime/environment.js';
|
|
10
11
|
import { ErrorType } from '../../types/index.js';
|
|
12
|
+
import { errorMessage } from '../../utils/error.js';
|
|
11
13
|
import { ArtifactStore } from '../artifacts/store.js';
|
|
12
14
|
import { cloneSubAgentContextSnapshot } from '../context-snapshot.js';
|
|
13
15
|
import { isReadOnlySubAgentContext, resolveSubAgentDryRun } from '../dispatch-policy.js';
|
|
14
16
|
import { validateSharedPrefixConsistency } from '../prefix-consistency.js';
|
|
15
17
|
import { getSubAgentRegistry } from '../registry.js';
|
|
18
|
+
import { generateSubAgentSummary, formatSubAgentSummary } from '../summary.js';
|
|
16
19
|
import { SmallfryLoop } from './loop.js';
|
|
17
20
|
/**
|
|
18
21
|
* SubAgentManager coordinates the lifecycle of Smallfrys.
|
|
@@ -22,6 +25,7 @@ export class SubAgentManager {
|
|
|
22
25
|
ctx;
|
|
23
26
|
controller;
|
|
24
27
|
activeAgents = new Map();
|
|
28
|
+
completedResults = [];
|
|
25
29
|
deps;
|
|
26
30
|
constructor(ctx, controller, deps) {
|
|
27
31
|
this.ctx = ctx;
|
|
@@ -31,45 +35,182 @@ export class SubAgentManager {
|
|
|
31
35
|
createRuntimeEnvironment: deps?.createRuntimeEnvironment ??
|
|
32
36
|
((options, emit) => new RuntimeEnvironment(options, emit)),
|
|
33
37
|
artifactStore: deps?.artifactStore ?? ArtifactStore,
|
|
38
|
+
eventBus: deps?.eventBus ?? createTaskEventBus(),
|
|
39
|
+
llmFactory: deps?.llmFactory,
|
|
40
|
+
onSubAgentComplete: deps?.onSubAgentComplete,
|
|
34
41
|
};
|
|
35
42
|
}
|
|
36
43
|
/**
|
|
37
|
-
* Spawns a new sub-agent
|
|
44
|
+
* Spawns a new sub-agent. When request.async is true, returns a handle immediately;
|
|
45
|
+
* otherwise blocks until the sub-agent completes.
|
|
38
46
|
*/
|
|
39
47
|
async execute(request) {
|
|
40
|
-
const normalizedRequest = request
|
|
41
|
-
? (() => {
|
|
42
|
-
const consistency = validateSharedPrefixConsistency({
|
|
43
|
-
requestSnapshot: request.contextSnapshot,
|
|
44
|
-
runtimeSnapshot: this.ctx.contextSnapshot,
|
|
45
|
-
});
|
|
46
|
-
if (consistency.compatible)
|
|
47
|
-
return request;
|
|
48
|
-
recordAuditEvent('sub_agent.shared.prefix_consistency_failed', {
|
|
49
|
-
metric: 'shared_fallback_rate',
|
|
50
|
-
fallbackMode: 'isolated',
|
|
51
|
-
reason: consistency.reason,
|
|
52
|
-
expected: consistency.expected,
|
|
53
|
-
actual: consistency.actual,
|
|
54
|
-
}, {
|
|
55
|
-
source: 'smallfry',
|
|
56
|
-
severity: 'medium',
|
|
57
|
-
scope: 'session',
|
|
58
|
-
phase: this.ctx.phase,
|
|
59
|
-
});
|
|
60
|
-
return {
|
|
61
|
-
...request,
|
|
62
|
-
session_target: 'isolated',
|
|
63
|
-
contextSnapshot: undefined,
|
|
64
|
-
};
|
|
65
|
-
})()
|
|
66
|
-
: request;
|
|
48
|
+
const normalizedRequest = this.normalizeRequest(request);
|
|
67
49
|
const profile = this.deps.registry.get(normalizedRequest.agent_ref);
|
|
68
50
|
if (!profile) {
|
|
69
51
|
return this.fail(normalizedRequest.agent_ref, text.smallfry.errors.profileNotFound(normalizedRequest.agent_ref), 'LOOP_FAILED');
|
|
70
52
|
}
|
|
71
53
|
const agentId = `smallfry-${randomBytes(4).toString('hex')}`;
|
|
72
|
-
|
|
54
|
+
if (normalizedRequest.async) {
|
|
55
|
+
return this.executeAsync(normalizedRequest, profile, agentId);
|
|
56
|
+
}
|
|
57
|
+
return this.executeSync(normalizedRequest, profile, agentId);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Waits for an async sub-agent to complete and returns its result.
|
|
61
|
+
* @param handle - The sub-agent handle returned by executeAsync
|
|
62
|
+
* @param timeoutMs - Maximum time to wait in milliseconds. Defaults to 300_000 (5 minutes).
|
|
63
|
+
*/
|
|
64
|
+
async awaitResult(handle, timeoutMs) {
|
|
65
|
+
// Check if already completed
|
|
66
|
+
const entry = this.activeAgents.get(handle.agentId);
|
|
67
|
+
if (entry?.result) {
|
|
68
|
+
return entry.result;
|
|
69
|
+
}
|
|
70
|
+
// Check historical events
|
|
71
|
+
const historical = this.deps.eventBus.list(handle.taskId, { limit: 10 });
|
|
72
|
+
const terminalEvent = historical.find((e) => e.type === 'subagent.completed' || e.type === 'subagent.failed');
|
|
73
|
+
if (terminalEvent) {
|
|
74
|
+
return terminalEvent.state === 'completed'
|
|
75
|
+
? terminalEvent.result
|
|
76
|
+
: this.fail(handle.agentId, terminalEvent.reason ?? 'Sub-agent failed', 'LOOP_FAILED');
|
|
77
|
+
}
|
|
78
|
+
const effectiveTimeout = timeoutMs ?? 300_000;
|
|
79
|
+
// Subscribe and wait for terminal event
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
unsub();
|
|
83
|
+
// Request stop on the sub-agent so it can clean up
|
|
84
|
+
this.controller.requestStop(handle.agentId);
|
|
85
|
+
reject(new Error(`Timed out waiting for sub-agent ${handle.agentId} after ${effectiveTimeout}ms`));
|
|
86
|
+
}, effectiveTimeout);
|
|
87
|
+
const unsub = this.deps.eventBus.subscribe((event) => {
|
|
88
|
+
if (event.taskId !== handle.taskId)
|
|
89
|
+
return;
|
|
90
|
+
if (event.type === 'subagent.completed' || event.type === 'subagent.failed') {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
unsub();
|
|
93
|
+
if (event.type === 'subagent.completed') {
|
|
94
|
+
resolve(event.result);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
resolve(this.fail(handle.agentId, event.reason ?? 'Sub-agent failed', 'LOOP_FAILED'));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get a summary of all completed sub-agent results.
|
|
105
|
+
* Includes conflict detection across patches.
|
|
106
|
+
*/
|
|
107
|
+
getSummary() {
|
|
108
|
+
if (this.completedResults.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
return generateSubAgentSummary(this.completedResults);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get a formatted summary string for LLM context injection.
|
|
114
|
+
*/
|
|
115
|
+
getFormattedSummary() {
|
|
116
|
+
const summary = this.getSummary();
|
|
117
|
+
if (!summary)
|
|
118
|
+
return null;
|
|
119
|
+
return formatSubAgentSummary(summary);
|
|
120
|
+
}
|
|
121
|
+
normalizeRequest(request) {
|
|
122
|
+
// Fork mode: no prefix consistency validation needed (it's a clone, not a shared session)
|
|
123
|
+
if (request.session_target === 'fork')
|
|
124
|
+
return request;
|
|
125
|
+
if (request.session_target !== 'shared')
|
|
126
|
+
return request;
|
|
127
|
+
const consistency = validateSharedPrefixConsistency({
|
|
128
|
+
requestSnapshot: request.contextSnapshot,
|
|
129
|
+
runtimeSnapshot: this.ctx.contextSnapshot,
|
|
130
|
+
});
|
|
131
|
+
if (consistency.compatible)
|
|
132
|
+
return request;
|
|
133
|
+
recordAuditEvent('sub_agent.shared.prefix_consistency_failed', {
|
|
134
|
+
metric: 'shared_fallback_rate',
|
|
135
|
+
fallbackMode: 'isolated',
|
|
136
|
+
reason: consistency.reason,
|
|
137
|
+
expected: consistency.expected,
|
|
138
|
+
actual: consistency.actual,
|
|
139
|
+
}, {
|
|
140
|
+
source: 'smallfry',
|
|
141
|
+
severity: 'medium',
|
|
142
|
+
scope: 'session',
|
|
143
|
+
phase: this.ctx.phase,
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
...request,
|
|
147
|
+
session_target: 'isolated',
|
|
148
|
+
contextSnapshot: undefined,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Async dispatch: fire-and-forget, publish events, return handle.
|
|
153
|
+
*/
|
|
154
|
+
executeAsync(request, profile, agentId) {
|
|
155
|
+
const taskId = agentId;
|
|
156
|
+
this.activeAgents.set(agentId, { profile, status: 'hiring' });
|
|
157
|
+
this.controller.registerAgent(agentId, profile, 'hiring');
|
|
158
|
+
this.deps.eventBus.publish({
|
|
159
|
+
type: 'subagent.accepted',
|
|
160
|
+
taskId,
|
|
161
|
+
state: 'accepted',
|
|
162
|
+
});
|
|
163
|
+
// Fire-and-forget: executeCore runs in the background
|
|
164
|
+
this.executeCore(request, profile, agentId)
|
|
165
|
+
.then((result) => {
|
|
166
|
+
const entry = this.activeAgents.get(agentId);
|
|
167
|
+
if (entry)
|
|
168
|
+
entry.result = result;
|
|
169
|
+
this.controller.setResult(agentId, result);
|
|
170
|
+
this.controller.updateStatus(agentId, 'terminated', result.summary);
|
|
171
|
+
this.deps.eventBus.publish({
|
|
172
|
+
type: result.success ? 'subagent.completed' : 'subagent.failed',
|
|
173
|
+
taskId,
|
|
174
|
+
state: result.success ? 'completed' : 'failed',
|
|
175
|
+
});
|
|
176
|
+
// Track for summary generation
|
|
177
|
+
this.completedResults.push({ agentId, result });
|
|
178
|
+
// Notify completion listener (for background auto-notify)
|
|
179
|
+
this.deps.onSubAgentComplete?.(agentId, result);
|
|
180
|
+
})
|
|
181
|
+
.catch((error) => {
|
|
182
|
+
const failResult = this.fail(profile.id, errorMessage(error), 'LOOP_CRASH');
|
|
183
|
+
const entry = this.activeAgents.get(agentId);
|
|
184
|
+
if (entry)
|
|
185
|
+
entry.result = failResult;
|
|
186
|
+
this.controller.setResult(agentId, failResult);
|
|
187
|
+
this.controller.updateStatus(agentId, 'terminated', failResult.summary);
|
|
188
|
+
this.deps.eventBus.publish({
|
|
189
|
+
type: 'subagent.failed',
|
|
190
|
+
taskId,
|
|
191
|
+
state: 'failed',
|
|
192
|
+
});
|
|
193
|
+
})
|
|
194
|
+
.finally(() => {
|
|
195
|
+
const entry = this.activeAgents.get(agentId);
|
|
196
|
+
if (!entry?.result) {
|
|
197
|
+
this.activeAgents.delete(agentId);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return { agentId, status: 'working', taskId };
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Synchronous dispatch: blocks until the sub-agent completes.
|
|
204
|
+
*/
|
|
205
|
+
async executeSync(request, profile, agentId) {
|
|
206
|
+
return this.executeCore(request, profile, agentId);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Core execution logic shared by async and sync paths.
|
|
210
|
+
* Retries up to profile.maxAttempts on LOOP_FAILED (not LOOP_CRASH).
|
|
211
|
+
*/
|
|
212
|
+
async executeCore(request, profile, agentId) {
|
|
213
|
+
const currentDepth = request.recursionDepth || 0;
|
|
73
214
|
const MAX_RECURSION_DEPTH = 2;
|
|
74
215
|
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
|
75
216
|
const msg = text.smallfry.errors.recursionLimitExceeded(currentDepth, MAX_RECURSION_DEPTH);
|
|
@@ -79,97 +220,128 @@ export class SubAgentManager {
|
|
|
79
220
|
this.activeAgents.set(agentId, { profile, status: 'hiring' });
|
|
80
221
|
this.controller.registerAgent(agentId, profile, 'hiring');
|
|
81
222
|
getLogger().debug(`[SubAgentManager] ${text.smallfry.status.spawning} (ID: ${agentId}, Role: ${profile.role})`);
|
|
82
|
-
|
|
83
|
-
|
|
223
|
+
// Resolve LLM: per-profile model override or inherit parent
|
|
224
|
+
const parentLlm = this.ctx.llm;
|
|
225
|
+
if (!parentLlm) {
|
|
84
226
|
const msg = text.smallfry.errors.dispatchMissingRuntimeLlm;
|
|
85
227
|
getLogger().error(`[SubAgentManager] ${msg}`);
|
|
86
228
|
return this.fail(profile.id, msg, 'LOOP_CRASH');
|
|
87
229
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const runtimeEnv = await this.setupIsolatedEnvironment(normalizedRequest, llm, agentId, effectiveDryRun);
|
|
230
|
+
const llm = this.resolveLlm(profile, parentLlm);
|
|
231
|
+
if (!llm) {
|
|
232
|
+
const msg = `Failed to resolve LLM for model "${profile.model}"`;
|
|
233
|
+
getLogger().error(`[SubAgentManager] ${msg}`);
|
|
234
|
+
return this.fail(profile.id, msg, 'LOOP_CRASH');
|
|
235
|
+
}
|
|
236
|
+
const maxAttempts = profile.maxAttempts ?? 1;
|
|
237
|
+
let lastResult;
|
|
238
|
+
let lastError;
|
|
239
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
99
240
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
workspace: {
|
|
109
|
-
workPath: activePath,
|
|
110
|
-
baseRepoPath: workspace.baseRepoPath,
|
|
111
|
-
strategy: workspace.strategy,
|
|
112
|
-
},
|
|
113
|
-
options: {
|
|
114
|
-
instruction: normalizedRequest.task,
|
|
115
|
-
repoPath: activePath,
|
|
116
|
-
dryRun: effectiveDryRun,
|
|
117
|
-
contextFiles: normalizedRequest.contextFiles || [],
|
|
118
|
-
llm,
|
|
119
|
-
recursionDepth: currentDepth + 1, // Increment depth for child
|
|
120
|
-
allowedToolNames: this.filterAllowedTools(profile.allowedTools, this.ctx.phase),
|
|
121
|
-
timeoutMs: normalizedRequest.timeout_seconds
|
|
122
|
-
? normalizedRequest.timeout_seconds * 1000
|
|
123
|
-
: profile.timeoutMs,
|
|
124
|
-
},
|
|
125
|
-
mode: flowMode,
|
|
126
|
-
fs: fsAdapter,
|
|
127
|
-
emit: (event) => {
|
|
128
|
-
// Bridge status to parent/UI
|
|
129
|
-
if (event.type === 'phase.start') {
|
|
130
|
-
this.updateStatus(agentId, 'working');
|
|
131
|
-
}
|
|
132
|
-
if (event.type === 'log') {
|
|
133
|
-
getLogger().debug(`[Smallfry:${agentId}] ${event.level}: ${event.message}`);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
getLogger().debug(`[Smallfry:${agentId}] ${event.type}`);
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
fileStateResolver: resolver,
|
|
140
|
-
shadowInitialRef: runtimeEnv?.initialSnapshotHash || 'HEAD',
|
|
241
|
+
this.updateStatus(agentId, 'working');
|
|
242
|
+
if (this.controller.isStopRequested(agentId)) {
|
|
243
|
+
throw new Error('Stop requested before launching Smallfry');
|
|
244
|
+
}
|
|
245
|
+
const effectiveDryRun = resolveSubAgentDryRun({
|
|
246
|
+
parentDryRun: this.ctx.dryRun,
|
|
247
|
+
flowMode: this.ctx.flowMode,
|
|
248
|
+
phase: this.ctx.phase,
|
|
141
249
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
250
|
+
const runtimeEnv = await this.setupIsolatedEnvironment(request, llm, agentId, effectiveDryRun);
|
|
251
|
+
try {
|
|
252
|
+
const workspace = runtimeEnv.workspace;
|
|
253
|
+
if (!workspace) {
|
|
254
|
+
throw new Error('Runtime environment setup succeeded but workspace was not initialized');
|
|
255
|
+
}
|
|
256
|
+
const activePath = workspace.workPath;
|
|
257
|
+
const git = new GitAdapter(activePath);
|
|
258
|
+
const resolver = new FileStateResolver(git, activePath);
|
|
259
|
+
const flowMode = 'patch';
|
|
260
|
+
const fsAdapter = createFileSystemAdapter(flowMode);
|
|
261
|
+
const initCtx = this.applyContextSnapshot(request.contextSnapshot, {
|
|
262
|
+
workspace: {
|
|
263
|
+
workPath: activePath,
|
|
264
|
+
baseRepoPath: workspace.baseRepoPath,
|
|
265
|
+
strategy: workspace.strategy,
|
|
266
|
+
},
|
|
267
|
+
options: {
|
|
268
|
+
instruction: request.task,
|
|
269
|
+
repoPath: activePath,
|
|
270
|
+
dryRun: effectiveDryRun,
|
|
271
|
+
contextFiles: request.contextFiles || [],
|
|
272
|
+
llm,
|
|
273
|
+
recursionDepth: currentDepth + 1,
|
|
274
|
+
allowedToolNames: this.resolveAllowedTools(profile, request.teamId),
|
|
275
|
+
timeoutMs: request.timeout_seconds
|
|
276
|
+
? request.timeout_seconds * 1000
|
|
277
|
+
: profile.timeoutMs,
|
|
278
|
+
subAgentSystemPrompt: profile.systemPrompt,
|
|
279
|
+
agentId,
|
|
280
|
+
},
|
|
281
|
+
lastError,
|
|
282
|
+
mode: flowMode,
|
|
283
|
+
fs: fsAdapter,
|
|
284
|
+
emit: (event) => {
|
|
285
|
+
if (event.type === 'phase.start') {
|
|
286
|
+
this.updateStatus(agentId, 'working');
|
|
287
|
+
}
|
|
288
|
+
if (event.type === 'log') {
|
|
289
|
+
getLogger().debug(`[Smallfry:${agentId}] ${event.level}: ${event.message}`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
getLogger().debug(`[Smallfry:${agentId}] ${event.type}`);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
fileStateResolver: resolver,
|
|
296
|
+
shadowInitialRef: runtimeEnv?.initialSnapshotHash || 'HEAD',
|
|
297
|
+
});
|
|
298
|
+
const subLoop = new SmallfryLoop(profile);
|
|
299
|
+
const result = await subLoop.execute(initCtx);
|
|
300
|
+
lastResult = result;
|
|
301
|
+
// Success or non-retryable failure — return immediately
|
|
302
|
+
if (result.success || result.reasonCode === 'LOOP_CRASH' || attempt >= maxAttempts) {
|
|
303
|
+
return await this.persistArtifacts(agentId, {
|
|
304
|
+
...result,
|
|
305
|
+
attempts: attempt,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// Retryable failure — log and continue
|
|
309
|
+
lastError = result.reason || result.summary;
|
|
310
|
+
getLogger().warn(`[SubAgentManager] Smallfry ${agentId} attempt ${attempt}/${maxAttempts} failed (${result.reasonCode}), retrying...`);
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
await runtimeEnv.teardown();
|
|
314
|
+
}
|
|
146
315
|
}
|
|
147
|
-
|
|
148
|
-
|
|
316
|
+
catch (error) {
|
|
317
|
+
this.controller.appendLog(agentId, `Execution failed: ${errorMessage(error)}`);
|
|
318
|
+
getLogger().error(`[SubAgentManager] Smallfry ${agentId} crashed: ${errorMessage(error)}`);
|
|
319
|
+
// Crashes are not retryable
|
|
320
|
+
return {
|
|
321
|
+
agent_ref: profile.id,
|
|
322
|
+
success: false,
|
|
323
|
+
summary: text.smallfry.errors.missionFailedWithReason(errorMessage(error)),
|
|
324
|
+
tokenUsage: 0,
|
|
325
|
+
reason: errorMessage(error),
|
|
326
|
+
reasonCode: 'LOOP_CRASH',
|
|
327
|
+
attempts: attempt,
|
|
328
|
+
logs: [],
|
|
329
|
+
errorType: ErrorType.UNKNOWN,
|
|
330
|
+
};
|
|
149
331
|
}
|
|
150
332
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
getLogger().error(`[SubAgentManager] Smallfry ${agentId} crashed: ${error instanceof Error ? error.message : String(error)}`);
|
|
154
|
-
return {
|
|
155
|
-
agent_ref: profile.id,
|
|
156
|
-
success: false,
|
|
157
|
-
summary: text.smallfry.errors.missionFailedWithReason(error instanceof Error ? error.message : String(error)),
|
|
158
|
-
tokenUsage: 0,
|
|
159
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
160
|
-
reasonCode: 'LOOP_CRASH',
|
|
161
|
-
attempts: 1,
|
|
162
|
-
logs: [],
|
|
163
|
-
errorType: ErrorType.UNKNOWN,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
finally {
|
|
167
|
-
this.activeAgents.delete(agentId);
|
|
168
|
-
}
|
|
333
|
+
// Should not reach here, but safety fallback
|
|
334
|
+
return lastResult ?? this.fail(profile.id, text.smallfry.errors.missionFailed, 'LOOP_FAILED');
|
|
169
335
|
}
|
|
170
|
-
// Backward compatibility for internal calls
|
|
336
|
+
// Backward compatibility for internal calls (always synchronous)
|
|
171
337
|
async spawn(request) {
|
|
172
|
-
|
|
338
|
+
const normalizedRequest = this.normalizeRequest(request);
|
|
339
|
+
const profile = this.deps.registry.get(normalizedRequest.agent_ref);
|
|
340
|
+
if (!profile) {
|
|
341
|
+
return this.fail(normalizedRequest.agent_ref, text.smallfry.errors.profileNotFound(normalizedRequest.agent_ref), 'LOOP_FAILED');
|
|
342
|
+
}
|
|
343
|
+
const agentId = `smallfry-${randomBytes(4).toString('hex')}`;
|
|
344
|
+
return this.executeSync(normalizedRequest, profile, agentId);
|
|
173
345
|
}
|
|
174
346
|
applyContextSnapshot(snapshot, initCtx) {
|
|
175
347
|
const normalized = cloneSubAgentContextSnapshot(snapshot);
|
|
@@ -209,6 +381,54 @@ export class SubAgentManager {
|
|
|
209
381
|
errorType: ErrorType.UNKNOWN,
|
|
210
382
|
};
|
|
211
383
|
}
|
|
384
|
+
resolveAllowedTools(profile, teamId) {
|
|
385
|
+
const base = this.filterAllowedTools(profile.allowedTools, this.ctx.phase, profile.toolInheritance);
|
|
386
|
+
// Apply disallowedTools (denylist) — subtract from resolved tools
|
|
387
|
+
let resolved = base;
|
|
388
|
+
if (resolved !== undefined && profile.disallowedTools && profile.disallowedTools.length > 0) {
|
|
389
|
+
const denied = new Set(profile.disallowedTools);
|
|
390
|
+
resolved = resolved.filter((name) => !denied.has(name));
|
|
391
|
+
}
|
|
392
|
+
if (!teamId)
|
|
393
|
+
return resolved;
|
|
394
|
+
// When a teamId is present, add agent_team to the allowed tools
|
|
395
|
+
if (resolved === undefined)
|
|
396
|
+
return undefined; // Inherited all tools — agent_team already available
|
|
397
|
+
return [...new Set([...resolved, 'agent_team'])];
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Resolve the LLM for a sub-agent based on profile.model.
|
|
401
|
+
* 'inherit' or undefined → use parent LLM.
|
|
402
|
+
* Other values → use llmFactory to create a model-specific LLM.
|
|
403
|
+
*/
|
|
404
|
+
resolveLlm(profile, parentLlm) {
|
|
405
|
+
const model = profile.model;
|
|
406
|
+
// Try llmFactory first for all models (including 'inherit').
|
|
407
|
+
// This allows test harnesses to provide isolated LLMs for sub-agents.
|
|
408
|
+
// In production, factories typically return undefined for 'inherit',
|
|
409
|
+
// so the fallback to parentLlm is preserved.
|
|
410
|
+
if (this.deps.llmFactory) {
|
|
411
|
+
const modelLlm = this.deps.llmFactory(model ?? 'inherit');
|
|
412
|
+
if (modelLlm) {
|
|
413
|
+
getLogger().debug(`[SubAgentManager] Using llmFactory LLM for profile "${profile.id}" (model="${model ?? 'inherit'}")`);
|
|
414
|
+
return modelLlm;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (!model || model === 'inherit') {
|
|
418
|
+
return parentLlm;
|
|
419
|
+
}
|
|
420
|
+
if (!this.deps.llmFactory) {
|
|
421
|
+
getLogger().warn(`[SubAgentManager] Profile "${profile.id}" requests model "${model}" but no llmFactory configured. Falling back to parent LLM.`);
|
|
422
|
+
return parentLlm;
|
|
423
|
+
}
|
|
424
|
+
const modelLlm = this.deps.llmFactory(model);
|
|
425
|
+
if (!modelLlm) {
|
|
426
|
+
getLogger().warn(`[SubAgentManager] llmFactory returned no LLM for model "${model}". Falling back to parent LLM.`);
|
|
427
|
+
return parentLlm;
|
|
428
|
+
}
|
|
429
|
+
getLogger().debug(`[SubAgentManager] Using model "${model}" for profile "${profile.id}"`);
|
|
430
|
+
return modelLlm;
|
|
431
|
+
}
|
|
212
432
|
async setupIsolatedEnvironment(request, llm, agentId, effectiveDryRun) {
|
|
213
433
|
if (isReadOnlySubAgentContext({
|
|
214
434
|
flowMode: this.ctx.flowMode,
|
|
@@ -251,7 +471,7 @@ export class SubAgentManager {
|
|
|
251
471
|
await env.teardown();
|
|
252
472
|
}
|
|
253
473
|
catch (teardownError) {
|
|
254
|
-
getLogger().warn(`[SubAgentManager] Failed to teardown isolated environment after setup error: ${
|
|
474
|
+
getLogger().warn(`[SubAgentManager] Failed to teardown isolated environment after setup error: ${errorMessage(teardownError)}`);
|
|
255
475
|
}
|
|
256
476
|
throw error;
|
|
257
477
|
}
|
|
@@ -275,11 +495,20 @@ export class SubAgentManager {
|
|
|
275
495
|
return {
|
|
276
496
|
...rest,
|
|
277
497
|
auditPath: auditArtifact?.handle ?? rest.auditPath,
|
|
278
|
-
auditArtifact: auditArtifact
|
|
498
|
+
auditArtifact: auditArtifact,
|
|
279
499
|
patchArtifact: saved,
|
|
280
500
|
};
|
|
281
501
|
}
|
|
282
|
-
filterAllowedTools(allowed, phase) {
|
|
502
|
+
filterAllowedTools(allowed, phase, toolInheritance) {
|
|
503
|
+
const readOnlyPhase = isReadOnlySubAgentContext({
|
|
504
|
+
flowMode: this.ctx.flowMode,
|
|
505
|
+
phase,
|
|
506
|
+
});
|
|
507
|
+
// When toolInheritance is 'safe' or 'all' in non-read-only phase,
|
|
508
|
+
// return undefined to skip allowlist filtering (inherits parent toolstack)
|
|
509
|
+
if (!readOnlyPhase && toolInheritance && toolInheritance !== 'none') {
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
283
512
|
const safeReadOnlyTools = new Set([
|
|
284
513
|
'agent_dispatch',
|
|
285
514
|
'code.search',
|
|
@@ -290,10 +519,6 @@ export class SubAgentManager {
|
|
|
290
519
|
'artifact.read',
|
|
291
520
|
]);
|
|
292
521
|
const readOnlyPlanTools = new Set(['plan.init', 'plan.read', 'plan.update']);
|
|
293
|
-
const readOnlyPhase = isReadOnlySubAgentContext({
|
|
294
|
-
flowMode: this.ctx.flowMode,
|
|
295
|
-
phase,
|
|
296
|
-
});
|
|
297
522
|
if (!readOnlyPhase) {
|
|
298
523
|
return allowed;
|
|
299
524
|
}
|
|
@@ -337,7 +562,8 @@ export class SubAgentManager {
|
|
|
337
562
|
fileExt: 'json',
|
|
338
563
|
});
|
|
339
564
|
}
|
|
340
|
-
catch {
|
|
565
|
+
catch (error) {
|
|
566
|
+
getLogger().debug(`[SubAgentManager] Failed to persist audit artifact: ${error instanceof Error ? error.message : String(error)}`);
|
|
341
567
|
return undefined;
|
|
342
568
|
}
|
|
343
569
|
}
|
|
@@ -7,9 +7,12 @@ const DEFAULT_SUB_AGENT_PROFILES = [
|
|
|
7
7
|
allowedTools: ['code.search', 'fs.read', 'git.status', 'git.cat', 'code.ast'],
|
|
8
8
|
readOnly: true,
|
|
9
9
|
stratagem: 'investigator',
|
|
10
|
+
toolInheritance: 'safe',
|
|
10
11
|
maxTokens: 50000,
|
|
11
12
|
maxAttempts: 3,
|
|
12
13
|
timeoutMs: 60_000,
|
|
14
|
+
maxTurns: 20,
|
|
15
|
+
model: 'inherit',
|
|
13
16
|
},
|
|
14
17
|
{
|
|
15
18
|
id: 'surgeon',
|
|
@@ -19,9 +22,12 @@ const DEFAULT_SUB_AGENT_PROFILES = [
|
|
|
19
22
|
allowedTools: ['code.search', 'fs.read', 'test.run', 'code.ast'],
|
|
20
23
|
readOnly: false,
|
|
21
24
|
stratagem: 'surgeon',
|
|
25
|
+
toolInheritance: 'none',
|
|
22
26
|
maxTokens: 100000,
|
|
23
27
|
maxAttempts: 5,
|
|
24
28
|
timeoutMs: 180_000,
|
|
29
|
+
maxTurns: 50,
|
|
30
|
+
model: 'inherit',
|
|
25
31
|
},
|
|
26
32
|
{
|
|
27
33
|
id: 'reviewer',
|
|
@@ -31,9 +37,12 @@ const DEFAULT_SUB_AGENT_PROFILES = [
|
|
|
31
37
|
allowedTools: ['code.search', 'fs.read', 'code.ast'],
|
|
32
38
|
readOnly: true,
|
|
33
39
|
stratagem: 'investigator',
|
|
40
|
+
toolInheritance: 'safe',
|
|
34
41
|
maxTokens: 30000,
|
|
35
42
|
maxAttempts: 2,
|
|
36
43
|
timeoutMs: 60_000,
|
|
44
|
+
maxTurns: 15,
|
|
45
|
+
model: 'inherit',
|
|
37
46
|
},
|
|
38
47
|
{
|
|
39
48
|
id: 'cleaner',
|
|
@@ -43,9 +52,12 @@ const DEFAULT_SUB_AGENT_PROFILES = [
|
|
|
43
52
|
allowedTools: ['fs.read', 'test.run', 'code.search'],
|
|
44
53
|
readOnly: false,
|
|
45
54
|
stratagem: 'surgeon',
|
|
55
|
+
toolInheritance: 'none',
|
|
46
56
|
maxTokens: 50000,
|
|
47
57
|
maxAttempts: 3,
|
|
48
58
|
timeoutMs: 120_000,
|
|
59
|
+
maxTurns: 30,
|
|
60
|
+
model: 'inherit',
|
|
49
61
|
},
|
|
50
62
|
];
|
|
51
63
|
export function registerDefaultSubAgentProfiles(registry) {
|
|
@@ -3,12 +3,20 @@ export class SubAgentRegistry {
|
|
|
3
3
|
register(profile) {
|
|
4
4
|
this.profiles.set(profile.id, profile);
|
|
5
5
|
}
|
|
6
|
+
registerMany(profiles) {
|
|
7
|
+
for (const profile of profiles) {
|
|
8
|
+
this.profiles.set(profile.id, profile);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
6
11
|
get(id) {
|
|
7
12
|
return this.profiles.get(id);
|
|
8
13
|
}
|
|
9
14
|
getAll() {
|
|
10
15
|
return Array.from(this.profiles.values());
|
|
11
16
|
}
|
|
17
|
+
has(id) {
|
|
18
|
+
return this.profiles.has(id);
|
|
19
|
+
}
|
|
12
20
|
clear() {
|
|
13
21
|
this.profiles.clear();
|
|
14
22
|
}
|