@imenam/mcp-design 1.0.13 → 1.0.15
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 +454 -403
- package/dist/shared/feedback.d.ts +3 -2
- package/dist/shared/feedback.js +4 -3
- package/dist/src/core/design-model.d.ts +54 -0
- package/dist/src/core/design-model.js +129 -0
- package/dist/src/core/design-store.d.ts +3 -18
- package/dist/src/core/design-store.js +15 -103
- package/dist/src/core/exchange.d.ts +86 -0
- package/dist/src/core/exchange.js +430 -0
- package/dist/src/core/feedback-model.d.ts +64 -0
- package/dist/src/core/feedback-model.js +211 -0
- package/dist/src/core/feedback-store.d.ts +4 -31
- package/dist/src/core/feedback-store.js +14 -195
- package/dist/src/core/ids.js +1 -2
- package/dist/src/gui-worker.js +42 -53
- package/dist/src/index.js +0 -7
- package/gui/dist/assets/index-DTIGOSpK.js +15 -0
- package/gui/dist/assets/{search-DHQx5ZT0.js → search-CH3uadrp.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +3 -2
- package/gui/dist/assets/index-SUmtOh8T.js +0 -15
package/dist/src/gui-worker.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { serve } from "@hono/node-server";
|
|
2
2
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
3
3
|
import { Hono } from "hono";
|
|
4
|
-
import { streamSSE } from "hono/streaming";
|
|
5
4
|
import { GUI_LISTEN_HOST, ProxyClient, createLogger, honoGuiGuard, setupLogging } from "@imenam/mcp-gui-interface";
|
|
6
5
|
import path from "path";
|
|
7
6
|
import fs from "fs";
|
|
7
|
+
import { randomUUID } from "crypto";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { IPCMessageSchema } from "../shared/schemas/design.schema.js";
|
|
10
10
|
import { resolveLogDir } from "./core/log-dir.js";
|
|
@@ -79,7 +79,6 @@ const parentCheckInterval = setInterval(() => {
|
|
|
79
79
|
const app = new Hono();
|
|
80
80
|
app.use("*", honoGuiGuard());
|
|
81
81
|
let proxyClient = null;
|
|
82
|
-
const sseControllers = new Set();
|
|
83
82
|
const pendingRequests = new Map();
|
|
84
83
|
function safeSend(message) {
|
|
85
84
|
if (process.send && process.connected) {
|
|
@@ -115,7 +114,23 @@ function ipcRequest(type, data, timeout = 5000) {
|
|
|
115
114
|
}
|
|
116
115
|
});
|
|
117
116
|
}
|
|
118
|
-
const PUSHED_EVENTS = new Set(["
|
|
117
|
+
const PUSHED_EVENTS = new Set(["DESIGNS_UPDATED", "DESIGN_UPDATED", "REMARKS_UPDATED"]);
|
|
118
|
+
/**
|
|
119
|
+
* Journal des évènements poussés par le master, relu par la GUI via
|
|
120
|
+
* `/api/changes`. Chaque entrée porte un numéro croissant ; seules les
|
|
121
|
+
* `JOURNAL_LIMIT` dernières sont gardées. `epoch` change à chaque démarrage du
|
|
122
|
+
* worker : un client qui le voit changer sait que sa position ne vaut plus rien.
|
|
123
|
+
*/
|
|
124
|
+
const JOURNAL_LIMIT = 200;
|
|
125
|
+
const epoch = randomUUID();
|
|
126
|
+
let lastSeq = 0;
|
|
127
|
+
const journal = [];
|
|
128
|
+
function appendToJournal(event) {
|
|
129
|
+
lastSeq += 1;
|
|
130
|
+
journal.push({ seq: lastSeq, event });
|
|
131
|
+
if (journal.length > JOURNAL_LIMIT)
|
|
132
|
+
journal.splice(0, journal.length - JOURNAL_LIMIT);
|
|
133
|
+
}
|
|
119
134
|
function setupIpcErrorHandlers() {
|
|
120
135
|
process.on("error", (err) => {
|
|
121
136
|
if (err.code === "ERR_IPC_CHANNEL_CLOSED")
|
|
@@ -123,7 +138,7 @@ function setupIpcErrorHandlers() {
|
|
|
123
138
|
logger.error(`Process error: ${err.message}`);
|
|
124
139
|
});
|
|
125
140
|
process.on("disconnect", () => {
|
|
126
|
-
logger.warn("IPC disconnected from MCP master. GUI worker will exit
|
|
141
|
+
logger.warn("IPC disconnected from MCP master. GUI worker will exit.");
|
|
127
142
|
process.exit(0);
|
|
128
143
|
});
|
|
129
144
|
process.on("message", (msg) => {
|
|
@@ -143,16 +158,8 @@ function setupIpcErrorHandlers() {
|
|
|
143
158
|
});
|
|
144
159
|
return;
|
|
145
160
|
}
|
|
146
|
-
if (msg && PUSHED_EVENTS.has(msg.type))
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
controller.writeSSE({ data: JSON.stringify(msg), event: "message" });
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
sseControllers.delete(controller);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
161
|
+
if (msg && PUSHED_EVENTS.has(msg.type))
|
|
162
|
+
appendToJournal(msg);
|
|
156
163
|
});
|
|
157
164
|
}
|
|
158
165
|
// --- Routes HTTP ------------------------------------------------------------
|
|
@@ -167,45 +174,27 @@ app.get("/proxy/health", (c) => {
|
|
|
167
174
|
timestamp: new Date().toISOString(),
|
|
168
175
|
});
|
|
169
176
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
await stream.writeSSE({ data: "ping", event: "ping" });
|
|
193
|
-
}
|
|
194
|
-
catch (err) {
|
|
195
|
-
logger.warn(`SSE /api/events: keepalive ping failed (id=${clientId}): ${err?.message ?? err}`);
|
|
196
|
-
sseControllers.delete(stream);
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}));
|
|
201
|
-
app.post("/api/client-diagnostics", async (c) => {
|
|
202
|
-
try {
|
|
203
|
-
logger.warn(`Client diagnostics (SSE/EventSource): ${JSON.stringify(await c.req.json())}`);
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
logger.warn(`Client diagnostics: invalid JSON body (${e?.message ?? e})`);
|
|
207
|
-
}
|
|
208
|
-
return c.json({ ok: true });
|
|
177
|
+
/**
|
|
178
|
+
* Évènements survenus depuis la position `since` du client. `reset: true`
|
|
179
|
+
* quand cette position n'est pas exploitable — absente, d'un autre démarrage du
|
|
180
|
+
* worker, plus ancienne que le journal ou en avance sur lui : le client
|
|
181
|
+
* recharge alors tout et repart de `seq`.
|
|
182
|
+
*/
|
|
183
|
+
app.get("/api/changes", (c) => {
|
|
184
|
+
const rawSince = c.req.query("since");
|
|
185
|
+
const clientEpoch = c.req.query("epoch");
|
|
186
|
+
if (rawSince !== undefined && !/^\d+$/.test(rawSince)) {
|
|
187
|
+
return c.json({ error: `Invalid "since" parameter: expected a non-negative integer, got "${rawSince}"` }, 400);
|
|
188
|
+
}
|
|
189
|
+
const reset = { epoch, seq: lastSeq, reset: true };
|
|
190
|
+
if (rawSince === undefined || !clientEpoch || clientEpoch !== epoch)
|
|
191
|
+
return c.json(reset);
|
|
192
|
+
const since = Number(rawSince);
|
|
193
|
+
const oldestSeq = journal.length > 0 ? journal[0].seq : lastSeq + 1;
|
|
194
|
+
if (since > lastSeq || since < oldestSeq - 1)
|
|
195
|
+
return c.json(reset);
|
|
196
|
+
const events = journal.filter((entry) => entry.seq > since).map((entry) => entry.event);
|
|
197
|
+
return c.json({ epoch, seq: lastSeq, reset: false, events });
|
|
209
198
|
});
|
|
210
199
|
app.get("/api/config", async (c) => {
|
|
211
200
|
const result = await ipcRequest("GET_CONFIG");
|
package/dist/src/index.js
CHANGED
|
@@ -140,13 +140,6 @@ function handleIPCMessage(msg) {
|
|
|
140
140
|
logger.info(`GUI ready at ${guiUrl}`);
|
|
141
141
|
guiStartWaiter?.({ outcome: "ready", url: msg.data?.url });
|
|
142
142
|
return;
|
|
143
|
-
case "GET_INITIAL_STATE":
|
|
144
|
-
broadcastToGUI({
|
|
145
|
-
type: "INITIAL_STATE",
|
|
146
|
-
data: { designs: store.list(), version: APP_VERSION },
|
|
147
|
-
timestamp: new Date().toISOString(),
|
|
148
|
-
});
|
|
149
|
-
return;
|
|
150
143
|
case "LIST_DESIGNS":
|
|
151
144
|
reply(msg, "LIST_DESIGNS_RESPONSE", { designs: store.list() });
|
|
152
145
|
return;
|