@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,436 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { parse as parseUrl } from 'node:url';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { ensureDefaultSubprocessOptions } from '../solidlsp/util/subprocess_util.js';
|
|
5
|
+
import { createSerenaLogger, MemoryLogHandler } from './util/logging.js';
|
|
6
|
+
const { logger } = createSerenaLogger({ name: 'serena.gui_log_viewer', emitToConsole: false, level: 'info' });
|
|
7
|
+
const LOG_HISTORY_LIMIT = 500;
|
|
8
|
+
function determineLogLevel(message) {
|
|
9
|
+
const trimmed = message.trimStart().toUpperCase();
|
|
10
|
+
if (trimmed.startsWith('DEBUG')) {
|
|
11
|
+
return 'debug';
|
|
12
|
+
}
|
|
13
|
+
if (trimmed.startsWith('INFO')) {
|
|
14
|
+
return 'info';
|
|
15
|
+
}
|
|
16
|
+
if (trimmed.startsWith('WARNING') || trimmed.startsWith('WARN')) {
|
|
17
|
+
return 'warning';
|
|
18
|
+
}
|
|
19
|
+
if (trimmed.startsWith('ERROR') || trimmed.startsWith('FATAL')) {
|
|
20
|
+
return 'error';
|
|
21
|
+
}
|
|
22
|
+
return 'default';
|
|
23
|
+
}
|
|
24
|
+
function escapeHtml(text) {
|
|
25
|
+
return text
|
|
26
|
+
.replace(/&/g, '&')
|
|
27
|
+
.replace(/</g, '<')
|
|
28
|
+
.replace(/>/g, '>')
|
|
29
|
+
.replace(/"/g, '"')
|
|
30
|
+
.replace(/'/g, ''');
|
|
31
|
+
}
|
|
32
|
+
function launchBrowser(url) {
|
|
33
|
+
try {
|
|
34
|
+
if (process.platform === 'darwin') {
|
|
35
|
+
const child = spawn('open', [url], ensureDefaultSubprocessOptions({ stdio: 'ignore', detached: true }));
|
|
36
|
+
child.unref();
|
|
37
|
+
}
|
|
38
|
+
else if (process.platform === 'win32') {
|
|
39
|
+
const child = spawn('cmd', ['/c', 'start', '', url], ensureDefaultSubprocessOptions({ stdio: 'ignore', detached: true, windowsHide: true }));
|
|
40
|
+
child.unref();
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const child = spawn('xdg-open', [url], ensureDefaultSubprocessOptions({ stdio: 'ignore', detached: true }));
|
|
44
|
+
child.unref();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger.warn('Failed to open browser for GUI log viewer', error instanceof Error ? error : undefined);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class GuiLogViewer {
|
|
52
|
+
channel;
|
|
53
|
+
title;
|
|
54
|
+
memoryLogHandler;
|
|
55
|
+
host;
|
|
56
|
+
requestedPort;
|
|
57
|
+
autoOpen;
|
|
58
|
+
historyLimit;
|
|
59
|
+
toolNames = [];
|
|
60
|
+
started = false;
|
|
61
|
+
server = null;
|
|
62
|
+
sseClients = new Set();
|
|
63
|
+
history = [];
|
|
64
|
+
memoryCallback;
|
|
65
|
+
readyPromise = null;
|
|
66
|
+
port = null;
|
|
67
|
+
logSequence = 0;
|
|
68
|
+
constructor(channel, options = {}) {
|
|
69
|
+
this.channel = channel;
|
|
70
|
+
this.title = options.title ?? 'Serena Logs';
|
|
71
|
+
this.memoryLogHandler = options.memoryLogHandler;
|
|
72
|
+
this.host = options.host ?? '127.0.0.1';
|
|
73
|
+
this.requestedPort = options.port;
|
|
74
|
+
this.autoOpen = options.autoOpen ?? false;
|
|
75
|
+
this.historyLimit = options.historyLimit ?? LOG_HISTORY_LIMIT;
|
|
76
|
+
if (this.memoryLogHandler) {
|
|
77
|
+
for (const message of this.memoryLogHandler.getLogMessages()) {
|
|
78
|
+
this.recordLogMessage(message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async start() {
|
|
83
|
+
if (this.started) {
|
|
84
|
+
await this.readyPromise;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.started = true;
|
|
88
|
+
this.server = createServer((req, res) => {
|
|
89
|
+
void this.handleRequest(req, res);
|
|
90
|
+
});
|
|
91
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
92
|
+
if (!this.server) {
|
|
93
|
+
reject(new Error('Server not initialised'));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.server.once('error', (error) => {
|
|
97
|
+
logger.error('GUI log viewer server encountered an error while starting', error instanceof Error ? error : undefined);
|
|
98
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
99
|
+
});
|
|
100
|
+
this.server.listen({ host: this.host, port: this.requestedPort ?? 0, exclusive: true }, () => {
|
|
101
|
+
const address = this.server?.address();
|
|
102
|
+
if (address && typeof address === 'object') {
|
|
103
|
+
this.port = address.port;
|
|
104
|
+
logger.info(`GUI log viewer '${this.title}' listening on http://${this.host}:${this.port}/`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
logger.info(`GUI log viewer '${this.title}' started.`);
|
|
108
|
+
}
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
if (this.memoryLogHandler) {
|
|
113
|
+
this.memoryCallback = (message) => {
|
|
114
|
+
this.recordLogMessage(message);
|
|
115
|
+
this.broadcastLog();
|
|
116
|
+
};
|
|
117
|
+
this.memoryLogHandler.addEmitCallback(this.memoryCallback);
|
|
118
|
+
}
|
|
119
|
+
await this.readyPromise;
|
|
120
|
+
if (this.autoOpen) {
|
|
121
|
+
const url = this.getBaseUrl();
|
|
122
|
+
if (url) {
|
|
123
|
+
launchBrowser(`${url}/`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async stop() {
|
|
128
|
+
if (!this.started) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (this.memoryLogHandler && this.memoryCallback) {
|
|
132
|
+
this.memoryLogHandler.removeEmitCallback(this.memoryCallback);
|
|
133
|
+
}
|
|
134
|
+
for (const client of this.sseClients) {
|
|
135
|
+
try {
|
|
136
|
+
client.end();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// ignore
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.sseClients.clear();
|
|
143
|
+
await new Promise((resolve) => {
|
|
144
|
+
if (!this.server) {
|
|
145
|
+
resolve();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.server.close(() => resolve());
|
|
149
|
+
});
|
|
150
|
+
this.server = null;
|
|
151
|
+
this.started = false;
|
|
152
|
+
this.readyPromise = null;
|
|
153
|
+
logger.info(`GUI log viewer '${this.title}' stopped.`);
|
|
154
|
+
}
|
|
155
|
+
setToolNames(toolNames) {
|
|
156
|
+
this.toolNames = [...toolNames];
|
|
157
|
+
this.broadcast('toolNames', this.toolNames);
|
|
158
|
+
}
|
|
159
|
+
addLog(message) {
|
|
160
|
+
this.recordLogMessage(message);
|
|
161
|
+
this.broadcastLog();
|
|
162
|
+
}
|
|
163
|
+
getBaseUrl() {
|
|
164
|
+
if (this.port === null) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return `http://${this.host}:${this.port}`;
|
|
168
|
+
}
|
|
169
|
+
recordLogMessage(message) {
|
|
170
|
+
const entry = {
|
|
171
|
+
id: this.logSequence++,
|
|
172
|
+
message,
|
|
173
|
+
level: determineLogLevel(message),
|
|
174
|
+
timestamp: Date.now()
|
|
175
|
+
};
|
|
176
|
+
this.history.push(entry);
|
|
177
|
+
if (this.history.length > this.historyLimit) {
|
|
178
|
+
this.history.splice(0, this.history.length - this.historyLimit);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
broadcastLog() {
|
|
182
|
+
const entry = this.history[this.history.length - 1];
|
|
183
|
+
if (!entry) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.broadcast('log', entry);
|
|
187
|
+
}
|
|
188
|
+
handleRequest(req, res) {
|
|
189
|
+
const parsed = parseUrl(req.url ?? '/');
|
|
190
|
+
const pathname = parsed.pathname ?? '/';
|
|
191
|
+
if (req.method === 'GET' && (pathname === '/' || pathname === '/index.html')) {
|
|
192
|
+
this.handleIndexRequest(res);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (req.method === 'GET' && pathname === '/events') {
|
|
196
|
+
this.handleEventStream(res);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (req.method === 'GET' && pathname === '/healthz') {
|
|
200
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
201
|
+
res.end(JSON.stringify({ status: 'ok', channel: this.channel }));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
205
|
+
res.end('Not Found');
|
|
206
|
+
}
|
|
207
|
+
handleIndexRequest(res) {
|
|
208
|
+
const html = this.renderHtmlPage();
|
|
209
|
+
res.writeHead(200, {
|
|
210
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
211
|
+
'Cache-Control': 'no-cache'
|
|
212
|
+
});
|
|
213
|
+
res.end(html);
|
|
214
|
+
}
|
|
215
|
+
handleEventStream(res) {
|
|
216
|
+
res.writeHead(200, {
|
|
217
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
218
|
+
'Cache-Control': 'no-cache',
|
|
219
|
+
Connection: 'keep-alive'
|
|
220
|
+
});
|
|
221
|
+
res.write(`event: toolNames\ndata: ${JSON.stringify(this.toolNames)}\n\n`);
|
|
222
|
+
res.write(`event: history\ndata: ${JSON.stringify(this.history)}\n\n`);
|
|
223
|
+
this.sseClients.add(res);
|
|
224
|
+
reqOnClose(res, () => {
|
|
225
|
+
this.sseClients.delete(res);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
broadcast(event, payload) {
|
|
229
|
+
const data = JSON.stringify(payload);
|
|
230
|
+
for (const client of this.sseClients) {
|
|
231
|
+
try {
|
|
232
|
+
client.write(`event: ${event}\ndata: ${data}\n\n`);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
logger.warn('Failed to deliver GUI log viewer event', error instanceof Error ? error : undefined);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
renderHtmlPage() {
|
|
240
|
+
const safeTitle = escapeHtml(this.title);
|
|
241
|
+
const channelLabel = escapeHtml(this.channel);
|
|
242
|
+
return `<!DOCTYPE html>
|
|
243
|
+
<html lang="ja">
|
|
244
|
+
<head>
|
|
245
|
+
<meta charset="utf-8" />
|
|
246
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
247
|
+
<title>${safeTitle}</title>
|
|
248
|
+
<style>
|
|
249
|
+
:root {
|
|
250
|
+
color-scheme: dark;
|
|
251
|
+
font-family: "Segoe UI", system-ui, sans-serif;
|
|
252
|
+
background-color: #111;
|
|
253
|
+
color: #f1f1f1;
|
|
254
|
+
}
|
|
255
|
+
body {
|
|
256
|
+
margin: 0;
|
|
257
|
+
padding: 0;
|
|
258
|
+
display: flex;
|
|
259
|
+
flex-direction: column;
|
|
260
|
+
min-height: 100vh;
|
|
261
|
+
}
|
|
262
|
+
header {
|
|
263
|
+
padding: 16px;
|
|
264
|
+
background: #1d1d1d;
|
|
265
|
+
border-bottom: 1px solid #333;
|
|
266
|
+
}
|
|
267
|
+
h1 {
|
|
268
|
+
margin: 0;
|
|
269
|
+
font-size: 20px;
|
|
270
|
+
}
|
|
271
|
+
#channel {
|
|
272
|
+
font-size: 12px;
|
|
273
|
+
opacity: 0.7;
|
|
274
|
+
}
|
|
275
|
+
#tool-list {
|
|
276
|
+
margin-top: 8px;
|
|
277
|
+
font-size: 12px;
|
|
278
|
+
line-height: 1.6;
|
|
279
|
+
color: #bbb;
|
|
280
|
+
}
|
|
281
|
+
#log-container {
|
|
282
|
+
flex: 1;
|
|
283
|
+
overflow-y: auto;
|
|
284
|
+
padding: 12px;
|
|
285
|
+
font-size: 13px;
|
|
286
|
+
line-height: 1.5;
|
|
287
|
+
background: #0b0b0b;
|
|
288
|
+
}
|
|
289
|
+
.log-entry {
|
|
290
|
+
padding: 4px 8px;
|
|
291
|
+
border-radius: 4px;
|
|
292
|
+
margin-bottom: 6px;
|
|
293
|
+
white-space: pre-wrap;
|
|
294
|
+
word-break: break-word;
|
|
295
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
296
|
+
}
|
|
297
|
+
.level-debug { color: #8ab4f8; }
|
|
298
|
+
.level-info { color: #e8eaed; }
|
|
299
|
+
.level-warning { color: #fbbc04; }
|
|
300
|
+
.level-error { color: #f28b82; }
|
|
301
|
+
.level-default { color: #e8eaed; }
|
|
302
|
+
.tool-name {
|
|
303
|
+
background: rgba(0, 150, 255, 0.2);
|
|
304
|
+
padding: 0 2px;
|
|
305
|
+
border-radius: 2px;
|
|
306
|
+
}
|
|
307
|
+
footer {
|
|
308
|
+
padding: 8px 16px;
|
|
309
|
+
font-size: 11px;
|
|
310
|
+
background: #1d1d1d;
|
|
311
|
+
border-top: 1px solid #333;
|
|
312
|
+
display: flex;
|
|
313
|
+
justify-content: space-between;
|
|
314
|
+
align-items: center;
|
|
315
|
+
}
|
|
316
|
+
</style>
|
|
317
|
+
</head>
|
|
318
|
+
<body>
|
|
319
|
+
<header>
|
|
320
|
+
<h1>${safeTitle}</h1>
|
|
321
|
+
<div id="channel">Channel: ${channelLabel}</div>
|
|
322
|
+
<div id="tool-list">Tools: なし</div>
|
|
323
|
+
</header>
|
|
324
|
+
<main id="log-container" aria-live="polite"></main>
|
|
325
|
+
<footer>
|
|
326
|
+
<span>Serena GUI Log Viewer</span>
|
|
327
|
+
<span id="status">接続中...</span>
|
|
328
|
+
</footer>
|
|
329
|
+
<script type="module">
|
|
330
|
+
const logContainer = document.getElementById('log-container');
|
|
331
|
+
const statusLabel = document.getElementById('status');
|
|
332
|
+
const toolList = document.getElementById('tool-list');
|
|
333
|
+
|
|
334
|
+
let toolNames = [];
|
|
335
|
+
const SPECIAL_CHARS_PATTERN = '[.*+?^' + String.fromCharCode(36) + '{}()|[\\\\]\\\\\\\\]';
|
|
336
|
+
const SPECIAL_CHARS_REGEX = new RegExp(SPECIAL_CHARS_PATTERN, 'g');
|
|
337
|
+
|
|
338
|
+
function escapeHtml(str) {
|
|
339
|
+
return str.replace(/&/g, '&')
|
|
340
|
+
.replace(/</g, '<')
|
|
341
|
+
.replace(/>/g, '>')
|
|
342
|
+
.replace(/"/g, '"')
|
|
343
|
+
.replace(/'/g, ''');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function highlightTools(text) {
|
|
347
|
+
if (!toolNames.length) {
|
|
348
|
+
return escapeHtml(text);
|
|
349
|
+
}
|
|
350
|
+
let escaped = escapeHtml(text);
|
|
351
|
+
for (const tool of toolNames) {
|
|
352
|
+
if (!tool) continue;
|
|
353
|
+
const escapedTool = escapeHtml(tool);
|
|
354
|
+
const regex = new RegExp('(\\\\b' + escapedTool.replace(SPECIAL_CHARS_REGEX, '\\\\$&') + '\\\\b)', 'g');
|
|
355
|
+
escaped = escaped.replace(regex, '<span class="tool-name">$1</span>');
|
|
356
|
+
}
|
|
357
|
+
return escaped;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function appendLog(entry) {
|
|
361
|
+
const div = document.createElement('div');
|
|
362
|
+
div.className = 'log-entry level-' + (entry.level ?? 'default');
|
|
363
|
+
const time = new Date(entry.timestamp ?? Date.now());
|
|
364
|
+
const timeLabel = time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
365
|
+
div.innerHTML = '<strong>[' + timeLabel + ']</strong> ' + highlightTools(entry.message ?? '');
|
|
366
|
+
const shouldScroll = logContainer.scrollTop + logContainer.clientHeight >= logContainer.scrollHeight - 10;
|
|
367
|
+
logContainer.appendChild(div);
|
|
368
|
+
if (shouldScroll) {
|
|
369
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function renderToolList() {
|
|
374
|
+
toolList.textContent = toolNames.length ? 'Tools: ' + toolNames.join(', ') : 'Tools: なし';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function connect() {
|
|
378
|
+
const eventSource = new EventSource('./events');
|
|
379
|
+
eventSource.addEventListener('open', () => {
|
|
380
|
+
statusLabel.textContent = '接続済み';
|
|
381
|
+
});
|
|
382
|
+
eventSource.addEventListener('error', () => {
|
|
383
|
+
statusLabel.textContent = '切断されました。再接続しています...';
|
|
384
|
+
});
|
|
385
|
+
eventSource.addEventListener('toolNames', (event) => {
|
|
386
|
+
try {
|
|
387
|
+
toolNames = JSON.parse(event.data) ?? [];
|
|
388
|
+
renderToolList();
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error('Failed to parse tool names', error);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
eventSource.addEventListener('history', (event) => {
|
|
394
|
+
try {
|
|
395
|
+
const history = JSON.parse(event.data) ?? [];
|
|
396
|
+
logContainer.textContent = '';
|
|
397
|
+
for (const entry of history) {
|
|
398
|
+
appendLog(entry);
|
|
399
|
+
}
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error('Failed to parse log history', error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
eventSource.addEventListener('log', (event) => {
|
|
405
|
+
try {
|
|
406
|
+
const entry = JSON.parse(event.data);
|
|
407
|
+
appendLog(entry);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error('Failed to parse log entry', error);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
connect();
|
|
415
|
+
</script>
|
|
416
|
+
</body>
|
|
417
|
+
</html>`;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function reqOnClose(res, handler) {
|
|
421
|
+
res.on('close', handler);
|
|
422
|
+
res.on('error', handler);
|
|
423
|
+
res.on('finish', handler);
|
|
424
|
+
}
|
|
425
|
+
export async function showFatalException(error) {
|
|
426
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
427
|
+
const handler = new MemoryLogHandler();
|
|
428
|
+
const viewer = new GuiLogViewer('error', {
|
|
429
|
+
title: 'Serena Fatal Exception',
|
|
430
|
+
memoryLogHandler: handler,
|
|
431
|
+
autoOpen: true
|
|
432
|
+
});
|
|
433
|
+
await viewer.start();
|
|
434
|
+
handler.handle(`ERROR Fatal exception: ${message}`);
|
|
435
|
+
logger.error('Fatal exception reported to GUI log viewer', error instanceof Error ? error : undefined);
|
|
436
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StreamableHTTPServerTransport, type StreamableHTTPServerTransportOptions } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
|
+
import { type Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { type IncomingMessage, type ServerResponse, type Server as HttpServer } from 'node:http';
|
|
7
|
+
import type { URL as NodeURLType } from 'node:url';
|
|
8
|
+
import { SerenaAgentContext, SerenaAgentMode } from './config/context_mode.js';
|
|
9
|
+
import { SerenaConfig } from './config/serena_config.js';
|
|
10
|
+
import { createSerenaLogger } from './util/logging.js';
|
|
11
|
+
import type { MemoryLogHandler } from './util/logging.js';
|
|
12
|
+
import type { SerenaAgentLike, Tool } from './tools/tools_base.js';
|
|
13
|
+
export interface SerenaMCPRequestContext {
|
|
14
|
+
agent: SerenaAgentLike;
|
|
15
|
+
}
|
|
16
|
+
export interface SerenaAgentFactoryOptions {
|
|
17
|
+
project: string | null;
|
|
18
|
+
serenaConfig: SerenaConfig;
|
|
19
|
+
context: SerenaAgentContext;
|
|
20
|
+
modes: SerenaAgentMode[];
|
|
21
|
+
memoryLogHandler?: MemoryLogHandler | null;
|
|
22
|
+
}
|
|
23
|
+
export type SerenaAgentFactory = (options: SerenaAgentFactoryOptions) => SerenaAgentLike;
|
|
24
|
+
export interface SerenaMCPServerOptions {
|
|
25
|
+
host?: string;
|
|
26
|
+
port?: number;
|
|
27
|
+
modes?: Iterable<string>;
|
|
28
|
+
enableWebDashboard?: boolean | null;
|
|
29
|
+
enableGuiLogWindow?: boolean | null;
|
|
30
|
+
logLevel?: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL' | null;
|
|
31
|
+
traceLspCommunication?: boolean | null;
|
|
32
|
+
toolTimeout?: number | null;
|
|
33
|
+
instructionsOverride?: string | null;
|
|
34
|
+
serverInfo?: Implementation;
|
|
35
|
+
serverOptions?: ServerOptions;
|
|
36
|
+
}
|
|
37
|
+
export type SerenaHttpRequestHandler = (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
38
|
+
export interface SerenaHttpServerOptions extends SerenaMCPServerOptions {
|
|
39
|
+
path?: string;
|
|
40
|
+
transportOptions?: Partial<StreamableHTTPServerTransportOptions>;
|
|
41
|
+
sessionIdGenerator?: (() => string) | null;
|
|
42
|
+
}
|
|
43
|
+
export interface SerenaHttpServerInstance {
|
|
44
|
+
httpServer: HttpServer;
|
|
45
|
+
transport: StreamableHTTPServerTransport;
|
|
46
|
+
mcpServer: McpServer;
|
|
47
|
+
url: NodeURLType;
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export interface SerenaGrpcServerOptions extends SerenaMCPServerOptions {
|
|
51
|
+
address?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare abstract class SerenaMCPFactory {
|
|
54
|
+
protected readonly context: SerenaAgentContext;
|
|
55
|
+
protected readonly project: string | null;
|
|
56
|
+
private agent;
|
|
57
|
+
private requestContext;
|
|
58
|
+
protected constructor(context?: string, project?: string | null);
|
|
59
|
+
protected getAgent(): SerenaAgentLike;
|
|
60
|
+
protected setAgent(agent: SerenaAgentLike): void;
|
|
61
|
+
protected getRequestContext(): SerenaMCPRequestContext;
|
|
62
|
+
protected abstract instantiateAgent(options: {
|
|
63
|
+
serenaConfig: SerenaConfig;
|
|
64
|
+
modes: SerenaAgentMode[];
|
|
65
|
+
}): SerenaAgentLike;
|
|
66
|
+
protected abstract iterTools(): Iterable<Tool>;
|
|
67
|
+
protected abstract getInitialInstructions(): string;
|
|
68
|
+
protected isOpenAiCompatibleContext(): boolean;
|
|
69
|
+
private registerDefaultCapabilities;
|
|
70
|
+
private buildPromptMetadata;
|
|
71
|
+
private buildPromptResult;
|
|
72
|
+
private describePromptTemplate;
|
|
73
|
+
private buildResourceList;
|
|
74
|
+
private buildResourceTemplates;
|
|
75
|
+
private readResource;
|
|
76
|
+
private parseMemoryUri;
|
|
77
|
+
private formatMemoryUri;
|
|
78
|
+
private listMemories;
|
|
79
|
+
private loadMemory;
|
|
80
|
+
private bindManagerFunction;
|
|
81
|
+
private stringifyUnknown;
|
|
82
|
+
private isIterable;
|
|
83
|
+
protected registerTools(server: McpServer, openAiToolCompatible: boolean): void;
|
|
84
|
+
private registerTool;
|
|
85
|
+
createMcpServer(options?: SerenaMCPServerOptions): McpServer;
|
|
86
|
+
protected getLogger(): ReturnType<typeof createSerenaLogger>['logger'];
|
|
87
|
+
}
|
|
88
|
+
export interface SerenaMCPFactorySingleProcessOptions {
|
|
89
|
+
context?: string;
|
|
90
|
+
project?: string | null;
|
|
91
|
+
memoryLogHandler?: MemoryLogHandler | null;
|
|
92
|
+
agentFactory?: SerenaAgentFactory;
|
|
93
|
+
}
|
|
94
|
+
export declare class SerenaMCPFactorySingleProcess extends SerenaMCPFactory {
|
|
95
|
+
private readonly memoryLogHandler;
|
|
96
|
+
private readonly agentFactory?;
|
|
97
|
+
private cachedTools;
|
|
98
|
+
constructor(options?: SerenaMCPFactorySingleProcessOptions);
|
|
99
|
+
protected instantiateAgent({ serenaConfig, modes }: {
|
|
100
|
+
serenaConfig: SerenaConfig;
|
|
101
|
+
modes: SerenaAgentMode[];
|
|
102
|
+
}): SerenaAgentLike;
|
|
103
|
+
protected iterTools(): Iterable<Tool>;
|
|
104
|
+
protected getInitialInstructions(): string;
|
|
105
|
+
}
|
|
106
|
+
export declare function createSerenaHttpRequestHandler({ transport, path, defaultHost }: {
|
|
107
|
+
transport: StreamableHTTPServerTransport;
|
|
108
|
+
path?: string;
|
|
109
|
+
defaultHost?: string;
|
|
110
|
+
}): SerenaHttpRequestHandler;
|
|
111
|
+
export declare function createSerenaHttpServer(factory: SerenaMCPFactory, options?: SerenaHttpServerOptions): Promise<SerenaHttpServerInstance>;
|
|
112
|
+
export interface SerenaStdioServerInstance {
|
|
113
|
+
transport: StdioServerTransport;
|
|
114
|
+
mcpServer: McpServer;
|
|
115
|
+
close(): Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
export declare function createSerenaStdioServer(factory: SerenaMCPFactory, options?: SerenaMCPServerOptions): Promise<SerenaStdioServerInstance>;
|
|
118
|
+
export declare function createSerenaGrpcServer(_factory: SerenaMCPFactory, _options?: SerenaGrpcServerOptions): Promise<never>;
|