@lensagent/core 0.1.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/LICENSE +21 -0
- package/README.md +48 -0
- package/config/lens.example.jsonc +92 -0
- package/config/pair.example.json +3 -0
- package/dist/adapters/executor.d.ts +35 -0
- package/dist/adapters/executor.js +296 -0
- package/dist/adapters/lsp.d.ts +18 -0
- package/dist/adapters/lsp.js +218 -0
- package/dist/adapters/mcp.d.ts +32 -0
- package/dist/adapters/mcp.js +283 -0
- package/dist/adapters/revert.d.ts +12 -0
- package/dist/adapters/revert.js +155 -0
- package/dist/config/runtime.d.ts +55 -0
- package/dist/config/runtime.js +371 -0
- package/dist/config/schema.d.ts +292 -0
- package/dist/config/schema.js +177 -0
- package/dist/contracts/adapter.d.ts +28 -0
- package/dist/contracts/adapter.js +8 -0
- package/dist/contracts/capability.d.ts +41 -0
- package/dist/contracts/capability.js +1 -0
- package/dist/contracts/domain.d.ts +61 -0
- package/dist/contracts/domain.js +1 -0
- package/dist/contracts/index.d.ts +5 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/model.d.ts +77 -0
- package/dist/contracts/model.js +1 -0
- package/dist/contracts/ports.d.ts +14 -0
- package/dist/contracts/ports.js +14 -0
- package/dist/core/access.d.ts +24 -0
- package/dist/core/access.js +205 -0
- package/dist/core/codec.d.ts +33 -0
- package/dist/core/codec.js +105 -0
- package/dist/core/domain.d.ts +13 -0
- package/dist/core/domain.js +24 -0
- package/dist/core/host.d.ts +19 -0
- package/dist/core/host.js +249 -0
- package/dist/core/identity.d.ts +19 -0
- package/dist/core/identity.js +54 -0
- package/dist/entry/bun.d.ts +1 -0
- package/dist/entry/bun.js +2 -0
- package/dist/entry/node.d.ts +2 -0
- package/dist/entry/node.js +3 -0
- package/dist/entry/rebuild.d.ts +1 -0
- package/dist/entry/rebuild.js +47 -0
- package/dist/entry/run.d.ts +1 -0
- package/dist/entry/run.js +43 -0
- package/dist/execution/lsp-process.d.ts +14 -0
- package/dist/execution/lsp-process.js +234 -0
- package/dist/http/server.d.ts +34 -0
- package/dist/http/server.js +1332 -0
- package/dist/launch.d.ts +3 -0
- package/dist/launch.js +4 -0
- package/dist/runtime/agent.d.ts +35 -0
- package/dist/runtime/agent.js +853 -0
- package/dist/runtime/artifacts.d.ts +15 -0
- package/dist/runtime/artifacts.js +72 -0
- package/dist/runtime/cache.d.ts +20 -0
- package/dist/runtime/cache.js +44 -0
- package/dist/runtime/calls.d.ts +21 -0
- package/dist/runtime/calls.js +130 -0
- package/dist/runtime/capabilities.d.ts +58 -0
- package/dist/runtime/capabilities.js +196 -0
- package/dist/runtime/cleanup.d.ts +7 -0
- package/dist/runtime/cleanup.js +33 -0
- package/dist/runtime/context.d.ts +34 -0
- package/dist/runtime/context.js +169 -0
- package/dist/runtime/diagnostics.d.ts +8 -0
- package/dist/runtime/diagnostics.js +71 -0
- package/dist/runtime/events.d.ts +22 -0
- package/dist/runtime/events.js +131 -0
- package/dist/runtime/kernel-capabilities.d.ts +32 -0
- package/dist/runtime/kernel-capabilities.js +200 -0
- package/dist/runtime/manifests.d.ts +14 -0
- package/dist/runtime/manifests.js +117 -0
- package/dist/runtime/model.d.ts +20 -0
- package/dist/runtime/model.js +241 -0
- package/dist/runtime/observability.d.ts +22 -0
- package/dist/runtime/observability.js +104 -0
- package/dist/runtime/policy.d.ts +8 -0
- package/dist/runtime/policy.js +31 -0
- package/dist/runtime/profile.d.ts +8 -0
- package/dist/runtime/profile.js +52 -0
- package/dist/runtime/projects.d.ts +40 -0
- package/dist/runtime/projects.js +115 -0
- package/dist/runtime/results.d.ts +7 -0
- package/dist/runtime/results.js +55 -0
- package/dist/runtime/review.d.ts +9 -0
- package/dist/runtime/review.js +54 -0
- package/dist/runtime/scheduler.d.ts +15 -0
- package/dist/runtime/scheduler.js +85 -0
- package/dist/runtime/skills.d.ts +2 -0
- package/dist/runtime/skills.js +83 -0
- package/dist/runtime/tokenizer.d.ts +8 -0
- package/dist/runtime/tokenizer.js +30 -0
- package/dist/store/database.d.ts +3 -0
- package/dist/store/database.js +49 -0
- package/dist/store/event-log.d.ts +19 -0
- package/dist/store/event-log.js +61 -0
- package/dist/store/schema.d.ts +1 -0
- package/dist/store/schema.js +347 -0
- package/dist/store/store.d.ts +344 -0
- package/dist/store/store.js +2554 -0
- package/package.json +73 -0
- package/proto/executor.proto +314 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export class LspProcess {
|
|
3
|
+
maxFrameBytes;
|
|
4
|
+
maxBufferBytes;
|
|
5
|
+
#child;
|
|
6
|
+
#pending = new Map();
|
|
7
|
+
#notifications = [];
|
|
8
|
+
#notificationBytes = 0;
|
|
9
|
+
#buffer = Buffer.alloc(0);
|
|
10
|
+
#nextId = 1;
|
|
11
|
+
#initialized = false;
|
|
12
|
+
#closed = false;
|
|
13
|
+
#closing;
|
|
14
|
+
constructor(command, args, cwd, maxFrameBytes, maxBufferBytes) {
|
|
15
|
+
this.maxFrameBytes = maxFrameBytes;
|
|
16
|
+
this.maxBufferBytes = maxBufferBytes;
|
|
17
|
+
this.#child = spawn(command, args, { cwd, env: processEnvironment(), shell: false, windowsHide: true });
|
|
18
|
+
this.#child.stdout.on('data', (chunk) => this.#receive(chunk));
|
|
19
|
+
this.#child.stdin.on('error', () => this.#fail(lspError('LSP_SERVER_UNAVAILABLE', 'The LSP input stream failed.')));
|
|
20
|
+
this.#child.once('error', () => this.#fail(lspError('LSP_SERVER_UNAVAILABLE', 'The LSP server could not start.')));
|
|
21
|
+
this.#child.once('close', () => this.#fail(lspError('LSP_SERVER_UNAVAILABLE', 'The LSP server exited.')));
|
|
22
|
+
}
|
|
23
|
+
get pid() {
|
|
24
|
+
return this.#child.pid;
|
|
25
|
+
}
|
|
26
|
+
get initialized() {
|
|
27
|
+
return this.#initialized;
|
|
28
|
+
}
|
|
29
|
+
get closed() {
|
|
30
|
+
return this.#closed;
|
|
31
|
+
}
|
|
32
|
+
markInitialized() {
|
|
33
|
+
this.#initialized = true;
|
|
34
|
+
}
|
|
35
|
+
request(method, params, signal, allowClosing = false) {
|
|
36
|
+
if (this.#closed || (this.#closing !== undefined && !allowClosing))
|
|
37
|
+
return Promise.reject(lspError('LSP_SERVER_UNAVAILABLE', 'The LSP session is closed.'));
|
|
38
|
+
if (signal?.aborted === true)
|
|
39
|
+
return Promise.reject(abortError(signal.reason));
|
|
40
|
+
const id = this.#nextId++;
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const onAbort = () => {
|
|
43
|
+
if (!this.#pending.delete(id))
|
|
44
|
+
return;
|
|
45
|
+
try {
|
|
46
|
+
this.notify('$/cancelRequest', { id }, allowClosing);
|
|
47
|
+
}
|
|
48
|
+
catch { /* The local cancellation remains authoritative. */ }
|
|
49
|
+
reject(abortError(signal?.reason));
|
|
50
|
+
};
|
|
51
|
+
const finish = (operation) => {
|
|
52
|
+
signal?.removeEventListener('abort', onAbort);
|
|
53
|
+
operation();
|
|
54
|
+
};
|
|
55
|
+
this.#pending.set(id, {
|
|
56
|
+
resolve: (value) => finish(() => resolve(value)),
|
|
57
|
+
reject: (error) => finish(() => reject(error))
|
|
58
|
+
});
|
|
59
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
60
|
+
try {
|
|
61
|
+
this.#send({ jsonrpc: '2.0', id, method, params });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.#pending.delete(id);
|
|
65
|
+
finish(() => reject(asError(error)));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
notify(method, params, allowClosing = false) {
|
|
70
|
+
if (this.#closed || (this.#closing !== undefined && !allowClosing))
|
|
71
|
+
throw lspError('LSP_SERVER_UNAVAILABLE', 'The LSP session is closed.');
|
|
72
|
+
this.#send({ jsonrpc: '2.0', method, params });
|
|
73
|
+
}
|
|
74
|
+
drainNotifications() {
|
|
75
|
+
const result = this.#notifications.splice(0);
|
|
76
|
+
this.#notificationBytes = 0;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
this.#closing ??= this.#shutdown();
|
|
81
|
+
return this.#closing;
|
|
82
|
+
}
|
|
83
|
+
async #shutdown() {
|
|
84
|
+
if (this.#initialized && !this.#closed) {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timeout = setTimeout(() => controller.abort(), 1000);
|
|
87
|
+
try {
|
|
88
|
+
await this.request('shutdown', null, controller.signal, true);
|
|
89
|
+
}
|
|
90
|
+
catch { /* Process termination follows. */ }
|
|
91
|
+
finally {
|
|
92
|
+
clearTimeout(timeout);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
this.notify('exit', null, true);
|
|
96
|
+
}
|
|
97
|
+
catch { /* The process may already be gone. */ }
|
|
98
|
+
}
|
|
99
|
+
await terminate(this.#child);
|
|
100
|
+
this.#fail(lspError('LSP_SERVER_UNAVAILABLE', 'The LSP session was closed.'));
|
|
101
|
+
}
|
|
102
|
+
#send(value) {
|
|
103
|
+
if (!this.#child.stdin.writable)
|
|
104
|
+
throw lspError('LSP_SERVER_UNAVAILABLE', 'The LSP server is not writable.');
|
|
105
|
+
const content = Buffer.from(JSON.stringify(value));
|
|
106
|
+
if (content.byteLength > this.maxFrameBytes)
|
|
107
|
+
throw lspError('LSP_PROTOCOL_ERROR', 'The LSP frame exceeds the configured limit.');
|
|
108
|
+
this.#child.stdin.write(`Content-Length: ${content.byteLength}\r\n\r\n`);
|
|
109
|
+
this.#child.stdin.write(content);
|
|
110
|
+
}
|
|
111
|
+
#receive(chunk) {
|
|
112
|
+
if (this.#closed)
|
|
113
|
+
return;
|
|
114
|
+
if (this.#buffer.byteLength + chunk.byteLength > this.maxBufferBytes) {
|
|
115
|
+
this.#protocolFailure('The LSP response buffer exceeds the configured limit.');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.#buffer = Buffer.concat([this.#buffer, chunk]);
|
|
119
|
+
try {
|
|
120
|
+
for (;;) {
|
|
121
|
+
const boundary = this.#buffer.indexOf('\r\n\r\n');
|
|
122
|
+
if (boundary < 0) {
|
|
123
|
+
if (this.#buffer.byteLength > 8192)
|
|
124
|
+
throw new Error('The LSP header exceeds the configured limit.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const headers = this.#buffer.subarray(0, boundary).toString('ascii').split('\r\n');
|
|
128
|
+
const lengths = headers.flatMap((line) => {
|
|
129
|
+
const match = /^Content-Length:\s*(\d+)\s*$/i.exec(line);
|
|
130
|
+
return match?.[1] === undefined ? [] : [Number(match[1])];
|
|
131
|
+
});
|
|
132
|
+
if (lengths.length !== 1 || !Number.isSafeInteger(lengths[0]) || (lengths[0] ?? 0) > this.maxFrameBytes)
|
|
133
|
+
throw new Error('The LSP Content-Length header is invalid.');
|
|
134
|
+
const start = boundary + 4;
|
|
135
|
+
const length = lengths[0] ?? 0;
|
|
136
|
+
if (this.#buffer.byteLength < start + length)
|
|
137
|
+
return;
|
|
138
|
+
const source = this.#buffer.subarray(start, start + length).toString();
|
|
139
|
+
this.#buffer = this.#buffer.subarray(start + length);
|
|
140
|
+
const message = JSON.parse(source);
|
|
141
|
+
if (!record(message))
|
|
142
|
+
throw new Error('The LSP message is not an object.');
|
|
143
|
+
this.#dispatch(message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.#protocolFailure(asError(error).message);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
#dispatch(message) {
|
|
151
|
+
if (typeof message.method === 'string') {
|
|
152
|
+
if (typeof message.id === 'number' || typeof message.id === 'string') {
|
|
153
|
+
this.#send({ jsonrpc: '2.0', id: message.id, error: { code: -32601, message: 'Server-initiated requests are not supported.' } });
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const bytes = Buffer.byteLength(JSON.stringify(message));
|
|
157
|
+
while (this.#notifications.length > 0 && (this.#notifications.length >= 128 || this.#notificationBytes + bytes > this.maxBufferBytes)) {
|
|
158
|
+
const removed = this.#notifications.shift();
|
|
159
|
+
this.#notificationBytes -= Buffer.byteLength(JSON.stringify(removed));
|
|
160
|
+
}
|
|
161
|
+
if (bytes <= this.maxBufferBytes) {
|
|
162
|
+
this.#notifications.push(message);
|
|
163
|
+
this.#notificationBytes += bytes;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (typeof message.id !== 'number')
|
|
169
|
+
return;
|
|
170
|
+
const pending = this.#pending.get(message.id);
|
|
171
|
+
if (pending === undefined)
|
|
172
|
+
return;
|
|
173
|
+
this.#pending.delete(message.id);
|
|
174
|
+
if (message.error !== undefined)
|
|
175
|
+
pending.reject(lspError('LSP_PROTOCOL_ERROR', 'The LSP server rejected the request.'));
|
|
176
|
+
else
|
|
177
|
+
pending.resolve(message.result ?? null);
|
|
178
|
+
}
|
|
179
|
+
#protocolFailure(message) {
|
|
180
|
+
this.#fail(lspError('LSP_PROTOCOL_ERROR', message));
|
|
181
|
+
void terminate(this.#child);
|
|
182
|
+
}
|
|
183
|
+
#fail(error) {
|
|
184
|
+
if (this.#closed && this.#pending.size === 0)
|
|
185
|
+
return;
|
|
186
|
+
this.#closed = true;
|
|
187
|
+
for (const pending of this.#pending.values())
|
|
188
|
+
pending.reject(error);
|
|
189
|
+
this.#pending.clear();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Duplicated from executor/src/execution/process.ts (processEnvironment) on purpose: core and
|
|
193
|
+
// executor are independent, non-shared projects. If this allowlist changes, mirror the change
|
|
194
|
+
// in both files.
|
|
195
|
+
function processEnvironment(explicit = {}) {
|
|
196
|
+
const bootstrap = new Set(['COMSPEC', 'LANG', 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'PATH', 'PATHEXT', 'SYSTEMROOT', 'TEMP', 'TMP', 'TMPDIR', 'TZ', 'WINDIR']);
|
|
197
|
+
const allowed = new Set([...bootstrap, 'APPDATA', 'CI', 'HOME', 'HOMEDRIVE', 'HOMEPATH', 'LOCALAPPDATA', 'NODE_ENV', 'NO_PROXY', 'USER', 'USERPROFILE', 'USERNAME', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME']);
|
|
198
|
+
const normalize = (name) => process.platform === 'win32' ? name.toUpperCase() : name;
|
|
199
|
+
const selected = new Map();
|
|
200
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
201
|
+
const key = normalize(name);
|
|
202
|
+
if (value !== undefined && bootstrap.has(key))
|
|
203
|
+
selected.set(key, [name, value]);
|
|
204
|
+
}
|
|
205
|
+
for (const [name, value] of Object.entries(explicit)) {
|
|
206
|
+
const key = normalize(name);
|
|
207
|
+
if (allowed.has(key))
|
|
208
|
+
selected.set(key, [name, value]);
|
|
209
|
+
}
|
|
210
|
+
return Object.fromEntries(selected.values());
|
|
211
|
+
}
|
|
212
|
+
async function terminate(child) {
|
|
213
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
214
|
+
return;
|
|
215
|
+
const closed = new Promise((resolve) => child.once('close', () => resolve()));
|
|
216
|
+
child.kill('SIGTERM');
|
|
217
|
+
const graceful = await Promise.race([closed.then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 1000))]);
|
|
218
|
+
if (!graceful) {
|
|
219
|
+
child.kill('SIGKILL');
|
|
220
|
+
await closed;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function lspError(code, message) {
|
|
224
|
+
return Object.assign(new Error(message), { code });
|
|
225
|
+
}
|
|
226
|
+
function abortError(reason) {
|
|
227
|
+
return reason instanceof Error ? reason : new Error('The LSP request was cancelled.');
|
|
228
|
+
}
|
|
229
|
+
function asError(value) {
|
|
230
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
231
|
+
}
|
|
232
|
+
function record(value) {
|
|
233
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
234
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type FastifyInstance } from 'fastify';
|
|
2
|
+
import type { PairStore } from '../core/access.js';
|
|
3
|
+
import type { ConfigRuntime } from '../config/runtime.js';
|
|
4
|
+
import type { Store } from '../store/store.js';
|
|
5
|
+
import type { EventBus } from '../runtime/events.js';
|
|
6
|
+
import type { AiModelGateway } from '../runtime/model.js';
|
|
7
|
+
import type { ProjectRuntime } from '../runtime/projects.js';
|
|
8
|
+
import type { ArtifactStore } from '../runtime/artifacts.js';
|
|
9
|
+
import type { Observability } from '../runtime/observability.js';
|
|
10
|
+
import type { ExecutorClient } from '../adapters/executor.js';
|
|
11
|
+
interface Dependencies {
|
|
12
|
+
readonly https?: {
|
|
13
|
+
readonly key: Buffer;
|
|
14
|
+
readonly cert: Buffer;
|
|
15
|
+
};
|
|
16
|
+
readonly config: ConfigRuntime;
|
|
17
|
+
readonly pair: PairStore;
|
|
18
|
+
readonly store: Store;
|
|
19
|
+
readonly events: EventBus;
|
|
20
|
+
readonly models: AiModelGateway;
|
|
21
|
+
readonly artifacts: ArtifactStore;
|
|
22
|
+
readonly observability: Observability;
|
|
23
|
+
readonly executor: ExecutorClient;
|
|
24
|
+
readonly defaultProjectId: string;
|
|
25
|
+
readonly runtime: (projectId: string) => ProjectRuntime;
|
|
26
|
+
readonly refreshProject: (projectId: string) => void;
|
|
27
|
+
readonly ready: () => {
|
|
28
|
+
readonly mode: 'health_only' | 'full';
|
|
29
|
+
readonly blockers: readonly string[];
|
|
30
|
+
};
|
|
31
|
+
readonly requestShutdown: () => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createServer(dependencies: Dependencies): FastifyInstance;
|
|
34
|
+
export {};
|