@yuaone/core 0.9.30 → 0.9.31
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/agent-coordinator.d.ts +8 -2
- package/dist/agent-coordinator.d.ts.map +1 -1
- package/dist/agent-coordinator.js +74 -25
- package/dist/agent-coordinator.js.map +1 -1
- package/dist/agent-loop.d.ts +12 -0
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +254 -11
- package/dist/agent-loop.js.map +1 -1
- package/dist/background-agent.d.ts.map +1 -1
- package/dist/background-agent.js +6 -2
- package/dist/background-agent.js.map +1 -1
- package/dist/context-manager.d.ts.map +1 -1
- package/dist/context-manager.js +33 -3
- package/dist/context-manager.js.map +1 -1
- package/dist/llm-client.d.ts +6 -0
- package/dist/llm-client.d.ts.map +1 -1
- package/dist/llm-client.js +31 -2
- package/dist/llm-client.js.map +1 -1
- package/dist/sub-agent.d.ts.map +1 -1
- package/dist/sub-agent.js +22 -2
- package/dist/sub-agent.js.map +1 -1
- package/package.json +1 -1
package/dist/agent-loop.js
CHANGED
|
@@ -47,6 +47,7 @@ import { SelfDebugLoop } from "./self-debug-loop.js";
|
|
|
47
47
|
import { SkillLearner } from "./skill-learner.js";
|
|
48
48
|
import { RepoKnowledgeGraph } from "./repo-knowledge-graph.js";
|
|
49
49
|
import { BackgroundAgentManager } from "./background-agent.js";
|
|
50
|
+
import { SubAgent } from "./sub-agent.js";
|
|
50
51
|
import { ReasoningAggregator } from "./reasoning-aggregator.js";
|
|
51
52
|
import { ReasoningTree } from "./reasoning-tree.js";
|
|
52
53
|
import { ContextCompressor } from "./context-compressor.js";
|
|
@@ -112,6 +113,52 @@ import { dlog, dlogSep } from "./debug-logger.js";
|
|
|
112
113
|
*/
|
|
113
114
|
/** Minimum confidence for classification-based hints/routing to activate */
|
|
114
115
|
const CLASSIFICATION_CONFIDENCE_THRESHOLD = 0.6;
|
|
116
|
+
/** spawn_sub_agent tool definition — allows the LLM to delegate tasks to sub-agents */
|
|
117
|
+
const SPAWN_SUB_AGENT_TOOL = {
|
|
118
|
+
name: "spawn_sub_agent",
|
|
119
|
+
description: "Spawn a sub-agent to work on a delegated task in parallel. " +
|
|
120
|
+
"The sub-agent runs an independent agent loop with its own tool access, " +
|
|
121
|
+
"inheriting the same LLM configuration. Use this for tasks that can be " +
|
|
122
|
+
"broken down (e.g., implement one module while you work on another, " +
|
|
123
|
+
"run a review pass, verify changes).",
|
|
124
|
+
parameters: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
goal: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "The specific task goal for the sub-agent to accomplish.",
|
|
130
|
+
},
|
|
131
|
+
role: {
|
|
132
|
+
type: "string",
|
|
133
|
+
enum: ["coder", "critic", "verifier", "specialist"],
|
|
134
|
+
description: "Sub-agent role. 'coder' writes code, 'critic' reviews for issues, " +
|
|
135
|
+
"'verifier' checks correctness, 'specialist' handles domain tasks. " +
|
|
136
|
+
"Defaults to 'coder'.",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
required: ["goal"],
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
},
|
|
142
|
+
source: "builtin",
|
|
143
|
+
readOnly: false,
|
|
144
|
+
requiresApproval: false,
|
|
145
|
+
riskLevel: "medium",
|
|
146
|
+
};
|
|
147
|
+
/** Map user-facing sub-agent roles to internal SubAgentRole */
|
|
148
|
+
function mapToSubAgentRole(role) {
|
|
149
|
+
switch (role) {
|
|
150
|
+
case "critic":
|
|
151
|
+
return "reviewer";
|
|
152
|
+
case "verifier":
|
|
153
|
+
return "tester";
|
|
154
|
+
case "specialist":
|
|
155
|
+
return "coder";
|
|
156
|
+
case "coder":
|
|
157
|
+
return "coder";
|
|
158
|
+
default:
|
|
159
|
+
return "coder";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
115
162
|
export class AgentLoop extends EventEmitter {
|
|
116
163
|
abortSignal;
|
|
117
164
|
llmClient;
|
|
@@ -140,6 +187,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
140
187
|
changedFiles = [];
|
|
141
188
|
aborted = false;
|
|
142
189
|
initialized = false;
|
|
190
|
+
partialInit = false;
|
|
143
191
|
continuationEngine = null;
|
|
144
192
|
mcpClient = null;
|
|
145
193
|
mcpToolDefinitions = [];
|
|
@@ -202,6 +250,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
202
250
|
iterationTsFilesModified = [];
|
|
203
251
|
/** Task 3: Whether tsc was run in the previous iteration (skip cooldown) */
|
|
204
252
|
tscRanLastIteration = false;
|
|
253
|
+
/** Tracks whether the last tool call in the most recent iteration failed */
|
|
254
|
+
_lastToolFailed = false;
|
|
255
|
+
/** Per-tool-call retry counter: tool_call_id → retry count (max 2) */
|
|
256
|
+
_toolRetryCount = new Map();
|
|
257
|
+
/** Count of consecutive iterations where LLM returned text-only (no tool calls, no task_complete) */
|
|
258
|
+
_consecutiveTextOnlyIterations = 0;
|
|
205
259
|
// ─── OverheadGovernor — subsystem execution policy ──────────────────────
|
|
206
260
|
overheadGovernor;
|
|
207
261
|
/** Writes since last verify ran (for QA/quickVerify trigger) */
|
|
@@ -399,7 +453,10 @@ export class AgentLoop extends EventEmitter {
|
|
|
399
453
|
async init() {
|
|
400
454
|
if (this.initialized)
|
|
401
455
|
return;
|
|
402
|
-
|
|
456
|
+
// Guard against concurrent re-entry while init is already running
|
|
457
|
+
if (this.partialInit)
|
|
458
|
+
return;
|
|
459
|
+
this.partialInit = true;
|
|
403
460
|
// Task 1: Initialize ContextBudgetManager with the total token budget
|
|
404
461
|
this.contextBudgetManager = new ContextBudgetManager({
|
|
405
462
|
totalBudget: this.config.loop.totalTokenBudget,
|
|
@@ -625,7 +682,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
625
682
|
const enhancedPrompt = buildSystemPrompt({
|
|
626
683
|
projectStructure,
|
|
627
684
|
yuanMdContent,
|
|
628
|
-
tools: [...this.config.loop.tools, ...this.mcpToolDefinitions],
|
|
685
|
+
tools: [...this.config.loop.tools, ...this.mcpToolDefinitions, SPAWN_SUB_AGENT_TOOL],
|
|
629
686
|
projectPath,
|
|
630
687
|
environment: this.environment,
|
|
631
688
|
});
|
|
@@ -879,6 +936,10 @@ export class AgentLoop extends EventEmitter {
|
|
|
879
936
|
this.continuousReflection.on("reflection:context_overflow", () => {
|
|
880
937
|
void this.handleSoftContextOverflow();
|
|
881
938
|
});
|
|
939
|
+
// Mark fully initialized — partialInit is cleared so timeout-interrupted
|
|
940
|
+
// runs can detect that init completed (partialInit=false + initialized=true).
|
|
941
|
+
this.initialized = true;
|
|
942
|
+
this.partialInit = false;
|
|
882
943
|
}
|
|
883
944
|
/**
|
|
884
945
|
* MemoryManager의 학습/실패 기록을 시스템 메시지로 변환.
|
|
@@ -944,6 +1005,9 @@ export class AgentLoop extends EventEmitter {
|
|
|
944
1005
|
// Task 1: reset context summarization guard per run
|
|
945
1006
|
this._contextSummarizationDone = false;
|
|
946
1007
|
this._unfulfilledContinuations = 0;
|
|
1008
|
+
this._lastToolFailed = false;
|
|
1009
|
+
this._toolRetryCount.clear();
|
|
1010
|
+
this._consecutiveTextOnlyIterations = 0;
|
|
947
1011
|
}
|
|
948
1012
|
// Reset resumedFromSession flag after consuming it — prevents stale state
|
|
949
1013
|
// from leaking into subsequent runs when the same AgentLoop instance is reused.
|
|
@@ -957,11 +1021,15 @@ export class AgentLoop extends EventEmitter {
|
|
|
957
1021
|
dlog("AGENT-LOOP", `emitting agent:start, sessionId=${this.sessionId}`);
|
|
958
1022
|
this.emitEvent({ kind: "agent:start", goal: userMessage });
|
|
959
1023
|
// 첫 실행 시 메모리/프로젝트 컨텍스트 자동 로드
|
|
960
|
-
// init은 최대
|
|
1024
|
+
// init은 최대 5초만 블로킹 — Ollama/analyzeProject 등 느린 작업은 백그라운드 계속
|
|
961
1025
|
await Promise.race([
|
|
962
1026
|
this.init(),
|
|
963
|
-
new Promise(resolve => setTimeout(resolve,
|
|
1027
|
+
new Promise(resolve => setTimeout(resolve, 5_000)),
|
|
964
1028
|
]);
|
|
1029
|
+
// If init timed out (partialInit still true), allow retry on next run
|
|
1030
|
+
if (this.partialInit && !this.initialized) {
|
|
1031
|
+
this.partialInit = false;
|
|
1032
|
+
}
|
|
965
1033
|
// Always generate a fresh sessionId per run — prevents BudgetGovernorV2
|
|
966
1034
|
// from accumulating exhausted task budget across multiple runs on the same instance.
|
|
967
1035
|
this.sessionId = randomUUID();
|
|
@@ -2337,16 +2405,20 @@ export class AgentLoop extends EventEmitter {
|
|
|
2337
2405
|
if (response.toolCalls.length === 0) {
|
|
2338
2406
|
const content = response.content ?? "";
|
|
2339
2407
|
let finalSummary = content || "Task completed.";
|
|
2408
|
+
// Track consecutive text-only iterations
|
|
2409
|
+
this._consecutiveTextOnlyIterations++;
|
|
2340
2410
|
// If LLM outputs text with no tool calls and hasn't signalled completion via task_complete,
|
|
2341
|
-
// remind it
|
|
2342
|
-
|
|
2343
|
-
|
|
2411
|
+
// remind it to use tools or call task_complete.
|
|
2412
|
+
// Nudge on: first 2 unfulfilled continuations, OR every 2 consecutive text-only iterations.
|
|
2413
|
+
if (this._unfulfilledContinuations < 2 || (this._consecutiveTextOnlyIterations >= 2 && this._consecutiveTextOnlyIterations % 2 === 0)) {
|
|
2414
|
+
if (this._unfulfilledContinuations < 2)
|
|
2415
|
+
this._unfulfilledContinuations++;
|
|
2344
2416
|
this.contextManager.addMessage({ role: "assistant", content });
|
|
2345
2417
|
this.contextManager.addMessage({
|
|
2346
2418
|
role: "user",
|
|
2347
|
-
content: "
|
|
2419
|
+
content: "Continue working. Use tools to make progress, or call task_complete if done.",
|
|
2348
2420
|
});
|
|
2349
|
-
this.emitEvent({ kind: "agent:thinking", content: `[nudge ${this._unfulfilledContinuations}/2] reminding LLM to use tools or call task_complete` });
|
|
2421
|
+
this.emitEvent({ kind: "agent:thinking", content: `[nudge ${this._unfulfilledContinuations}/2, text-only: ${this._consecutiveTextOnlyIterations}] reminding LLM to use tools or call task_complete` });
|
|
2350
2422
|
continue;
|
|
2351
2423
|
}
|
|
2352
2424
|
// Do NOT reset _unfulfilledContinuations here — counter stays spent to prevent infinite nudge loop
|
|
@@ -2529,6 +2601,23 @@ export class AgentLoop extends EventEmitter {
|
|
|
2529
2601
|
if (taskCompleteCall) {
|
|
2530
2602
|
const callArgs = this.parseToolArgs(taskCompleteCall.arguments);
|
|
2531
2603
|
const taskCompleteSummary = String(callArgs["summary"] ?? response.content ?? "Task completed.");
|
|
2604
|
+
// Stale GOAL_ACHIEVED prevention: if the last tool call failed, don't accept
|
|
2605
|
+
// completion — nudge the LLM to fix the issue first.
|
|
2606
|
+
if (this._lastToolFailed) {
|
|
2607
|
+
this._lastToolFailed = false; // reset so it doesn't block forever
|
|
2608
|
+
const nonProtocolCalls = response.toolCalls.filter((tc) => tc.name !== "task_complete");
|
|
2609
|
+
this.contextManager.addMessage({
|
|
2610
|
+
role: "assistant",
|
|
2611
|
+
content: response.content,
|
|
2612
|
+
tool_calls: nonProtocolCalls.length > 0 ? nonProtocolCalls : undefined,
|
|
2613
|
+
});
|
|
2614
|
+
this.contextManager.addMessage({
|
|
2615
|
+
role: "user",
|
|
2616
|
+
content: "The last tool call failed. Fix the issue before completing the task.",
|
|
2617
|
+
});
|
|
2618
|
+
this.emitEvent({ kind: "agent:thinking", content: "[stale-completion guard] last tool failed, rejecting task_complete" });
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2532
2621
|
// Save assistant message to context — filter task_complete from tool_calls.
|
|
2533
2622
|
// task_complete is an internal protocol signal; including it in LLM history without
|
|
2534
2623
|
// a matching role:"tool" result causes API 400 on the next turn.
|
|
@@ -2578,6 +2667,23 @@ export class AgentLoop extends EventEmitter {
|
|
|
2578
2667
|
const { results: toolResults, deferredFixPrompts } = await this.executeTools(response.toolCalls);
|
|
2579
2668
|
// Reflexion: 도구 결과 수집
|
|
2580
2669
|
this.allToolResults.push(...toolResults);
|
|
2670
|
+
// Track whether any tool call failed this iteration (for stale GOAL_ACHIEVED prevention)
|
|
2671
|
+
const failedResults = toolResults.filter(r => !r.success);
|
|
2672
|
+
this._lastToolFailed = failedResults.length > 0;
|
|
2673
|
+
// Tool calls were made — reset consecutive text-only counter
|
|
2674
|
+
this._consecutiveTextOnlyIterations = 0;
|
|
2675
|
+
// Tool call failure auto-retry: inject error as user message so LLM can self-correct
|
|
2676
|
+
// (max 2 retries per tool_call_id to prevent infinite retry loops)
|
|
2677
|
+
for (const failed of failedResults) {
|
|
2678
|
+
const retryKey = failed.tool_call_id ?? failed.name;
|
|
2679
|
+
const retryCount = this._toolRetryCount.get(retryKey) ?? 0;
|
|
2680
|
+
if (retryCount < 2) {
|
|
2681
|
+
this._toolRetryCount.set(retryKey, retryCount + 1);
|
|
2682
|
+
// The error is already in the tool result message added below;
|
|
2683
|
+
// no extra injection needed — the LLM will see the failure and retry naturally.
|
|
2684
|
+
dlog("AGENT-LOOP", `tool failure auto-retry eligible`, { tool: failed.name, retryCount: retryCount + 1, maxRetries: 2 });
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2581
2687
|
// Phase 6: Record tool outcomes in capability graph
|
|
2582
2688
|
for (const tr of toolResults) {
|
|
2583
2689
|
recordToolOutcomeInGraph(this.capabilityGraph, tr.name, tr.success);
|
|
@@ -3087,7 +3193,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
3087
3193
|
const toolCalls = [];
|
|
3088
3194
|
let usage = { input: 0, output: 0 };
|
|
3089
3195
|
let finishReason = "stop";
|
|
3090
|
-
const allTools = [...this.config.loop.tools, ...this.mcpToolDefinitions];
|
|
3196
|
+
const allTools = [...this.config.loop.tools, ...this.mcpToolDefinitions, SPAWN_SUB_AGENT_TOOL];
|
|
3091
3197
|
const stream = this.llmClient.chatStream(messages, allTools, this.abortSignal ?? undefined);
|
|
3092
3198
|
// 텍스트 버퍼링 — 1토큰씩 emit하지 않고 청크 단위로 모아서 emit
|
|
3093
3199
|
let textBuffer = "";
|
|
@@ -3223,7 +3329,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
3223
3329
|
*/
|
|
3224
3330
|
async executeSingleTool(toolCall, toolCalls) {
|
|
3225
3331
|
const args = this.parseToolArgs(toolCall.arguments);
|
|
3226
|
-
const allDefinitions = [...this.config.loop.tools, ...this.mcpToolDefinitions];
|
|
3332
|
+
const allDefinitions = [...this.config.loop.tools, ...this.mcpToolDefinitions, SPAWN_SUB_AGENT_TOOL];
|
|
3227
3333
|
const matchedDefinition = allDefinitions.find((t) => t.name === toolCall.name);
|
|
3228
3334
|
// Governor: 안전성 검증
|
|
3229
3335
|
try {
|
|
@@ -3293,6 +3399,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
3293
3399
|
return { result: pluginApprovalResult, deferredFixPrompt: null };
|
|
3294
3400
|
}
|
|
3295
3401
|
}
|
|
3402
|
+
// spawn_sub_agent 도구 호출 — SubAgent로 위임
|
|
3403
|
+
if (toolCall.name === "spawn_sub_agent") {
|
|
3404
|
+
const subAgentResult = await this.executeSpawnSubAgent(toolCall, args);
|
|
3405
|
+
return { result: subAgentResult, deferredFixPrompt: null };
|
|
3406
|
+
}
|
|
3296
3407
|
// MCP 도구 호출 확인
|
|
3297
3408
|
if (this.mcpClient && this.isMCPTool(toolCall.name)) {
|
|
3298
3409
|
// Emit tool_start before execution (required for trace, QA pipeline, replay)
|
|
@@ -3678,6 +3789,138 @@ export class AgentLoop extends EventEmitter {
|
|
|
3678
3789
|
}
|
|
3679
3790
|
return { results, deferredFixPrompts };
|
|
3680
3791
|
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Execute spawn_sub_agent tool — creates a SubAgent, runs it, returns result.
|
|
3794
|
+
* The sub-agent inherits the parent's LLM config (provider, model, apiKey).
|
|
3795
|
+
*/
|
|
3796
|
+
async executeSpawnSubAgent(toolCall, args) {
|
|
3797
|
+
const startTime = Date.now();
|
|
3798
|
+
const goal = String(args.goal ?? "");
|
|
3799
|
+
const roleInput = typeof args.role === "string" ? args.role : "coder";
|
|
3800
|
+
const role = mapToSubAgentRole(roleInput);
|
|
3801
|
+
if (!goal) {
|
|
3802
|
+
return {
|
|
3803
|
+
tool_call_id: toolCall.id,
|
|
3804
|
+
name: toolCall.name,
|
|
3805
|
+
output: "Error: 'goal' parameter is required for spawn_sub_agent.",
|
|
3806
|
+
success: false,
|
|
3807
|
+
durationMs: Date.now() - startTime,
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3810
|
+
// Emit tool_start
|
|
3811
|
+
this.emitEvent({
|
|
3812
|
+
kind: "agent:tool_start",
|
|
3813
|
+
tool: toolCall.name,
|
|
3814
|
+
input: args,
|
|
3815
|
+
source: "builtin",
|
|
3816
|
+
});
|
|
3817
|
+
const taskId = `sub-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
3818
|
+
const projectPath = this.config.loop.projectPath || process.cwd();
|
|
3819
|
+
try {
|
|
3820
|
+
const subAgent = new SubAgent({
|
|
3821
|
+
taskId,
|
|
3822
|
+
goal,
|
|
3823
|
+
targetFiles: [], // sub-agent discovers files via tools
|
|
3824
|
+
readFiles: [],
|
|
3825
|
+
maxIterations: Math.min(this.config.loop.maxIterations, 15),
|
|
3826
|
+
projectPath,
|
|
3827
|
+
byokConfig: this.config.byok,
|
|
3828
|
+
tools: this.config.loop.tools.map((t) => t.name),
|
|
3829
|
+
createToolExecutor: (_workDir, _enabledTools) => this.toolExecutor,
|
|
3830
|
+
role,
|
|
3831
|
+
});
|
|
3832
|
+
// Forward sub-agent events as bg_update for TUI visibility
|
|
3833
|
+
subAgent.on("subagent:phase", (tid, phase) => {
|
|
3834
|
+
this.emitEvent({
|
|
3835
|
+
kind: "agent:bg_update",
|
|
3836
|
+
agentId: taskId,
|
|
3837
|
+
agentLabel: `sub-agent (${roleInput})`,
|
|
3838
|
+
eventType: "info",
|
|
3839
|
+
message: `Phase: ${phase}`,
|
|
3840
|
+
timestamp: Date.now(),
|
|
3841
|
+
});
|
|
3842
|
+
});
|
|
3843
|
+
subAgent.on("event", (event) => {
|
|
3844
|
+
// Re-emit sub-agent text/thinking for observability
|
|
3845
|
+
if (event.kind === "agent:text_delta") {
|
|
3846
|
+
this.emitEvent({
|
|
3847
|
+
kind: "agent:bg_update",
|
|
3848
|
+
agentId: taskId,
|
|
3849
|
+
agentLabel: `sub-agent (${roleInput})`,
|
|
3850
|
+
eventType: "info",
|
|
3851
|
+
message: String(event.text ?? ""),
|
|
3852
|
+
timestamp: Date.now(),
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
else if (event.kind === "agent:thinking") {
|
|
3856
|
+
this.emitEvent({
|
|
3857
|
+
kind: "agent:bg_update",
|
|
3858
|
+
agentId: taskId,
|
|
3859
|
+
agentLabel: `sub-agent (${roleInput})`,
|
|
3860
|
+
eventType: "info",
|
|
3861
|
+
message: String(event.content ?? ""),
|
|
3862
|
+
timestamp: Date.now(),
|
|
3863
|
+
});
|
|
3864
|
+
}
|
|
3865
|
+
});
|
|
3866
|
+
// Build DAG context for the sub-agent
|
|
3867
|
+
const dagContext = {
|
|
3868
|
+
overallGoal: goal,
|
|
3869
|
+
totalTasks: 1,
|
|
3870
|
+
completedTasks: [],
|
|
3871
|
+
runningTasks: [taskId],
|
|
3872
|
+
};
|
|
3873
|
+
// Run sub-agent
|
|
3874
|
+
const result = await subAgent.run(dagContext);
|
|
3875
|
+
// Build output summary
|
|
3876
|
+
const changedFilesList = result.changedFiles.length > 0
|
|
3877
|
+
? result.changedFiles.map((f) => f.path).join(", ")
|
|
3878
|
+
: "none";
|
|
3879
|
+
const output = [
|
|
3880
|
+
`## Sub-Agent Result (${roleInput})`,
|
|
3881
|
+
`- Task ID: ${taskId}`,
|
|
3882
|
+
`- Success: ${result.success}`,
|
|
3883
|
+
`- Iterations: ${result.iterations}`,
|
|
3884
|
+
`- Tokens: input=${result.tokensUsed.input}, output=${result.tokensUsed.output}`,
|
|
3885
|
+
`- Changed files: ${changedFilesList}`,
|
|
3886
|
+
``,
|
|
3887
|
+
`### Summary`,
|
|
3888
|
+
result.summary,
|
|
3889
|
+
result.error ? `\n### Error\n${result.error}` : "",
|
|
3890
|
+
].join("\n");
|
|
3891
|
+
this.emitEvent({
|
|
3892
|
+
kind: "agent:tool_result",
|
|
3893
|
+
tool: toolCall.name,
|
|
3894
|
+
output: output.length > 200 ? output.slice(0, 200) + "..." : output,
|
|
3895
|
+
durationMs: Date.now() - startTime,
|
|
3896
|
+
});
|
|
3897
|
+
return {
|
|
3898
|
+
tool_call_id: toolCall.id,
|
|
3899
|
+
name: toolCall.name,
|
|
3900
|
+
output,
|
|
3901
|
+
success: result.success,
|
|
3902
|
+
durationMs: Date.now() - startTime,
|
|
3903
|
+
};
|
|
3904
|
+
}
|
|
3905
|
+
catch (err) {
|
|
3906
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3907
|
+
this.emitEvent({
|
|
3908
|
+
kind: "agent:bg_update",
|
|
3909
|
+
agentId: taskId,
|
|
3910
|
+
agentLabel: `sub-agent (${roleInput})`,
|
|
3911
|
+
eventType: "error",
|
|
3912
|
+
message: `Sub-agent failed: ${errorMsg}`,
|
|
3913
|
+
timestamp: Date.now(),
|
|
3914
|
+
});
|
|
3915
|
+
return {
|
|
3916
|
+
tool_call_id: toolCall.id,
|
|
3917
|
+
name: toolCall.name,
|
|
3918
|
+
output: `Sub-agent error: ${errorMsg}`,
|
|
3919
|
+
success: false,
|
|
3920
|
+
durationMs: Date.now() - startTime,
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3681
3924
|
/**
|
|
3682
3925
|
* Governor의 ApprovalRequiredError를 ApprovalManager로 처리.
|
|
3683
3926
|
* 승인되면 null 반환 (실행 계속), 거부되면 ToolResult 반환 (실행 차단).
|