unreal-mcp-proxy 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 +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +37 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy-server.d.ts +8 -0
- package/dist/proxy-server.js +333 -0
- package/dist/proxy-server.js.map +1 -0
- package/dist/redaction.d.ts +10 -0
- package/dist/redaction.js +55 -0
- package/dist/redaction.js.map +1 -0
- package/dist/session-model.d.ts +4 -0
- package/dist/session-model.js +270 -0
- package/dist/session-model.js.map +1 -0
- package/dist/session-store.d.ts +50 -0
- package/dist/session-store.js +111 -0
- package/dist/session-store.js.map +1 -0
- package/dist/sinks.d.ts +49 -0
- package/dist/sinks.js +42 -0
- package/dist/sinks.js.map +1 -0
- package/dist/stdio-bridge.d.ts +3 -0
- package/dist/stdio-bridge.js +137 -0
- package/dist/stdio-bridge.js.map +1 -0
- package/dist/types.d.ts +124 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/viewer.html +991 -0
- package/package.json +28 -0
- package/skills/unreal-mcp-observer/SKILL.md +88 -0
- package/skills/unreal-mcp-observer/scripts/query.mjs +225 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
function requestLabel(body) {
|
|
2
|
+
const shape = (body && typeof body === "object" && !Array.isArray(body) ? body : {});
|
|
3
|
+
if (!body || shape.omitted)
|
|
4
|
+
return { title: "MCP request", category: "transport" };
|
|
5
|
+
if (shape.method === "initialize")
|
|
6
|
+
return { title: "MCP initialize", category: "connection" };
|
|
7
|
+
if (shape.method === "notifications/initialized")
|
|
8
|
+
return { title: "MCP initialized", category: "connection" };
|
|
9
|
+
if (shape.method === "tools/list")
|
|
10
|
+
return { title: "List tools", category: "discovery" };
|
|
11
|
+
if (shape.method !== "tools/call")
|
|
12
|
+
return { title: shape.method ?? "MCP request", category: "transport" };
|
|
13
|
+
const name = shape.params?.name ?? "unknown tool";
|
|
14
|
+
const args = shape.params?.arguments ?? {};
|
|
15
|
+
if (name === "list_toolsets" || name === "describe_toolset") {
|
|
16
|
+
return {
|
|
17
|
+
title: name === "list_toolsets" ? "List Unreal toolsets" : "Describe Unreal toolset",
|
|
18
|
+
category: "discovery", toolName: name, arguments: args
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (name === "call_tool") {
|
|
22
|
+
// Epic's Unreal MCP wraps every engine tool behind call_tool(toolset_name, tool_name, arguments)
|
|
23
|
+
const toolsetName = typeof args.toolset_name === "string" ? args.toolset_name : undefined;
|
|
24
|
+
const toolName = typeof args.tool_name === "string" ? args.tool_name : undefined;
|
|
25
|
+
const toolset = toolsetName ? toolsetName.split(".").at(-1) : "TopLevel";
|
|
26
|
+
return {
|
|
27
|
+
title: `${toolset}.${toolName ?? "unknown"}`,
|
|
28
|
+
subtitle: toolsetName,
|
|
29
|
+
category: "unreal_tool",
|
|
30
|
+
toolName,
|
|
31
|
+
toolsetName,
|
|
32
|
+
arguments: (args.arguments ?? {})
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { title: name, category: "mcp_tool", toolName: name, arguments: args };
|
|
36
|
+
}
|
|
37
|
+
function responseError(body, status) {
|
|
38
|
+
if (status != null && status >= 400)
|
|
39
|
+
return `HTTP ${status}`;
|
|
40
|
+
const record = body && typeof body === "object" && !Array.isArray(body) ? body : null;
|
|
41
|
+
const messages = record?.transport === "sse" ? (record.events ?? []) : [body];
|
|
42
|
+
for (const message of messages) {
|
|
43
|
+
const shape = (message ?? {});
|
|
44
|
+
if (shape.error)
|
|
45
|
+
return shape.error.message ?? "MCP error";
|
|
46
|
+
if (shape.result?.isError)
|
|
47
|
+
return "Tool returned an error";
|
|
48
|
+
const content = shape.result?.content;
|
|
49
|
+
if (Array.isArray(content)) {
|
|
50
|
+
const text = content.map((item) => item?.text ?? "").join("\n");
|
|
51
|
+
if (/^(error|failed|exception|timeout)\s*:/i.test(text.trim()))
|
|
52
|
+
return text.slice(0, 300);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const round = (value) => Math.round((value ?? 0) * 100) / 100;
|
|
58
|
+
function median(values) {
|
|
59
|
+
if (!values.length)
|
|
60
|
+
return 0;
|
|
61
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
62
|
+
const middle = Math.floor(sorted.length / 2);
|
|
63
|
+
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
|
64
|
+
}
|
|
65
|
+
function timing(event) {
|
|
66
|
+
const total = event.durationMs ?? 0;
|
|
67
|
+
const headers = Math.min(total, event.responseHeadersMs ?? total);
|
|
68
|
+
const result = Math.min(total, event.resultEventMs ?? total);
|
|
69
|
+
return {
|
|
70
|
+
requestToHeadersMs: round(headers),
|
|
71
|
+
toolResultMs: round(Math.max(0, result - headers)),
|
|
72
|
+
proxyFinalizeMs: round(Math.max(0, total - result)),
|
|
73
|
+
completionReason: event.completionReason ?? "stream_end"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function targetNode(call) {
|
|
77
|
+
const actor = call.arguments?.actor;
|
|
78
|
+
const refPath = actor && typeof actor === "object" && !Array.isArray(actor)
|
|
79
|
+
? actor.refPath : undefined;
|
|
80
|
+
if (typeof refPath === "string" && call.toolName === "get_actor_transform") {
|
|
81
|
+
return { key: `actor:${refPath}`, label: refPath.split(/[.:]/).at(-1) ?? refPath, subtitle: call.toolName, kind: "target" };
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
key: `tool:${call.toolsetName ?? call.category}:${call.toolName ?? call.title}`,
|
|
85
|
+
label: call.toolName ?? call.title,
|
|
86
|
+
subtitle: call.toolsetName ?? call.category,
|
|
87
|
+
kind: "tool"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function buildFlowGraph(calls) {
|
|
91
|
+
const nodes = [];
|
|
92
|
+
const nodesByKey = new Map();
|
|
93
|
+
const edges = [];
|
|
94
|
+
let previousNodeId = "start";
|
|
95
|
+
for (const [index, call] of calls.entries()) {
|
|
96
|
+
const descriptor = targetNode(call);
|
|
97
|
+
let node = nodesByKey.get(descriptor.key);
|
|
98
|
+
if (!node) {
|
|
99
|
+
node = { id: `node-${nodes.length + 1}`, ...descriptor, callIds: [] };
|
|
100
|
+
nodesByKey.set(descriptor.key, node);
|
|
101
|
+
nodes.push(node);
|
|
102
|
+
}
|
|
103
|
+
node.callIds.push(call.id);
|
|
104
|
+
edges.push({
|
|
105
|
+
id: `edge-${index + 1}`,
|
|
106
|
+
from: previousNodeId,
|
|
107
|
+
to: node.id,
|
|
108
|
+
order: index + 1,
|
|
109
|
+
callId: call.id,
|
|
110
|
+
durationMs: round(call.durationMs),
|
|
111
|
+
outcome: call.outcome
|
|
112
|
+
});
|
|
113
|
+
previousNodeId = node.id;
|
|
114
|
+
}
|
|
115
|
+
return { basis: "raw_mcp", nodes, edges };
|
|
116
|
+
}
|
|
117
|
+
export function buildSessionModel(sessionId, events, webBaseUrl, now = Date.now()) {
|
|
118
|
+
const started = new Map();
|
|
119
|
+
const completed = new Set();
|
|
120
|
+
const annotations = [];
|
|
121
|
+
const calls = [];
|
|
122
|
+
const base = webBaseUrl.replace(/\/$/, "");
|
|
123
|
+
for (const event of events) {
|
|
124
|
+
if (event.type === "mcp_request_started")
|
|
125
|
+
started.set(event.callId, event);
|
|
126
|
+
if (event.type === "ai_annotation") {
|
|
127
|
+
annotations.push({
|
|
128
|
+
callId: event.callId,
|
|
129
|
+
severity: event.severity ?? "info",
|
|
130
|
+
title: event.title,
|
|
131
|
+
summary: event.summary,
|
|
132
|
+
cause: event.cause,
|
|
133
|
+
suggestion: event.suggestion,
|
|
134
|
+
author: event.author ?? "agent",
|
|
135
|
+
timestamp: event.timestamp
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (event.type === "mcp_request_completed" || event.type === "mcp_request_failed") {
|
|
139
|
+
const payload = event;
|
|
140
|
+
const callId = payload.callId ?? "";
|
|
141
|
+
const begin = started.get(callId);
|
|
142
|
+
completed.add(callId);
|
|
143
|
+
const beginBody = (begin?.body ?? null);
|
|
144
|
+
const label = requestLabel(beginBody);
|
|
145
|
+
const error = event.type === "mcp_request_failed"
|
|
146
|
+
? (payload.error ?? "proxy or upstream connection failed")
|
|
147
|
+
: responseError(payload.body, payload.status ?? null);
|
|
148
|
+
calls.push({
|
|
149
|
+
id: callId,
|
|
150
|
+
sequence: begin?.sequence ?? event.sequence,
|
|
151
|
+
startedAt: begin?.timestamp ?? event.timestamp,
|
|
152
|
+
completedAt: event.timestamp,
|
|
153
|
+
durationMs: payload.durationMs ?? null,
|
|
154
|
+
statusCode: payload.status ?? null,
|
|
155
|
+
outcome: error ? "error" : "success",
|
|
156
|
+
error,
|
|
157
|
+
request: beginBody,
|
|
158
|
+
response: (payload.body ?? null),
|
|
159
|
+
requestHeaders: (begin?.headers ?? null),
|
|
160
|
+
responseHeaders: (payload.headers ?? null),
|
|
161
|
+
timing: timing(payload),
|
|
162
|
+
clientSource: begin?.clientSource ?? "agent",
|
|
163
|
+
operationId: begin?.operationId,
|
|
164
|
+
operationTitle: begin?.operationTitle,
|
|
165
|
+
operationStep: begin?.operationStep,
|
|
166
|
+
deepLink: `${base}/sessions/${sessionId}?call=${callId}`,
|
|
167
|
+
annotations: [],
|
|
168
|
+
isSystem: false,
|
|
169
|
+
...label
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const [callId, begin] of started) {
|
|
174
|
+
if (completed.has(callId))
|
|
175
|
+
continue;
|
|
176
|
+
const label = requestLabel((begin.body ?? null));
|
|
177
|
+
calls.push({
|
|
178
|
+
id: callId,
|
|
179
|
+
sequence: begin.sequence,
|
|
180
|
+
startedAt: begin.timestamp,
|
|
181
|
+
completedAt: null,
|
|
182
|
+
durationMs: Math.max(0, now - new Date(begin.timestamp).getTime()),
|
|
183
|
+
statusCode: null,
|
|
184
|
+
outcome: "running",
|
|
185
|
+
error: null,
|
|
186
|
+
request: (begin.body ?? null),
|
|
187
|
+
response: null,
|
|
188
|
+
requestHeaders: (begin.headers ?? null),
|
|
189
|
+
responseHeaders: null,
|
|
190
|
+
timing: null,
|
|
191
|
+
clientSource: begin.clientSource ?? "agent",
|
|
192
|
+
operationId: begin.operationId,
|
|
193
|
+
operationTitle: begin.operationTitle,
|
|
194
|
+
operationStep: begin.operationStep,
|
|
195
|
+
deepLink: `${base}/sessions/${sessionId}?call=${callId}`,
|
|
196
|
+
annotations: [],
|
|
197
|
+
isSystem: false,
|
|
198
|
+
...label
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
for (const call of calls) {
|
|
202
|
+
call.isSystem = call.clientSource === "smoke-test" || call.category === "connection" || call.category === "discovery";
|
|
203
|
+
}
|
|
204
|
+
const workCalls = calls.filter((call) => !call.isSystem);
|
|
205
|
+
const errors = workCalls.filter((call) => call.outcome === "error").length;
|
|
206
|
+
const running = workCalls.filter((call) => call.outcome === "running").length;
|
|
207
|
+
const completedWork = workCalls.filter((call) => call.outcome !== "running");
|
|
208
|
+
const recent = [...completedWork].sort((a, b) => b.sequence - a.sequence).slice(0, 20);
|
|
209
|
+
// Per-tool baseline so the viewer can flag calls that are unusually slow for this session
|
|
210
|
+
const durationsByTool = new Map();
|
|
211
|
+
for (const call of completedWork) {
|
|
212
|
+
const values = durationsByTool.get(call.title) ?? [];
|
|
213
|
+
values.push(call.durationMs ?? 0);
|
|
214
|
+
durationsByTool.set(call.title, values);
|
|
215
|
+
}
|
|
216
|
+
for (const call of completedWork) {
|
|
217
|
+
const samples = durationsByTool.get(call.title) ?? [];
|
|
218
|
+
const baseline = median(samples);
|
|
219
|
+
const ratio = baseline ? (call.durationMs ?? 0) / baseline : 1;
|
|
220
|
+
call.performance = {
|
|
221
|
+
sampleCount: samples.length,
|
|
222
|
+
medianMs: round(baseline),
|
|
223
|
+
ratio: round(ratio),
|
|
224
|
+
classification: samples.length >= 3 && ratio >= 2 && (call.durationMs ?? 0) >= 50 ? "slow" : "normal"
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
for (const call of calls) {
|
|
228
|
+
const latestByAuthor = new Map();
|
|
229
|
+
for (const note of annotations.filter((item) => item.callId === call.id)) {
|
|
230
|
+
latestByAuthor.set(note.author, note);
|
|
231
|
+
}
|
|
232
|
+
call.annotations = [...latestByAuthor.values()];
|
|
233
|
+
}
|
|
234
|
+
const chronological = [...workCalls].sort((a, b) => a.sequence - b.sequence);
|
|
235
|
+
return {
|
|
236
|
+
id: sessionId,
|
|
237
|
+
startedAt: events.find((event) => event.type === "session_started")?.timestamp,
|
|
238
|
+
lastEventAt: events.at(-1)?.timestamp,
|
|
239
|
+
summary: {
|
|
240
|
+
totalCalls: workCalls.length,
|
|
241
|
+
successes: completedWork.length - errors,
|
|
242
|
+
errors,
|
|
243
|
+
running,
|
|
244
|
+
systemCalls: calls.length - workCalls.length,
|
|
245
|
+
averageDurationMs: recent.length
|
|
246
|
+
? Math.round(recent.reduce((sum, call) => sum + (call.durationMs ?? 0), 0) / recent.length)
|
|
247
|
+
: 0,
|
|
248
|
+
medianDurationMs: round(median(recent.map((call) => call.durationMs ?? 0)))
|
|
249
|
+
},
|
|
250
|
+
graph: buildFlowGraph(chronological),
|
|
251
|
+
calls: calls.sort((a, b) => b.sequence - a.sequence),
|
|
252
|
+
annotations,
|
|
253
|
+
rawEventCount: events.length
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** Normalize an error message for similarity matching: mask paths, UUIDs, hex, and numbers. */
|
|
257
|
+
export function normalizeError(text) {
|
|
258
|
+
if (!text)
|
|
259
|
+
return null;
|
|
260
|
+
return text.toLowerCase()
|
|
261
|
+
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, "<uuid>")
|
|
262
|
+
.replace(/[a-z]:\\[^\s"'`]+/g, "<path>")
|
|
263
|
+
.replace(/(?:\/[\w.-]+){2,}/g, "<path>")
|
|
264
|
+
.replace(/0x[0-9a-f]+/g, "<hex>")
|
|
265
|
+
.replace(/\d+/g, "<n>")
|
|
266
|
+
.replace(/\s+/g, " ")
|
|
267
|
+
.trim()
|
|
268
|
+
.slice(0, 500);
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=session-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-model.js","sourceRoot":"","sources":["../src/session-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ActiveSession, JsonObject, SessionEvent } from "./types.js";
|
|
2
|
+
export type EventListener = (event: SessionEvent) => void | Promise<void>;
|
|
3
|
+
export interface SessionStoreOptions {
|
|
4
|
+
/** Envelope source tag. Defaults to "unreal-mcp-proxy". */
|
|
5
|
+
source?: string;
|
|
6
|
+
/** Optional metadata stamped on every event (user/machine/project). */
|
|
7
|
+
identity?: JsonObject | null;
|
|
8
|
+
now?: () => Date;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Append-only JSONL session store.
|
|
12
|
+
* An observation session survives proxy and Unreal Editor restarts; it only rolls over
|
|
13
|
+
* when the user explicitly clears it. Event sinks attach via subscribe() and are awaited,
|
|
14
|
+
* so a sink can persist events durably in the same write chain.
|
|
15
|
+
*/
|
|
16
|
+
export declare class SessionStore {
|
|
17
|
+
readonly dataDir: string;
|
|
18
|
+
readonly sessionsDir: string;
|
|
19
|
+
private readonly activePath;
|
|
20
|
+
private readonly webBaseUrl;
|
|
21
|
+
private readonly source;
|
|
22
|
+
private readonly identity;
|
|
23
|
+
private readonly now;
|
|
24
|
+
session: ActiveSession | null;
|
|
25
|
+
private writeChain;
|
|
26
|
+
private readonly listeners;
|
|
27
|
+
constructor(dataDir: string, webBaseUrl: string, options?: SessionStoreOptions);
|
|
28
|
+
initialize(): Promise<ActiveSession>;
|
|
29
|
+
createSession(reason?: string): Promise<ActiveSession & {
|
|
30
|
+
url: string;
|
|
31
|
+
}>;
|
|
32
|
+
describe(): ActiveSession & {
|
|
33
|
+
url: string;
|
|
34
|
+
};
|
|
35
|
+
clear(): Promise<{
|
|
36
|
+
previous: ActiveSession & {
|
|
37
|
+
url: string;
|
|
38
|
+
};
|
|
39
|
+
current: ActiveSession & {
|
|
40
|
+
url: string;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
append(type: string, payload?: Record<string, unknown>): Promise<SessionEvent>;
|
|
44
|
+
withWriteLock<T>(action: () => Promise<T>): Promise<T>;
|
|
45
|
+
/** Attach an event sink. Listeners are awaited inside the write chain. Returns an unsubscribe fn. */
|
|
46
|
+
subscribe(listener: EventListener): () => void;
|
|
47
|
+
eventPath(sessionId: string): string;
|
|
48
|
+
readEvents(sessionId?: string): Promise<SessionEvent[]>;
|
|
49
|
+
private persistActive;
|
|
50
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile, appendFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Append-only JSONL session store.
|
|
6
|
+
* An observation session survives proxy and Unreal Editor restarts; it only rolls over
|
|
7
|
+
* when the user explicitly clears it. Event sinks attach via subscribe() and are awaited,
|
|
8
|
+
* so a sink can persist events durably in the same write chain.
|
|
9
|
+
*/
|
|
10
|
+
export class SessionStore {
|
|
11
|
+
dataDir;
|
|
12
|
+
sessionsDir;
|
|
13
|
+
activePath;
|
|
14
|
+
webBaseUrl;
|
|
15
|
+
source;
|
|
16
|
+
identity;
|
|
17
|
+
now;
|
|
18
|
+
session = null;
|
|
19
|
+
writeChain = Promise.resolve();
|
|
20
|
+
listeners = new Set();
|
|
21
|
+
constructor(dataDir, webBaseUrl, options = {}) {
|
|
22
|
+
this.dataDir = dataDir;
|
|
23
|
+
this.webBaseUrl = webBaseUrl.replace(/\/$/, "");
|
|
24
|
+
this.source = options.source ?? "unreal-mcp-proxy";
|
|
25
|
+
this.identity = options.identity ?? null;
|
|
26
|
+
this.now = options.now ?? (() => new Date());
|
|
27
|
+
this.activePath = path.join(dataDir, "active-session.json");
|
|
28
|
+
this.sessionsDir = path.join(dataDir, "sessions");
|
|
29
|
+
}
|
|
30
|
+
async initialize() {
|
|
31
|
+
await mkdir(this.sessionsDir, { recursive: true });
|
|
32
|
+
try {
|
|
33
|
+
this.session = JSON.parse(await readFile(this.activePath, "utf8"));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error.code !== "ENOENT")
|
|
37
|
+
throw error;
|
|
38
|
+
await this.createSession("initial_start");
|
|
39
|
+
}
|
|
40
|
+
return this.session;
|
|
41
|
+
}
|
|
42
|
+
async createSession(reason = "user_clear") {
|
|
43
|
+
this.session = { id: randomUUID(), createdAt: this.now().toISOString(), reason, sequence: 0 };
|
|
44
|
+
await this.persistActive();
|
|
45
|
+
await this.append("session_started", { reason });
|
|
46
|
+
return this.describe();
|
|
47
|
+
}
|
|
48
|
+
describe() {
|
|
49
|
+
return { ...this.session, url: `${this.webBaseUrl}/sessions/${this.session.id}` };
|
|
50
|
+
}
|
|
51
|
+
async clear() {
|
|
52
|
+
const previous = this.describe();
|
|
53
|
+
await this.append("session_closed", { reason: "user_clear" });
|
|
54
|
+
const current = await this.createSession("user_clear");
|
|
55
|
+
return { previous, current };
|
|
56
|
+
}
|
|
57
|
+
async append(type, payload = {}) {
|
|
58
|
+
return this.withWriteLock(async () => {
|
|
59
|
+
const session = this.session;
|
|
60
|
+
const event = {
|
|
61
|
+
...payload,
|
|
62
|
+
schemaVersion: 2,
|
|
63
|
+
source: this.source,
|
|
64
|
+
sessionId: session.id,
|
|
65
|
+
sequence: ++session.sequence,
|
|
66
|
+
timestamp: this.now().toISOString(),
|
|
67
|
+
type,
|
|
68
|
+
identity: this.identity
|
|
69
|
+
};
|
|
70
|
+
await appendFile(this.eventPath(session.id), `${JSON.stringify(event)}\n`, "utf8");
|
|
71
|
+
await this.persistActive();
|
|
72
|
+
for (const listener of this.listeners) {
|
|
73
|
+
try {
|
|
74
|
+
await listener(event);
|
|
75
|
+
}
|
|
76
|
+
catch { /* a sink must never break recording */ }
|
|
77
|
+
}
|
|
78
|
+
return event;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
withWriteLock(action) {
|
|
82
|
+
const result = this.writeChain.then(action);
|
|
83
|
+
this.writeChain = result.catch(() => { });
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/** Attach an event sink. Listeners are awaited inside the write chain. Returns an unsubscribe fn. */
|
|
87
|
+
subscribe(listener) {
|
|
88
|
+
this.listeners.add(listener);
|
|
89
|
+
return () => this.listeners.delete(listener);
|
|
90
|
+
}
|
|
91
|
+
eventPath(sessionId) {
|
|
92
|
+
return path.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
93
|
+
}
|
|
94
|
+
async readEvents(sessionId = this.session.id) {
|
|
95
|
+
try {
|
|
96
|
+
const text = await readFile(this.eventPath(sessionId), "utf8");
|
|
97
|
+
return text.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error.code === "ENOENT")
|
|
101
|
+
return [];
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async persistActive() {
|
|
106
|
+
const temporaryPath = `${this.activePath}.tmp`;
|
|
107
|
+
await writeFile(temporaryPath, JSON.stringify(this.session, null, 2), "utf8");
|
|
108
|
+
await rename(temporaryPath, this.activePath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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"}
|
package/dist/sinks.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ProxyConfig } from "./config.js";
|
|
2
|
+
import type { SessionStore } from "./session-store.js";
|
|
3
|
+
import type { SessionEvent } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Event sinks: forward recorded events somewhere else (a team server, a message queue,
|
|
6
|
+
* a log shipper) without touching the proxy core. A sink is an ES module listed in the
|
|
7
|
+
* config, either as a plain path or with injected options:
|
|
8
|
+
*
|
|
9
|
+
* "sinks": [
|
|
10
|
+
* "./my-sink.mjs",
|
|
11
|
+
* { "module": "@myteam/collector-sink", "options": { "url": "https://...", "token": "..." } }
|
|
12
|
+
* ]
|
|
13
|
+
*
|
|
14
|
+
* `module` may be a relative path (resolved against the config file's directory), an
|
|
15
|
+
* absolute path, or a bare npm specifier. The default export is a factory:
|
|
16
|
+
*
|
|
17
|
+
* export default function createSink({ config, options, log }) {
|
|
18
|
+
* return {
|
|
19
|
+
* onEvent(event) { ... }, // awaited; errors are swallowed per-event
|
|
20
|
+
* async close() { ... } // optional; called on shutdown
|
|
21
|
+
* };
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Recording locally always comes first: a sink that throws never breaks recording.
|
|
25
|
+
* A sink that fails to LOAD, however, fails startup - a misconfigured monitoring
|
|
26
|
+
* pipeline should be visible, not silent.
|
|
27
|
+
*/
|
|
28
|
+
export type SinkSpec = string | {
|
|
29
|
+
module: string;
|
|
30
|
+
options?: unknown;
|
|
31
|
+
};
|
|
32
|
+
export interface SinkContext {
|
|
33
|
+
config: ProxyConfig;
|
|
34
|
+
/** The `options` value from this sink's config entry (undefined for plain-path entries). */
|
|
35
|
+
options: unknown;
|
|
36
|
+
log: (message: string) => void;
|
|
37
|
+
}
|
|
38
|
+
export interface Sink {
|
|
39
|
+
onEvent: (event: SessionEvent) => void | Promise<void>;
|
|
40
|
+
close?: () => void | Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export type SinkFactory = (context: SinkContext) => Sink | Promise<Sink>;
|
|
43
|
+
export interface AttachedSink {
|
|
44
|
+
modulePath: string;
|
|
45
|
+
sink: Sink;
|
|
46
|
+
unsubscribe: () => void;
|
|
47
|
+
}
|
|
48
|
+
export declare function loadSinks(store: SessionStore, config: ProxyConfig, log: (message: string) => void): Promise<AttachedSink[]>;
|
|
49
|
+
export declare function closeSinks(sinks: AttachedSink[]): Promise<void>;
|
package/dist/sinks.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function resolveSpecifier(module, baseDir) {
|
|
4
|
+
if (module.startsWith("./") || module.startsWith("../")) {
|
|
5
|
+
return pathToFileURL(path.resolve(baseDir, module)).href;
|
|
6
|
+
}
|
|
7
|
+
if (path.isAbsolute(module))
|
|
8
|
+
return pathToFileURL(module).href;
|
|
9
|
+
return module; // bare specifier: an npm package (e.g. a team-published sink)
|
|
10
|
+
}
|
|
11
|
+
export async function loadSinks(store, config, log) {
|
|
12
|
+
const attached = [];
|
|
13
|
+
for (const spec of config.sinks) {
|
|
14
|
+
const { module, options } = typeof spec === "string" ? { module: spec, options: undefined } : spec;
|
|
15
|
+
const specifier = resolveSpecifier(module, config.baseDir);
|
|
16
|
+
let factory;
|
|
17
|
+
try {
|
|
18
|
+
const loaded = await import(specifier);
|
|
19
|
+
if (typeof loaded.default !== "function")
|
|
20
|
+
throw new Error("default export is not a sink factory function");
|
|
21
|
+
factory = loaded.default;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`failed to load sink "${module}": ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
const sink = await factory({ config, options, log });
|
|
27
|
+
const unsubscribe = store.subscribe((event) => sink.onEvent(event));
|
|
28
|
+
attached.push({ modulePath: module, sink, unsubscribe });
|
|
29
|
+
log(`sink attached: ${module}`);
|
|
30
|
+
}
|
|
31
|
+
return attached;
|
|
32
|
+
}
|
|
33
|
+
export async function closeSinks(sinks) {
|
|
34
|
+
for (const { sink, unsubscribe } of sinks) {
|
|
35
|
+
unsubscribe();
|
|
36
|
+
try {
|
|
37
|
+
await sink.close?.();
|
|
38
|
+
}
|
|
39
|
+
catch { /* shutdown must not fail on a sink */ }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=sinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.js","sourceRoot":"","sources":["../src/sinks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAoD7B,SAAS,gBAAgB,CAAC,MAAc,EAAE,OAAe;IACvD,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;IAC/D,OAAO,MAAM,CAAC,CAAC,8DAA8D;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAmB,EAAE,MAAmB,EAAE,GAA8B;IACtG,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACnG,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,OAAoB,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAA8B,CAAC;YACpE,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC3G,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,MAAO,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACrD,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACzD,GAAG,CAAC,kBAAkB,MAAM,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAqB;IACpD,KAAK,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,KAAK,EAAE,CAAC;QAC1C,WAAW,EAAE,CAAC;QACd,IAAI,CAAC;YAAC,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,sCAAsC,CAAC,CAAC;IAChF,CAAC;AACH,CAAC"}
|