@nogataka/smart-edit 0.0.14
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 +22 -0
- package/README.md +244 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +7 -0
- package/dist/devtools/generate_prompt_factory.d.ts +5 -0
- package/dist/devtools/generate_prompt_factory.js +114 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +34 -0
- package/dist/interprompt/index.d.ts +2 -0
- package/dist/interprompt/index.js +1 -0
- package/dist/interprompt/jinja_template.d.ts +10 -0
- package/dist/interprompt/jinja_template.js +174 -0
- package/dist/interprompt/multilang_prompt.d.ts +54 -0
- package/dist/interprompt/multilang_prompt.js +302 -0
- package/dist/interprompt/prompt_factory.d.ts +16 -0
- package/dist/interprompt/prompt_factory.js +189 -0
- package/dist/interprompt/util/class_decorators.d.ts +1 -0
- package/dist/interprompt/util/class_decorators.js +1 -0
- package/dist/interprompt/util/index.d.ts +1 -0
- package/dist/interprompt/util/index.js +1 -0
- package/dist/serena/agent.d.ts +118 -0
- package/dist/serena/agent.js +675 -0
- package/dist/serena/agno.d.ts +111 -0
- package/dist/serena/agno.js +278 -0
- package/dist/serena/analytics.d.ts +24 -0
- package/dist/serena/analytics.js +119 -0
- package/dist/serena/cli.d.ts +9 -0
- package/dist/serena/cli.js +731 -0
- package/dist/serena/code_editor.d.ts +42 -0
- package/dist/serena/code_editor.js +239 -0
- package/dist/serena/config/context_mode.d.ts +41 -0
- package/dist/serena/config/context_mode.js +239 -0
- package/dist/serena/config/serena_config.d.ts +134 -0
- package/dist/serena/config/serena_config.js +718 -0
- package/dist/serena/constants.d.ts +18 -0
- package/dist/serena/constants.js +27 -0
- package/dist/serena/dashboard.d.ts +55 -0
- package/dist/serena/dashboard.js +472 -0
- package/dist/serena/generated/generated_prompt_factory.d.ts +27 -0
- package/dist/serena/generated/generated_prompt_factory.js +42 -0
- package/dist/serena/gui_log_viewer.d.ts +41 -0
- package/dist/serena/gui_log_viewer.js +436 -0
- package/dist/serena/mcp.d.ts +118 -0
- package/dist/serena/mcp.js +904 -0
- package/dist/serena/project.d.ts +62 -0
- package/dist/serena/project.js +321 -0
- package/dist/serena/prompt_factory.d.ts +20 -0
- package/dist/serena/prompt_factory.js +42 -0
- package/dist/serena/resources/config/contexts/agent.yml +8 -0
- package/dist/serena/resources/config/contexts/chatgpt.yml +28 -0
- package/dist/serena/resources/config/contexts/codex.yml +27 -0
- package/dist/serena/resources/config/contexts/context.template.yml +11 -0
- package/dist/serena/resources/config/contexts/desktop-app.yml +17 -0
- package/dist/serena/resources/config/contexts/ide-assistant.yml +26 -0
- package/dist/serena/resources/config/contexts/oaicompat-agent.yml +8 -0
- package/dist/serena/resources/config/internal_modes/jetbrains.yml +15 -0
- package/dist/serena/resources/config/modes/editing.yml +112 -0
- package/dist/serena/resources/config/modes/interactive.yml +11 -0
- package/dist/serena/resources/config/modes/mode.template.yml +7 -0
- package/dist/serena/resources/config/modes/no-onboarding.yml +8 -0
- package/dist/serena/resources/config/modes/onboarding.yml +16 -0
- package/dist/serena/resources/config/modes/one-shot.yml +15 -0
- package/dist/serena/resources/config/modes/planning.yml +15 -0
- package/dist/serena/resources/config/prompt_templates/simple_tool_outputs.yml +75 -0
- package/dist/serena/resources/config/prompt_templates/system_prompt.yml +66 -0
- package/dist/serena/resources/dashboard/dashboard.js +815 -0
- package/dist/serena/resources/dashboard/index.html +314 -0
- package/dist/serena/resources/dashboard/jquery.min.js +3 -0
- package/dist/serena/resources/dashboard/serena-icon-16.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-32.png +0 -0
- package/dist/serena/resources/dashboard/serena-icon-48.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs-dark-mode.png +0 -0
- package/dist/serena/resources/dashboard/serena-logs.png +0 -0
- package/dist/serena/resources/project.template.yml +67 -0
- package/dist/serena/resources/serena_config.template.yml +85 -0
- package/dist/serena/symbol.d.ts +199 -0
- package/dist/serena/symbol.js +616 -0
- package/dist/serena/text_utils.d.ts +51 -0
- package/dist/serena/text_utils.js +267 -0
- package/dist/serena/tools/cmd_tools.d.ts +31 -0
- package/dist/serena/tools/cmd_tools.js +48 -0
- package/dist/serena/tools/config_tools.d.ts +53 -0
- package/dist/serena/tools/config_tools.js +176 -0
- package/dist/serena/tools/file_tools.d.ts +231 -0
- package/dist/serena/tools/file_tools.js +511 -0
- package/dist/serena/tools/index.d.ts +7 -0
- package/dist/serena/tools/index.js +7 -0
- package/dist/serena/tools/memory_tools.d.ts +60 -0
- package/dist/serena/tools/memory_tools.js +135 -0
- package/dist/serena/tools/symbol_tools.d.ts +165 -0
- package/dist/serena/tools/symbol_tools.js +362 -0
- package/dist/serena/tools/tools_base.d.ts +162 -0
- package/dist/serena/tools/tools_base.js +378 -0
- package/dist/serena/tools/workflow_tools.d.ts +35 -0
- package/dist/serena/tools/workflow_tools.js +161 -0
- package/dist/serena/util/class_decorators.d.ts +7 -0
- package/dist/serena/util/class_decorators.js +37 -0
- package/dist/serena/util/exception.d.ts +8 -0
- package/dist/serena/util/exception.js +53 -0
- package/dist/serena/util/file_system.d.ts +30 -0
- package/dist/serena/util/file_system.js +352 -0
- package/dist/serena/util/general.d.ts +11 -0
- package/dist/serena/util/general.js +42 -0
- package/dist/serena/util/git.d.ts +11 -0
- package/dist/serena/util/git.js +37 -0
- package/dist/serena/util/inspection.d.ts +45 -0
- package/dist/serena/util/inspection.js +221 -0
- package/dist/serena/util/logging.d.ts +46 -0
- package/dist/serena/util/logging.js +205 -0
- package/dist/serena/util/shell.d.ts +21 -0
- package/dist/serena/util/shell.js +95 -0
- package/dist/serena/util/thread.d.ts +23 -0
- package/dist/serena/util/thread.js +88 -0
- package/dist/serena/version.d.ts +1 -0
- package/dist/serena/version.js +23 -0
- package/dist/solidlsp/language_servers/autoload.d.ts +23 -0
- package/dist/solidlsp/language_servers/autoload.js +25 -0
- package/dist/solidlsp/language_servers/bash_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/bash_language_server.js +64 -0
- package/dist/solidlsp/language_servers/clangd_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/clangd_language_server.js +110 -0
- package/dist/solidlsp/language_servers/clojure_lsp.d.ts +13 -0
- package/dist/solidlsp/language_servers/clojure_lsp.js +137 -0
- package/dist/solidlsp/language_servers/common.d.ts +41 -0
- package/dist/solidlsp/language_servers/common.js +365 -0
- package/dist/solidlsp/language_servers/csharp_language_server.d.ts +21 -0
- package/dist/solidlsp/language_servers/csharp_language_server.js +694 -0
- package/dist/solidlsp/language_servers/dart_language_server.d.ts +10 -0
- package/dist/solidlsp/language_servers/dart_language_server.js +122 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.d.ts +24 -0
- package/dist/solidlsp/language_servers/eclipse_jdtls.js +671 -0
- package/dist/solidlsp/language_servers/erlang_language_server.d.ts +22 -0
- package/dist/solidlsp/language_servers/erlang_language_server.js +327 -0
- package/dist/solidlsp/language_servers/gopls.d.ts +12 -0
- package/dist/solidlsp/language_servers/gopls.js +59 -0
- package/dist/solidlsp/language_servers/intelephense.d.ts +13 -0
- package/dist/solidlsp/language_servers/intelephense.js +121 -0
- package/dist/solidlsp/language_servers/jedi_server.d.ts +18 -0
- package/dist/solidlsp/language_servers/jedi_server.js +234 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/kotlin_language_server.js +474 -0
- package/dist/solidlsp/language_servers/lua_ls.d.ts +18 -0
- package/dist/solidlsp/language_servers/lua_ls.js +319 -0
- package/dist/solidlsp/language_servers/nixd_language_server.d.ts +17 -0
- package/dist/solidlsp/language_servers/nixd_language_server.js +341 -0
- package/dist/solidlsp/language_servers/pyright_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/pyright_server.js +180 -0
- package/dist/solidlsp/language_servers/r_language_server.d.ts +19 -0
- package/dist/solidlsp/language_servers/r_language_server.js +184 -0
- package/dist/solidlsp/language_servers/ruby_common.d.ts +10 -0
- package/dist/solidlsp/language_servers/ruby_common.js +136 -0
- package/dist/solidlsp/language_servers/ruby_lsp.d.ts +18 -0
- package/dist/solidlsp/language_servers/ruby_lsp.js +230 -0
- package/dist/solidlsp/language_servers/rust_analyzer.d.ts +13 -0
- package/dist/solidlsp/language_servers/rust_analyzer.js +96 -0
- package/dist/solidlsp/language_servers/solargraph.d.ts +18 -0
- package/dist/solidlsp/language_servers/solargraph.js +208 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.d.ts +24 -0
- package/dist/solidlsp/language_servers/sourcekit_lsp.js +449 -0
- package/dist/solidlsp/language_servers/terraform_ls.d.ts +13 -0
- package/dist/solidlsp/language_servers/terraform_ls.js +139 -0
- package/dist/solidlsp/language_servers/typescript_language_server.d.ts +20 -0
- package/dist/solidlsp/language_servers/typescript_language_server.js +237 -0
- package/dist/solidlsp/language_servers/vts_language_server.d.ts +13 -0
- package/dist/solidlsp/language_servers/vts_language_server.js +121 -0
- package/dist/solidlsp/language_servers/zls.d.ts +20 -0
- package/dist/solidlsp/language_servers/zls.js +254 -0
- package/dist/solidlsp/ls.d.ts +197 -0
- package/dist/solidlsp/ls.js +507 -0
- package/dist/solidlsp/ls_config.d.ts +43 -0
- package/dist/solidlsp/ls_config.js +157 -0
- package/dist/solidlsp/ls_exceptions.d.ts +5 -0
- package/dist/solidlsp/ls_exceptions.js +14 -0
- package/dist/solidlsp/ls_handler.d.ts +54 -0
- package/dist/solidlsp/ls_handler.js +406 -0
- package/dist/solidlsp/ls_request.d.ts +31 -0
- package/dist/solidlsp/ls_request.js +42 -0
- package/dist/solidlsp/ls_types.d.ts +7 -0
- package/dist/solidlsp/ls_types.js +8 -0
- package/dist/solidlsp/lsp_protocol_handler/server.d.ts +61 -0
- package/dist/solidlsp/lsp_protocol_handler/server.js +68 -0
- package/dist/solidlsp/util/subprocess_util.d.ts +6 -0
- package/dist/solidlsp/util/subprocess_util.js +11 -0
- package/dist/solidlsp/util/zip.d.ts +25 -0
- package/dist/solidlsp/util/zip.js +188 -0
- package/package.json +65 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { ensureDefaultSubprocessOptions } from '../solidlsp/util/subprocess_util.js';
|
|
6
|
+
import { ToolRegistry, ToolMarkerCanEdit, ToolMarkerDoesNotRequireActiveProject, ToolMarkerOptional, ToolMarkerSymbolicEdit, ToolMarkerSymbolicRead } from './tools/tools_base.js';
|
|
7
|
+
import { SerenaConfig, ToolInclusionDefinition, ToolSet, getSerenaManagedInProjectDir, RegisteredTokenCountEstimator } from './config/serena_config.js';
|
|
8
|
+
import { SerenaAgentContext, SerenaAgentMode } from './config/context_mode.js';
|
|
9
|
+
import { createSerenaLogger, MemoryLogHandler } from './util/logging.js';
|
|
10
|
+
import { SerenaPromptFactory } from './prompt_factory.js';
|
|
11
|
+
import { SerenaDashboardAPI } from './dashboard.js';
|
|
12
|
+
import { GuiLogViewer } from './gui_log_viewer.js';
|
|
13
|
+
import { serenaVersion } from './version.js';
|
|
14
|
+
import { ToolUsageStats } from './analytics.js';
|
|
15
|
+
import { LanguageServerCodeEditor } from './code_editor.js';
|
|
16
|
+
import { LanguageServerSymbolRetriever } from './symbol.js';
|
|
17
|
+
import { Project } from './project.js';
|
|
18
|
+
import { ExecuteShellCommandTool } from './tools/cmd_tools.js';
|
|
19
|
+
import { ActivateProjectTool, GetCurrentConfigTool, RemoveProjectTool, SwitchModesTool } from './tools/config_tools.js';
|
|
20
|
+
import { ReadFileTool, CreateTextFileTool, ListDirTool, FindFileTool, ReplaceRegexTool, DeleteLinesTool, ReplaceLinesTool, InsertAtLineTool, SearchForPatternTool } from './tools/file_tools.js';
|
|
21
|
+
import { WriteMemoryTool, ReadMemoryTool, ListMemoriesTool, DeleteMemoryTool } from './tools/memory_tools.js';
|
|
22
|
+
import { RestartLanguageServerTool, GetSymbolsOverviewTool, FindSymbolTool, FindReferencingSymbolsTool, ReplaceSymbolBodyTool, InsertAfterSymbolTool, InsertBeforeSymbolTool } from './tools/symbol_tools.js';
|
|
23
|
+
import { CheckOnboardingPerformedTool, OnboardingTool, ThinkAboutCollectedInformationTool, ThinkAboutTaskAdherenceTool, ThinkAboutWhetherYouAreDoneTool, SummarizeChangesTool, PrepareForNewConversationTool, InitialInstructionsTool } from './tools/workflow_tools.js';
|
|
24
|
+
const { logger: log, memoryHandler: defaultMemoryHandler } = createSerenaLogger({
|
|
25
|
+
name: 'serena.agent',
|
|
26
|
+
emitToConsole: true,
|
|
27
|
+
level: 'info'
|
|
28
|
+
});
|
|
29
|
+
const TOOL_MARKER_CANDIDATES = [
|
|
30
|
+
ToolMarkerCanEdit,
|
|
31
|
+
ToolMarkerDoesNotRequireActiveProject,
|
|
32
|
+
ToolMarkerOptional,
|
|
33
|
+
ToolMarkerSymbolicEdit,
|
|
34
|
+
ToolMarkerSymbolicRead
|
|
35
|
+
];
|
|
36
|
+
const IDE_ASSISTANT_CONTEXT_NAME = 'ide-assistant';
|
|
37
|
+
const DEFAULT_TOOL_CLASSES = [
|
|
38
|
+
ExecuteShellCommandTool,
|
|
39
|
+
ActivateProjectTool,
|
|
40
|
+
RemoveProjectTool,
|
|
41
|
+
SwitchModesTool,
|
|
42
|
+
GetCurrentConfigTool,
|
|
43
|
+
WriteMemoryTool,
|
|
44
|
+
ReadMemoryTool,
|
|
45
|
+
ListMemoriesTool,
|
|
46
|
+
DeleteMemoryTool,
|
|
47
|
+
ReadFileTool,
|
|
48
|
+
CreateTextFileTool,
|
|
49
|
+
ListDirTool,
|
|
50
|
+
FindFileTool,
|
|
51
|
+
ReplaceRegexTool,
|
|
52
|
+
DeleteLinesTool,
|
|
53
|
+
ReplaceLinesTool,
|
|
54
|
+
InsertAtLineTool,
|
|
55
|
+
SearchForPatternTool,
|
|
56
|
+
RestartLanguageServerTool,
|
|
57
|
+
GetSymbolsOverviewTool,
|
|
58
|
+
FindSymbolTool,
|
|
59
|
+
FindReferencingSymbolsTool,
|
|
60
|
+
ReplaceSymbolBodyTool,
|
|
61
|
+
InsertAfterSymbolTool,
|
|
62
|
+
InsertBeforeSymbolTool,
|
|
63
|
+
CheckOnboardingPerformedTool,
|
|
64
|
+
OnboardingTool,
|
|
65
|
+
ThinkAboutCollectedInformationTool,
|
|
66
|
+
ThinkAboutTaskAdherenceTool,
|
|
67
|
+
ThinkAboutWhetherYouAreDoneTool,
|
|
68
|
+
SummarizeChangesTool,
|
|
69
|
+
PrepareForNewConversationTool,
|
|
70
|
+
InitialInstructionsTool
|
|
71
|
+
];
|
|
72
|
+
export class ProjectNotFoundError extends Error {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = 'ProjectNotFoundError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export class LinesRead {
|
|
79
|
+
files = new Map();
|
|
80
|
+
addLinesRead(relativePath, lines) {
|
|
81
|
+
const key = formatLineRange(lines);
|
|
82
|
+
const existing = this.files.get(relativePath) ?? new Set();
|
|
83
|
+
existing.add(key);
|
|
84
|
+
this.files.set(relativePath, existing);
|
|
85
|
+
}
|
|
86
|
+
wereLinesRead(relativePath, lines) {
|
|
87
|
+
const key = formatLineRange(lines);
|
|
88
|
+
const ranges = this.files.get(relativePath);
|
|
89
|
+
if (!ranges) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return ranges.has(key);
|
|
93
|
+
}
|
|
94
|
+
invalidateLinesRead(relativePath) {
|
|
95
|
+
this.files.delete(relativePath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export class MemoriesManager {
|
|
99
|
+
memoryDir;
|
|
100
|
+
constructor(projectRoot) {
|
|
101
|
+
this.memoryDir = path.join(getSerenaManagedInProjectDir(projectRoot), 'memories');
|
|
102
|
+
fs.mkdirSync(this.memoryDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
resolveMemoryPath(name) {
|
|
105
|
+
const normalized = name.replace(/\.md$/iu, '');
|
|
106
|
+
return path.join(this.memoryDir, `${normalized}.md`);
|
|
107
|
+
}
|
|
108
|
+
loadMemory(name) {
|
|
109
|
+
const memoryPath = this.resolveMemoryPath(name);
|
|
110
|
+
if (!fs.existsSync(memoryPath)) {
|
|
111
|
+
return `Memory file ${name} not found, consider creating it with the write_memory tool if you need it.`;
|
|
112
|
+
}
|
|
113
|
+
return fs.readFileSync(memoryPath, { encoding: 'utf-8' });
|
|
114
|
+
}
|
|
115
|
+
saveMemory(name, content) {
|
|
116
|
+
const memoryPath = this.resolveMemoryPath(name);
|
|
117
|
+
fs.writeFileSync(memoryPath, content, { encoding: 'utf-8' });
|
|
118
|
+
return `Memory ${name} written.`;
|
|
119
|
+
}
|
|
120
|
+
listMemories() {
|
|
121
|
+
return fs
|
|
122
|
+
.readdirSync(this.memoryDir, { withFileTypes: true })
|
|
123
|
+
.filter((entry) => entry.isFile())
|
|
124
|
+
.map((entry) => entry.name.replace(/\.md$/iu, ''));
|
|
125
|
+
}
|
|
126
|
+
deleteMemory(name) {
|
|
127
|
+
const memoryPath = this.resolveMemoryPath(name);
|
|
128
|
+
if (!fs.existsSync(memoryPath)) {
|
|
129
|
+
throw new Error(`Memory ${name} not found.`);
|
|
130
|
+
}
|
|
131
|
+
fs.unlinkSync(memoryPath);
|
|
132
|
+
return `Memory ${name} deleted.`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
class AvailableTools {
|
|
136
|
+
tools;
|
|
137
|
+
toolNames;
|
|
138
|
+
toolMarkerNames;
|
|
139
|
+
constructor(tools) {
|
|
140
|
+
this.tools = Array.from(tools);
|
|
141
|
+
this.toolNames = this.tools.map((tool) => tool.getName());
|
|
142
|
+
this.toolMarkerNames = new Set();
|
|
143
|
+
for (const marker of TOOL_MARKER_CANDIDATES) {
|
|
144
|
+
if (this.tools.some((tool) => tool.hasMarker(marker))) {
|
|
145
|
+
this.toolMarkerNames.add(marker);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
get size() {
|
|
150
|
+
return this.tools.length;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
class SerializedTaskExecutor {
|
|
154
|
+
queue = Promise.resolve();
|
|
155
|
+
index = 1;
|
|
156
|
+
issue(task, metadata, timeoutLabelLogger = log) {
|
|
157
|
+
const taskName = `Task-${this.index++}[${metadata?.name ?? task.name ?? 'anonymous'}]`;
|
|
158
|
+
let resolveFn;
|
|
159
|
+
let rejectFn;
|
|
160
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
161
|
+
resolveFn = resolve;
|
|
162
|
+
rejectFn = reject;
|
|
163
|
+
});
|
|
164
|
+
const wrapped = async () => {
|
|
165
|
+
timeoutLabelLogger.info(`Scheduling ${taskName}`);
|
|
166
|
+
const start = Date.now();
|
|
167
|
+
try {
|
|
168
|
+
const value = await Promise.resolve(task());
|
|
169
|
+
resolveFn?.(value);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
rejectFn?.(error);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
const elapsed = Date.now() - start;
|
|
176
|
+
timeoutLabelLogger.info(`${taskName} finished in ${elapsed} ms`);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
this.queue = this.queue.then(wrapped).catch((error) => {
|
|
180
|
+
timeoutLabelLogger.error(`Error executing ${taskName}`, error);
|
|
181
|
+
});
|
|
182
|
+
return new AgentTaskHandleImpl(resultPromise, taskName);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
class AgentTaskHandleImpl {
|
|
186
|
+
promise;
|
|
187
|
+
taskName;
|
|
188
|
+
constructor(promise, taskName) {
|
|
189
|
+
this.promise = promise;
|
|
190
|
+
this.taskName = taskName;
|
|
191
|
+
}
|
|
192
|
+
async result(options = {}) {
|
|
193
|
+
const { timeout } = options;
|
|
194
|
+
if (timeout === undefined || timeout === null) {
|
|
195
|
+
return this.promise;
|
|
196
|
+
}
|
|
197
|
+
return withTimeout(this.promise, timeout, this.taskName);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export class SerenaAgent {
|
|
201
|
+
serenaConfig;
|
|
202
|
+
promptFactory;
|
|
203
|
+
languageServer = null;
|
|
204
|
+
memoriesManager = null;
|
|
205
|
+
linesRead = null;
|
|
206
|
+
projectActivationCallback;
|
|
207
|
+
taskExecutor = new SerializedTaskExecutor();
|
|
208
|
+
memoryLogHandler;
|
|
209
|
+
toolRegistry = new ToolRegistry();
|
|
210
|
+
_context;
|
|
211
|
+
_modes;
|
|
212
|
+
_allTools = new Map();
|
|
213
|
+
_activeTools = new Map();
|
|
214
|
+
_exposedTools = new AvailableTools([]);
|
|
215
|
+
_baseToolSet;
|
|
216
|
+
_activeProject = null;
|
|
217
|
+
_dashboardApi = null;
|
|
218
|
+
_dashboardThread = null;
|
|
219
|
+
_dashboardPort = null;
|
|
220
|
+
_guiLogViewer = null;
|
|
221
|
+
_toolUsageStats = null;
|
|
222
|
+
disposed = false;
|
|
223
|
+
constructor(options = {}) {
|
|
224
|
+
this.serenaConfig = options.serenaConfig ?? SerenaConfig.fromConfigFile();
|
|
225
|
+
this.memoryLogHandler = options.memoryLogHandler ?? defaultMemoryHandler ?? new MemoryLogHandler();
|
|
226
|
+
this.projectActivationCallback = options.projectActivationCallback;
|
|
227
|
+
this._context = options.context ?? SerenaAgentContext.loadDefault();
|
|
228
|
+
this._modes = options.modes ?? SerenaAgentMode.loadDefaultModes();
|
|
229
|
+
this.promptFactory = new SerenaPromptFactory();
|
|
230
|
+
this.instantiateAllTools();
|
|
231
|
+
this._baseToolSet = this.computeBaseToolSet(options.project ?? null);
|
|
232
|
+
this._exposedTools = new AvailableTools(Array.from(this._allTools.values()).filter((tool) => this._baseToolSet.includesName(tool.getName())));
|
|
233
|
+
if (this.serenaConfig.recordToolUsageStats) {
|
|
234
|
+
this._toolUsageStats = new ToolUsageStats(this.serenaConfig.tokenCountEstimator ?? RegisteredTokenCountEstimator.TIKTOKEN_GPT4O);
|
|
235
|
+
log.info(`Will record tool usage statistics with token count estimator: ${this._toolUsageStats.tokenEstimatorName}.`);
|
|
236
|
+
}
|
|
237
|
+
if (this.serenaConfig.webDashboard) {
|
|
238
|
+
const dashboardApi = new SerenaDashboardAPI(this.memoryLogHandler, this._exposedTools.toolNames, this, {
|
|
239
|
+
shutdownCallback: () => this.dispose(),
|
|
240
|
+
toolUsageStats: this._toolUsageStats
|
|
241
|
+
});
|
|
242
|
+
this._dashboardApi = dashboardApi;
|
|
243
|
+
void dashboardApi
|
|
244
|
+
.runInThread()
|
|
245
|
+
.then(([thread, port]) => {
|
|
246
|
+
this._dashboardThread = thread;
|
|
247
|
+
this._dashboardPort = port;
|
|
248
|
+
if (this.serenaConfig.webDashboardOpenOnLaunch && port > 0) {
|
|
249
|
+
this.openDashboard(`http://127.0.0.1:${port}/dashboard/index.html`);
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
.catch((error) => {
|
|
253
|
+
this._dashboardApi = null;
|
|
254
|
+
log.warn('Failed to start Serena dashboard.', error instanceof Error ? error : undefined);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (this.serenaConfig.guiLogWindowEnabled) {
|
|
258
|
+
if (process.platform === 'darwin') {
|
|
259
|
+
log.warn('GUI log window is not supported on macOS');
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
this._guiLogViewer = new GuiLogViewer('dashboard', {
|
|
263
|
+
title: 'Serena Logs',
|
|
264
|
+
memoryLogHandler: this.memoryLogHandler,
|
|
265
|
+
autoOpen: false
|
|
266
|
+
});
|
|
267
|
+
void this._guiLogViewer.start().catch((error) => {
|
|
268
|
+
log.warn('Failed to start GUI log viewer', error instanceof Error ? error : undefined);
|
|
269
|
+
});
|
|
270
|
+
this._guiLogViewer.setToolNames(this._exposedTools.toolNames);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
log.info(`Starting Serena server (version=${serenaVersion()}, process id=${process.pid}, parent process id=${process.ppid})`);
|
|
274
|
+
log.info(`Configuration file: ${this.serenaConfig.configFilePath ?? '(not persisted)'}`);
|
|
275
|
+
log.info(`Available projects: ${this.serenaConfig.projectNames.join(', ') || '(none)'}`);
|
|
276
|
+
log.info(`Loaded tools (${this._allTools.size}): ${Array.from(this._allTools.values())
|
|
277
|
+
.map((tool) => tool.getName())
|
|
278
|
+
.join(', ')}`);
|
|
279
|
+
this.checkShellSettings();
|
|
280
|
+
this._updateActiveTools();
|
|
281
|
+
log.info(`Number of exposed tools: ${this._exposedTools.size}`);
|
|
282
|
+
if (options.project) {
|
|
283
|
+
this.activateProjectFromPathOrName(options.project).catch((error) => {
|
|
284
|
+
log.error(`Error activating project '${options.project}' at startup`, error instanceof Error ? error : undefined);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
get context() {
|
|
289
|
+
return this._context;
|
|
290
|
+
}
|
|
291
|
+
getContext() {
|
|
292
|
+
return this._context;
|
|
293
|
+
}
|
|
294
|
+
getToolDescriptionOverride(toolName) {
|
|
295
|
+
return this._context.toolDescriptionOverrides[toolName] ?? null;
|
|
296
|
+
}
|
|
297
|
+
get toolUsageStats() {
|
|
298
|
+
return this._toolUsageStats;
|
|
299
|
+
}
|
|
300
|
+
get toolUsageStatsEnabled() {
|
|
301
|
+
return this._toolUsageStats !== null;
|
|
302
|
+
}
|
|
303
|
+
getActiveProject() {
|
|
304
|
+
return this._activeProject;
|
|
305
|
+
}
|
|
306
|
+
getActiveProjectOrThrow() {
|
|
307
|
+
const project = this.getActiveProject();
|
|
308
|
+
if (!project) {
|
|
309
|
+
throw new Error('No active project. Please activate a project first.');
|
|
310
|
+
}
|
|
311
|
+
return project;
|
|
312
|
+
}
|
|
313
|
+
getProjectRoot() {
|
|
314
|
+
const project = this.getActiveProjectOrThrow();
|
|
315
|
+
return project.projectRoot;
|
|
316
|
+
}
|
|
317
|
+
getActiveToolNames() {
|
|
318
|
+
return Array.from(this._activeTools.values())
|
|
319
|
+
.map((tool) => tool.getName())
|
|
320
|
+
.sort();
|
|
321
|
+
}
|
|
322
|
+
getActiveToolClasses() {
|
|
323
|
+
return Array.from(this._activeTools.keys());
|
|
324
|
+
}
|
|
325
|
+
getExposedToolInstances() {
|
|
326
|
+
return [...this._exposedTools.tools];
|
|
327
|
+
}
|
|
328
|
+
toolIsActive(toolClass) {
|
|
329
|
+
if (typeof toolClass === 'string') {
|
|
330
|
+
return this.getActiveToolNames().includes(toolClass);
|
|
331
|
+
}
|
|
332
|
+
return this._activeTools.has(toolClass);
|
|
333
|
+
}
|
|
334
|
+
setModes(modes) {
|
|
335
|
+
this._modes = [...modes];
|
|
336
|
+
this._updateActiveTools();
|
|
337
|
+
log.info(`Set modes to ${this._modes.map((mode) => mode.name).join(', ')}`);
|
|
338
|
+
}
|
|
339
|
+
getActiveModes() {
|
|
340
|
+
return [...this._modes];
|
|
341
|
+
}
|
|
342
|
+
createSystemPrompt() {
|
|
343
|
+
log.info('Generating system prompt with available_tools=(see exposed tools), available_markers=%s', [
|
|
344
|
+
...this._exposedTools.toolMarkerNames
|
|
345
|
+
]);
|
|
346
|
+
const systemPrompt = this.promptFactory.createSystemPrompt({
|
|
347
|
+
contextSystemPrompt: this.formatPrompt(this._context.prompt),
|
|
348
|
+
modeSystemPrompts: this._modes.map((mode) => this.formatPrompt(mode.prompt)),
|
|
349
|
+
availableTools: this._exposedTools.toolNames,
|
|
350
|
+
availableMarkers: this._exposedTools.toolMarkerNames
|
|
351
|
+
});
|
|
352
|
+
log.info(`System prompt:\n${systemPrompt}`);
|
|
353
|
+
return systemPrompt;
|
|
354
|
+
}
|
|
355
|
+
issueTask(task, metadata) {
|
|
356
|
+
return this.taskExecutor.issue(task, metadata);
|
|
357
|
+
}
|
|
358
|
+
async executeTask(task) {
|
|
359
|
+
const future = this.issueTask(task);
|
|
360
|
+
return future.result();
|
|
361
|
+
}
|
|
362
|
+
isUsingLanguageServer() {
|
|
363
|
+
return !this.serenaConfig.jetbrains;
|
|
364
|
+
}
|
|
365
|
+
isLanguageServerRunning() {
|
|
366
|
+
return this.languageServer?.isRunning() ?? false;
|
|
367
|
+
}
|
|
368
|
+
resetLanguageServer() {
|
|
369
|
+
const toolTimeout = this.serenaConfig.toolTimeout;
|
|
370
|
+
const lsTimeout = toolTimeout === undefined || toolTimeout === null || toolTimeout < 0
|
|
371
|
+
? null
|
|
372
|
+
: toolTimeout < 10
|
|
373
|
+
? (() => {
|
|
374
|
+
throw new Error(`Tool timeout must be at least 10 seconds, but is ${toolTimeout} seconds`);
|
|
375
|
+
})()
|
|
376
|
+
: toolTimeout - 5;
|
|
377
|
+
if (this.isLanguageServerRunning() && this.languageServer) {
|
|
378
|
+
log.info(`Stopping the current language server at ${this.languageServer.getRepositoryRootPath()} ...`);
|
|
379
|
+
this.languageServer.stop();
|
|
380
|
+
this.languageServer = null;
|
|
381
|
+
}
|
|
382
|
+
const project = this.getActiveProjectOrThrow();
|
|
383
|
+
this.languageServer = project.createLanguageServer({
|
|
384
|
+
logLevel: this.serenaConfig.logLevel,
|
|
385
|
+
lsTimeout,
|
|
386
|
+
traceLspCommunication: this.serenaConfig.traceLspCommunication,
|
|
387
|
+
lsSpecificSettings: this.serenaConfig.lsSpecificSettings
|
|
388
|
+
});
|
|
389
|
+
log.info(`Starting the language server for ${resolveProjectName(project)}`);
|
|
390
|
+
this.languageServer.start();
|
|
391
|
+
if (!this.languageServer.isRunning()) {
|
|
392
|
+
throw new Error(`Failed to start the language server for ${resolveProjectName(project)} at ${project.projectRoot}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
activateProjectFromPathOrName(projectRootOrName) {
|
|
396
|
+
const project = this.loadProjectFromPathOrName(projectRootOrName, true);
|
|
397
|
+
if (!project) {
|
|
398
|
+
throw new ProjectNotFoundError(`Project '${projectRootOrName}' not found: Not a valid project name or directory. Existing project names: ${this.serenaConfig.projectNames.join(', ')}`);
|
|
399
|
+
}
|
|
400
|
+
this.activateProject(project);
|
|
401
|
+
return Promise.resolve(project);
|
|
402
|
+
}
|
|
403
|
+
markFileModified(relativePath) {
|
|
404
|
+
this.linesRead?.invalidateLinesRead(relativePath);
|
|
405
|
+
}
|
|
406
|
+
recordToolUsageIfEnabled(input, toolResult, tool) {
|
|
407
|
+
if (!this._toolUsageStats) {
|
|
408
|
+
log.debug(`Tool usage statistics recording is disabled, not recording usage of '${tool.getName()}'.`);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const inputStr = JSON.stringify(input);
|
|
412
|
+
const outputStr = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
|
|
413
|
+
log.debug(`Recording tool usage for tool '${tool.getName()}'`);
|
|
414
|
+
this._toolUsageStats.recordToolUsage(tool.getName(), inputStr, outputStr);
|
|
415
|
+
}
|
|
416
|
+
getCurrentConfigOverview() {
|
|
417
|
+
const lines = [];
|
|
418
|
+
lines.push('Current configuration:');
|
|
419
|
+
lines.push(`Serena version: ${serenaVersion()}`);
|
|
420
|
+
lines.push(`Loglevel: ${this.serenaConfig.logLevel}, trace_lsp_communication=${this.serenaConfig.traceLspCommunication}`);
|
|
421
|
+
const project = this.getActiveProject();
|
|
422
|
+
if (project) {
|
|
423
|
+
lines.push(`Active project: ${resolveProjectName(project)}`);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
lines.push('No active project');
|
|
427
|
+
}
|
|
428
|
+
lines.push(`Available projects:\n${this.serenaConfig.projectNames.join('\n') || '(none)'}`);
|
|
429
|
+
lines.push(`Active context: ${this._context.name}`);
|
|
430
|
+
const activeModeNames = this.getActiveModes().map((mode) => mode.name);
|
|
431
|
+
lines.push(`Active modes: ${activeModeNames.join(', ') || '(none)'}`);
|
|
432
|
+
const inactiveModes = SerenaAgentMode.listRegisteredModeNames().filter((name) => !activeModeNames.includes(name));
|
|
433
|
+
if (inactiveModes.length > 0) {
|
|
434
|
+
lines.push(`Available but not active modes: ${inactiveModes.join(', ')}`);
|
|
435
|
+
}
|
|
436
|
+
lines.push('Active tools (after all exclusions from the project, context, and modes):');
|
|
437
|
+
lines.push(chunkedList(this.getActiveToolNames(), 4));
|
|
438
|
+
const allToolNames = Array.from(this._allTools.values())
|
|
439
|
+
.map((tool) => tool.getName())
|
|
440
|
+
.sort();
|
|
441
|
+
const inactiveToolNames = allToolNames.filter((tool) => !this.getActiveToolNames().includes(tool));
|
|
442
|
+
if (inactiveToolNames.length > 0) {
|
|
443
|
+
lines.push('Available but not active tools:');
|
|
444
|
+
lines.push(chunkedList(inactiveToolNames, 4));
|
|
445
|
+
}
|
|
446
|
+
return lines.join('\n') + '\n';
|
|
447
|
+
}
|
|
448
|
+
getTool(toolClass) {
|
|
449
|
+
const tool = this._allTools.get(toolClass);
|
|
450
|
+
if (!tool) {
|
|
451
|
+
throw new Error(`Tool ${toolClass.name} is not registered.`);
|
|
452
|
+
}
|
|
453
|
+
return tool;
|
|
454
|
+
}
|
|
455
|
+
getToolByName(toolName) {
|
|
456
|
+
const toolClass = this.toolRegistry.getToolClassByName(toolName);
|
|
457
|
+
return this.getTool(toolClass);
|
|
458
|
+
}
|
|
459
|
+
printToolOverview() {
|
|
460
|
+
this.toolRegistry.printToolOverview(this._activeTools.values());
|
|
461
|
+
}
|
|
462
|
+
createLanguageServerSymbolRetriever() {
|
|
463
|
+
if (!this.isUsingLanguageServer() || !this.languageServer) {
|
|
464
|
+
throw new Error('Cannot create LanguageServerSymbolRetriever; agent is not using a language server.');
|
|
465
|
+
}
|
|
466
|
+
return new LanguageServerSymbolRetriever(this.languageServer, this);
|
|
467
|
+
}
|
|
468
|
+
createCodeEditor() {
|
|
469
|
+
if (!this.isUsingLanguageServer() || !this.languageServer) {
|
|
470
|
+
throw new Error('Cannot create CodeEditor; agent is not using a language server.');
|
|
471
|
+
}
|
|
472
|
+
const retriever = this.createLanguageServerSymbolRetriever();
|
|
473
|
+
return new LanguageServerCodeEditor(retriever, this);
|
|
474
|
+
}
|
|
475
|
+
dispose() {
|
|
476
|
+
if (this.disposed) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
this.disposed = true;
|
|
480
|
+
log.info('SerenaAgent is shutting down ...');
|
|
481
|
+
if (this.languageServer?.isRunning()) {
|
|
482
|
+
log.info('Stopping the language server ...');
|
|
483
|
+
this.languageServer.saveCache();
|
|
484
|
+
this.languageServer.stop();
|
|
485
|
+
}
|
|
486
|
+
if (this._guiLogViewer) {
|
|
487
|
+
void this._guiLogViewer.stop().catch((error) => {
|
|
488
|
+
log.warn('Failed to stop GUI log viewer', error instanceof Error ? error : undefined);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
this._dashboardThread?.stop();
|
|
492
|
+
this._dashboardThread = null;
|
|
493
|
+
this._dashboardApi = null;
|
|
494
|
+
}
|
|
495
|
+
instantiateAllTools() {
|
|
496
|
+
if (this.toolRegistry.getAllToolClasses().length === 0) {
|
|
497
|
+
this.toolRegistry.registerMany(DEFAULT_TOOL_CLASSES);
|
|
498
|
+
}
|
|
499
|
+
for (const toolClass of this.toolRegistry.getAllToolClasses()) {
|
|
500
|
+
const toolInstance = new toolClass(this);
|
|
501
|
+
this._allTools.set(toolClass, toolInstance);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
computeBaseToolSet(initialProject) {
|
|
505
|
+
const definitions = [this.serenaConfig, this._context];
|
|
506
|
+
if (this._context.name === IDE_ASSISTANT_CONTEXT_NAME) {
|
|
507
|
+
definitions.push(...this.ideAssistantContextToolInclusionDefinitions(initialProject));
|
|
508
|
+
}
|
|
509
|
+
if (this.serenaConfig.jetbrains) {
|
|
510
|
+
definitions.push(SerenaAgentMode.fromNameInternal('jetbrains'));
|
|
511
|
+
}
|
|
512
|
+
return ToolSet.default().apply(...definitions);
|
|
513
|
+
}
|
|
514
|
+
ideAssistantContextToolInclusionDefinitions(projectRootOrName) {
|
|
515
|
+
const definitions = [];
|
|
516
|
+
if (!projectRootOrName) {
|
|
517
|
+
return definitions;
|
|
518
|
+
}
|
|
519
|
+
const project = this.loadProjectFromPathOrName(projectRootOrName, false);
|
|
520
|
+
if (!project) {
|
|
521
|
+
return definitions;
|
|
522
|
+
}
|
|
523
|
+
definitions.push(new ToolInclusionDefinition({
|
|
524
|
+
excludedTools: [ActivateProjectTool.getNameFromCls(), GetCurrentConfigTool.getNameFromCls()]
|
|
525
|
+
}));
|
|
526
|
+
definitions.push(project.projectConfig);
|
|
527
|
+
return definitions;
|
|
528
|
+
}
|
|
529
|
+
activateProject(project) {
|
|
530
|
+
log.info(`Activating ${resolveProjectName(project)} at ${project.projectRoot}`);
|
|
531
|
+
this._activeProject = project;
|
|
532
|
+
this._updateActiveTools();
|
|
533
|
+
this.memoriesManager = new MemoriesManager(project.projectRoot);
|
|
534
|
+
this.linesRead = new LinesRead();
|
|
535
|
+
if (this.isUsingLanguageServer()) {
|
|
536
|
+
this.issueTask(() => this.resetLanguageServer(), { name: 'LanguageServerInitialization' });
|
|
537
|
+
}
|
|
538
|
+
this.projectActivationCallback?.();
|
|
539
|
+
}
|
|
540
|
+
loadProjectFromPathOrName(projectRootOrName, autogenerate) {
|
|
541
|
+
const registered = this.resolveRegisteredProject(projectRootOrName);
|
|
542
|
+
if (registered) {
|
|
543
|
+
return materializeProject(registered);
|
|
544
|
+
}
|
|
545
|
+
if (autogenerate && fs.existsSync(projectRootOrName) && fs.statSync(projectRootOrName).isDirectory()) {
|
|
546
|
+
const newProject = this.serenaConfig.addProjectFromPath(projectRootOrName);
|
|
547
|
+
return materializeProject(newProject);
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
resolveRegisteredProject(projectRootOrName) {
|
|
552
|
+
const byName = this.serenaConfig.projects.filter((project) => project.projectName === projectRootOrName);
|
|
553
|
+
if (byName.length === 1) {
|
|
554
|
+
return byName[0];
|
|
555
|
+
}
|
|
556
|
+
if (byName.length > 1) {
|
|
557
|
+
throw new Error(`Multiple projects found with name '${projectRootOrName}'. Please activate it by location instead. Locations: ${byName
|
|
558
|
+
.map((p) => p.projectRoot)
|
|
559
|
+
.join(', ')}`);
|
|
560
|
+
}
|
|
561
|
+
const resolved = path.resolve(projectRootOrName);
|
|
562
|
+
for (const project of this.serenaConfig.projects) {
|
|
563
|
+
if (project.matchesRootPath(resolved)) {
|
|
564
|
+
return project;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
checkShellSettings() {
|
|
570
|
+
if (process.platform !== 'win32') {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const comspec = process.env.COMSPEC ?? '';
|
|
574
|
+
if (comspec.toLowerCase().includes('bash')) {
|
|
575
|
+
process.env.COMSPEC = '';
|
|
576
|
+
log.info(`Adjusting COMSPEC environment variable to use the default shell instead of '${comspec}'`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
updateGuiLogViewerToolNames() {
|
|
580
|
+
if (this._guiLogViewer) {
|
|
581
|
+
this._guiLogViewer.setToolNames(this._exposedTools.toolNames);
|
|
582
|
+
}
|
|
583
|
+
this._dashboardApi?.setToolNames(this._exposedTools.toolNames);
|
|
584
|
+
}
|
|
585
|
+
openDashboard(url) {
|
|
586
|
+
try {
|
|
587
|
+
if (process.platform === 'darwin') {
|
|
588
|
+
spawn('open', [url], ensureDefaultSubprocessOptions({ detached: true, stdio: 'ignore' })).unref();
|
|
589
|
+
}
|
|
590
|
+
else if (process.platform === 'win32') {
|
|
591
|
+
spawn('cmd', ['/c', 'start', '', url], ensureDefaultSubprocessOptions({ detached: true, stdio: 'ignore' })).unref();
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
spawn('xdg-open', [url], ensureDefaultSubprocessOptions({ detached: true, stdio: 'ignore' })).unref();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
log.warn(`Failed to open dashboard automatically. Please open ${url} manually.`, error);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
_updateActiveTools() {
|
|
602
|
+
let toolSet = this._baseToolSet.apply(...this._modes);
|
|
603
|
+
if (this._activeProject) {
|
|
604
|
+
toolSet = toolSet.apply(this._activeProject.projectConfig);
|
|
605
|
+
if (this._activeProject.projectConfig.readOnly) {
|
|
606
|
+
toolSet = toolSet.withoutEditingTools();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
this._activeTools = new Map(Array.from(this._allTools.entries()).filter(([, tool]) => toolSet.includesName(tool.getName())));
|
|
610
|
+
log.info(`Active tools (${this._activeTools.size}): ${this.getActiveToolNames().join(', ')}`);
|
|
611
|
+
this.updateGuiLogViewerToolNames();
|
|
612
|
+
}
|
|
613
|
+
formatPrompt(template) {
|
|
614
|
+
const replacements = {
|
|
615
|
+
available_tools: this._exposedTools.toolNames.join(', '),
|
|
616
|
+
available_markers: Array.from(this._exposedTools.toolMarkerNames).join(', ')
|
|
617
|
+
};
|
|
618
|
+
return template.replace(/{{\s*([a-zA-Z_]+)\s*}}/g, (_match, key) => replacements[key] ?? '');
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function materializeProject(registered) {
|
|
622
|
+
if (registered.hasProjectInstance()) {
|
|
623
|
+
const instance = registered.getProjectInstance();
|
|
624
|
+
return ensureProjectHasLanguageServer(instance, registered);
|
|
625
|
+
}
|
|
626
|
+
const project = new Project({
|
|
627
|
+
projectRoot: registered.projectRoot,
|
|
628
|
+
projectConfig: registered.projectConfig
|
|
629
|
+
});
|
|
630
|
+
registered.attachProjectInstance(project);
|
|
631
|
+
return project;
|
|
632
|
+
}
|
|
633
|
+
function ensureProjectHasLanguageServer(project, registered) {
|
|
634
|
+
if (typeof project.createLanguageServer === 'function') {
|
|
635
|
+
return project;
|
|
636
|
+
}
|
|
637
|
+
const fallback = new Project({
|
|
638
|
+
projectRoot: registered.projectRoot,
|
|
639
|
+
projectConfig: registered.projectConfig
|
|
640
|
+
});
|
|
641
|
+
registered.attachProjectInstance(fallback);
|
|
642
|
+
return fallback;
|
|
643
|
+
}
|
|
644
|
+
function resolveProjectName(project) {
|
|
645
|
+
return project.projectName ?? project.projectConfig.projectName;
|
|
646
|
+
}
|
|
647
|
+
function chunkedList(values, chunkSize) {
|
|
648
|
+
if (values.length === 0) {
|
|
649
|
+
return ' (none)';
|
|
650
|
+
}
|
|
651
|
+
const chunks = [];
|
|
652
|
+
for (let i = 0; i < values.length; i += chunkSize) {
|
|
653
|
+
chunks.push(` ${values.slice(i, i + chunkSize).join(', ')}`);
|
|
654
|
+
}
|
|
655
|
+
return chunks.join('\n');
|
|
656
|
+
}
|
|
657
|
+
function formatLineRange([start, end]) {
|
|
658
|
+
return `${start}:${end}`;
|
|
659
|
+
}
|
|
660
|
+
function withTimeout(promise, timeoutMs, taskName) {
|
|
661
|
+
return new Promise((resolve, reject) => {
|
|
662
|
+
const timer = globalThis.setTimeout(() => {
|
|
663
|
+
reject(new Error(`Timeout waiting for ${taskName} after ${timeoutMs} ms`));
|
|
664
|
+
}, timeoutMs);
|
|
665
|
+
promise
|
|
666
|
+
.then((value) => {
|
|
667
|
+
globalThis.clearTimeout(timer);
|
|
668
|
+
resolve(value);
|
|
669
|
+
})
|
|
670
|
+
.catch((error) => {
|
|
671
|
+
globalThis.clearTimeout(timer);
|
|
672
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
}
|