foliko 1.1.93 → 2.0.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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +202 -0
- package/plugins/core/default/config.js +220 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +2 -2
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +261 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework 核心容器
|
|
5
|
+
* 管理所有子系统,提供统一入口
|
|
6
|
+
* 目标 ~250 行
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
12
|
+
const { EventEmitter } = require('../common/events');
|
|
13
|
+
const { PluginManager } = require('../plugin/manager');
|
|
14
|
+
const { ToolRegistry } = require('../tool/registry');
|
|
15
|
+
const { ContextManager } = require('../context/manager');
|
|
16
|
+
const { createFrameworkLogger } = require('../common/logger');
|
|
17
|
+
const { AGENT_DIR, HOME_AGENT_DIR_NAME } = require('../common/constants');
|
|
18
|
+
const { SessionTTL } = require('../session/ttl');
|
|
19
|
+
|
|
20
|
+
// Add framework node_modules to search path
|
|
21
|
+
const frameworkNodeModules = path.join(__dirname, '..', '..', 'node_modules');
|
|
22
|
+
if (!module.paths.includes(frameworkNodeModules)) {
|
|
23
|
+
module.paths.unshift(frameworkNodeModules);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// AsyncLocalStorage instances (backward compat)
|
|
27
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
28
|
+
const requestStorage = new AsyncLocalStorage();
|
|
29
|
+
const sessionStorage = new AsyncLocalStorage();
|
|
30
|
+
|
|
31
|
+
class Framework extends EventEmitter {
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
super();
|
|
34
|
+
|
|
35
|
+
this._silent = config.silent || false;
|
|
36
|
+
this._debug = config.debug || false;
|
|
37
|
+
this._ready = false;
|
|
38
|
+
this._readyPromise = null;
|
|
39
|
+
this._resolveReady = null;
|
|
40
|
+
|
|
41
|
+
// Subsystems
|
|
42
|
+
this.toolRegistry = new ToolRegistry();
|
|
43
|
+
this.pluginManager = new PluginManager(this);
|
|
44
|
+
|
|
45
|
+
// Agent management
|
|
46
|
+
this._agents = [];
|
|
47
|
+
this._mainAgent = null;
|
|
48
|
+
this._toolRegistryListeners = [];
|
|
49
|
+
this._subAgentConfigManager = null;
|
|
50
|
+
this._executionContext = null;
|
|
51
|
+
|
|
52
|
+
// Context system
|
|
53
|
+
this._contextManager = new ContextManager(this);
|
|
54
|
+
this._sessionContexts = new Map();
|
|
55
|
+
this._sessionStorageConfig = { baseDir: '.foliko/sessions' };
|
|
56
|
+
|
|
57
|
+
// Working directory
|
|
58
|
+
this._cwd = config.cwd || process.cwd();
|
|
59
|
+
this._agentDir = config.agentDir || AGENT_DIR;
|
|
60
|
+
this._homeAgentDir = config.homeAgentDir || process.env.FOLIKO_HOME || path.join(os.homedir(), HOME_AGENT_DIR_NAME);
|
|
61
|
+
this._changingCwd = false;
|
|
62
|
+
|
|
63
|
+
// Logger
|
|
64
|
+
this.logger = createFrameworkLogger(this);
|
|
65
|
+
|
|
66
|
+
// Coordinator
|
|
67
|
+
this._coordinatorManager = null;
|
|
68
|
+
|
|
69
|
+
// Session TTL
|
|
70
|
+
this._sessionTTL = new SessionTTL({
|
|
71
|
+
getSessionContexts: () => this._sessionContexts,
|
|
72
|
+
destroySession: (sid) => this.destroySessionContext(sid),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Event forwarding
|
|
76
|
+
this._setupToolEventForwarding();
|
|
77
|
+
this._setupAgentEventForwarding();
|
|
78
|
+
this._registerBuiltinTools();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_setupToolEventForwarding() {
|
|
82
|
+
const handlers = [
|
|
83
|
+
['tool:registered', (tool) => this.emit('tool:registered', tool)],
|
|
84
|
+
['tool:call', (data) => { this.emit('tool:call', data); this.emit('tool-call', data); }],
|
|
85
|
+
['tool:result', (data) => { this.emit('tool:result', data); }],
|
|
86
|
+
['tool:error', (data) => { this.emit('tool:error', data); }],
|
|
87
|
+
];
|
|
88
|
+
for (const [event, handler] of handlers) {
|
|
89
|
+
this.toolRegistry.on(event, handler);
|
|
90
|
+
this._toolRegistryListeners.push({ event, handler });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_setupAgentEventForwarding() {
|
|
95
|
+
this._agentForwardingListeners = new Map();
|
|
96
|
+
this.on('agent:created', (agent) => {
|
|
97
|
+
const handlers = {};
|
|
98
|
+
handlers.toolCall = (data) => this.emit('tool:call', data);
|
|
99
|
+
handlers.toolResult = (data) => this.emit('tool:result', data);
|
|
100
|
+
handlers.toolError = (data) => this.emit('tool:error', data);
|
|
101
|
+
handlers.message = (msg) => this.emit('agent:message', msg);
|
|
102
|
+
agent.on('tool-call', handlers.toolCall);
|
|
103
|
+
agent.on('tool-result', handlers.toolResult);
|
|
104
|
+
agent.on('tool-error', handlers.toolError);
|
|
105
|
+
agent.on('message', handlers.message);
|
|
106
|
+
this._agentForwardingListeners.set(agent, handlers);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
registerPlugin(plugin) { this.pluginManager.register(plugin); return this; }
|
|
111
|
+
registerProvider(name, config) { const { registerProvider } = require('../llm/provider'); registerProvider(name, config); return this; }
|
|
112
|
+
getProviderRegistry() { const { getProviderRegistry } = require('../llm/provider'); return getProviderRegistry(); }
|
|
113
|
+
registerCommand(name, handler, description, getArgumentCompletions) {
|
|
114
|
+
const { getCommandRegistry } = require('./command-registry');
|
|
115
|
+
getCommandRegistry().registerCommand(name, handler, description, getArgumentCompletions);
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
getCommandRegistry() { const { getCommandRegistry } = require('./command-registry'); return getCommandRegistry(); }
|
|
119
|
+
createUIContext(pluginName, chatUI) {
|
|
120
|
+
const { ExtensionUIContext } = require('./ui-extension');
|
|
121
|
+
if (!this._uiExtensions) this._uiExtensions = new Map();
|
|
122
|
+
const ctx = new ExtensionUIContext(chatUI, pluginName);
|
|
123
|
+
this._uiExtensions.set(pluginName, ctx);
|
|
124
|
+
return ctx;
|
|
125
|
+
}
|
|
126
|
+
getUIContext(pluginName) { return this._uiExtensions?.get(pluginName) || null; }
|
|
127
|
+
|
|
128
|
+
async loadPlugin(plugin) { await this.pluginManager.load(plugin); return this; }
|
|
129
|
+
async unloadPlugin(name) { await this.pluginManager.unload(name); return this; }
|
|
130
|
+
async reloadPlugin(name) { await this.pluginManager.reload(name); return this; }
|
|
131
|
+
async reloadAllPlugins() { await this.pluginManager.reloadAll(); return this; }
|
|
132
|
+
async enablePlugin(name) { await this.pluginManager.enable(name); return this; }
|
|
133
|
+
async disablePlugin(name) { await this.pluginManager.disable(name); return this; }
|
|
134
|
+
updatePluginConfig(name, config) { return this.pluginManager.updatePluginConfig(name, config); }
|
|
135
|
+
|
|
136
|
+
registerTool(tool) { this.toolRegistry.register(tool); return this; }
|
|
137
|
+
tool(toolDef) {
|
|
138
|
+
const tool = { ...toolDef };
|
|
139
|
+
if (tool.input && !tool.inputSchema) tool.inputSchema = tool.input;
|
|
140
|
+
delete tool.input;
|
|
141
|
+
this.toolRegistry.register(tool);
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
getTools() { return this.toolRegistry.getAll(); }
|
|
145
|
+
|
|
146
|
+
getMainAgent() { return this._mainAgent; }
|
|
147
|
+
getSystemPrompt() { return this._mainAgent?._buildSystemPrompt?.() || ''; }
|
|
148
|
+
getPluginInstance(name) { return this.pluginManager.get(name) || null; }
|
|
149
|
+
getAgents() { return [...this._agents]; }
|
|
150
|
+
listSessionContexts() { return Array.from(this._sessionContexts.keys()); }
|
|
151
|
+
|
|
152
|
+
getCwd() { return this._cwd; }
|
|
153
|
+
|
|
154
|
+
registerEventDescription(eventType, description, schema = null) { this.pluginManager.registerEventDescription(eventType, description, schema); return this; }
|
|
155
|
+
getEventDescriptions() { return this.pluginManager.getEventDescriptions(); }
|
|
156
|
+
getEventDescription(eventType) { return this.pluginManager.getEventDescription(eventType); }
|
|
157
|
+
|
|
158
|
+
async executeTool(name, args) { return this.toolRegistry.execute(name, args, this); }
|
|
159
|
+
|
|
160
|
+
runWithContext(context, fn) { return asyncLocalStorage.run(context, fn); }
|
|
161
|
+
getExecutionContext() { return asyncLocalStorage.getStore() || null; }
|
|
162
|
+
setExecutionContext() {}
|
|
163
|
+
clearExecutionContext() {}
|
|
164
|
+
|
|
165
|
+
// Session context
|
|
166
|
+
getOrCreateSessionContext(sessionId, options = {}) {
|
|
167
|
+
if (!sessionId) return null;
|
|
168
|
+
let manager = this._sessionContexts.get(sessionId);
|
|
169
|
+
if (manager) return manager;
|
|
170
|
+
const cwd = options.cwd || this._cwd;
|
|
171
|
+
const sessionDir = path.join(cwd, '.foliko', 'sessions');
|
|
172
|
+
const sessionFile = options.sessionFile || path.join(sessionDir, `${sessionId}.jsonl`);
|
|
173
|
+
try {
|
|
174
|
+
if (require('fs').existsSync(sessionFile)) {
|
|
175
|
+
const { SessionManager } = require('../session/session');
|
|
176
|
+
manager = SessionManager.open(sessionFile, sessionDir, cwd);
|
|
177
|
+
}
|
|
178
|
+
} catch { /* ignore */ }
|
|
179
|
+
if (!manager) {
|
|
180
|
+
const { SessionManager } = require('../session/session');
|
|
181
|
+
manager = new SessionManager(cwd, sessionDir, sessionFile, true);
|
|
182
|
+
}
|
|
183
|
+
this._sessionContexts.set(sessionId, manager);
|
|
184
|
+
this.touchSession(sessionId);
|
|
185
|
+
this.emit('session:context-created', { sessionId, manager });
|
|
186
|
+
return manager;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async loadSessionContext(sessionId, options = {}) {
|
|
190
|
+
if (!sessionId) return null;
|
|
191
|
+
let manager = this._sessionContexts.get(sessionId);
|
|
192
|
+
if (manager) return manager;
|
|
193
|
+
const cwd = options.cwd || this._cwd;
|
|
194
|
+
const sessionDir = path.join(cwd, '.foliko', 'sessions');
|
|
195
|
+
const sessionFile = path.join(sessionDir, `${sessionId}.jsonl`);
|
|
196
|
+
try {
|
|
197
|
+
if (require('fs').existsSync(sessionFile)) {
|
|
198
|
+
const { SessionManager } = require('../session/session');
|
|
199
|
+
manager = SessionManager.open(sessionFile, sessionDir, cwd);
|
|
200
|
+
this._sessionContexts.set(sessionId, manager);
|
|
201
|
+
this.emit('session:context-created', { sessionId, manager });
|
|
202
|
+
}
|
|
203
|
+
} catch { /* ignore */ }
|
|
204
|
+
return manager || null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
configureSessionStorage(config) { this._sessionStorageConfig = config; }
|
|
208
|
+
getSessionStorage() { return this._sessionStorageConfig; }
|
|
209
|
+
getSessionContext(sessionId) {
|
|
210
|
+
const ctx = this._sessionContexts.get(sessionId) || null;
|
|
211
|
+
if (ctx) this.touchSession(sessionId);
|
|
212
|
+
return ctx;
|
|
213
|
+
}
|
|
214
|
+
getCurrentSessionContext() {
|
|
215
|
+
const store = sessionStorage.getStore();
|
|
216
|
+
if (!store) return null;
|
|
217
|
+
return store.sessionId ? this._sessionContexts.get(store.sessionId) || null : null;
|
|
218
|
+
}
|
|
219
|
+
getCurrentSessionId() { return sessionStorage.getStore()?.sessionId || null; }
|
|
220
|
+
|
|
221
|
+
runInSession(sessionId, options, fn) {
|
|
222
|
+
if (!sessionId) return fn();
|
|
223
|
+
const manager = this.getOrCreateSessionContext(sessionId, options);
|
|
224
|
+
return sessionStorage.run({ sessionContext: manager, sessionId }, fn);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
destroySessionContext(sessionId) {
|
|
228
|
+
if (this._sessionContexts.has(sessionId)) {
|
|
229
|
+
this._sessionContexts.delete(sessionId);
|
|
230
|
+
this.emit('session:context-destroyed', { sessionId });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setSessionTTL(ttlMs) { this._sessionTTL.setTTL(ttlMs); return this; }
|
|
235
|
+
startSessionCleanup() { this._sessionTTL.start(); return this; }
|
|
236
|
+
stopSessionCleanup() { this._sessionTTL.stop(); return this; }
|
|
237
|
+
touchSession(sessionId) { this._sessionTTL.touch(sessionId); return this; }
|
|
238
|
+
|
|
239
|
+
// Request context
|
|
240
|
+
getRequestContext() { return requestStorage.getStore() || null; }
|
|
241
|
+
runWithRequestContext(context, fn) { return requestStorage.run(context, fn); }
|
|
242
|
+
|
|
243
|
+
async runInContext(requestOptions, sessionId, fn) {
|
|
244
|
+
const { SessionManager } = require('../session/session');
|
|
245
|
+
const sessionCtx = sessionId ? this.getOrCreateSessionContext(sessionId) : null;
|
|
246
|
+
return requestStorage.run(requestOptions, () => {
|
|
247
|
+
if (sessionCtx) return sessionStorage.run({ sessionContext: sessionCtx, sessionId }, fn);
|
|
248
|
+
return fn();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Agent creation
|
|
253
|
+
createAgent(config) {
|
|
254
|
+
const { createAgent } = require('../agent');
|
|
255
|
+
return createAgent(this, config);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
createSessionAgent(sessionId, config = {}) {
|
|
259
|
+
const { createSessionAgent } = require('../agent');
|
|
260
|
+
return createSessionAgent(this, sessionId, config);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
createSubAgent(config) {
|
|
264
|
+
const { createSubAgent } = require('../agent');
|
|
265
|
+
return createSubAgent(this, config);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
syncPromptPartsToSubagents() {
|
|
269
|
+
const { _syncPromptParts } = require('../agent');
|
|
270
|
+
for (const agent of this._agents) {
|
|
271
|
+
if (agent === this._mainAgent || !agent._isSubagent) continue;
|
|
272
|
+
_syncPromptParts(this, agent);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getAIPlugin() { return this.pluginManager.get('ai'); }
|
|
277
|
+
|
|
278
|
+
// Coordinator
|
|
279
|
+
initCoordinatorManager(coordinatorAgent) {
|
|
280
|
+
const { CoordinatorManager } = require('../agent/coordinator');
|
|
281
|
+
if (!this._coordinatorManager) this._coordinatorManager = new CoordinatorManager(this);
|
|
282
|
+
this._coordinatorManager.setCoordinatorAgent(coordinatorAgent);
|
|
283
|
+
return this._coordinatorManager;
|
|
284
|
+
}
|
|
285
|
+
getCoordinatorManager() { return this._coordinatorManager; }
|
|
286
|
+
async createWorker(config) {
|
|
287
|
+
if (!this._coordinatorManager) throw new Error('CoordinatorManager not initialized');
|
|
288
|
+
return this._coordinatorManager.createWorker(config);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_registerBuiltinTools() {}
|
|
292
|
+
|
|
293
|
+
_setReady() {
|
|
294
|
+
this._ready = true;
|
|
295
|
+
if (this._resolveReady) this._resolveReady(this);
|
|
296
|
+
this.emit('framework:ready');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = { Framework, asyncLocalStorage, requestStorage, sessionStorage };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Framework } = require('./framework');
|
|
4
|
+
const { bootstrap, ready, destroy, setCwd, rescanProject } = require('./lifecycle');
|
|
5
|
+
const { FrameworkRegistry } = require('./registry');
|
|
6
|
+
const { loadAgentConfig, loadDefaultPlugins } = require('./loader');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
Framework,
|
|
10
|
+
FrameworkRegistry,
|
|
11
|
+
bootstrap,
|
|
12
|
+
ready,
|
|
13
|
+
destroy,
|
|
14
|
+
setCwd,
|
|
15
|
+
rescanProject,
|
|
16
|
+
loadAgentConfig,
|
|
17
|
+
loadDefaultPlugins,
|
|
18
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework Lifecycle - 生命周期管理
|
|
5
|
+
* bootstrap / ready / destroy / setCwd / rescanProject
|
|
6
|
+
* 目标 ~150 行
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Bootstrap - 使用默认配置启动框架
|
|
13
|
+
* @param {Framework} framework
|
|
14
|
+
* @param {Object} config
|
|
15
|
+
* @returns {Promise<Framework>}
|
|
16
|
+
*/
|
|
17
|
+
async function bootstrap(framework, config = {}) {
|
|
18
|
+
const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/core/default');
|
|
19
|
+
const defaultsPlugin = new DefaultPlugins({ agentDir: config.agentDir || '.foliko' });
|
|
20
|
+
await framework.loadPlugin(defaultsPlugin);
|
|
21
|
+
const agentConfig = defaultsPlugin.getConfig();
|
|
22
|
+
if (config.aiConfig) {
|
|
23
|
+
agentConfig.ai = { ...agentConfig.ai, ...config.aiConfig };
|
|
24
|
+
}
|
|
25
|
+
// 合并 _config.ai(新门面 API 的格式)
|
|
26
|
+
if (config._config?.ai) {
|
|
27
|
+
agentConfig.ai = { ...agentConfig.ai, ...config._config.ai };
|
|
28
|
+
}
|
|
29
|
+
await bootstrapDefaults(framework, { _config: agentConfig, _skipConfigLoad: true });
|
|
30
|
+
framework._setReady();
|
|
31
|
+
framework.startSessionCleanup();
|
|
32
|
+
return framework;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Wait for framework ready
|
|
37
|
+
* @param {Framework} framework
|
|
38
|
+
* @returns {Promise<Framework>}
|
|
39
|
+
*/
|
|
40
|
+
async function ready(framework) {
|
|
41
|
+
if (framework._ready) return framework;
|
|
42
|
+
if (!framework._readyPromise) {
|
|
43
|
+
framework._readyPromise = new Promise(resolve => { framework._resolveReady = resolve; });
|
|
44
|
+
}
|
|
45
|
+
await framework._readyPromise;
|
|
46
|
+
return framework;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Destroy framework
|
|
51
|
+
* @param {Framework} framework
|
|
52
|
+
*/
|
|
53
|
+
async function destroy(framework) {
|
|
54
|
+
// Destroy coordinator
|
|
55
|
+
if (framework._coordinatorManager) {
|
|
56
|
+
framework._coordinatorManager.destroy();
|
|
57
|
+
framework._coordinatorManager = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Destroy all agents
|
|
61
|
+
for (const agent of framework._agents) {
|
|
62
|
+
const listeners = framework._agentForwardingListeners?.get(agent);
|
|
63
|
+
if (listeners) {
|
|
64
|
+
agent.off('tool-call', listeners.toolCall);
|
|
65
|
+
agent.off('tool-result', listeners.toolResult);
|
|
66
|
+
agent.off('tool-error', listeners.toolError);
|
|
67
|
+
agent.off('message', listeners.message);
|
|
68
|
+
framework._agentForwardingListeners.delete(agent);
|
|
69
|
+
}
|
|
70
|
+
if (typeof agent.destroy === 'function') agent.destroy();
|
|
71
|
+
}
|
|
72
|
+
framework._agents = [];
|
|
73
|
+
|
|
74
|
+
// Remove toolRegistry listeners
|
|
75
|
+
if (framework._toolRegistryListeners) {
|
|
76
|
+
for (const { event, handler } of framework._toolRegistryListeners) {
|
|
77
|
+
framework.toolRegistry.off(event, handler);
|
|
78
|
+
}
|
|
79
|
+
framework._toolRegistryListeners = [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
framework.stopSessionCleanup();
|
|
83
|
+
framework._sessionContexts.clear();
|
|
84
|
+
|
|
85
|
+
// Unload all plugins
|
|
86
|
+
const plugins = framework.pluginManager.getAll();
|
|
87
|
+
for (const { name } of plugins) {
|
|
88
|
+
try { await framework.pluginManager.unload(name); }
|
|
89
|
+
catch (err) { framework.logger.warn(`Failed to unload '${name}': ${err.message}`); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
framework.toolRegistry.clear();
|
|
93
|
+
framework.removeAllListeners();
|
|
94
|
+
framework.emit('framework:destroyed');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Set working directory
|
|
99
|
+
* @param {Framework} framework
|
|
100
|
+
* @param {string} newCwd
|
|
101
|
+
* @param {Object} options
|
|
102
|
+
*/
|
|
103
|
+
async function setCwd(framework, newCwd, options = {}) {
|
|
104
|
+
if (framework._changingCwd) throw new Error('setCwd re-entered');
|
|
105
|
+
const oldCwd = framework._cwd;
|
|
106
|
+
const resolved = path.resolve(newCwd);
|
|
107
|
+
if (resolved === oldCwd) return framework;
|
|
108
|
+
|
|
109
|
+
framework._changingCwd = true;
|
|
110
|
+
try {
|
|
111
|
+
framework._cwd = resolved;
|
|
112
|
+
if (options.agentDir) framework._agentDir = options.agentDir;
|
|
113
|
+
if (options.homeAgentDir) framework._homeAgentDir = options.homeAgentDir;
|
|
114
|
+
|
|
115
|
+
framework.pluginManager._setStateFile(resolved);
|
|
116
|
+
|
|
117
|
+
const count = framework._sessionContexts.size;
|
|
118
|
+
for (const sid of Array.from(framework._sessionContexts.keys())) {
|
|
119
|
+
framework.destroySessionContext(sid);
|
|
120
|
+
}
|
|
121
|
+
framework.emit('session:contexts-invalidated', { oldCwd, newCwd: resolved, count });
|
|
122
|
+
|
|
123
|
+
if (options.reload !== false) {
|
|
124
|
+
for (const entry of framework.pluginManager.getAll()) {
|
|
125
|
+
const inst = entry.instance;
|
|
126
|
+
try {
|
|
127
|
+
if (typeof inst?.onCwdChanged === 'function') await inst.onCwdChanged(oldCwd, resolved, framework);
|
|
128
|
+
await framework.pluginManager.reload(entry.name);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
framework.logger.warn(`Plugin ${entry.name} failed during cwd change: ${err.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
framework.emit('cwd:changed', { oldCwd, newCwd: resolved, framework });
|
|
135
|
+
} finally {
|
|
136
|
+
framework._changingCwd = false;
|
|
137
|
+
}
|
|
138
|
+
return framework;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Rescan project - reload plugins and skills
|
|
143
|
+
* @param {Framework} framework
|
|
144
|
+
* @param {Object} options
|
|
145
|
+
*/
|
|
146
|
+
async function rescanProject(framework, options = {}) {
|
|
147
|
+
const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/core/default');
|
|
148
|
+
const { SYSTEM_PLUGINS } = require('../common/constants');
|
|
149
|
+
const keepSet = new Set(options.keepLoaded || Array.from(SYSTEM_PLUGINS));
|
|
150
|
+
const loadList = [];
|
|
151
|
+
|
|
152
|
+
// Unload non-kept plugins using PluginManager.unloadExcept
|
|
153
|
+
const unloadList = await framework.pluginManager.unloadExcept(keepSet);
|
|
154
|
+
|
|
155
|
+
// Reload defaults
|
|
156
|
+
let defaultsPlugin = framework.pluginManager.get('defaults');
|
|
157
|
+
if (!defaultsPlugin) {
|
|
158
|
+
const dp = new DefaultPlugins({ agentDir: framework._agentDir });
|
|
159
|
+
try { await framework.loadPlugin(dp); defaultsPlugin = dp; }
|
|
160
|
+
catch (err) { framework.logger.warn(`rescanProject: load defaults failed: ${err.message}`); }
|
|
161
|
+
} else {
|
|
162
|
+
try { await defaultsPlugin.reload(framework); }
|
|
163
|
+
catch (err) { framework.logger.warn(`rescanProject: reload defaults failed: ${err.message}`); }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const agentConfig = defaultsPlugin
|
|
167
|
+
? (defaultsPlugin.getConfig() || loadAgentConfig(framework, framework._agentDir))
|
|
168
|
+
: loadAgentConfig(framework, framework._agentDir);
|
|
169
|
+
|
|
170
|
+
// Bootstrap defaults
|
|
171
|
+
const beforeNames = new Set(framework.pluginManager.getAll().map(p => p.name));
|
|
172
|
+
try { await bootstrapDefaults(framework, { _config: agentConfig, _skipConfigLoad: true }); }
|
|
173
|
+
catch (err) { framework.logger.warn(`rescanProject: bootstrapDefaults failed: ${err.message}`); }
|
|
174
|
+
for (const name of framework.pluginManager.getAll().map(p => p.name)) {
|
|
175
|
+
if (!beforeNames.has(name)) loadList.push(name);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Sync skills using setSearchDirs
|
|
179
|
+
if (options.reloadSkillManager !== false) {
|
|
180
|
+
const skillManager = framework.pluginManager.get('skill-manager');
|
|
181
|
+
if (skillManager) {
|
|
182
|
+
if (Array.isArray(agentConfig.skillsDirs)) skillManager.setSearchDirs(agentConfig.skillsDirs);
|
|
183
|
+
try { await skillManager.reload(framework); }
|
|
184
|
+
catch (err) { framework.logger.warn(`rescanProject: skill-manager reload failed: ${err.message}`); }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Reload kept plugins (fix: use keepSet, not undefined keepList)
|
|
189
|
+
if (options.reloadKept) {
|
|
190
|
+
for (const name of keepSet) {
|
|
191
|
+
if (!framework.pluginManager.has(name)) continue;
|
|
192
|
+
try { await framework.pluginManager.reload(name); }
|
|
193
|
+
catch (err) { framework.logger.warn(`rescanProject: reload kept ${name} failed: ${err.message}`); }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const result = { unloaded: unloadList, loaded: loadList, keepLoaded: [...keepSet] };
|
|
198
|
+
framework.emit('rescan:complete', result);
|
|
199
|
+
framework.logger.info(`rescanProject: unloaded=${unloadList.length} loaded=${loadList.length} kept=${keepSet.size}`);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { bootstrap, ready, destroy, setCwd, rescanProject };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework Loader - 加载 .foliko/ 配置
|
|
5
|
+
* 目标 ~150 行
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load agent config from .foliko/ directory
|
|
13
|
+
* @param {Framework} framework
|
|
14
|
+
* @param {string} agentDir
|
|
15
|
+
* @returns {Object}
|
|
16
|
+
*/
|
|
17
|
+
function loadAgentConfig(framework, agentDir = '.foliko') {
|
|
18
|
+
const cwd = framework.getCwd ? framework.getCwd() : process.cwd();
|
|
19
|
+
const folikoDir = path.resolve(cwd, agentDir);
|
|
20
|
+
const config = {
|
|
21
|
+
plugins: [],
|
|
22
|
+
skills: [],
|
|
23
|
+
ai: {},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Load plugins.json
|
|
27
|
+
const pluginsPath = path.join(folikoDir, 'plugins.json');
|
|
28
|
+
if (fs.existsSync(pluginsPath)) {
|
|
29
|
+
try {
|
|
30
|
+
const pluginsConfig = JSON.parse(fs.readFileSync(pluginsPath, 'utf-8'));
|
|
31
|
+
if (Array.isArray(pluginsConfig)) {
|
|
32
|
+
config.plugins = pluginsConfig;
|
|
33
|
+
} else if (pluginsConfig.plugins) {
|
|
34
|
+
config.plugins = pluginsConfig.plugins;
|
|
35
|
+
}
|
|
36
|
+
if (pluginsConfig.ai) config.ai = { ...config.ai, ...pluginsConfig.ai };
|
|
37
|
+
if (pluginsConfig.skillsDirs) config.skillsDirs = pluginsConfig.skillsDirs;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
framework.logger?.warn?.('Failed to load plugins.json:', err.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Load agent configs from agents/ directory
|
|
44
|
+
const agentsDir = path.join(folikoDir, 'agents');
|
|
45
|
+
if (fs.existsSync(agentsDir)) {
|
|
46
|
+
try {
|
|
47
|
+
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.json'));
|
|
48
|
+
config.agentFiles = files.map(f => path.join(agentsDir, f));
|
|
49
|
+
} catch { /* ignore */ }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Load skills config
|
|
53
|
+
const skillsDir = path.join(folikoDir, 'skills');
|
|
54
|
+
if (fs.existsSync(skillsDir)) {
|
|
55
|
+
try {
|
|
56
|
+
config.skillsDirs = config.skillsDirs || [];
|
|
57
|
+
config.skillsDirs.push(skillsDir);
|
|
58
|
+
} catch { /* ignore */ }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return config;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Load default plugins based on config
|
|
66
|
+
* @param {Framework} framework
|
|
67
|
+
* @param {Object} config
|
|
68
|
+
* @returns {Promise<string[]>} loaded plugin names
|
|
69
|
+
*/
|
|
70
|
+
async function loadDefaultPlugins(framework, config = {}) {
|
|
71
|
+
const { bootstrapDefaults, DefaultPlugins } = require('../../plugins/core/default');
|
|
72
|
+
const defaultsPlugin = new DefaultPlugins({ agentDir: config.agentDir || '.foliko' });
|
|
73
|
+
await framework.loadPlugin(defaultsPlugin);
|
|
74
|
+
await bootstrapDefaults(framework, { _config: config, _skipConfigLoad: true });
|
|
75
|
+
return framework.pluginManager.getAll().map(p => p.name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { loadAgentConfig, loadDefaultPlugins };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework Registry - 统一注册表
|
|
5
|
+
* 聚合 plugin/tool/command 注册表
|
|
6
|
+
* 目标 ~200 行
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class FrameworkRegistry {
|
|
10
|
+
constructor(framework) {
|
|
11
|
+
this.framework = framework;
|
|
12
|
+
this._plugins = [];
|
|
13
|
+
this._tools = [];
|
|
14
|
+
this._commands = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// === Plugins ===
|
|
18
|
+
registerPlugin(plugin) {
|
|
19
|
+
this._plugins.push(plugin);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getPlugin(name) {
|
|
24
|
+
return this._plugins.find(p => p.name === name || p.constructor?.name === name) || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getAllPlugins() {
|
|
28
|
+
return [...this._plugins];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
unregisterPlugin(name) {
|
|
32
|
+
const idx = this._plugins.findIndex(p => p.name === name);
|
|
33
|
+
if (idx !== -1) this._plugins.splice(idx, 1);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
hasPlugin(name) {
|
|
38
|
+
return this._plugins.some(p => p.name === name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// === Tools ===
|
|
42
|
+
registerTool(tool) {
|
|
43
|
+
this._tools.push(tool);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getTool(name) {
|
|
48
|
+
return this._tools.find(t => t.name === name) || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getAllTools() {
|
|
52
|
+
return [...this._tools];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
unregisterTool(name) {
|
|
56
|
+
const idx = this._tools.findIndex(t => t.name === name);
|
|
57
|
+
if (idx !== -1) this._tools.splice(idx, 1);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
clearTools() {
|
|
62
|
+
this._tools = [];
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// === Commands ===
|
|
67
|
+
registerCommand(name, handler, description) {
|
|
68
|
+
this._commands.set(name, { handler, description });
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getCommand(name) {
|
|
73
|
+
return this._commands.get(name) || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getAllCommands() {
|
|
77
|
+
return Array.from(this._commands.entries()).map(([name, cmd]) => ({ name, ...cmd }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
unregisterCommand(name) {
|
|
81
|
+
this._commands.delete(name);
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { FrameworkRegistry };
|