opencode-mem 1.0.1 → 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/README.md +80 -477
- package/dist/config.d.ts +5 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +46 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -88
- package/dist/services/ai/ai-provider-factory.d.ts +8 -0
- package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
- package/dist/services/ai/ai-provider-factory.js +25 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +13 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/services/ai/providers/anthropic-messages.js +176 -0
- package/dist/services/ai/providers/base-provider.d.ts +21 -0
- package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
- package/dist/services/ai/providers/base-provider.js +6 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +12 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +181 -0
- package/dist/services/ai/providers/openai-responses.d.ts +14 -0
- package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-responses.js +191 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
- package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
- package/dist/services/ai/session/ai-session-manager.js +165 -0
- package/dist/services/ai/session/session-types.d.ts +43 -0
- package/dist/services/ai/session/session-types.d.ts.map +1 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +41 -0
- package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/api-handlers.d.ts +11 -3
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +143 -30
- package/dist/services/auto-capture.d.ts +1 -30
- package/dist/services/auto-capture.d.ts.map +1 -1
- package/dist/services/auto-capture.js +199 -396
- package/dist/services/cleanup-service.d.ts +3 -0
- package/dist/services/cleanup-service.d.ts.map +1 -1
- package/dist/services/cleanup-service.js +31 -4
- package/dist/services/client.d.ts +1 -0
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +3 -11
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/connection-manager.js +8 -4
- package/dist/services/user-memory-learning.d.ts +3 -0
- package/dist/services/user-memory-learning.d.ts.map +1 -0
- package/dist/services/user-memory-learning.js +157 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +38 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
- package/dist/services/user-prompt/user-prompt-manager.js +164 -0
- package/dist/services/web-server-worker.js +27 -6
- package/dist/services/web-server.d.ts.map +1 -1
- package/dist/services/web-server.js +0 -5
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/web/app.js +210 -120
- package/dist/web/index.html +14 -10
- package/dist/web/styles.css +326 -1
- package/package.json +4 -1
- package/dist/services/compaction.d.ts +0 -92
- package/dist/services/compaction.d.ts.map +0 -1
- package/dist/services/compaction.js +0 -421
- package/dist/services/sqlite-client.d.ts +0 -116
- package/dist/services/sqlite-client.d.ts.map +0 -1
- package/dist/services/sqlite-client.js +0 -284
- package/dist/services/web-server-lock.d.ts +0 -12
- package/dist/services/web-server-lock.d.ts.map +0 -1
- package/dist/services/web-server-lock.js +0 -157
- package/dist/web/favicon.svg +0 -14
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import Database from "better-sqlite3";
|
|
2
|
-
import * as sqliteVec from "sqlite-vec";
|
|
3
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { CONFIG } from "../config.js";
|
|
6
|
-
import { log } from "./logger.js";
|
|
7
|
-
export class SQLiteMemoryClient {
|
|
8
|
-
db = null;
|
|
9
|
-
embedder;
|
|
10
|
-
isInitialized = false;
|
|
11
|
-
constructor(embedder) {
|
|
12
|
-
this.embedder = embedder;
|
|
13
|
-
}
|
|
14
|
-
initializeDatabase() {
|
|
15
|
-
if (!existsSync(CONFIG.storagePath)) {
|
|
16
|
-
mkdirSync(CONFIG.storagePath, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
const dbPath = join(CONFIG.storagePath, "memories.db");
|
|
19
|
-
this.db = new Database(dbPath);
|
|
20
|
-
this.db.pragma("journal_mode = WAL");
|
|
21
|
-
this.db.pragma("synchronous = NORMAL");
|
|
22
|
-
this.db.pragma("cache_size = -64000");
|
|
23
|
-
this.db.pragma("temp_store = MEMORY");
|
|
24
|
-
sqliteVec.load(this.db);
|
|
25
|
-
this.db.exec(`
|
|
26
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
27
|
-
id TEXT PRIMARY KEY,
|
|
28
|
-
content TEXT NOT NULL,
|
|
29
|
-
vector BLOB NOT NULL,
|
|
30
|
-
containerTag TEXT NOT NULL,
|
|
31
|
-
type TEXT,
|
|
32
|
-
createdAt INTEGER NOT NULL,
|
|
33
|
-
updatedAt INTEGER NOT NULL,
|
|
34
|
-
metadata TEXT,
|
|
35
|
-
displayName TEXT,
|
|
36
|
-
userName TEXT,
|
|
37
|
-
userEmail TEXT,
|
|
38
|
-
projectPath TEXT,
|
|
39
|
-
projectName TEXT,
|
|
40
|
-
gitRepoUrl TEXT
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
CREATE INDEX IF NOT EXISTS idx_containerTag ON memories(containerTag);
|
|
44
|
-
CREATE INDEX IF NOT EXISTS idx_createdAt ON memories(createdAt DESC);
|
|
45
|
-
CREATE INDEX IF NOT EXISTS idx_type ON memories(type);
|
|
46
|
-
|
|
47
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(
|
|
48
|
-
id TEXT PRIMARY KEY,
|
|
49
|
-
embedding FLOAT[384]
|
|
50
|
-
);
|
|
51
|
-
`);
|
|
52
|
-
log("SQLite database initialized", { path: dbPath });
|
|
53
|
-
}
|
|
54
|
-
async warmup(progressCallback) {
|
|
55
|
-
if (this.isInitialized)
|
|
56
|
-
return;
|
|
57
|
-
await this.embedder.warmup(progressCallback);
|
|
58
|
-
this.initializeDatabase();
|
|
59
|
-
this.isInitialized = true;
|
|
60
|
-
log("SQLite memory client ready");
|
|
61
|
-
}
|
|
62
|
-
async isReady() {
|
|
63
|
-
return this.isInitialized && this.embedder.isWarmedUp;
|
|
64
|
-
}
|
|
65
|
-
getStatus() {
|
|
66
|
-
return {
|
|
67
|
-
dbConnected: this.db !== null,
|
|
68
|
-
modelLoaded: this.embedder.isWarmedUp,
|
|
69
|
-
ready: this.isInitialized && this.embedder.isWarmedUp,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
async addMemory(content, containerTag, metadata) {
|
|
73
|
-
log("addMemory: start", { containerTag, contentLength: content.length });
|
|
74
|
-
try {
|
|
75
|
-
if (!this.db)
|
|
76
|
-
throw new Error("Database not initialized");
|
|
77
|
-
const vector = await this.embedder.embed(content);
|
|
78
|
-
const vectorBuffer = Buffer.from(new Float32Array(vector).buffer);
|
|
79
|
-
const id = `mem_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
80
|
-
const now = Date.now();
|
|
81
|
-
const record = {
|
|
82
|
-
id,
|
|
83
|
-
content,
|
|
84
|
-
containerTag,
|
|
85
|
-
type: metadata?.type,
|
|
86
|
-
createdAt: now,
|
|
87
|
-
updatedAt: now,
|
|
88
|
-
metadata: metadata ? JSON.stringify(metadata) : undefined,
|
|
89
|
-
displayName: metadata?.displayName,
|
|
90
|
-
userName: metadata?.userName,
|
|
91
|
-
userEmail: metadata?.userEmail,
|
|
92
|
-
projectPath: metadata?.projectPath,
|
|
93
|
-
projectName: metadata?.projectName,
|
|
94
|
-
gitRepoUrl: metadata?.gitRepoUrl,
|
|
95
|
-
};
|
|
96
|
-
const insertMemory = this.db.prepare(`
|
|
97
|
-
INSERT INTO memories (
|
|
98
|
-
id, content, vector, containerTag, type, createdAt, updatedAt,
|
|
99
|
-
metadata, displayName, userName, userEmail, projectPath, projectName, gitRepoUrl
|
|
100
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
101
|
-
`);
|
|
102
|
-
const insertVec = this.db.prepare(`
|
|
103
|
-
INSERT INTO vec_memories (id, embedding) VALUES (?, ?)
|
|
104
|
-
`);
|
|
105
|
-
this.db.transaction(() => {
|
|
106
|
-
insertMemory.run(record.id, record.content, vectorBuffer, record.containerTag, record.type, record.createdAt, record.updatedAt, record.metadata, record.displayName, record.userName, record.userEmail, record.projectPath, record.projectName, record.gitRepoUrl);
|
|
107
|
-
insertVec.run(record.id, vectorBuffer);
|
|
108
|
-
})();
|
|
109
|
-
log("addMemory: success", { id });
|
|
110
|
-
return { success: true, id };
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
114
|
-
log("addMemory: error", { error: errorMessage });
|
|
115
|
-
return { success: false, error: errorMessage };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async searchMemories(query, containerTag) {
|
|
119
|
-
log("searchMemories: start", { containerTag });
|
|
120
|
-
try {
|
|
121
|
-
if (!this.db)
|
|
122
|
-
throw new Error("Database not initialized");
|
|
123
|
-
const queryVector = await this.embedder.embed(query);
|
|
124
|
-
const queryBuffer = Buffer.from(new Float32Array(queryVector).buffer);
|
|
125
|
-
const stmt = this.db.prepare(`
|
|
126
|
-
SELECT
|
|
127
|
-
m.id, m.content, m.metadata, m.displayName, m.userName, m.userEmail,
|
|
128
|
-
m.projectPath, m.projectName, m.gitRepoUrl,
|
|
129
|
-
vec_distance_L2(v.embedding, ?) as distance
|
|
130
|
-
FROM memories m
|
|
131
|
-
JOIN vec_memories v ON m.id = v.id
|
|
132
|
-
WHERE m.containerTag = ?
|
|
133
|
-
ORDER BY distance ASC
|
|
134
|
-
LIMIT ?
|
|
135
|
-
`);
|
|
136
|
-
const rows = stmt.all(queryBuffer, containerTag, CONFIG.maxMemories * 2);
|
|
137
|
-
const results = rows
|
|
138
|
-
.map((r) => ({
|
|
139
|
-
id: r.id,
|
|
140
|
-
memory: r.content,
|
|
141
|
-
similarity: 1 / (1 + r.distance),
|
|
142
|
-
metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
|
|
143
|
-
displayName: r.displayName,
|
|
144
|
-
userName: r.userName,
|
|
145
|
-
userEmail: r.userEmail,
|
|
146
|
-
projectPath: r.projectPath,
|
|
147
|
-
projectName: r.projectName,
|
|
148
|
-
gitRepoUrl: r.gitRepoUrl,
|
|
149
|
-
}))
|
|
150
|
-
.filter((r) => r.similarity >= CONFIG.similarityThreshold);
|
|
151
|
-
log("searchMemories: success", { count: results.length });
|
|
152
|
-
return {
|
|
153
|
-
success: true,
|
|
154
|
-
results,
|
|
155
|
-
total: results.length,
|
|
156
|
-
timing: 0,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
161
|
-
log("searchMemories: error", { error: errorMessage });
|
|
162
|
-
return {
|
|
163
|
-
success: false,
|
|
164
|
-
error: errorMessage,
|
|
165
|
-
results: [],
|
|
166
|
-
total: 0,
|
|
167
|
-
timing: 0,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
async getProfile(containerTag, query) {
|
|
172
|
-
log("getProfile: start", { containerTag });
|
|
173
|
-
try {
|
|
174
|
-
if (!this.db)
|
|
175
|
-
throw new Error("Database not initialized");
|
|
176
|
-
const stmt = this.db.prepare(`
|
|
177
|
-
SELECT content, type
|
|
178
|
-
FROM memories
|
|
179
|
-
WHERE containerTag = ?
|
|
180
|
-
ORDER BY createdAt DESC
|
|
181
|
-
LIMIT ?
|
|
182
|
-
`);
|
|
183
|
-
const rows = stmt.all(containerTag, CONFIG.maxProfileItems * 2);
|
|
184
|
-
const staticFacts = [];
|
|
185
|
-
const dynamicFacts = [];
|
|
186
|
-
for (const r of rows) {
|
|
187
|
-
if (r.type === "preference") {
|
|
188
|
-
staticFacts.push(r.content);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
dynamicFacts.push(r.content);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const profile = {
|
|
195
|
-
static: staticFacts.slice(0, CONFIG.maxProfileItems),
|
|
196
|
-
dynamic: dynamicFacts.slice(0, CONFIG.maxProfileItems),
|
|
197
|
-
};
|
|
198
|
-
log("getProfile: success");
|
|
199
|
-
return { success: true, profile };
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
203
|
-
log("getProfile: error", { error: errorMessage });
|
|
204
|
-
return { success: false, error: errorMessage, profile: null };
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
async listMemories(containerTag, limit = 20) {
|
|
208
|
-
log("listMemories: start", { containerTag, limit });
|
|
209
|
-
try {
|
|
210
|
-
if (!this.db)
|
|
211
|
-
throw new Error("Database not initialized");
|
|
212
|
-
const stmt = this.db.prepare(`
|
|
213
|
-
SELECT * FROM memories
|
|
214
|
-
WHERE containerTag = ?
|
|
215
|
-
ORDER BY createdAt DESC
|
|
216
|
-
LIMIT ?
|
|
217
|
-
`);
|
|
218
|
-
const rows = stmt.all(containerTag, limit);
|
|
219
|
-
const memories = rows.map((r) => ({
|
|
220
|
-
id: r.id,
|
|
221
|
-
summary: r.content,
|
|
222
|
-
createdAt: new Date(r.createdAt).toISOString(),
|
|
223
|
-
metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
|
|
224
|
-
displayName: r.displayName,
|
|
225
|
-
userName: r.userName,
|
|
226
|
-
userEmail: r.userEmail,
|
|
227
|
-
projectPath: r.projectPath,
|
|
228
|
-
projectName: r.projectName,
|
|
229
|
-
gitRepoUrl: r.gitRepoUrl,
|
|
230
|
-
}));
|
|
231
|
-
log("listMemories: success", { count: memories.length });
|
|
232
|
-
return {
|
|
233
|
-
success: true,
|
|
234
|
-
memories,
|
|
235
|
-
pagination: { currentPage: 1, totalItems: memories.length, totalPages: 1 },
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
240
|
-
log("listMemories: error", { error: errorMessage });
|
|
241
|
-
return {
|
|
242
|
-
success: false,
|
|
243
|
-
error: errorMessage,
|
|
244
|
-
memories: [],
|
|
245
|
-
pagination: { currentPage: 1, totalItems: 0, totalPages: 0 },
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
async deleteMemory(memoryId) {
|
|
250
|
-
log("deleteMemory: start", { memoryId });
|
|
251
|
-
try {
|
|
252
|
-
if (!this.db)
|
|
253
|
-
throw new Error("Database not initialized");
|
|
254
|
-
const deleteMemory = this.db.prepare("DELETE FROM memories WHERE id = ?");
|
|
255
|
-
const deleteVec = this.db.prepare("DELETE FROM vec_memories WHERE id = ?");
|
|
256
|
-
const result = this.db.transaction(() => {
|
|
257
|
-
const info = deleteMemory.run(memoryId);
|
|
258
|
-
deleteVec.run(memoryId);
|
|
259
|
-
return info.changes > 0;
|
|
260
|
-
})();
|
|
261
|
-
if (result) {
|
|
262
|
-
log("deleteMemory: success", { memoryId });
|
|
263
|
-
return { success: true };
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
log("deleteMemory: not found", { memoryId });
|
|
267
|
-
return { success: false, error: "Memory not found" };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
272
|
-
log("deleteMemory: error", { memoryId, error: errorMessage });
|
|
273
|
-
return { success: false, error: errorMessage };
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
shutdown() {
|
|
277
|
-
if (this.db) {
|
|
278
|
-
this.db.close();
|
|
279
|
-
this.db = null;
|
|
280
|
-
}
|
|
281
|
-
this.isInitialized = false;
|
|
282
|
-
log("SQLite memory client shutdown");
|
|
283
|
-
}
|
|
284
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare class WebServerLock {
|
|
2
|
-
private pid;
|
|
3
|
-
private cleanupRegistered;
|
|
4
|
-
constructor();
|
|
5
|
-
private registerCleanupHandlers;
|
|
6
|
-
acquire(port: number, host: string): Promise<boolean>;
|
|
7
|
-
release(): Promise<boolean>;
|
|
8
|
-
private isProcessAlive;
|
|
9
|
-
private writeLock;
|
|
10
|
-
static cleanup(): void;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=web-server-lock.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"web-server-lock.d.ts","sourceRoot":"","sources":["../../src/services/web-server-lock.ts"],"names":[],"mappings":"AAeA,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,iBAAiB,CAAkB;;IAO3C,OAAO,CAAC,uBAAuB;IAwBzB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmDrD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAqCjC,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,SAAS;IAIjB,MAAM,CAAC,OAAO,IAAI,IAAI;CAiCvB"}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { log } from "./logger.js";
|
|
5
|
-
const LOCK_DIR = join(homedir(), ".opencode-mem");
|
|
6
|
-
const LOCK_FILE = join(LOCK_DIR, "webserver.lock");
|
|
7
|
-
export class WebServerLock {
|
|
8
|
-
pid;
|
|
9
|
-
cleanupRegistered = false;
|
|
10
|
-
constructor() {
|
|
11
|
-
this.pid = process.pid;
|
|
12
|
-
this.registerCleanupHandlers();
|
|
13
|
-
}
|
|
14
|
-
registerCleanupHandlers() {
|
|
15
|
-
if (this.cleanupRegistered)
|
|
16
|
-
return;
|
|
17
|
-
this.cleanupRegistered = true;
|
|
18
|
-
const cleanup = () => {
|
|
19
|
-
this.release().catch(() => { });
|
|
20
|
-
};
|
|
21
|
-
process.on('exit', cleanup);
|
|
22
|
-
process.on('SIGTERM', () => {
|
|
23
|
-
cleanup();
|
|
24
|
-
process.exit(0);
|
|
25
|
-
});
|
|
26
|
-
process.on('SIGINT', () => {
|
|
27
|
-
cleanup();
|
|
28
|
-
process.exit(0);
|
|
29
|
-
});
|
|
30
|
-
process.on('uncaughtException', (error) => {
|
|
31
|
-
log("WebServerLock: uncaught exception", { error: String(error) });
|
|
32
|
-
cleanup();
|
|
33
|
-
process.exit(1);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
async acquire(port, host) {
|
|
37
|
-
try {
|
|
38
|
-
if (existsSync(LOCK_FILE)) {
|
|
39
|
-
const content = readFileSync(LOCK_FILE, "utf-8");
|
|
40
|
-
const lockData = JSON.parse(content);
|
|
41
|
-
const alivePids = lockData.pids.filter(pid => this.isProcessAlive(pid));
|
|
42
|
-
if (alivePids.length > 0) {
|
|
43
|
-
if (lockData.port === port && lockData.host === host) {
|
|
44
|
-
alivePids.push(this.pid);
|
|
45
|
-
this.writeLock({
|
|
46
|
-
pids: alivePids,
|
|
47
|
-
port: lockData.port,
|
|
48
|
-
host: lockData.host,
|
|
49
|
-
startedAt: lockData.startedAt,
|
|
50
|
-
});
|
|
51
|
-
log("WebServerLock: joined existing server", {
|
|
52
|
-
pid: this.pid,
|
|
53
|
-
totalInstances: alivePids.length
|
|
54
|
-
});
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
log("WebServerLock: port conflict", {
|
|
59
|
-
requestedPort: port,
|
|
60
|
-
existingPort: lockData.port
|
|
61
|
-
});
|
|
62
|
-
throw new Error(`Web server already running on ${lockData.host}:${lockData.port}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
this.writeLock({
|
|
67
|
-
pids: [this.pid],
|
|
68
|
-
port,
|
|
69
|
-
host,
|
|
70
|
-
startedAt: Date.now(),
|
|
71
|
-
});
|
|
72
|
-
log("WebServerLock: acquired", { pid: this.pid, port, host });
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
if (error instanceof Error && error.message.includes("already running")) {
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
log("WebServerLock: acquire error", { error: String(error) });
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async release() {
|
|
84
|
-
try {
|
|
85
|
-
if (!existsSync(LOCK_FILE)) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
const content = readFileSync(LOCK_FILE, "utf-8");
|
|
89
|
-
const lockData = JSON.parse(content);
|
|
90
|
-
const remainingPids = lockData.pids.filter(pid => pid !== this.pid && this.isProcessAlive(pid));
|
|
91
|
-
if (remainingPids.length === 0) {
|
|
92
|
-
unlinkSync(LOCK_FILE);
|
|
93
|
-
log("WebServerLock: released (last instance)", { pid: this.pid });
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
this.writeLock({
|
|
98
|
-
pids: remainingPids,
|
|
99
|
-
port: lockData.port,
|
|
100
|
-
host: lockData.host,
|
|
101
|
-
startedAt: lockData.startedAt,
|
|
102
|
-
});
|
|
103
|
-
log("WebServerLock: released (instances remaining)", {
|
|
104
|
-
pid: this.pid,
|
|
105
|
-
remaining: remainingPids.length
|
|
106
|
-
});
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
log("WebServerLock: release error", { error: String(error) });
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
isProcessAlive(pid) {
|
|
116
|
-
try {
|
|
117
|
-
process.kill(pid, 0);
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
writeLock(data) {
|
|
125
|
-
writeFileSync(LOCK_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
126
|
-
}
|
|
127
|
-
static cleanup() {
|
|
128
|
-
try {
|
|
129
|
-
if (existsSync(LOCK_FILE)) {
|
|
130
|
-
const content = readFileSync(LOCK_FILE, "utf-8");
|
|
131
|
-
const lockData = JSON.parse(content);
|
|
132
|
-
const alivePids = lockData.pids.filter(pid => {
|
|
133
|
-
try {
|
|
134
|
-
process.kill(pid, 0);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
if (alivePids.length === 0) {
|
|
142
|
-
unlinkSync(LOCK_FILE);
|
|
143
|
-
log("WebServerLock: cleanup completed (no alive processes)");
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
writeFileSync(LOCK_FILE, JSON.stringify({ ...lockData, pids: alivePids }, null, 2), "utf-8");
|
|
147
|
-
log("WebServerLock: cleanup completed (alive processes remain)", {
|
|
148
|
-
count: alivePids.length
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
log("WebServerLock: cleanup error", { error: String(error) });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
package/dist/web/favicon.svg
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<rect width="64" height="64" fill="#0a0a0a"/>
|
|
3
|
-
<rect x="4" y="4" width="56" height="56" stroke="#00ff00" stroke-width="2" fill="none"/>
|
|
4
|
-
<path d="M16 20 L24 20 L24 28 L16 28 Z" fill="#00ff00"/>
|
|
5
|
-
<path d="M28 20 L36 20 L36 28 L28 28 Z" fill="#00ff00" opacity="0.7"/>
|
|
6
|
-
<path d="M40 20 L48 20 L48 28 L40 28 Z" fill="#00ff00" opacity="0.4"/>
|
|
7
|
-
<path d="M16 32 L24 32 L24 40 L16 40 Z" fill="#00ff00" opacity="0.7"/>
|
|
8
|
-
<path d="M28 32 L36 32 L36 40 L28 40 Z" fill="#00ff00"/>
|
|
9
|
-
<path d="M40 32 L48 32 L48 40 L40 40 Z" fill="#00ff00" opacity="0.7"/>
|
|
10
|
-
<path d="M16 44 L24 44 L24 52 L16 52 Z" fill="#00ff00" opacity="0.4"/>
|
|
11
|
-
<path d="M28 44 L36 44 L36 52 L28 52 Z" fill="#00ff00" opacity="0.7"/>
|
|
12
|
-
<path d="M40 44 L48 44 L48 52 L40 52 Z" fill="#00ff00"/>
|
|
13
|
-
<circle cx="32" cy="32" r="3" fill="#00ff00"/>
|
|
14
|
-
</svg>
|