@map-audio/pam-mcp-server 1.25.0 → 1.28.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.
Files changed (75) hide show
  1. package/README.md +32 -10
  2. package/dist/audioAnalysis.d.ts +1 -1
  3. package/dist/audioAnalysis.js +3 -4
  4. package/dist/audioAnalysis.js.map +1 -1
  5. package/dist/bridge.d.ts +5 -1
  6. package/dist/bridge.js +48 -48
  7. package/dist/bridge.js.map +1 -1
  8. package/dist/daemon.d.ts +32 -0
  9. package/dist/daemon.js +871 -0
  10. package/dist/daemon.js.map +7 -0
  11. package/dist/drumSynth.d.ts +12 -0
  12. package/dist/drumSynth.js +15 -3
  13. package/dist/drumSynth.js.map +1 -1
  14. package/dist/drumSynthEngines.json +9 -1
  15. package/dist/{fgemma-format.d.ts → functiongemma-format.d.ts} +25 -7
  16. package/dist/{fgemma-format.js → functiongemma-format.js} +34 -11
  17. package/dist/functiongemma-format.js.map +1 -0
  18. package/dist/functiongemma-grammar.d.ts +14 -0
  19. package/dist/functiongemma-grammar.js +140 -0
  20. package/dist/functiongemma-grammar.js.map +1 -0
  21. package/dist/functiongemma-params.d.ts +33 -0
  22. package/dist/functiongemma-params.js +128 -0
  23. package/dist/functiongemma-params.js.map +1 -0
  24. package/dist/{fgemma-runner.d.ts → functiongemma-runner.d.ts} +8 -5
  25. package/dist/{fgemma-runner.js → functiongemma-runner.js} +107 -25
  26. package/dist/functiongemma-runner.js.map +1 -0
  27. package/dist/generated/kvWriteOptions.d.ts +20 -0
  28. package/dist/generated/kvWriteOptions.js +45 -0
  29. package/dist/generated/kvWriteOptions.js.map +1 -0
  30. package/dist/generated/variationSnapshot.js +0 -19
  31. package/dist/generated/variationSnapshot.js.map +1 -1
  32. package/dist/generativePlayerPresets.json +9 -0
  33. package/dist/generativePlayers.json +15 -1
  34. package/dist/launcher.d.ts +2 -0
  35. package/dist/launcher.js +36 -0
  36. package/dist/launcher.js.map +1 -0
  37. package/dist/manifest-mfx.json +12944 -311
  38. package/dist/manifest-msq.json +11446 -0
  39. package/dist/manifest.d.ts +2 -0
  40. package/dist/manifest.js +12 -27
  41. package/dist/manifest.js.map +1 -1
  42. package/dist/manifest.json +37047 -6487
  43. package/dist/mfxCombos.d.ts +2 -0
  44. package/dist/mfxCombos.js +179 -56
  45. package/dist/mfxCombos.js.map +1 -1
  46. package/dist/midiFile.d.ts +20 -6
  47. package/dist/midiFile.js +22 -14
  48. package/dist/midiFile.js.map +1 -1
  49. package/dist/mixAssist.d.ts +35 -0
  50. package/dist/mixAssist.js +250 -21
  51. package/dist/mixAssist.js.map +1 -1
  52. package/dist/patternLanes.d.ts +40 -0
  53. package/dist/patternLanes.js +108 -0
  54. package/dist/patternLanes.js.map +1 -0
  55. package/dist/players.d.ts +22 -0
  56. package/dist/players.js +70 -0
  57. package/dist/players.js.map +1 -1
  58. package/dist/presetSave.d.ts +3 -0
  59. package/dist/presetSave.js +13 -0
  60. package/dist/presetSave.js.map +1 -1
  61. package/dist/productProfile.d.ts +4 -4
  62. package/dist/productProfile.js +10 -1
  63. package/dist/productProfile.js.map +1 -1
  64. package/dist/qwen-format.d.ts +85 -0
  65. package/dist/qwen-format.js +355 -0
  66. package/dist/qwen-format.js.map +1 -0
  67. package/dist/registration.mjs +41 -0
  68. package/dist/server.js +2112 -470
  69. package/dist/server.js.map +1 -1
  70. package/dist/targetDenylist.json +20 -0
  71. package/dist/variationSessionKeys.json +7 -5
  72. package/package.json +13 -7
  73. package/dist/fgemma-format.js.map +0 -1
  74. package/dist/fgemma-runner.js.map +0 -1
  75. package/dist/manifest-cell1.json +0 -12968
package/dist/daemon.js ADDED
@@ -0,0 +1,871 @@
1
+ // ../../daemon/client/src/index.ts
2
+ import { constants } from "node:fs";
3
+ import { lstat, open, readFile } from "node:fs/promises";
4
+ import { Socket } from "node:net";
5
+
6
+ // ../../daemon/client/src/generated/protocol.ts
7
+ var PROTOCOL_VERSION = { major: 1, minor: 1 };
8
+ var FRAME_HEADER_BYTES = 4;
9
+ var MAX_FRAME_BODY_BYTES = 1048576;
10
+ var LIMITS = {
11
+ maxClients: 32,
12
+ maxInFlightRequests: 64,
13
+ maxQueuedOutboundBytes: 4194304,
14
+ maxPromptBytes: 8192,
15
+ maxCataloguePageRecords: 100,
16
+ maxArtifactEntries: 256,
17
+ maxRequestIdDigits: 20,
18
+ maxStringBytes: 65536,
19
+ maxMcpMessageBytes: 786432,
20
+ maxArrayItems: 256,
21
+ maxObjectMembers: 128,
22
+ maxJsonDepth: 32,
23
+ maxErrorMessageBytes: 4096,
24
+ maxRequiredFields: 64
25
+ };
26
+ var METHODS = [
27
+ "system.authenticate",
28
+ "system.capabilities",
29
+ "system.health",
30
+ "catalogue.list",
31
+ "catalogue.get",
32
+ "catalogue.install",
33
+ "catalogue.remove",
34
+ "session.open",
35
+ "session.status",
36
+ "session.close",
37
+ "model.load",
38
+ "model.unload",
39
+ "model.configure",
40
+ "render.submit",
41
+ "render.status",
42
+ "render.cancel",
43
+ "metrics.read",
44
+ "metrics.reset",
45
+ "system.shutdown",
46
+ "job.status",
47
+ "job.cancel",
48
+ "model.ps",
49
+ "mcp.open",
50
+ "mcp.message",
51
+ "job.result",
52
+ "pack.install"
53
+ ];
54
+ var ERROR_CODES = [
55
+ "auth_failed",
56
+ "frame_too_large",
57
+ "version_unsupported",
58
+ "invalid_request",
59
+ "method_not_found",
60
+ "not_found",
61
+ "not_admitted",
62
+ "resource_exhausted",
63
+ "session_not_owned",
64
+ "model_crashed",
65
+ "cancelled",
66
+ "timeout",
67
+ "unavailable",
68
+ "model_in_use",
69
+ "job_expired",
70
+ "daemon_restarted",
71
+ "result_too_large",
72
+ "pack_license_required",
73
+ "pack_not_found",
74
+ "pack_not_installed",
75
+ "pack_in_use",
76
+ "pack_catalog_unavailable",
77
+ "pack_download_failed",
78
+ "pack_signing_unavailable",
79
+ "pack_invalid",
80
+ "pack_install_failed",
81
+ "pack_unsupported_product"
82
+ ];
83
+ var isErrorCode = (value) => typeof value === "string" && ERROR_CODES.includes(value);
84
+
85
+ // ../../daemon/client/src/codec.ts
86
+ var ProtocolError = class extends TypeError {
87
+ constructor(message, code = "invalid_request") {
88
+ super(message);
89
+ this.code = code;
90
+ this.name = "PamdProtocolError";
91
+ }
92
+ code;
93
+ };
94
+ var textEncoder = new TextEncoder();
95
+ var textDecoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
96
+ var COMMON_FIELDS = ["major", "minor", "requiredFields"];
97
+ var KIND_FIELDS = {
98
+ method: ["id", "method", "params"],
99
+ event: ["event", "data"],
100
+ result: ["id", "result"],
101
+ error: ["id", "error"]
102
+ };
103
+ function isRecord(value) {
104
+ return typeof value === "object" && value !== null && !Array.isArray(value);
105
+ }
106
+ function isDecimalRequestId(value) {
107
+ return typeof value === "string" && /^(0|[1-9][0-9]*)$/.test(value) && value.length <= LIMITS.maxRequestIdDigits && BigInt(value) <= 18446744073709551615n;
108
+ }
109
+ function isWireName(value) {
110
+ return typeof value === "string" && value.length > 0 && textEncoder.encode(value).byteLength <= LIMITS.maxStringBytes;
111
+ }
112
+ function validateJsonValue(value, depth = 1, opaqueMcp = false, opaqueString = false) {
113
+ if (depth > LIMITS.maxJsonDepth) throw new ProtocolError("JSON nesting exceeds the protocol limit");
114
+ if (typeof value === "string") {
115
+ if (/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(value)) {
116
+ throw new ProtocolError("JSON string contains an unpaired surrogate");
117
+ }
118
+ if (textEncoder.encode(value).byteLength > (opaqueString ? LIMITS.maxMcpMessageBytes : LIMITS.maxStringBytes)) throw new ProtocolError("JSON string exceeds the protocol limit");
119
+ } else if (typeof value === "number" && !Number.isFinite(value)) {
120
+ throw new ProtocolError("non-finite JSON number");
121
+ } else if (Array.isArray(value)) {
122
+ if (value.length > LIMITS.maxArrayItems) throw new ProtocolError("JSON array exceeds the protocol limit");
123
+ for (const item of value) validateJsonValue(item, depth + 1);
124
+ } else if (isRecord(value)) {
125
+ const entries = Object.entries(value);
126
+ if (entries.length > LIMITS.maxObjectMembers) throw new ProtocolError("JSON object exceeds the protocol limit");
127
+ for (const [key, item] of entries) {
128
+ validateJsonValue(key);
129
+ validateJsonValue(item, depth + 1, opaqueMcp && ["params", "data", "result"].includes(key), opaqueMcp && ["message", "resultJson"].includes(key));
130
+ }
131
+ } else if (value !== null && typeof value !== "boolean" && typeof value !== "number") {
132
+ throw new ProtocolError("value is not representable in JSON");
133
+ }
134
+ }
135
+ function parseBody(text) {
136
+ const stack = [];
137
+ const tokens = /"(?:[^"\\\u0000-\u001f]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"|[{}\[\]]/g;
138
+ for (const match of text.matchAll(tokens)) {
139
+ const token = match[0];
140
+ if (token === "{" || token === "[") {
141
+ stack.push(token === "{" ? /* @__PURE__ */ new Set() : void 0);
142
+ if (stack.length > LIMITS.maxJsonDepth) throw new ProtocolError("JSON nesting exceeds the protocol limit");
143
+ } else if (token === "}" || token === "]") {
144
+ stack.pop();
145
+ } else {
146
+ let next = match.index + token.length;
147
+ while (next < text.length && /[ \t\r\n]/.test(text[next])) ++next;
148
+ if (text[next] === ":") {
149
+ const key = JSON.parse(token);
150
+ const keys = stack.at(-1);
151
+ if (keys?.has(key)) throw new ProtocolError("duplicate JSON object key");
152
+ keys?.add(key);
153
+ }
154
+ }
155
+ }
156
+ return JSON.parse(text);
157
+ }
158
+ function validateRequiredFields(value, kind) {
159
+ if (!Object.hasOwn(value, "requiredFields")) return;
160
+ const fields = value.requiredFields;
161
+ if (!Array.isArray(fields) || fields.length > LIMITS.maxRequiredFields || new Set(fields).size !== fields.length) {
162
+ throw new ProtocolError("requiredFields is invalid");
163
+ }
164
+ const known = [...COMMON_FIELDS, ...KIND_FIELDS[kind] ?? []];
165
+ for (const field of fields) {
166
+ if (typeof field !== "string") throw new ProtocolError("requiredFields contains a non-string value");
167
+ if (!known.includes(field) || !Object.hasOwn(value, field)) {
168
+ throw new ProtocolError(`envelope requires an unsupported field: ${field}`, "version_unsupported");
169
+ }
170
+ }
171
+ }
172
+ function validateEnvelope(value) {
173
+ const opaqueMcp = isRecord(value) && (value.method === "mcp.message" || value.method === "job.result" || value.event === "mcp.message" || Object.hasOwn(value, "result"));
174
+ validateJsonValue(value, 1, opaqueMcp);
175
+ if (!isRecord(value)) throw new ProtocolError("protocol envelope must be an object");
176
+ if (!Number.isInteger(value.major) || Number(value.major) < 0 || Number(value.major) > 65535 || !Number.isInteger(value.minor) || Number(value.minor) < 0 || Number(value.minor) > 65535) {
177
+ throw new ProtocolError("invalid protocol version");
178
+ }
179
+ if (value.major !== PROTOCOL_VERSION.major) throw new ProtocolError("unsupported protocol major", "version_unsupported");
180
+ const discriminators = Object.keys(KIND_FIELDS).filter((field) => Object.hasOwn(value, field));
181
+ const kind = discriminators[0];
182
+ if (discriminators.length !== 1 || kind === void 0) throw new ProtocolError("protocol envelope must contain one discriminator");
183
+ if (kind === "method") {
184
+ if (!isDecimalRequestId(value.id) || typeof value.method !== "string" || !isRecord(value.params)) {
185
+ throw new ProtocolError("invalid request envelope");
186
+ }
187
+ if (!METHODS.some((method) => method === value.method)) throw new ProtocolError("unknown method", "method_not_found");
188
+ } else if (kind === "event") {
189
+ if (!isWireName(value.event) || !Object.hasOwn(value, "data")) throw new ProtocolError("invalid event envelope");
190
+ } else if (kind === "result") {
191
+ if (!isDecimalRequestId(value.id)) throw new ProtocolError("invalid response envelope");
192
+ } else if (!isDecimalRequestId(value.id) || !isRecord(value.error) || !isWireName(value.error.code) || typeof value.error.message !== "string" || textEncoder.encode(value.error.message).byteLength > LIMITS.maxErrorMessageBytes) {
193
+ throw new ProtocolError("invalid error envelope");
194
+ }
195
+ validateRequiredFields(value, kind);
196
+ }
197
+ var isEventEnvelope = (envelope) => Object.hasOwn(envelope, "event");
198
+ var isErrorEnvelope = (envelope) => Object.hasOwn(envelope, "error");
199
+ var isResponseEnvelope = (envelope) => Object.hasOwn(envelope, "result");
200
+ function readNetworkLength(header) {
201
+ return new DataView(header.buffer, header.byteOffset, header.byteLength).getUint32(0, false);
202
+ }
203
+ function frameBodyLength(header) {
204
+ if (header.byteLength !== FRAME_HEADER_BYTES) throw new ProtocolError("invalid protocol frame header");
205
+ const bodyBytes = readNetworkLength(header);
206
+ if (bodyBytes === 0) throw new ProtocolError("empty protocol frame");
207
+ if (bodyBytes > MAX_FRAME_BODY_BYTES) throw new ProtocolError("protocol frame exceeds its limit", "frame_too_large");
208
+ return bodyBytes;
209
+ }
210
+ function decodeBody(body) {
211
+ if (body.byteLength === 0) throw new ProtocolError("empty protocol body");
212
+ if (body.byteLength > MAX_FRAME_BODY_BYTES) throw new ProtocolError("protocol body exceeds its limit", "frame_too_large");
213
+ try {
214
+ const value = parseBody(textDecoder.decode(body));
215
+ validateEnvelope(value);
216
+ return value;
217
+ } catch (error) {
218
+ if (error instanceof ProtocolError) throw error;
219
+ throw new ProtocolError("malformed UTF-8 JSON body");
220
+ }
221
+ }
222
+ var ProtocolCodec = class {
223
+ header = new Uint8Array(FRAME_HEADER_BYTES);
224
+ headerSize = 0;
225
+ body = new Uint8Array(0);
226
+ bodySize = 0;
227
+ static encode(envelope) {
228
+ validateEnvelope(envelope);
229
+ const body = textEncoder.encode(JSON.stringify(envelope));
230
+ if (body.byteLength > MAX_FRAME_BODY_BYTES) throw new ProtocolError("protocol body exceeds its limit", "frame_too_large");
231
+ const frame = new Uint8Array(FRAME_HEADER_BYTES + body.byteLength);
232
+ new DataView(frame.buffer).setUint32(0, body.byteLength, false);
233
+ frame.set(body, FRAME_HEADER_BYTES);
234
+ return frame;
235
+ }
236
+ push(bytes) {
237
+ const output = [];
238
+ let offset = 0;
239
+ try {
240
+ while (offset < bytes.byteLength) {
241
+ if (this.headerSize < FRAME_HEADER_BYTES) {
242
+ const count2 = Math.min(FRAME_HEADER_BYTES - this.headerSize, bytes.byteLength - offset);
243
+ this.header.set(bytes.subarray(offset, offset + count2), this.headerSize);
244
+ this.headerSize += count2;
245
+ offset += count2;
246
+ if (this.headerSize < FRAME_HEADER_BYTES) continue;
247
+ this.body = new Uint8Array(frameBodyLength(this.header));
248
+ this.bodySize = 0;
249
+ }
250
+ const count = Math.min(this.body.byteLength - this.bodySize, bytes.byteLength - offset);
251
+ this.body.set(bytes.subarray(offset, offset + count), this.bodySize);
252
+ this.bodySize += count;
253
+ offset += count;
254
+ if (this.bodySize !== this.body.byteLength) continue;
255
+ output.push(decodeBody(this.body));
256
+ this.reset();
257
+ }
258
+ return output;
259
+ } catch (error) {
260
+ this.reset();
261
+ throw error;
262
+ }
263
+ }
264
+ reset() {
265
+ this.header.fill(0);
266
+ this.headerSize = 0;
267
+ this.body = new Uint8Array(0);
268
+ this.bodySize = 0;
269
+ }
270
+ };
271
+
272
+ // ../../daemon/client/src/index.ts
273
+ var LOOPBACK_ADDRESS = "127.0.0.1";
274
+ var TOKEN = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
275
+ var MAX_REQUEST_ID = 18446744073709551615n;
276
+ var MAX_OWNER_FILE_BYTES = 64 * 1024;
277
+ var O_SHLOCK = 16;
278
+ var PamdApiError = class extends Error {
279
+ constructor(wireCode, message) {
280
+ super(message);
281
+ this.wireCode = wireCode;
282
+ this.name = "PamdApiError";
283
+ this.code = isErrorCode(wireCode) ? wireCode : "unavailable";
284
+ }
285
+ wireCode;
286
+ code;
287
+ };
288
+ function assertToken(token) {
289
+ if (!TOKEN.test(token)) throw new TypeError("PAM token must encode 32 bytes as unpadded base64url");
290
+ }
291
+ var PamdClient = class {
292
+ constructor(transport, token, onEvent = () => {
293
+ }, authenticationTimeoutMs = 5e3) {
294
+ this.transport = transport;
295
+ this.onEvent = onEvent;
296
+ assertToken(token);
297
+ this.authentication = this.submit(
298
+ "system.authenticate",
299
+ { token },
300
+ { timeoutMs: authenticationTimeoutMs },
301
+ true
302
+ ).then((result) => {
303
+ if (result?.authenticated !== true) throw new PamdApiError("auth_failed", "server did not confirm authentication");
304
+ });
305
+ void this.authentication.catch((error) => this.fail(this.asError(error)));
306
+ void this.readLoop();
307
+ }
308
+ transport;
309
+ onEvent;
310
+ nextRequestId = 1n;
311
+ pending = /* @__PURE__ */ new Map();
312
+ retired = /* @__PURE__ */ new Set();
313
+ writeQueue = Promise.resolve();
314
+ queuedBytes = 0;
315
+ writing;
316
+ failure;
317
+ closed = false;
318
+ authentication;
319
+ events = [];
320
+ eventBytes = 0;
321
+ eventDispatch;
322
+ ready() {
323
+ return this.authentication;
324
+ }
325
+ connectionError() {
326
+ return this.failure;
327
+ }
328
+ request(method, params = {}, options = {}) {
329
+ return this.submit(method, params, options);
330
+ }
331
+ close() {
332
+ this.closed = true;
333
+ this.fail(new PamdApiError("unavailable", "client closed"));
334
+ if (this.eventDispatch !== void 0) clearImmediate(this.eventDispatch);
335
+ this.events = [];
336
+ this.eventBytes = 0;
337
+ }
338
+ submit(method, params, options, authenticating = false) {
339
+ if (this.failure !== void 0) return Promise.reject(this.failure);
340
+ const timeout = options.timeoutMs ?? 3e4;
341
+ if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647) {
342
+ return Promise.reject(new PamdApiError("timeout", "invalid or expired request deadline"));
343
+ }
344
+ if (options.signal?.aborted) return Promise.reject(new PamdApiError("cancelled", "request cancelled locally"));
345
+ if (this.pending.size + this.retired.size >= LIMITS.maxInFlightRequests || this.nextRequestId > MAX_REQUEST_ID) {
346
+ return Promise.reject(new PamdApiError("resource_exhausted", "client request limit reached"));
347
+ }
348
+ const deadline = performance.now() + timeout;
349
+ const id = String(this.nextRequestId);
350
+ let bytes;
351
+ try {
352
+ bytes = ProtocolCodec.encode({ major: PROTOCOL_VERSION.major, minor: PROTOCOL_VERSION.minor, id, method, params });
353
+ } catch (error) {
354
+ return Promise.reject(this.asError(error));
355
+ }
356
+ if (this.queuedBytes + bytes.byteLength > LIMITS.maxQueuedOutboundBytes) {
357
+ return Promise.reject(new PamdApiError("resource_exhausted", "client outbound queue is full"));
358
+ }
359
+ ++this.nextRequestId;
360
+ this.queuedBytes += bytes.byteLength;
361
+ const result = new Promise((resolve, reject) => {
362
+ const abort = () => this.abandon(id, new PamdApiError("cancelled", "request cancelled locally; remote work may continue"));
363
+ const timer = setTimeout(
364
+ () => this.abandon(id, new PamdApiError("timeout", "request deadline expired; remote work may continue")),
365
+ Math.max(0, deadline - performance.now())
366
+ );
367
+ this.pending.set(id, {
368
+ resolve: (value) => resolve(value),
369
+ reject,
370
+ deadline,
371
+ timer,
372
+ dispatched: false,
373
+ removeAbort: () => options.signal?.removeEventListener("abort", abort)
374
+ });
375
+ options.signal?.addEventListener("abort", abort, { once: true });
376
+ if (options.signal?.aborted) abort();
377
+ });
378
+ this.writeQueue = this.writeQueue.then(async () => {
379
+ if (!authenticating) await this.authentication;
380
+ const pending = this.pending.get(id);
381
+ if (pending === void 0 || this.failure !== void 0) return;
382
+ if (performance.now() >= pending.deadline) {
383
+ this.abandon(id, new PamdApiError("timeout", "request deadline expired"));
384
+ return;
385
+ }
386
+ pending.dispatched = true;
387
+ this.writing = id;
388
+ try {
389
+ await this.transport.write(bytes);
390
+ } finally {
391
+ this.writing = void 0;
392
+ }
393
+ }).catch((error) => this.fail(this.asError(error))).finally(() => {
394
+ this.queuedBytes -= bytes.byteLength;
395
+ });
396
+ return result;
397
+ }
398
+ remove(id) {
399
+ const pending = this.pending.get(id);
400
+ if (pending !== void 0) {
401
+ this.pending.delete(id);
402
+ clearTimeout(pending.timer);
403
+ pending.removeAbort();
404
+ }
405
+ return pending;
406
+ }
407
+ abandon(id, error) {
408
+ const pending = this.remove(id);
409
+ if (pending === void 0) return;
410
+ pending.reject(error);
411
+ if (this.writing === id) {
412
+ this.fail(error);
413
+ } else if (pending.dispatched) {
414
+ this.retired.add(id);
415
+ }
416
+ }
417
+ asError(error) {
418
+ if (error instanceof PamdApiError) return error;
419
+ return new PamdApiError(
420
+ error instanceof ProtocolError ? error.code : "unavailable",
421
+ error instanceof Error ? error.message : String(error)
422
+ );
423
+ }
424
+ fail(error) {
425
+ if (this.failure !== void 0) return;
426
+ this.failure = error;
427
+ for (const id of this.pending.keys()) this.remove(id)?.reject(error);
428
+ this.transport.close();
429
+ }
430
+ queueEvent(event) {
431
+ if (this.failure !== void 0) return;
432
+ const size = Buffer.byteLength(JSON.stringify(event));
433
+ if (this.events.length >= LIMITS.maxArrayItems || this.eventBytes + size > LIMITS.maxQueuedOutboundBytes) {
434
+ throw new PamdApiError("resource_exhausted", "client event queue is full");
435
+ }
436
+ this.events.push(event);
437
+ this.eventBytes += size;
438
+ if (this.eventDispatch !== void 0) return;
439
+ this.eventDispatch = setImmediate(() => {
440
+ this.eventDispatch = void 0;
441
+ const events = this.events;
442
+ this.events = [];
443
+ this.eventBytes = 0;
444
+ for (const event2 of events) {
445
+ if (this.closed) break;
446
+ try {
447
+ this.onEvent(event2);
448
+ } catch {
449
+ }
450
+ }
451
+ });
452
+ }
453
+ async readLoop() {
454
+ try {
455
+ while (this.failure === void 0) {
456
+ const envelope = decodeBody(await this.transport.readExactly(frameBodyLength(await this.transport.readExactly(FRAME_HEADER_BYTES))));
457
+ if (this.failure !== void 0) return;
458
+ if (isEventEnvelope(envelope)) {
459
+ this.queueEvent(envelope);
460
+ continue;
461
+ }
462
+ if (!isErrorEnvelope(envelope) && !isResponseEnvelope(envelope)) throw new PamdApiError("invalid_request", "server sent a request envelope");
463
+ if (envelope.id === "0" && isErrorEnvelope(envelope)) throw new PamdApiError(envelope.error.code, envelope.error.message);
464
+ const pending = this.pending.get(envelope.id);
465
+ if (pending === void 0) {
466
+ if (this.retired.delete(envelope.id)) continue;
467
+ throw new PamdApiError("invalid_request", "server sent an unknown response id");
468
+ }
469
+ if (!pending.dispatched) throw new PamdApiError("invalid_request", "server replied before request dispatch");
470
+ this.remove(envelope.id);
471
+ if (performance.now() >= pending.deadline) pending.reject(new PamdApiError("timeout", "reply arrived after request deadline"));
472
+ else if (isErrorEnvelope(envelope)) pending.reject(new PamdApiError(envelope.error.code, envelope.error.message));
473
+ else pending.resolve(envelope.result);
474
+ }
475
+ } catch (error) {
476
+ this.fail(this.asError(error));
477
+ }
478
+ }
479
+ };
480
+ var SocketTransport = class {
481
+ constructor(socket) {
482
+ this.socket = socket;
483
+ const limit = LIMITS.maxQueuedOutboundBytes + FRAME_HEADER_BYTES + MAX_FRAME_BODY_BYTES;
484
+ socket.on("data", (chunk) => {
485
+ this.chunks.push(chunk);
486
+ this.size += chunk.byteLength;
487
+ if (this.size > limit) {
488
+ this.fail(new Error("PAM server sent more unread data than the protocol allows"));
489
+ socket.destroy();
490
+ }
491
+ this.wake();
492
+ });
493
+ socket.on("error", (error) => this.fail(error));
494
+ socket.on("close", () => this.fail(new Error("PAM connection closed")));
495
+ }
496
+ socket;
497
+ chunks = [];
498
+ size = 0;
499
+ waiter;
500
+ failure;
501
+ fail(error) {
502
+ this.failure ??= error;
503
+ this.wake();
504
+ }
505
+ wake() {
506
+ this.waiter?.();
507
+ this.waiter = void 0;
508
+ }
509
+ write(bytes) {
510
+ return new Promise((resolve, reject) => {
511
+ this.socket.write(bytes, (error) => error ? reject(error) : resolve());
512
+ });
513
+ }
514
+ async readExactly(bytes) {
515
+ while (this.size < bytes) {
516
+ if (this.failure !== void 0) throw this.failure;
517
+ await new Promise((resolve) => {
518
+ this.waiter = resolve;
519
+ });
520
+ }
521
+ const [only] = this.chunks;
522
+ const joined = this.chunks.length === 1 && only !== void 0 ? only : Buffer.concat(this.chunks, this.size);
523
+ const result = joined.subarray(0, bytes);
524
+ const rest = joined.subarray(bytes);
525
+ this.chunks = rest.byteLength > 0 ? [rest] : [];
526
+ this.size = rest.byteLength;
527
+ return result;
528
+ }
529
+ close() {
530
+ this.socket.destroy();
531
+ }
532
+ };
533
+ async function connectPamd(port, token, onEvent, timeoutMs = 5e3) {
534
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new RangeError("PAM port must be between 1 and 65535");
535
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new RangeError("invalid connection timeout");
536
+ assertToken(token);
537
+ const deadline = performance.now() + timeoutMs;
538
+ const socket = new Socket();
539
+ await new Promise((resolve, reject) => {
540
+ const timer = setTimeout(() => socket.destroy(new PamdApiError("timeout", "connection deadline expired")), timeoutMs);
541
+ const onError = (error) => {
542
+ clearTimeout(timer);
543
+ reject(error instanceof PamdApiError ? error : new PamdApiError("unavailable", error.message));
544
+ };
545
+ socket.once("error", onError);
546
+ socket.connect(port, LOOPBACK_ADDRESS, () => {
547
+ clearTimeout(timer);
548
+ socket.removeListener("error", onError);
549
+ resolve();
550
+ });
551
+ });
552
+ const remaining = deadline - performance.now();
553
+ if (remaining <= 0) {
554
+ socket.destroy();
555
+ throw new PamdApiError("timeout", "connection deadline expired");
556
+ }
557
+ const client = new PamdClient(new SocketTransport(socket), token, onEvent, remaining);
558
+ try {
559
+ await client.ready();
560
+ return client;
561
+ } catch (error) {
562
+ client.close();
563
+ throw error;
564
+ }
565
+ }
566
+ async function readOwnerOnlyFile(path, label) {
567
+ const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
568
+ try {
569
+ const stats = await handle.stat();
570
+ const effectiveUserId = process.geteuid?.();
571
+ if (!stats.isFile() || stats.size > MAX_OWNER_FILE_BYTES || process.platform !== "win32" && ((stats.mode & 63) !== 0 || effectiveUserId === void 0 || stats.uid !== effectiveUserId)) {
572
+ throw new PamdApiError("invalid_request", `${label} must be a small regular owner-only file`);
573
+ }
574
+ const buffer = Buffer.alloc(stats.size);
575
+ const { bytesRead } = await handle.read(buffer, 0, stats.size, 0);
576
+ return buffer.subarray(0, bytesRead).toString("utf8");
577
+ } finally {
578
+ await handle.close();
579
+ }
580
+ }
581
+ async function readPamdDiscovery(path) {
582
+ const record2 = JSON.parse(await readOwnerOnlyFile(path, "PAM discovery record"));
583
+ if (!isRecord(record2) || record2.protocolMajor !== PROTOCOL_VERSION.major || !Number.isInteger(record2.protocolMinor) || Number(record2.protocolMinor) < 0 || Number(record2.protocolMinor) > 65535 || record2.address !== LOOPBACK_ADDRESS || !Number.isInteger(record2.port) || Number(record2.port) < 1 || Number(record2.port) > 65535 || !Number.isSafeInteger(record2.processId) || Number(record2.processId) <= 0 || typeof record2.daemonEpoch !== "string" || record2.daemonEpoch.length === 0 || typeof record2.tokenFile !== "string" || record2.tokenFile.length === 0) {
584
+ throw new PamdApiError("invalid_request", "PAM discovery record is invalid or incompatible");
585
+ }
586
+ return {
587
+ protocolMajor: record2.protocolMajor,
588
+ protocolMinor: Number(record2.protocolMinor),
589
+ address: LOOPBACK_ADDRESS,
590
+ port: Number(record2.port),
591
+ processId: Number(record2.processId),
592
+ daemonEpoch: record2.daemonEpoch,
593
+ tokenFile: record2.tokenFile
594
+ };
595
+ }
596
+ function errorCode(error) {
597
+ return isRecord(error) && typeof error.code === "string" ? error.code : void 0;
598
+ }
599
+ async function linuxFlockHeld(lock) {
600
+ const info = await lstat(lock, { bigint: true });
601
+ const major = info.dev >> 8n & 0xfffn | info.dev >> 32n & ~0xfffn;
602
+ const minor = info.dev & 0xffn | info.dev >> 12n & ~0xffn;
603
+ const inode = `${major.toString(16).padStart(2, "0")}:${minor.toString(16).padStart(2, "0")}:${info.ino}`;
604
+ return (await readFile("/proc/locks", "utf8")).split("\n").some((line) => {
605
+ const fields = line.trim().split(/\s+/);
606
+ return fields[1] === "FLOCK" && fields[3] === "WRITE" && fields[5] === inode;
607
+ });
608
+ }
609
+ async function daemonHoldsLock(discovery) {
610
+ const lock = `${discovery}.lock`;
611
+ try {
612
+ if (process.platform !== "win32") {
613
+ const info = await lstat(lock);
614
+ if (!info.isFile() || info.uid !== process.geteuid?.() || (info.mode & 63) !== 0) {
615
+ throw new PamdApiError("invalid_request", "PAM discovery lock must be a regular owner-only file");
616
+ }
617
+ }
618
+ if (process.platform === "linux") return await linuxFlockHeld(lock);
619
+ if (process.platform !== "darwin" && process.platform !== "win32") {
620
+ throw new PamdApiError("unavailable", "this platform cannot verify which PAM daemon owns discovery");
621
+ }
622
+ const flags = process.platform === "darwin" ? constants.O_RDONLY | constants.O_NOFOLLOW | O_SHLOCK | constants.O_NONBLOCK : constants.O_RDONLY;
623
+ await (await open(lock, flags)).close();
624
+ return false;
625
+ } catch (error) {
626
+ if (error instanceof PamdApiError) throw error;
627
+ const code = errorCode(error);
628
+ if (code === "ENOENT") return false;
629
+ if (code === (process.platform === "win32" ? "EBUSY" : "EAGAIN")) return true;
630
+ throw new PamdApiError("invalid_request", "PAM discovery lock is unreadable or unsafe");
631
+ }
632
+ }
633
+ async function connectPamdFromDiscovery(path, onEvent) {
634
+ const unowned = new PamdApiError("unavailable", "no running PAM daemon has published this discovery record");
635
+ if (!await daemonHoldsLock(path)) throw unowned;
636
+ const record2 = await readPamdDiscovery(path).catch((error) => {
637
+ throw errorCode(error) === "ENOENT" ? unowned : error;
638
+ });
639
+ const token = (await readOwnerOnlyFile(record2.tokenFile, "PAM token")).trim();
640
+ const confirmed = await readPamdDiscovery(path).catch(() => void 0);
641
+ if (confirmed?.daemonEpoch !== record2.daemonEpoch || confirmed.port !== record2.port || confirmed.tokenFile !== record2.tokenFile) {
642
+ throw new PamdApiError("unavailable", "the PAM daemon discovery record changed while connecting");
643
+ }
644
+ return connectPamd(record2.port, token, onEvent);
645
+ }
646
+
647
+ // src/daemon.ts
648
+ import { createHash, randomUUID } from "node:crypto";
649
+ import { constants as constants2 } from "node:fs";
650
+ import { mkdir, open as open2, realpath, rename, unlink } from "node:fs/promises";
651
+ import { basename, dirname, isAbsolute, join } from "node:path";
652
+ import { setTimeout as delay } from "node:timers/promises";
653
+ function record(value) {
654
+ return value !== null && typeof value === "object" && !Array.isArray(value);
655
+ }
656
+ function failure(code, message, detail = {}) {
657
+ return new Error(JSON.stringify({ ok: false, code, error: message, ...detail }));
658
+ }
659
+ function daemonError(error, detail) {
660
+ if (error instanceof PamdApiError) return failure(error.wireCode, error.message, detail);
661
+ if (error instanceof Error && error.message.startsWith('{"ok":false,')) return error;
662
+ return failure("unavailable", error instanceof Error ? error.message : String(error), detail);
663
+ }
664
+ var DaemonSession = class {
665
+ constructor(discovery, dataRoot, outputDirectory) {
666
+ this.discovery = discovery;
667
+ this.dataRoot = dataRoot;
668
+ this.outputDirectory = outputDirectory;
669
+ }
670
+ discovery;
671
+ dataRoot;
672
+ outputDirectory;
673
+ client;
674
+ closing = false;
675
+ calls = /* @__PURE__ */ new Map();
676
+ connection() {
677
+ if (this.closing) throw failure("cancelled", "Plugin session is closing");
678
+ return this.client ??= connectPamdFromDiscovery(this.discovery);
679
+ }
680
+ async job(method, params, signal) {
681
+ const controller = new AbortController();
682
+ const requestKey = randomUUID();
683
+ const cancel = () => {
684
+ controller.abort();
685
+ if (this.client) void this.client.then((client) => this.cancelRequest(client, requestKey)).catch(() => {
686
+ });
687
+ };
688
+ signal?.addEventListener("abort", cancel, { once: true });
689
+ if (signal?.aborted) controller.abort();
690
+ this.calls.set(controller, requestKey);
691
+ let jobId;
692
+ let daemonEpoch;
693
+ try {
694
+ if (controller.signal.aborted) throw failure("cancelled", "Request cancelled");
695
+ const client = await this.connection();
696
+ if (controller.signal.aborted) throw failure("cancelled", "Request cancelled");
697
+ const accepted = await this.submit(client, method, params, requestKey);
698
+ jobId = accepted.jobId;
699
+ daemonEpoch = accepted.daemonEpoch;
700
+ let snapshot = accepted;
701
+ const deadline = performance.now() + 5 * 60 * 1e3;
702
+ let cancellationSent = false;
703
+ for (; ; ) {
704
+ if (snapshot.jobId !== jobId || snapshot.daemonEpoch !== daemonEpoch)
705
+ throw failure("daemon_restarted", "Job identity changed", { jobId, daemonEpoch, requestKey });
706
+ if (typeof snapshot.progress !== "number" || !Number.isFinite(snapshot.progress) || snapshot.progress < 0 || snapshot.progress > 1)
707
+ throw failure("invalid_result", "Invalid daemon job progress", { jobId, daemonEpoch, requestKey });
708
+ if (snapshot.state === "succeeded") {
709
+ if (controller.signal.aborted) throw failure("cancelled", "Request cancelled", { jobId, daemonEpoch, requestKey });
710
+ if (!record(snapshot.result)) throw failure("invalid_result", "Daemon job has no result");
711
+ return snapshot.result;
712
+ }
713
+ if (snapshot.state === "failed" || snapshot.state === "cancelled") {
714
+ const detail = record(snapshot.error) ? snapshot.error : {};
715
+ throw failure(String(detail.code ?? "invalid_result"), String(detail.message ?? "Daemon job failed"), { jobId, daemonEpoch, requestKey });
716
+ }
717
+ if (snapshot.state !== "queued" && snapshot.state !== "running") throw failure("invalid_result", "Invalid daemon job state");
718
+ if (controller.signal.aborted && !cancellationSent) {
719
+ await client.request("job.cancel", { jobId }, { timeoutMs: 500 });
720
+ cancellationSent = true;
721
+ }
722
+ if (performance.now() >= deadline) throw failure("timeout", "Inspect this job before retrying", { jobId, daemonEpoch, requestKey });
723
+ await delay(25);
724
+ snapshot = await client.request("job.status", { jobId, expectedEpoch: daemonEpoch }, { timeoutMs: 5e3 });
725
+ if (!record(snapshot)) throw failure("invalid_result", "Invalid daemon job snapshot");
726
+ }
727
+ } catch (error) {
728
+ throw daemonError(error, { ...jobId ? { jobId, daemonEpoch } : {}, requestKey });
729
+ } finally {
730
+ this.calls.delete(controller);
731
+ signal?.removeEventListener("abort", cancel);
732
+ }
733
+ }
734
+ async submit(client, method, params, requestKey) {
735
+ const snapshot = await client.request(method, { ...params, requestKey }, { timeoutMs: 5e3 });
736
+ if (!record(snapshot) || typeof snapshot.jobId !== "string" || !snapshot.jobId || typeof snapshot.daemonEpoch !== "string" || !snapshot.daemonEpoch)
737
+ throw failure("invalid_result", "Daemon returned no job identity", { requestKey });
738
+ return snapshot;
739
+ }
740
+ async remove(modelId, signal) {
741
+ await this.job("catalogue.remove", { modelId }, signal);
742
+ return { ok: true, model: modelId };
743
+ }
744
+ // Downloads outlive the MCP request: a client timeout must not cancel a multi-GB pull.
745
+ async startDownload(modelId) {
746
+ const requestKey = randomUUID();
747
+ try {
748
+ const { jobId, daemonEpoch, state, progress } = await this.submit(await this.connection(), "catalogue.install", { modelId }, requestKey);
749
+ return { ok: true, model: modelId, jobId, daemonEpoch, requestKey, state, progress };
750
+ } catch (error) {
751
+ throw daemonError(error, { requestKey });
752
+ }
753
+ }
754
+ async downloadStatus({ model, jobId, daemonEpoch, requestKey }) {
755
+ try {
756
+ const snapshot = await (await this.connection()).request("job.status", { jobId, expectedEpoch: daemonEpoch }, { timeoutMs: 5e3 });
757
+ if (!record(snapshot) || snapshot.jobId !== jobId || snapshot.daemonEpoch !== daemonEpoch)
758
+ throw failure("daemon_restarted", "Job identity changed", { jobId, daemonEpoch, requestKey });
759
+ const { state, progress, error } = snapshot;
760
+ return {
761
+ model,
762
+ jobId,
763
+ daemonEpoch,
764
+ requestKey,
765
+ state,
766
+ progress,
767
+ inFlight: state === "queued" || state === "running",
768
+ ...state === "succeeded" ? { downloaded: true } : {},
769
+ ...record(error) ? { error } : {}
770
+ };
771
+ } catch (error) {
772
+ throw daemonError(error, { jobId, daemonEpoch, requestKey });
773
+ }
774
+ }
775
+ async generate(args, signal) {
776
+ const result = await this.job("render.submit", {
777
+ modelId: "stable-audio-3-small-sfx",
778
+ prompt: args.prompt,
779
+ category: args.category,
780
+ durationSeconds: args.durationSeconds,
781
+ seed: args.seed ?? 0
782
+ }, signal);
783
+ if (signal?.aborted || this.closing) throw failure("cancelled", "Request cancelled");
784
+ try {
785
+ const wavPath = await this.publish(result, signal);
786
+ return { ok: true, wavPath, sampleRate: result.sampleRate, seed: result.seed };
787
+ } catch (error) {
788
+ if (error instanceof Error && error.message.startsWith('{"ok":false,')) throw error;
789
+ throw failure("unavailable", error instanceof Error ? error.message : String(error));
790
+ }
791
+ }
792
+ async publish(result, signal) {
793
+ if (typeof result.path !== "string" || !isAbsolute(result.path) || typeof result.fileSha256 !== "string" || !/^[a-f0-9]{64}$/.test(result.fileSha256) || !Number.isInteger(result.seed) || Number(result.seed) <= 0 || Number(result.seed) > 4294967295 || !Number.isInteger(result.sampleRate) || Number(result.sampleRate) <= 0 || Number(result.sampleRate) > 384e3)
794
+ throw failure("invalid_result", "Daemon returned invalid render metadata");
795
+ const source = await realpath(result.path);
796
+ const root = await realpath(join(this.dataRoot, "pam-renders"));
797
+ if (dirname(dirname(source)) !== root || basename(source) !== "audio.wav")
798
+ throw failure("invalid_result", "Render artifact escapes the daemon render directory");
799
+ await mkdir(this.outputDirectory, { recursive: true });
800
+ const output = await realpath(this.outputDirectory);
801
+ const destination = join(output, `${randomUUID()}.wav`);
802
+ const partial = `${destination}.partial`;
803
+ let published = false;
804
+ let partialCreated = false;
805
+ const input = await open2(source, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
806
+ try {
807
+ if (!(await input.stat()).isFile()) throw failure("invalid_result", "Render artifact is not a regular file");
808
+ const reserved = await open2(destination, "wx", 384);
809
+ await reserved.close();
810
+ try {
811
+ const target = await open2(partial, "wx", 384);
812
+ partialCreated = true;
813
+ try {
814
+ const hash = createHash("sha256");
815
+ const bytes = Buffer.alloc(65536);
816
+ for (; ; ) {
817
+ if (signal?.aborted || this.closing) throw failure("cancelled", "Request cancelled");
818
+ const { bytesRead } = await input.read(bytes, 0, bytes.length, null);
819
+ if (!bytesRead) break;
820
+ hash.update(bytes.subarray(0, bytesRead));
821
+ let offset = 0;
822
+ while (offset < bytesRead) {
823
+ const written = await target.write(bytes, offset, bytesRead - offset);
824
+ if (!written.bytesWritten) throw failure("unavailable", "Cannot copy render artifact");
825
+ offset += written.bytesWritten;
826
+ }
827
+ }
828
+ if (hash.digest("hex") !== result.fileSha256) throw failure("invalid_result", "Render artifact failed integrity verification");
829
+ } finally {
830
+ await target.close();
831
+ }
832
+ if (signal?.aborted || this.closing) throw failure("cancelled", "Request cancelled");
833
+ await rename(partial, destination);
834
+ published = true;
835
+ return join(this.outputDirectory, basename(destination));
836
+ } finally {
837
+ if (partialCreated) await unlink(partial).catch(() => {
838
+ });
839
+ if (!published) await unlink(destination).catch(() => {
840
+ });
841
+ }
842
+ } finally {
843
+ await input.close();
844
+ }
845
+ }
846
+ async cancelRequest(client, requestKey) {
847
+ try {
848
+ const snapshot = await client.request("job.status", { requestKey }, { timeoutMs: 300 });
849
+ if (record(snapshot) && typeof snapshot.jobId === "string" && (snapshot.state === "queued" || snapshot.state === "running"))
850
+ await client.request("job.cancel", { jobId: snapshot.jobId }, { timeoutMs: 300 });
851
+ } catch {
852
+ }
853
+ }
854
+ async close() {
855
+ this.closing = true;
856
+ for (const call of this.calls.keys()) call.abort();
857
+ if (!this.client) return;
858
+ try {
859
+ const client = await this.client;
860
+ await Promise.all([...this.calls.values()].map((key) => this.cancelRequest(client, key)));
861
+ const deadline = performance.now() + 500;
862
+ while (this.calls.size && performance.now() < deadline) await delay(10);
863
+ client.close();
864
+ } catch {
865
+ }
866
+ }
867
+ };
868
+ export {
869
+ DaemonSession
870
+ };
871
+ //# sourceMappingURL=daemon.js.map