kagent-ts 0.1.4 → 0.1.5
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/LICENSE +68 -21
- package/README.md +27 -371
- package/dist/compression/progressive-compressor.d.ts +66 -0
- package/dist/compression/progressive-compressor.d.ts.map +1 -0
- package/dist/compression/progressive-compressor.js +367 -0
- package/dist/compression/progressive-compressor.js.map +1 -0
- package/dist/compression/types.d.ts +1 -5
- package/dist/compression/types.d.ts.map +1 -1
- package/dist/context/context-manager.d.ts +34 -15
- package/dist/context/context-manager.d.ts.map +1 -1
- package/dist/context/context-manager.js +78 -28
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/types.d.ts +20 -4
- package/dist/context/types.d.ts.map +1 -1
- package/dist/core/agent.d.ts +354 -25
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +646 -64
- package/dist/core/agent.js.map +1 -1
- package/dist/core/fusion-agent.d.ts +207 -0
- package/dist/core/fusion-agent.d.ts.map +1 -0
- package/dist/core/fusion-agent.js +769 -0
- package/dist/core/fusion-agent.js.map +1 -0
- package/dist/core/hooks.d.ts +19 -7
- package/dist/core/hooks.d.ts.map +1 -1
- package/dist/core/plan-solve-agent.d.ts +1 -15
- package/dist/core/plan-solve-agent.d.ts.map +1 -1
- package/dist/core/plan-solve-agent.js +142 -117
- package/dist/core/plan-solve-agent.js.map +1 -1
- package/dist/core/react-agent.d.ts +0 -13
- package/dist/core/react-agent.d.ts.map +1 -1
- package/dist/core/react-agent.js +127 -102
- package/dist/core/react-agent.js.map +1 -1
- package/dist/core/response-schema.d.ts +65 -0
- package/dist/core/response-schema.d.ts.map +1 -1
- package/dist/core/response-schema.js +174 -1
- package/dist/core/response-schema.js.map +1 -1
- package/dist/core/system-prompts.d.ts +27 -0
- package/dist/core/system-prompts.d.ts.map +1 -0
- package/dist/core/system-prompts.js +112 -0
- package/dist/core/system-prompts.js.map +1 -0
- package/dist/eval/benchmark.d.ts +81 -0
- package/dist/eval/benchmark.d.ts.map +1 -0
- package/dist/eval/benchmark.js +292 -0
- package/dist/eval/benchmark.js.map +1 -0
- package/dist/eval/eval-runner.d.ts +79 -0
- package/dist/eval/eval-runner.d.ts.map +1 -0
- package/dist/eval/eval-runner.js +252 -0
- package/dist/eval/eval-runner.js.map +1 -0
- package/dist/eval/index.d.ts +7 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +13 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/tool-call-evaluator.d.ts +72 -0
- package/dist/eval/tool-call-evaluator.d.ts.map +1 -0
- package/dist/eval/tool-call-evaluator.js +265 -0
- package/dist/eval/tool-call-evaluator.js.map +1 -0
- package/dist/eval/types.d.ts +219 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +3 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/index.d.ts +58 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -8
- package/dist/index.js.map +1 -1
- package/dist/llm/anthropic-provider.d.ts +141 -0
- package/dist/llm/anthropic-provider.d.ts.map +1 -0
- package/dist/llm/anthropic-provider.js +486 -0
- package/dist/llm/anthropic-provider.js.map +1 -0
- package/dist/llm/errors.d.ts +26 -0
- package/dist/llm/errors.d.ts.map +1 -0
- package/dist/llm/errors.js +19 -0
- package/dist/llm/errors.js.map +1 -0
- package/dist/llm/factory.d.ts +73 -0
- package/dist/llm/factory.d.ts.map +1 -0
- package/dist/llm/factory.js +77 -0
- package/dist/llm/factory.js.map +1 -0
- package/dist/llm/fallback-provider.d.ts +47 -0
- package/dist/llm/fallback-provider.d.ts.map +1 -0
- package/dist/llm/fallback-provider.js +91 -0
- package/dist/llm/fallback-provider.js.map +1 -0
- package/dist/llm/interface.d.ts +54 -11
- package/dist/llm/interface.d.ts.map +1 -1
- package/dist/llm/interface.js +34 -0
- package/dist/llm/interface.js.map +1 -1
- package/dist/llm/model-router.d.ts +126 -0
- package/dist/llm/model-router.d.ts.map +1 -0
- package/dist/llm/model-router.js +178 -0
- package/dist/llm/model-router.js.map +1 -0
- package/dist/llm/openai-provider.d.ts +8 -32
- package/dist/llm/openai-provider.d.ts.map +1 -1
- package/dist/llm/openai-provider.js +27 -60
- package/dist/llm/openai-provider.js.map +1 -1
- package/dist/llm/rate-limiter.d.ts +41 -0
- package/dist/llm/rate-limiter.d.ts.map +1 -0
- package/dist/llm/rate-limiter.js +93 -0
- package/dist/llm/rate-limiter.js.map +1 -0
- package/dist/llm/retry.d.ts +26 -0
- package/dist/llm/retry.d.ts.map +1 -0
- package/dist/llm/retry.js +44 -0
- package/dist/llm/retry.js.map +1 -0
- package/dist/llm/token-budget.d.ts +97 -0
- package/dist/llm/token-budget.d.ts.map +1 -0
- package/dist/llm/token-budget.js +115 -0
- package/dist/llm/token-budget.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +7 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +38 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +34 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/mcp/mcp-client-manager.d.ts +10 -2
- package/dist/mcp/mcp-client-manager.d.ts.map +1 -1
- package/dist/mcp/mcp-client-manager.js +20 -9
- package/dist/mcp/mcp-client-manager.js.map +1 -1
- package/dist/memory/index.d.ts +3 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +6 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +119 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +334 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/messages/types.d.ts +2 -0
- package/dist/messages/types.d.ts.map +1 -1
- package/dist/orchestrator/index.d.ts +5 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +13 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/json-extractor.d.ts +18 -0
- package/dist/orchestrator/json-extractor.d.ts.map +1 -0
- package/dist/orchestrator/json-extractor.js +111 -0
- package/dist/orchestrator/json-extractor.js.map +1 -0
- package/dist/orchestrator/orchestrator-agent.d.ts +152 -0
- package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator-agent.js +675 -0
- package/dist/orchestrator/orchestrator-agent.js.map +1 -0
- package/dist/orchestrator/orchestrator-response.d.ts +40 -0
- package/dist/orchestrator/orchestrator-response.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator-response.js +275 -0
- package/dist/orchestrator/orchestrator-response.js.map +1 -0
- package/dist/orchestrator/orchestrator-types.d.ts +116 -0
- package/dist/orchestrator/orchestrator-types.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator-types.js +3 -0
- package/dist/orchestrator/orchestrator-types.js.map +1 -0
- package/dist/preferences/preference-manager.d.ts +8 -3
- package/dist/preferences/preference-manager.d.ts.map +1 -1
- package/dist/preferences/preference-manager.js +17 -4
- package/dist/preferences/preference-manager.js.map +1 -1
- package/dist/rag/chroma-store.d.ts +52 -0
- package/dist/rag/chroma-store.d.ts.map +1 -0
- package/dist/rag/chroma-store.js +110 -0
- package/dist/rag/chroma-store.js.map +1 -0
- package/dist/rag/document-loader.d.ts +21 -0
- package/dist/rag/document-loader.d.ts.map +1 -0
- package/dist/rag/document-loader.js +129 -0
- package/dist/rag/document-loader.js.map +1 -0
- package/dist/rag/embedding-provider.d.ts +36 -0
- package/dist/rag/embedding-provider.d.ts.map +1 -0
- package/dist/rag/embedding-provider.js +74 -0
- package/dist/rag/embedding-provider.js.map +1 -0
- package/dist/rag/index.d.ts +17 -0
- package/dist/rag/index.d.ts.map +1 -0
- package/dist/rag/index.js +27 -0
- package/dist/rag/index.js.map +1 -0
- package/dist/rag/keyword-index.d.ts +53 -0
- package/dist/rag/keyword-index.d.ts.map +1 -0
- package/dist/rag/keyword-index.js +161 -0
- package/dist/rag/keyword-index.js.map +1 -0
- package/dist/rag/llm-reranker.d.ts +36 -0
- package/dist/rag/llm-reranker.d.ts.map +1 -0
- package/dist/rag/llm-reranker.js +95 -0
- package/dist/rag/llm-reranker.js.map +1 -0
- package/dist/rag/rag-manager.d.ts +54 -0
- package/dist/rag/rag-manager.d.ts.map +1 -0
- package/dist/rag/rag-manager.js +179 -0
- package/dist/rag/rag-manager.js.map +1 -0
- package/dist/rag/rag-types.d.ts +143 -0
- package/dist/rag/rag-types.d.ts.map +1 -0
- package/dist/rag/rag-types.js +9 -0
- package/dist/rag/rag-types.js.map +1 -0
- package/dist/rag/rrf.d.ts +47 -0
- package/dist/rag/rrf.d.ts.map +1 -0
- package/dist/rag/rrf.js +70 -0
- package/dist/rag/rrf.js.map +1 -0
- package/dist/rag/search-knowledge.d.ts +24 -0
- package/dist/rag/search-knowledge.d.ts.map +1 -0
- package/dist/rag/search-knowledge.js +86 -0
- package/dist/rag/search-knowledge.js.map +1 -0
- package/dist/rag/text-splitter.d.ts +25 -0
- package/dist/rag/text-splitter.d.ts.map +1 -0
- package/dist/rag/text-splitter.js +136 -0
- package/dist/rag/text-splitter.js.map +1 -0
- package/dist/rag/vector-store.d.ts +34 -0
- package/dist/rag/vector-store.d.ts.map +1 -0
- package/dist/rag/vector-store.js +73 -0
- package/dist/rag/vector-store.js.map +1 -0
- package/dist/reflection/error-notebook.d.ts +125 -0
- package/dist/reflection/error-notebook.d.ts.map +1 -0
- package/dist/reflection/error-notebook.js +368 -0
- package/dist/reflection/error-notebook.js.map +1 -0
- package/dist/reflection/index.d.ts +8 -0
- package/dist/reflection/index.d.ts.map +1 -0
- package/dist/reflection/index.js +12 -0
- package/dist/reflection/index.js.map +1 -0
- package/dist/reflection/memory-reflector.d.ts +97 -0
- package/dist/reflection/memory-reflector.d.ts.map +1 -0
- package/dist/reflection/memory-reflector.js +215 -0
- package/dist/reflection/memory-reflector.js.map +1 -0
- package/dist/reflection/reflection-agent.d.ts +105 -0
- package/dist/reflection/reflection-agent.d.ts.map +1 -0
- package/dist/reflection/reflection-agent.js +234 -0
- package/dist/reflection/reflection-agent.js.map +1 -0
- package/dist/reflection/reflection-hook.d.ts +50 -0
- package/dist/reflection/reflection-hook.d.ts.map +1 -0
- package/dist/reflection/reflection-hook.js +108 -0
- package/dist/reflection/reflection-hook.js.map +1 -0
- package/dist/rules/project-rules.d.ts +47 -0
- package/dist/rules/project-rules.d.ts.map +1 -0
- package/dist/rules/project-rules.js +166 -0
- package/dist/rules/project-rules.js.map +1 -0
- package/dist/security/boundaries.d.ts +81 -0
- package/dist/security/boundaries.d.ts.map +1 -0
- package/dist/security/boundaries.js +158 -0
- package/dist/security/boundaries.js.map +1 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +11 -0
- package/dist/security/index.js.map +1 -0
- package/dist/session/session-types.d.ts +25 -4
- package/dist/session/session-types.d.ts.map +1 -1
- package/dist/skills/file-skill-loader.d.ts +4 -6
- package/dist/skills/file-skill-loader.d.ts.map +1 -1
- package/dist/skills/file-skill-loader.js +8 -19
- package/dist/skills/file-skill-loader.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -2
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/skill-manager.d.ts +18 -8
- package/dist/skills/skill-manager.d.ts.map +1 -1
- package/dist/skills/skill-manager.js +58 -36
- package/dist/skills/skill-manager.js.map +1 -1
- package/dist/skills/types.d.ts +3 -8
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/subagent/index.d.ts +4 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +8 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/subagent-loader.d.ts +53 -0
- package/dist/subagent/subagent-loader.d.ts.map +1 -0
- package/dist/subagent/subagent-loader.js +155 -0
- package/dist/subagent/subagent-loader.js.map +1 -0
- package/dist/subagent/subagent-manager.d.ts +161 -0
- package/dist/subagent/subagent-manager.d.ts.map +1 -0
- package/dist/subagent/subagent-manager.js +468 -0
- package/dist/subagent/subagent-manager.js.map +1 -0
- package/dist/subagent/subagent-types.d.ts +77 -0
- package/dist/subagent/subagent-types.d.ts.map +1 -0
- package/dist/subagent/subagent-types.js +3 -0
- package/dist/subagent/subagent-types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +3 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +87 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit-file.d.ts.map +1 -1
- package/dist/tools/builtin/edit-file.js +1 -0
- package/dist/tools/builtin/edit-file.js.map +1 -1
- package/dist/tools/builtin/index.d.ts +14 -0
- package/dist/tools/builtin/index.d.ts.map +1 -1
- package/dist/tools/builtin/index.js +45 -1
- package/dist/tools/builtin/index.js.map +1 -1
- package/dist/tools/builtin/list-errors.d.ts +7 -0
- package/dist/tools/builtin/list-errors.d.ts.map +1 -0
- package/dist/tools/builtin/list-errors.js +64 -0
- package/dist/tools/builtin/list-errors.js.map +1 -0
- package/dist/tools/builtin/list-subagents.d.ts +7 -0
- package/dist/tools/builtin/list-subagents.d.ts.map +1 -0
- package/dist/tools/builtin/list-subagents.js +21 -0
- package/dist/tools/builtin/list-subagents.js.map +1 -0
- package/dist/tools/builtin/recall.d.ts +11 -0
- package/dist/tools/builtin/recall.d.ts.map +1 -0
- package/dist/tools/builtin/recall.js +60 -0
- package/dist/tools/builtin/recall.js.map +1 -0
- package/dist/tools/builtin/remember.d.ts +12 -0
- package/dist/tools/builtin/remember.d.ts.map +1 -0
- package/dist/tools/builtin/remember.js +72 -0
- package/dist/tools/builtin/remember.js.map +1 -0
- package/dist/tools/builtin/skill.d.ts +14 -0
- package/dist/tools/builtin/skill.d.ts.map +1 -0
- package/dist/tools/builtin/skill.js +71 -0
- package/dist/tools/builtin/skill.js.map +1 -0
- package/dist/tools/builtin/spawn-subagent.d.ts +7 -0
- package/dist/tools/builtin/spawn-subagent.d.ts.map +1 -0
- package/dist/tools/builtin/spawn-subagent.js +43 -0
- package/dist/tools/builtin/spawn-subagent.js.map +1 -0
- package/dist/tools/builtin/web-fetch.d.ts +3 -0
- package/dist/tools/builtin/web-fetch.d.ts.map +1 -0
- package/dist/tools/builtin/web-fetch.js +101 -0
- package/dist/tools/builtin/web-fetch.js.map +1 -0
- package/dist/tools/builtin/write-file.d.ts.map +1 -1
- package/dist/tools/builtin/write-file.js +1 -0
- package/dist/tools/builtin/write-file.js.map +1 -1
- package/dist/tools/circuit-breaker.d.ts +19 -10
- package/dist/tools/circuit-breaker.d.ts.map +1 -1
- package/dist/tools/circuit-breaker.js +22 -11
- package/dist/tools/circuit-breaker.js.map +1 -1
- package/dist/tools/error-tracker.d.ts +28 -44
- package/dist/tools/error-tracker.d.ts.map +1 -1
- package/dist/tools/error-tracker.js +39 -156
- package/dist/tools/error-tracker.js.map +1 -1
- package/dist/tools/tool-filter.d.ts +70 -0
- package/dist/tools/tool-filter.d.ts.map +1 -0
- package/dist/tools/tool-filter.js +92 -0
- package/dist/tools/tool-filter.js.map +1 -0
- package/dist/tools/tool-output-truncator.d.ts +36 -0
- package/dist/tools/tool-output-truncator.d.ts.map +1 -0
- package/dist/tools/tool-output-truncator.js +117 -0
- package/dist/tools/tool-output-truncator.js.map +1 -0
- package/dist/tools/tool-registry.d.ts +25 -9
- package/dist/tools/tool-registry.d.ts.map +1 -1
- package/dist/tools/tool-registry.js +77 -28
- package/dist/tools/tool-registry.js.map +1 -1
- package/dist/tools/tool-validator.d.ts +13 -0
- package/dist/tools/tool-validator.d.ts.map +1 -0
- package/dist/tools/tool-validator.js +116 -0
- package/dist/tools/tool-validator.js.map +1 -0
- package/dist/tools/types.d.ts +86 -3
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +51 -2
- package/dist/tools/types.js.map +1 -1
- package/dist/trace/trace-logger.d.ts +30 -4
- package/dist/trace/trace-logger.d.ts.map +1 -1
- package/dist/trace/trace-logger.js +82 -6
- package/dist/trace/trace-logger.js.map +1 -1
- package/package.json +13 -4
- package/dist/compression/sliding-window.d.ts +0 -21
- package/dist/compression/sliding-window.d.ts.map +0 -1
- package/dist/compression/sliding-window.js +0 -44
- package/dist/compression/sliding-window.js.map +0 -1
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FusionAgent = void 0;
|
|
4
|
+
const agent_1 = require("./agent");
|
|
5
|
+
const message_1 = require("../messages/message");
|
|
6
|
+
const types_1 = require("../messages/types");
|
|
7
|
+
const response_schema_1 = require("./response-schema");
|
|
8
|
+
const system_prompts_1 = require("./system-prompts");
|
|
9
|
+
const errors_1 = require("../llm/errors");
|
|
10
|
+
const interface_1 = require("../llm/interface");
|
|
11
|
+
const boundaries_1 = require("../security/boundaries");
|
|
12
|
+
const reflection_agent_1 = require("../reflection/reflection-agent");
|
|
13
|
+
// ─── System Prompt ────────────────────────────────────────────────────────
|
|
14
|
+
const DEFAULT_FUSION_SYSTEM_PROMPT = `You are a helpful AI assistant powered by the Fusion Agent paradigm.
|
|
15
|
+
You dynamically adapt your strategy to the task's complexity.
|
|
16
|
+
|
|
17
|
+
For SIMPLE tasks: direct ReAct (Reasoning + Acting) loop.
|
|
18
|
+
For COMPLEX tasks: Plan → Execute → Reflect.
|
|
19
|
+
|
|
20
|
+
You have access to a set of tools you can use to answer the user's question.
|
|
21
|
+
${system_prompts_1.SECURITY_GUIDANCE}
|
|
22
|
+
${system_prompts_1.TOOL_ERROR_RECOVERY}${response_schema_1.FUSION_EXECUTION_INSTRUCTIONS}`;
|
|
23
|
+
// ─── FusionAgent ──────────────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Fusion Agent that dynamically combines ReAct, Plan-and-Solve, and
|
|
26
|
+
* Reflection paradigms based on task complexity.
|
|
27
|
+
*
|
|
28
|
+
* ## Execution flow:
|
|
29
|
+
* ```
|
|
30
|
+
* User Input
|
|
31
|
+
* ↓
|
|
32
|
+
* [1. Route] LLM judges complexity → simple / complex
|
|
33
|
+
* ↓
|
|
34
|
+
* ├─ simple ──→ [3. ReAct Execute Loop]
|
|
35
|
+
* │
|
|
36
|
+
* └─ complex → [2. Plan] LLM creates step-by-step plan
|
|
37
|
+
* ↓
|
|
38
|
+
* [Confirm?] Optional user approval
|
|
39
|
+
* ↓
|
|
40
|
+
* [3. ReAct Execute Loop] With plan tracking
|
|
41
|
+
* ↓
|
|
42
|
+
* [4. Reflect] off | post-hoc | inline | both
|
|
43
|
+
* ↓
|
|
44
|
+
* Final Answer
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ## Session persistence
|
|
48
|
+
* When `enableCheckpointing` is set, the agent auto-saves checkpoints
|
|
49
|
+
* that include full fusion state (complexity, plan, reflection progress)
|
|
50
|
+
* so the session can be resumed after a network interruption.
|
|
51
|
+
*/
|
|
52
|
+
class FusionAgent extends agent_1.Agent {
|
|
53
|
+
// ── Configuration ───────────────────────────────────────────────────
|
|
54
|
+
routing;
|
|
55
|
+
planConfirmation;
|
|
56
|
+
onPlanConfirm;
|
|
57
|
+
maxPlanSteps;
|
|
58
|
+
reflectionMode;
|
|
59
|
+
reflectionInterval;
|
|
60
|
+
notebook;
|
|
61
|
+
maxIterations;
|
|
62
|
+
replanThreshold;
|
|
63
|
+
// ── Runtime state ───────────────────────────────────────────────────
|
|
64
|
+
/** Task complexity determined during routing. */
|
|
65
|
+
complexity = "complex";
|
|
66
|
+
/** Whether routing has been completed. */
|
|
67
|
+
routed = false;
|
|
68
|
+
/** The current plan steps (empty until the plan is created). */
|
|
69
|
+
currentPlan = [];
|
|
70
|
+
/** Whether a plan has been created in this run. */
|
|
71
|
+
hasPlan = false;
|
|
72
|
+
/** How many plan steps have been completed. */
|
|
73
|
+
completedSteps = 0;
|
|
74
|
+
/** Consecutive tool failures (resets on success). */
|
|
75
|
+
consecutiveFailures = 0;
|
|
76
|
+
/** How many inline reflections have been done in this run. */
|
|
77
|
+
inlineReflectionsDone = 0;
|
|
78
|
+
/** Internal flag: when true, run() skips state reset (used by resume()). */
|
|
79
|
+
_skipStateReset = false;
|
|
80
|
+
/** Routing reason for logging. */
|
|
81
|
+
routeReason = "";
|
|
82
|
+
constructor(config) {
|
|
83
|
+
const mergedConfig = {
|
|
84
|
+
...config,
|
|
85
|
+
systemPrompt: config.systemPrompt ?? DEFAULT_FUSION_SYSTEM_PROMPT,
|
|
86
|
+
};
|
|
87
|
+
super(mergedConfig);
|
|
88
|
+
this.routing = config.routing ?? "auto";
|
|
89
|
+
this.planConfirmation = config.planConfirmation ?? "auto";
|
|
90
|
+
this.onPlanConfirm = config.onPlanConfirm;
|
|
91
|
+
this.maxPlanSteps = config.maxPlanSteps ?? 12;
|
|
92
|
+
this.reflectionMode = config.reflection ?? "off";
|
|
93
|
+
this.reflectionInterval = config.reflectionInterval ?? 3;
|
|
94
|
+
this.notebook = config.notebook;
|
|
95
|
+
this.maxIterations = config.maxIterations ?? 15;
|
|
96
|
+
this.replanThreshold = config.replanThreshold ?? 2;
|
|
97
|
+
// Validate: notebook required for post-hoc/both modes
|
|
98
|
+
if ((this.reflectionMode === "post-hoc" || this.reflectionMode === "both") &&
|
|
99
|
+
!this.notebook) {
|
|
100
|
+
throw new Error("FusionAgent: 'notebook' (ErrorNotebook) is required when reflection is 'post-hoc' or 'both'.");
|
|
101
|
+
}
|
|
102
|
+
this.rebuildSystemPrompt();
|
|
103
|
+
}
|
|
104
|
+
// ─── Main Entry Point ───────────────────────────────────────────────
|
|
105
|
+
async run(input) {
|
|
106
|
+
// Consume the skip-reset flag immediately
|
|
107
|
+
const skipStateReset = this._skipStateReset;
|
|
108
|
+
this._skipStateReset = false;
|
|
109
|
+
// ── Pre-flight: reject oversized input ───────────────────────────
|
|
110
|
+
const sizeError = this.validateInputSize(input);
|
|
111
|
+
if (sizeError)
|
|
112
|
+
return sizeError;
|
|
113
|
+
// ── Create fresh abort controller for this run ───────────────────
|
|
114
|
+
this._abortController = new AbortController();
|
|
115
|
+
// ── Async initialization ─────────────────────────────────────────
|
|
116
|
+
await this.init();
|
|
117
|
+
// ── Reload dynamic resources ─────────────────────────────────────
|
|
118
|
+
await this.reloadDynamicResources();
|
|
119
|
+
// ── Recover orphaned sub-agent results ───────────────────────────
|
|
120
|
+
this.recoverOrphanedSubAgentResults();
|
|
121
|
+
// ── Create user message ──────────────────────────────────────────
|
|
122
|
+
const userMessage = message_1.Message.user(input);
|
|
123
|
+
this.contextManager.addMessage(userMessage.toDict());
|
|
124
|
+
// ── Reset runtime state for this run ─────────────────────────────
|
|
125
|
+
if (!skipStateReset) {
|
|
126
|
+
this.complexity = "complex";
|
|
127
|
+
this.routed = false;
|
|
128
|
+
this.routeReason = "";
|
|
129
|
+
this.currentPlan = [];
|
|
130
|
+
this.hasPlan = false;
|
|
131
|
+
this.completedSteps = 0;
|
|
132
|
+
this.consecutiveFailures = 0;
|
|
133
|
+
this.inlineReflectionsDone = 0;
|
|
134
|
+
}
|
|
135
|
+
// Save initial checkpoint
|
|
136
|
+
if (this.checkpointingEnabled) {
|
|
137
|
+
this.saveCheckpoint("active");
|
|
138
|
+
}
|
|
139
|
+
// ── Phase 1: Route ───────────────────────────────────────────────
|
|
140
|
+
if (this.routing === "auto") {
|
|
141
|
+
const routeResult = await this.route(input);
|
|
142
|
+
this.complexity = routeResult.complexity;
|
|
143
|
+
this.routeReason = routeResult.reason;
|
|
144
|
+
this.routed = true;
|
|
145
|
+
this.logger.info("Fusion", `Route: ${this.complexity} — ${this.routeReason}`);
|
|
146
|
+
}
|
|
147
|
+
else if (this.routing === "force-react") {
|
|
148
|
+
this.complexity = "simple";
|
|
149
|
+
this.routed = true;
|
|
150
|
+
this.logger.info("Fusion", "Route: simple (forced)");
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// force-plan
|
|
154
|
+
this.complexity = "complex";
|
|
155
|
+
this.routed = true;
|
|
156
|
+
this.logger.info("Fusion", "Route: complex (forced)");
|
|
157
|
+
}
|
|
158
|
+
// Save checkpoint after routing
|
|
159
|
+
if (this.checkpointingEnabled) {
|
|
160
|
+
this.saveCheckpoint("active");
|
|
161
|
+
}
|
|
162
|
+
// ── Phase 2: Plan (complex tasks only) ────────────────────────────
|
|
163
|
+
if (this.complexity === "complex") {
|
|
164
|
+
const planResult = await this.createPlan(input);
|
|
165
|
+
if (typeof planResult === "string") {
|
|
166
|
+
// planResult is a string → plan was aborted / returned as answer
|
|
167
|
+
for (const h of this.hooks)
|
|
168
|
+
h.onFinish?.(planResult);
|
|
169
|
+
return planResult;
|
|
170
|
+
}
|
|
171
|
+
// planResult is string[] → plan was confirmed, continue
|
|
172
|
+
this.currentPlan = planResult;
|
|
173
|
+
this.hasPlan = true;
|
|
174
|
+
for (const h of this.hooks)
|
|
175
|
+
h.onPlanCreated?.(this.currentPlan);
|
|
176
|
+
// Save checkpoint after plan creation
|
|
177
|
+
if (this.checkpointingEnabled) {
|
|
178
|
+
this.saveCheckpoint("active");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// ── Phase 3: ReAct Execute Loop ───────────────────────────────────
|
|
182
|
+
const answer = await this.executeReActLoop();
|
|
183
|
+
// ── Phase 4: Reflection ───────────────────────────────────────────
|
|
184
|
+
await this.runReflection(input, answer);
|
|
185
|
+
for (const h of this.hooks)
|
|
186
|
+
h.onFinish?.(answer);
|
|
187
|
+
return answer;
|
|
188
|
+
}
|
|
189
|
+
// ─── Phase 1: Route (Complexity Classification) ─────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* Ask the LLM to classify the task complexity.
|
|
192
|
+
*
|
|
193
|
+
* Sends a lightweight prompt separate from the main conversation so
|
|
194
|
+
* the routing doesn't pollute the execution context.
|
|
195
|
+
*/
|
|
196
|
+
async route(input) {
|
|
197
|
+
const messages = [
|
|
198
|
+
{ role: types_1.Role.System, content: response_schema_1.FUSION_ROUTE_INSTRUCTIONS },
|
|
199
|
+
{ role: types_1.Role.User, content: input },
|
|
200
|
+
];
|
|
201
|
+
try {
|
|
202
|
+
const response = await this.llm.chat(messages, [], this._abortController?.signal);
|
|
203
|
+
const parsed = (0, response_schema_1.parseFusionRouteResponse)(response.content);
|
|
204
|
+
// Record token usage
|
|
205
|
+
if (response.usage) {
|
|
206
|
+
this.tokenBudget?.recordUsage(response.usage.prompt_tokens, response.usage.completion_tokens);
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
if (err instanceof errors_1.LLMNetworkError) {
|
|
212
|
+
this.logger.warn("Fusion", `Route LLM call failed: ${err.message}. Defaulting to complex.`);
|
|
213
|
+
}
|
|
214
|
+
// Default to complex for safety
|
|
215
|
+
return {
|
|
216
|
+
complexity: "complex",
|
|
217
|
+
reason: "Route classification failed; defaulting to complex to be safe.",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// ─── Phase 2: Plan (Plan Generation) ────────────────────────────────
|
|
222
|
+
/**
|
|
223
|
+
* Generate a plan for a complex task.
|
|
224
|
+
*
|
|
225
|
+
* Sends the user input to the LLM with plan-generation instructions.
|
|
226
|
+
* If plan confirmation is configured, asks the user before proceeding.
|
|
227
|
+
*
|
|
228
|
+
* @returns The plan steps (string[]), or the plan text if confirmation
|
|
229
|
+
* was denied (returned as the final answer string).
|
|
230
|
+
*/
|
|
231
|
+
async createPlan(input) {
|
|
232
|
+
// Rebuild system prompt with plan-generation context
|
|
233
|
+
const planPrompt = this.buildSystemPrompt() +
|
|
234
|
+
"\n\nYou are in the PLANNING phase. Analyze the user's request and create a detailed, " +
|
|
235
|
+
"step-by-step plan. Output ONLY: {\"thought\": \"...\", \"plan\": [\"Step 1: ...\", ...]}\n" +
|
|
236
|
+
`Maximum ${this.maxPlanSteps} steps. Each step must be concrete and actionable.`;
|
|
237
|
+
this.contextManager.setSystemMessage(planPrompt);
|
|
238
|
+
const contextMessages = this.contextManager.getContextMessages();
|
|
239
|
+
// Token budget check
|
|
240
|
+
const estimatedInput = this.contextManager.getCurrentTokens();
|
|
241
|
+
const budgetError = this.checkTokenBudget(estimatedInput);
|
|
242
|
+
if (budgetError)
|
|
243
|
+
return budgetError;
|
|
244
|
+
for (const h of this.hooks)
|
|
245
|
+
h.onLLMStart?.(contextMessages, this.toolRegistry.getTools());
|
|
246
|
+
let response;
|
|
247
|
+
try {
|
|
248
|
+
response = await this.llm.chat(contextMessages, this.toolRegistry.getTools(), this._abortController?.signal);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
// Cancellation by user (AbortController)
|
|
252
|
+
if (this.isCancelled) {
|
|
253
|
+
this.saveCheckpoint("cancelled");
|
|
254
|
+
const sid = this.sessionManager?.getSessionId() ?? "unknown";
|
|
255
|
+
const cancelMsg = `Execution cancelled by user. Session "${sid}" preserved — ` +
|
|
256
|
+
`resume with agent.resume("${sid}", "<your prompt>").`;
|
|
257
|
+
for (const h of this.hooks)
|
|
258
|
+
h.onFinish?.(cancelMsg);
|
|
259
|
+
return cancelMsg;
|
|
260
|
+
}
|
|
261
|
+
if (err instanceof errors_1.LLMNetworkError) {
|
|
262
|
+
for (const h of this.hooks)
|
|
263
|
+
h.onLLMError?.(err);
|
|
264
|
+
return this.handleNetworkError(err, 0, "continue creating a plan");
|
|
265
|
+
}
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
for (const h of this.hooks)
|
|
269
|
+
h.onLLMEnd?.(response);
|
|
270
|
+
if (response.usage) {
|
|
271
|
+
this.tokenBudget?.recordUsage(response.usage.prompt_tokens, response.usage.completion_tokens);
|
|
272
|
+
}
|
|
273
|
+
const parsed = (0, response_schema_1.parseFusionResponse)(response.content);
|
|
274
|
+
// Store the assistant message in context
|
|
275
|
+
const assistantMessage = message_1.Message.assistant(response.content, response.tool_calls);
|
|
276
|
+
this.contextManager.addMessage(assistantMessage.toDict());
|
|
277
|
+
if (parsed.plan && parsed.plan.length > 0) {
|
|
278
|
+
const plan = parsed.plan.slice(0, this.maxPlanSteps);
|
|
279
|
+
this.logger.info("Plan", `Created ${plan.length}-step plan:`);
|
|
280
|
+
for (let i = 0; i < plan.length; i++) {
|
|
281
|
+
this.logger.info("Plan", ` ${i + 1}. ${plan[i]}`);
|
|
282
|
+
}
|
|
283
|
+
// ── Plan confirmation ──────────────────────────────────────────
|
|
284
|
+
const shouldConfirm = this.shouldConfirmPlan(plan);
|
|
285
|
+
if (shouldConfirm && this.onPlanConfirm) {
|
|
286
|
+
const confirmed = await this.onPlanConfirm(plan, this.routeReason);
|
|
287
|
+
if (!confirmed) {
|
|
288
|
+
// User rejected the plan — return it as the answer
|
|
289
|
+
const planText = `I've created the following plan for your task. ` +
|
|
290
|
+
`Please review and let me know if you'd like any changes:\n\n` +
|
|
291
|
+
plan.map((s, i) => `${i + 1}. ${s}`).join("\n") +
|
|
292
|
+
`\n\nYou can resume execution by saying "proceed" or "execute the plan".`;
|
|
293
|
+
return planText;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (shouldConfirm && !this.onPlanConfirm) {
|
|
297
|
+
// Confirmation needed but no callback — return plan as answer
|
|
298
|
+
const planText = `I've created the following plan for your task:\n\n` +
|
|
299
|
+
plan.map((s, i) => `${i + 1}. ${s}`).join("\n") +
|
|
300
|
+
`\n\n(Plan confirmation is required but no confirmation callback is configured. ` +
|
|
301
|
+
`Send your approval to continue, or set planConfirmation to "never" to auto-execute.)`;
|
|
302
|
+
return planText;
|
|
303
|
+
}
|
|
304
|
+
if (parsed.thought) {
|
|
305
|
+
this.logger.info("Thought", parsed.thought);
|
|
306
|
+
for (const h of this.hooks)
|
|
307
|
+
h.onThought?.(parsed.thought);
|
|
308
|
+
}
|
|
309
|
+
return plan;
|
|
310
|
+
}
|
|
311
|
+
// LLM didn't generate a plan — fall back to ReAct
|
|
312
|
+
this.logger.info("Fusion", "No plan generated by LLM — falling back to direct ReAct.");
|
|
313
|
+
this.complexity = "simple";
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Determine whether plan confirmation is needed based on the
|
|
318
|
+
* planConfirmation setting and plan content.
|
|
319
|
+
*/
|
|
320
|
+
shouldConfirmPlan(_plan) {
|
|
321
|
+
switch (this.planConfirmation) {
|
|
322
|
+
case "always":
|
|
323
|
+
return true;
|
|
324
|
+
case "never":
|
|
325
|
+
return false;
|
|
326
|
+
case "auto":
|
|
327
|
+
// Auto: check if plan contains risky keywords
|
|
328
|
+
// (deploy, delete, drop, migration, etc.)
|
|
329
|
+
const riskyKeywords = [
|
|
330
|
+
"deploy", "delete", "drop", "migrate", "truncate",
|
|
331
|
+
"destroy", "purge", "reset", "format",
|
|
332
|
+
];
|
|
333
|
+
const planText = _plan.join(" ").toLowerCase();
|
|
334
|
+
return riskyKeywords.some((kw) => planText.includes(kw));
|
|
335
|
+
default:
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// ─── Phase 3: ReAct Execute Loop ────────────────────────────────────
|
|
340
|
+
/**
|
|
341
|
+
* Main execution loop with ReAct reasoning, plan tracking, and optional
|
|
342
|
+
* inline reflection.
|
|
343
|
+
*/
|
|
344
|
+
async executeReActLoop() {
|
|
345
|
+
// Rebuild system prompt for execution mode
|
|
346
|
+
this.rebuildContextWithPlan();
|
|
347
|
+
let consecutiveEmptyIterations = 0;
|
|
348
|
+
const EMPTY_ITERATION_LIMIT = 5;
|
|
349
|
+
let consecutiveTruncations = 0;
|
|
350
|
+
const MAX_TRUNCATION_CONTINUES = 3;
|
|
351
|
+
for (let iteration = 0; iteration < this.maxIterations; iteration++) {
|
|
352
|
+
// ── Check cancellation ──────────────────────────────────────────
|
|
353
|
+
if (this.isCancelled) {
|
|
354
|
+
this.saveCheckpoint("cancelled");
|
|
355
|
+
const sid = this.sessionManager?.getSessionId() ?? "unknown";
|
|
356
|
+
const cancelMsg = `Execution cancelled by user. Session "${sid}" preserved — ` +
|
|
357
|
+
`resume with agent.resume("${sid}", "<your prompt>").`;
|
|
358
|
+
return cancelMsg;
|
|
359
|
+
}
|
|
360
|
+
// ── Poll sub-agent results ──────────────────────────────────────
|
|
361
|
+
const subResults = await this.pollSubAgentResults();
|
|
362
|
+
for (const r of subResults) {
|
|
363
|
+
const source = `subagent:${r.name}`;
|
|
364
|
+
const msg = new message_1.Message(types_1.Role.User, (0, boundaries_1.wrapAndScan)(source, r.output), {
|
|
365
|
+
name: source,
|
|
366
|
+
});
|
|
367
|
+
this.contextManager.addMessage(msg.toDict());
|
|
368
|
+
}
|
|
369
|
+
await this.checkAndCompress();
|
|
370
|
+
// ── Rebuild system prompt (plan progress + replan hint) ─────────
|
|
371
|
+
this.rebuildContextWithPlan();
|
|
372
|
+
// ── Token budget ────────────────────────────────────────────────
|
|
373
|
+
const contextMessages = this.contextManager.getContextMessages();
|
|
374
|
+
const budgetError = this.checkTokenBudget(this.tokenBudget ? this.contextManager.getCurrentTokens() : 0);
|
|
375
|
+
if (budgetError)
|
|
376
|
+
return budgetError;
|
|
377
|
+
// ── LLM call ────────────────────────────────────────────────────
|
|
378
|
+
for (const h of this.hooks)
|
|
379
|
+
h.onLLMStart?.(contextMessages, this.toolRegistry.getTools());
|
|
380
|
+
let response;
|
|
381
|
+
try {
|
|
382
|
+
response = await this.llm.chat(contextMessages, this.toolRegistry.getTools(), this._abortController?.signal);
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
// Cancellation by user (AbortController) — exit the loop cleanly
|
|
386
|
+
if (this.isCancelled) {
|
|
387
|
+
this.saveCheckpoint("cancelled");
|
|
388
|
+
const sid = this.sessionManager?.getSessionId() ?? "unknown";
|
|
389
|
+
const cancelMsg = `Execution cancelled by user. Session "${sid}" preserved — ` +
|
|
390
|
+
`resume with agent.resume("${sid}", "<your prompt>").`;
|
|
391
|
+
for (const h of this.hooks)
|
|
392
|
+
h.onFinish?.(cancelMsg);
|
|
393
|
+
return cancelMsg;
|
|
394
|
+
}
|
|
395
|
+
if (err instanceof errors_1.LLMNetworkError) {
|
|
396
|
+
for (const h of this.hooks)
|
|
397
|
+
h.onLLMError?.(err);
|
|
398
|
+
return this.handleNetworkError(err, iteration + 1, "continue with what you were doing");
|
|
399
|
+
}
|
|
400
|
+
throw err;
|
|
401
|
+
}
|
|
402
|
+
for (const h of this.hooks)
|
|
403
|
+
h.onLLMEnd?.(response);
|
|
404
|
+
if (response.usage) {
|
|
405
|
+
this.tokenBudget?.recordUsage(response.usage.prompt_tokens, response.usage.completion_tokens);
|
|
406
|
+
}
|
|
407
|
+
const parsed = (0, response_schema_1.parseFusionResponse)(response.content);
|
|
408
|
+
// Capture LLM analysis of active tool error traces
|
|
409
|
+
if (parsed.thought) {
|
|
410
|
+
this.captureErrorAnalysis(parsed.thought);
|
|
411
|
+
}
|
|
412
|
+
// ── Store assistant message ─────────────────────────────────────
|
|
413
|
+
const assistantMessage = message_1.Message.assistant(response.content, response.tool_calls);
|
|
414
|
+
// ── Handle max_tokens truncation ────────────────────────────────
|
|
415
|
+
const isTruncated = response.responseError?.code === interface_1.LLMResponseErrorCode.MAX_TOKENS;
|
|
416
|
+
if (isTruncated) {
|
|
417
|
+
consecutiveTruncations++;
|
|
418
|
+
if (consecutiveTruncations > MAX_TRUNCATION_CONTINUES) {
|
|
419
|
+
const fallback = parsed.answer
|
|
420
|
+
? parsed.answer +
|
|
421
|
+
"\n\n[Note: Response may be incomplete due to repeated output limits.]"
|
|
422
|
+
: "I apologize, but I'm unable to complete this response due to output length constraints. " +
|
|
423
|
+
"Please try breaking your request into smaller steps.";
|
|
424
|
+
return fallback;
|
|
425
|
+
}
|
|
426
|
+
this.contextManager.addMessage(assistantMessage.toDict());
|
|
427
|
+
const continueMsg = message_1.Message.user("Your previous response was cut off (max output tokens reached). " +
|
|
428
|
+
"Continue exactly where you left off — do NOT repeat any content already written. " +
|
|
429
|
+
"If you were calling tools, re-invoke them with complete arguments.");
|
|
430
|
+
this.contextManager.addMessage(continueMsg.toDict());
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
this.contextManager.addMessage(assistantMessage.toDict());
|
|
434
|
+
consecutiveTruncations = 0;
|
|
435
|
+
}
|
|
436
|
+
// ── Tool calls ──────────────────────────────────────────────────
|
|
437
|
+
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
438
|
+
consecutiveEmptyIterations = 0;
|
|
439
|
+
if (parsed.thought) {
|
|
440
|
+
this.logger.info("Thought", parsed.thought);
|
|
441
|
+
for (const h of this.hooks)
|
|
442
|
+
h.onThought?.(parsed.thought);
|
|
443
|
+
}
|
|
444
|
+
// Non-truncation quality issues
|
|
445
|
+
if (response.responseError &&
|
|
446
|
+
response.responseError.code !== interface_1.LLMResponseErrorCode.MAX_TOKENS &&
|
|
447
|
+
response.responseError.code !== interface_1.LLMResponseErrorCode.OK) {
|
|
448
|
+
const warnMsg = message_1.Message.system(`LLM response quality issue: ${response.responseError.message}`);
|
|
449
|
+
this.contextManager.addMessage(warnMsg.toDict());
|
|
450
|
+
}
|
|
451
|
+
const mcpWarnedServers = new Set();
|
|
452
|
+
const { hadFailure } = await this.executeToolCallsBatch(response.tool_calls, mcpWarnedServers);
|
|
453
|
+
// Update failure counter and step progress
|
|
454
|
+
if (hadFailure) {
|
|
455
|
+
this.consecutiveFailures++;
|
|
456
|
+
this.logger.info("Fusion", `Consecutive failures: ${this.consecutiveFailures}` +
|
|
457
|
+
(this.replanThreshold > 0 &&
|
|
458
|
+
this.consecutiveFailures >= this.replanThreshold
|
|
459
|
+
? ` — threshold reached`
|
|
460
|
+
: ""));
|
|
461
|
+
// Inline reflection on first failure if enabled
|
|
462
|
+
if ((this.reflectionMode === "inline" ||
|
|
463
|
+
this.reflectionMode === "both") &&
|
|
464
|
+
this.inlineReflectionsDone === 0) {
|
|
465
|
+
await this.reflectInline(iteration);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
this.consecutiveFailures = 0;
|
|
470
|
+
// Advance completed steps
|
|
471
|
+
if (parsed.currentStep && this.hasPlan) {
|
|
472
|
+
this.completedSteps = Math.max(this.completedSteps, Math.min(parsed.currentStep - 1, this.currentPlan.length));
|
|
473
|
+
}
|
|
474
|
+
else if (this.hasPlan) {
|
|
475
|
+
this.completedSteps = Math.min(this.completedSteps + 1, this.currentPlan.length);
|
|
476
|
+
}
|
|
477
|
+
if (this.hasPlan) {
|
|
478
|
+
this.logger.info("Progress", `Step ${this.completedSteps}/${this.currentPlan.length} completed`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Save checkpoint
|
|
482
|
+
if (this.checkpointingEnabled) {
|
|
483
|
+
this.saveCheckpoint("active");
|
|
484
|
+
}
|
|
485
|
+
// Inline reflection at regular intervals
|
|
486
|
+
if ((this.reflectionMode === "inline" ||
|
|
487
|
+
this.reflectionMode === "both") &&
|
|
488
|
+
(iteration + 1) % this.reflectionInterval === 0) {
|
|
489
|
+
await this.reflectInline(iteration);
|
|
490
|
+
}
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
// ── Final answer ────────────────────────────────────────────────
|
|
494
|
+
if (parsed.answer) {
|
|
495
|
+
if (isTruncated) {
|
|
496
|
+
consecutiveEmptyIterations = 0;
|
|
497
|
+
if (parsed.thought) {
|
|
498
|
+
this.logger.info("Thought", parsed.thought);
|
|
499
|
+
for (const h of this.hooks)
|
|
500
|
+
h.onThought?.(parsed.thought);
|
|
501
|
+
}
|
|
502
|
+
if (iteration === this.maxIterations - 1) {
|
|
503
|
+
return (parsed.answer +
|
|
504
|
+
"\n\n[Note: Response may be incomplete due to output length constraints.]");
|
|
505
|
+
}
|
|
506
|
+
this.logger.info("Fusion", "Answer truncated — continuing in next iteration.");
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (parsed.thought) {
|
|
510
|
+
this.logger.info("Thought", parsed.thought);
|
|
511
|
+
for (const h of this.hooks)
|
|
512
|
+
h.onThought?.(parsed.thought);
|
|
513
|
+
}
|
|
514
|
+
this.logger.info("Fusion", "Task complete — returning final answer.");
|
|
515
|
+
if (this.checkpointingEnabled) {
|
|
516
|
+
this.saveCheckpoint("completed");
|
|
517
|
+
}
|
|
518
|
+
return parsed.answer;
|
|
519
|
+
}
|
|
520
|
+
// ── Plan creation (if LLM generates plan mid-execution) ─────────
|
|
521
|
+
if (!this.hasPlan &&
|
|
522
|
+
parsed.plan &&
|
|
523
|
+
Array.isArray(parsed.plan) &&
|
|
524
|
+
parsed.plan.length > 0) {
|
|
525
|
+
consecutiveEmptyIterations = 0;
|
|
526
|
+
this.currentPlan = parsed.plan.slice(0, this.maxPlanSteps);
|
|
527
|
+
this.hasPlan = true;
|
|
528
|
+
this.logger.info("Plan", `Created ${this.currentPlan.length}-step plan mid-execution:`);
|
|
529
|
+
for (let i = 0; i < this.currentPlan.length; i++) {
|
|
530
|
+
this.logger.info("Plan", ` ${i + 1}. ${this.currentPlan[i]}`);
|
|
531
|
+
}
|
|
532
|
+
for (const h of this.hooks)
|
|
533
|
+
h.onPlanCreated?.(this.currentPlan);
|
|
534
|
+
if (parsed.thought) {
|
|
535
|
+
this.logger.info("Thought", parsed.thought);
|
|
536
|
+
for (const h of this.hooks)
|
|
537
|
+
h.onThought?.(parsed.thought);
|
|
538
|
+
}
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
// ── Plan revision ───────────────────────────────────────────────
|
|
542
|
+
if (parsed.revised_plan &&
|
|
543
|
+
Array.isArray(parsed.revised_plan) &&
|
|
544
|
+
parsed.revised_plan.length > 0) {
|
|
545
|
+
consecutiveEmptyIterations = 0;
|
|
546
|
+
this.currentPlan = parsed.revised_plan.slice(0, this.maxPlanSteps);
|
|
547
|
+
this.completedSteps = 0;
|
|
548
|
+
this.consecutiveFailures = 0;
|
|
549
|
+
this.logger.info("Plan", `Revised — ${this.currentPlan.length} steps remaining:`);
|
|
550
|
+
for (let i = 0; i < this.currentPlan.length; i++) {
|
|
551
|
+
this.logger.info("Plan", ` ${i + 1}. ${this.currentPlan[i]}`);
|
|
552
|
+
}
|
|
553
|
+
for (const h of this.hooks)
|
|
554
|
+
h.onPlanRevised?.(this.currentPlan);
|
|
555
|
+
if (parsed.thought) {
|
|
556
|
+
this.logger.info("Thought", parsed.thought);
|
|
557
|
+
for (const h of this.hooks)
|
|
558
|
+
h.onThought?.(parsed.thought);
|
|
559
|
+
}
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
// ── Thought-only iteration ──────────────────────────────────────
|
|
563
|
+
if (parsed.thought) {
|
|
564
|
+
consecutiveEmptyIterations++;
|
|
565
|
+
this.logger.info("Thought", parsed.thought);
|
|
566
|
+
for (const h of this.hooks)
|
|
567
|
+
h.onThought?.(parsed.thought);
|
|
568
|
+
if (consecutiveEmptyIterations >= EMPTY_ITERATION_LIMIT) {
|
|
569
|
+
const stuckMsg = "I apologize, but I'm having difficulty making progress on your request. " +
|
|
570
|
+
"Please try rephrasing or breaking it down into smaller, more specific steps.";
|
|
571
|
+
const stuckAssistantMessage = message_1.Message.assistant(stuckMsg);
|
|
572
|
+
this.contextManager.addMessage(stuckAssistantMessage.toDict());
|
|
573
|
+
return stuckMsg;
|
|
574
|
+
}
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
// ── Empty response ──────────────────────────────────────────────
|
|
578
|
+
consecutiveEmptyIterations++;
|
|
579
|
+
if (consecutiveEmptyIterations >= EMPTY_ITERATION_LIMIT) {
|
|
580
|
+
const stuckMsg = "I apologize, but I'm having difficulty making progress on your request. " +
|
|
581
|
+
"Please try rephrasing or breaking it down into smaller, more specific steps.";
|
|
582
|
+
const stuckAssistantMessage = message_1.Message.assistant(stuckMsg);
|
|
583
|
+
this.contextManager.addMessage(stuckAssistantMessage.toDict());
|
|
584
|
+
return stuckMsg;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// ── Max iterations reached ────────────────────────────────────────
|
|
588
|
+
const timeoutMsg = `I apologize, but I was unable to complete the task within ${this.maxIterations} iterations. ` +
|
|
589
|
+
`Please try breaking your request into smaller steps.`;
|
|
590
|
+
const timeoutAssistantMessage = message_1.Message.assistant(timeoutMsg);
|
|
591
|
+
this.contextManager.addMessage(timeoutAssistantMessage.toDict());
|
|
592
|
+
return timeoutMsg;
|
|
593
|
+
}
|
|
594
|
+
// ─── Phase 4: Reflection ────────────────────────────────────────────
|
|
595
|
+
/**
|
|
596
|
+
* Run the configured reflection strategy.
|
|
597
|
+
*/
|
|
598
|
+
async runReflection(input, answer) {
|
|
599
|
+
if (this.reflectionMode === "off")
|
|
600
|
+
return;
|
|
601
|
+
// ── Post-hoc reflection ──────────────────────────────────────────
|
|
602
|
+
if (this.reflectionMode === "post-hoc" ||
|
|
603
|
+
this.reflectionMode === "both") {
|
|
604
|
+
await this.reflectPostHoc(input, answer);
|
|
605
|
+
}
|
|
606
|
+
// Inline reflection is done during the loop — nothing extra here
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Inline reflection: pause execution and ask the LLM to self-check
|
|
610
|
+
* its progress. Results are logged and injected into context.
|
|
611
|
+
*/
|
|
612
|
+
async reflectInline(iteration) {
|
|
613
|
+
this.inlineReflectionsDone++;
|
|
614
|
+
this.logger.info("Reflection", `Inline reflection #${this.inlineReflectionsDone} at iteration ${iteration + 1}`);
|
|
615
|
+
const reflectionMsg = message_1.Message.user(response_schema_1.INLINE_REFLECTION_PROMPT);
|
|
616
|
+
this.contextManager.addMessage(reflectionMsg.toDict());
|
|
617
|
+
// Do NOT call the LLM here — the reflection prompt is injected as a
|
|
618
|
+
// user message and will be processed in the next iteration of the
|
|
619
|
+
// main loop. This keeps the reflection lightweight and naturally
|
|
620
|
+
// integrated into the flow.
|
|
621
|
+
// Save checkpoint after inline reflection
|
|
622
|
+
if (this.checkpointingEnabled) {
|
|
623
|
+
this.saveCheckpoint("active");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Post-hoc reflection: after execution completes, run the
|
|
628
|
+
* ReflectionAgent to review the full session.
|
|
629
|
+
*/
|
|
630
|
+
async reflectPostHoc(input, answer) {
|
|
631
|
+
if (!this.notebook)
|
|
632
|
+
return;
|
|
633
|
+
this.logger.info("Reflection", "Starting post-hoc reflection...");
|
|
634
|
+
try {
|
|
635
|
+
const reflector = new reflection_agent_1.ReflectionAgent({
|
|
636
|
+
llm: this.llm,
|
|
637
|
+
notebook: this.notebook,
|
|
638
|
+
});
|
|
639
|
+
const contextMessages = this.contextManager.getContextMessages();
|
|
640
|
+
const entries = await reflector.reflect({
|
|
641
|
+
userQuery: input,
|
|
642
|
+
finalAnswer: answer,
|
|
643
|
+
conversation: contextMessages,
|
|
644
|
+
sessionId: this.sessionManager?.getSessionId() ?? "unknown",
|
|
645
|
+
});
|
|
646
|
+
if (entries.length > 0) {
|
|
647
|
+
this.logger.info("Reflection", `Post-hoc complete — ${entries.length} finding(s) written to notebook.`);
|
|
648
|
+
for (const entry of entries) {
|
|
649
|
+
this.logger.info("Reflection", ` [${entry.category}] ${entry.description}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
this.logger.info("Reflection", "Post-hoc complete — no issues found.");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
catch (err) {
|
|
657
|
+
this.logger.warn("Reflection", `Post-hoc reflection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// ─── System Prompt Management ───────────────────────────────────────
|
|
661
|
+
/**
|
|
662
|
+
* Rebuild the system prompt to include plan progress and replan hints.
|
|
663
|
+
*
|
|
664
|
+
* Called each iteration so the LLM always sees the up-to-date state.
|
|
665
|
+
*/
|
|
666
|
+
rebuildContextWithPlan() {
|
|
667
|
+
let prompt = this.buildSystemPrompt();
|
|
668
|
+
// Plan context — injected when a plan exists
|
|
669
|
+
if (this.hasPlan && this.currentPlan.length > 0) {
|
|
670
|
+
prompt += `\n\n=== Current Plan (${this.completedSteps}/${this.currentPlan.length} completed) ===\n`;
|
|
671
|
+
for (let i = 0; i < this.currentPlan.length; i++) {
|
|
672
|
+
const marker = i < this.completedSteps
|
|
673
|
+
? "✅"
|
|
674
|
+
: i === this.completedSteps
|
|
675
|
+
? "➡️"
|
|
676
|
+
: " ";
|
|
677
|
+
prompt += ` ${marker} ${i + 1}. ${this.currentPlan[i]}\n`;
|
|
678
|
+
}
|
|
679
|
+
prompt +=
|
|
680
|
+
`\nWork through the plan step by step. ` +
|
|
681
|
+
`When you complete a step, move to the next one. ` +
|
|
682
|
+
`If you need to revise remaining steps, output a "revised_plan".`;
|
|
683
|
+
}
|
|
684
|
+
// Replan hint — injected when consecutive failures are detected
|
|
685
|
+
const replanHint = this.computeReplanHint();
|
|
686
|
+
if (replanHint) {
|
|
687
|
+
prompt += `\n\n${replanHint}`;
|
|
688
|
+
}
|
|
689
|
+
this.contextManager.setSystemMessage(prompt);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Compute a replan hint based on consecutive failure count.
|
|
693
|
+
*/
|
|
694
|
+
computeReplanHint() {
|
|
695
|
+
if (this.replanThreshold <= 0)
|
|
696
|
+
return undefined;
|
|
697
|
+
if (!this.hasPlan)
|
|
698
|
+
return undefined;
|
|
699
|
+
if (this.consecutiveFailures >= this.replanThreshold) {
|
|
700
|
+
return (`⚠️ REPLAN SUGGESTED: Multiple consecutive tool failures detected (${this.consecutiveFailures}).\n` +
|
|
701
|
+
`The current approach may be fundamentally wrong.\n` +
|
|
702
|
+
`Please consider whether your plan needs revision. If so, output a "revised_plan" ` +
|
|
703
|
+
`with a new strategy for the remaining steps. Only the REMAINING steps — ` +
|
|
704
|
+
`do NOT re-list already completed steps.`);
|
|
705
|
+
}
|
|
706
|
+
return undefined;
|
|
707
|
+
}
|
|
708
|
+
// ─── Session Persistence ────────────────────────────────────────────
|
|
709
|
+
/**
|
|
710
|
+
* Agent type identifier for session metadata.
|
|
711
|
+
*/
|
|
712
|
+
getAgentType() {
|
|
713
|
+
return "fusion";
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Include fusion-specific state in session checkpoints.
|
|
717
|
+
*/
|
|
718
|
+
buildBaseSessionState(status) {
|
|
719
|
+
const base = super.buildBaseSessionState(status);
|
|
720
|
+
return {
|
|
721
|
+
...base,
|
|
722
|
+
planState: undefined, // Clear PlanSolve state — fusion uses its own
|
|
723
|
+
fusionState: {
|
|
724
|
+
complexity: this.complexity,
|
|
725
|
+
routed: this.routed,
|
|
726
|
+
currentPlan: this.currentPlan,
|
|
727
|
+
hasPlan: this.hasPlan,
|
|
728
|
+
completedSteps: this.completedSteps,
|
|
729
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
730
|
+
reflectionEnabled: this.reflectionMode !== "off",
|
|
731
|
+
inlineReflectionsDone: this.inlineReflectionsDone,
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Restore fusion state from a saved session.
|
|
737
|
+
*/
|
|
738
|
+
loadAndRestoreSession(sessionId) {
|
|
739
|
+
const state = super.loadAndRestoreSession(sessionId);
|
|
740
|
+
if (state.fusionState) {
|
|
741
|
+
const fs = state.fusionState;
|
|
742
|
+
this.complexity = fs.complexity;
|
|
743
|
+
this.routed = fs.routed;
|
|
744
|
+
this.currentPlan = fs.currentPlan;
|
|
745
|
+
this.hasPlan = fs.hasPlan;
|
|
746
|
+
this.completedSteps = fs.completedSteps;
|
|
747
|
+
this.consecutiveFailures = fs.consecutiveFailures;
|
|
748
|
+
this.inlineReflectionsDone = fs.inlineReflectionsDone;
|
|
749
|
+
}
|
|
750
|
+
return state;
|
|
751
|
+
}
|
|
752
|
+
// ─── Resume ─────────────────────────────────────────────────────────
|
|
753
|
+
/**
|
|
754
|
+
* Resume a previously interrupted session.
|
|
755
|
+
*
|
|
756
|
+
* Restores messages, system prompt, plan state, and fusion metadata
|
|
757
|
+
* so the agent can continue from where it left off.
|
|
758
|
+
*
|
|
759
|
+
* @param sessionId The session ID to resume.
|
|
760
|
+
* @param input New user input to continue the conversation.
|
|
761
|
+
*/
|
|
762
|
+
async resume(sessionId, input) {
|
|
763
|
+
this.loadAndRestoreSession(sessionId);
|
|
764
|
+
this._skipStateReset = true;
|
|
765
|
+
return this.run(input);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
exports.FusionAgent = FusionAgent;
|
|
769
|
+
//# sourceMappingURL=fusion-agent.js.map
|