project-graph-mcp 2.3.1 → 2.3.2

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 (54) hide show
  1. package/package.json +1 -1
  2. package/vendor/symbiote-node/engine/AgentUICommands.js +100 -0
  3. package/vendor/symbiote-node/engine/Executor.js +371 -0
  4. package/vendor/symbiote-node/engine/Graph.js +314 -0
  5. package/vendor/symbiote-node/engine/GraphServer.js +353 -0
  6. package/vendor/symbiote-node/engine/HandlerLoader.js +145 -0
  7. package/vendor/symbiote-node/engine/History.js +83 -0
  8. package/vendor/symbiote-node/engine/Lifecycle.js +118 -0
  9. package/vendor/symbiote-node/engine/Persistence.js +84 -0
  10. package/vendor/symbiote-node/engine/Registry.js +264 -0
  11. package/vendor/symbiote-node/engine/SocketTypes.js +79 -0
  12. package/vendor/symbiote-node/engine/cli.js +404 -0
  13. package/vendor/symbiote-node/engine/index.js +56 -0
  14. package/vendor/symbiote-node/engine/nanoid.js +28 -0
  15. package/vendor/symbiote-node/engine/package.json +26 -0
  16. package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +215 -0
  17. package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +238 -0
  18. package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +287 -0
  19. package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +565 -0
  20. package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +414 -0
  21. package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +343 -0
  22. package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +164 -0
  23. package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +341 -0
  24. package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +241 -0
  25. package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +191 -0
  26. package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +67 -0
  27. package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +281 -0
  28. package/vendor/symbiote-node/engine/packs/data/personas.handler.js +160 -0
  29. package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +193 -0
  30. package/vendor/symbiote-node/engine/packs/data/roles.handler.js +216 -0
  31. package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +244 -0
  32. package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +52 -0
  33. package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +73 -0
  34. package/vendor/symbiote-node/engine/packs/flow/if.handler.js +107 -0
  35. package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +58 -0
  36. package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +60 -0
  37. package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +65 -0
  38. package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +64 -0
  39. package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +39 -0
  40. package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +82 -0
  41. package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +60 -0
  42. package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +63 -0
  43. package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +494 -0
  44. package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +417 -0
  45. package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +43 -0
  46. package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +339 -0
  47. package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +432 -0
  48. package/vendor/symbiote-node/engine/packs/transform/set.handler.js +57 -0
  49. package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +134 -0
  50. package/vendor/symbiote-node/engine/packs/transform/template.handler.js +79 -0
  51. package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +399 -0
  52. package/vendor/symbiote-node/engine/packs/util/delay.handler.js +39 -0
  53. package/vendor/symbiote-node/engine/packs/util/log.handler.js +44 -0
  54. package/vendor/symbiote-node/engine/packs/video-pack.js +323 -0
@@ -0,0 +1,417 @@
1
+ /**
2
+ * transform/effects-skeleton — Beat data → visual effects metadata
3
+ *
4
+ * Pure data transform: analyzes beat/energy arrays to produce
5
+ * structured metadata for driving visual effects (transitions,
6
+ * drops, fades, intensity zones).
7
+ *
8
+ * Input: beatData from ai/beat-detect handler
9
+ * Output: effects skeleton with zones, anchors, markers
10
+ *
11
+ * Ported from Mr-Computer/modules/ai-music-video/src/services/effects-skeleton.js
12
+ *
13
+ * @module agi-graph/packs/transform/effects-skeleton
14
+ */
15
+
16
+ export default {
17
+ type: 'transform/effects-skeleton',
18
+ category: 'transform',
19
+ icon: 'equalizer',
20
+
21
+ driver: {
22
+ description: 'Analyze beat data → intensity zones, drops, fades, transitions',
23
+ inputs: [
24
+ { name: 'beatData', type: 'any' },
25
+ ],
26
+ outputs: [
27
+ { name: 'skeleton', type: 'any' },
28
+ { name: 'error', type: 'string' },
29
+ ],
30
+ params: {
31
+ energyPerSecond: { type: 'int', default: 10, description: 'Energy samples per second' },
32
+ fadeWindowSize: { type: 'number', default: 2.0, description: 'Fade detection window (seconds)' },
33
+ transitionThreshold: { type: 'number', default: 0.25, description: 'Intensity change threshold' },
34
+ dropThreshold: { type: 'number', default: 0.7, description: 'Drop peak threshold (0-1)' },
35
+ // annotate mode
36
+ segments: { type: 'any', default: null, description: 'Timeline segments to annotate with effects' },
37
+ },
38
+ },
39
+
40
+ lifecycle: {
41
+ validate: (inputs) => {
42
+ if (!inputs.beatData) return false;
43
+ return true;
44
+ },
45
+
46
+ cacheKey: (inputs, params) => {
47
+ const bd = inputs.beatData;
48
+ return `effects:${bd.duration || 0}:${bd.tempo || 0}:${params.energyPerSecond}`;
49
+ },
50
+
51
+ execute: async (inputs, params) => {
52
+ try {
53
+ const skeleton = generateEffectsSkeleton(inputs.beatData, params);
54
+
55
+ // If segments provided, annotate them with effects
56
+ if (params.segments) {
57
+ skeleton.annotatedSegments = annotateSegmentsWithEffects(params.segments, skeleton);
58
+ }
59
+
60
+ return { skeleton, error: null };
61
+ } catch (err) {
62
+ return { skeleton: null, error: err.message };
63
+ }
64
+ },
65
+ },
66
+ };
67
+
68
+ // --- Pure analysis functions (ported from effects-skeleton.js) ---
69
+
70
+ /**
71
+ * Detect intensity zones from energy data
72
+ * @param {number[]} energy - Energy values per time unit
73
+ * @param {number} eps - Samples per second
74
+ * @returns {Array<{start: number, end: number, level: string, avgEnergy: number}>}
75
+ */
76
+ function detectIntensityZones(energy, eps) {
77
+ if (!energy || energy.length === 0) return [];
78
+
79
+ const sorted = [...energy].sort((a, b) => a - b);
80
+ const lowThreshold = sorted[Math.floor(sorted.length * 0.33)];
81
+ const highThreshold = sorted[Math.floor(sorted.length * 0.66)];
82
+
83
+ const zones = [];
84
+ let current = null;
85
+
86
+ for (let i = 0; i < energy.length; i++) {
87
+ const time = i / eps;
88
+ const e = energy[i];
89
+ const level = e <= lowThreshold ? 'low' : e >= highThreshold ? 'high' : 'medium';
90
+
91
+ if (!current || current.level !== level) {
92
+ if (current) {
93
+ current.end = time;
94
+ current.avgEnergy = current._sum / current._n;
95
+ delete current._sum;
96
+ delete current._n;
97
+ zones.push(current);
98
+ }
99
+ current = { start: time, end: time, level, _sum: e, _n: 1 };
100
+ } else {
101
+ current.end = time;
102
+ current._sum += e;
103
+ current._n++;
104
+ }
105
+ }
106
+
107
+ if (current) {
108
+ current.avgEnergy = current._sum / current._n;
109
+ delete current._sum;
110
+ delete current._n;
111
+ zones.push(current);
112
+ }
113
+
114
+ // Merge short zones (< 1s)
115
+ const merged = [];
116
+ for (const zone of zones) {
117
+ if (zone.end - zone.start < 1.0 && merged.length > 0) {
118
+ merged[merged.length - 1].end = zone.end;
119
+ } else {
120
+ merged.push(zone);
121
+ }
122
+ }
123
+ return merged;
124
+ }
125
+
126
+ /**
127
+ * Find fade zones — where intensity decreases significantly
128
+ * @param {number[]} energy
129
+ * @param {number} eps
130
+ * @param {number} windowSize - Seconds
131
+ * @returns {Array<{start: number, end: number, fadeAmount: number}>}
132
+ */
133
+ function detectFadeZones(energy, eps, windowSize) {
134
+ if (!energy || energy.length === 0) return [];
135
+
136
+ const ws = Math.floor(windowSize * eps);
137
+ const fades = [];
138
+
139
+ for (let i = ws; i < energy.length; i++) {
140
+ const prevAvg = energy.slice(i - ws, i).reduce((a, b) => a + b, 0) / ws;
141
+ const currEnd = Math.min(i + ws, energy.length);
142
+ const currAvg = energy.slice(i, currEnd).reduce((a, b) => a + b, 0) / (currEnd - i);
143
+
144
+ const fadeAmount = prevAvg - currAvg;
145
+ if (fadeAmount > 0.15) {
146
+ const time = i / eps;
147
+ const last = fades[fades.length - 1];
148
+ if (!last || time - last.end > 2.0) {
149
+ fades.push({
150
+ start: time - windowSize,
151
+ end: time + windowSize,
152
+ fadeAmount: Math.round(fadeAmount * 100) / 100,
153
+ });
154
+ }
155
+ }
156
+ }
157
+ return fades;
158
+ }
159
+
160
+ /**
161
+ * Detect transition anchors — significant intensity changes
162
+ * @param {number[]} energy
163
+ * @param {number} eps
164
+ * @param {number} threshold
165
+ * @returns {Array<{time: number, type: string, magnitude: number}>}
166
+ */
167
+ function detectTransitionAnchors(energy, eps, threshold) {
168
+ if (!energy || energy.length === 0) return [];
169
+
170
+ const anchors = [];
171
+ for (let i = 1; i < energy.length; i++) {
172
+ const change = energy[i] - energy[i - 1];
173
+ if (Math.abs(change) > threshold) {
174
+ anchors.push({
175
+ time: i / eps,
176
+ type: change > 0 ? 'rise' : 'drop',
177
+ magnitude: Math.round(Math.abs(change) * 100) / 100,
178
+ });
179
+ }
180
+ }
181
+
182
+ // Deduplicate within 1s
183
+ const deduped = [];
184
+ for (const a of anchors) {
185
+ const last = deduped[deduped.length - 1];
186
+ if (!last || a.time - last.time > 1.0) {
187
+ deduped.push(a);
188
+ } else if (a.magnitude > last.magnitude) {
189
+ deduped[deduped.length - 1] = a;
190
+ }
191
+ }
192
+ return deduped;
193
+ }
194
+
195
+ /**
196
+ * Detect drop points — high energy peaks
197
+ * @param {number[]} energy
198
+ * @param {number} eps
199
+ * @param {number[]} strongOnsets
200
+ * @param {number} threshold
201
+ * @returns {Array<{time: number, intensity: number, source: string}>}
202
+ */
203
+ function detectDropPoints(energy, eps, strongOnsets, threshold) {
204
+ if (!energy || energy.length === 0) return [];
205
+
206
+ const drops = [];
207
+ const maxEnergy = Math.max(...energy);
208
+
209
+ for (let i = 1; i < energy.length - 1; i++) {
210
+ if (energy[i] > energy[i - 1] && energy[i] > energy[i + 1] && energy[i] > maxEnergy * threshold) {
211
+ drops.push({
212
+ time: i / eps,
213
+ intensity: Math.round((energy[i] / maxEnergy) * 100) / 100,
214
+ source: 'energy-peak',
215
+ });
216
+ }
217
+ }
218
+
219
+ for (const onset of strongOnsets) {
220
+ drops.push({ time: onset, intensity: 0.8, source: 'strong-onset' });
221
+ }
222
+
223
+ drops.sort((a, b) => a.time - b.time);
224
+
225
+ const deduped = [];
226
+ for (const d of drops) {
227
+ const last = deduped[deduped.length - 1];
228
+ if (!last || d.time - last.time > 0.5) deduped.push(d);
229
+ else if (d.intensity > last.intensity) deduped[deduped.length - 1] = d;
230
+ }
231
+ return deduped;
232
+ }
233
+
234
+ /**
235
+ * Get interpolated energy at a time point
236
+ * @param {number} time
237
+ * @param {number[]} energy
238
+ * @param {number} eps
239
+ * @returns {number}
240
+ */
241
+ function getEnergyAtTime(time, energy, eps) {
242
+ if (!energy || energy.length === 0) return 0.5;
243
+ const idx = time * eps;
244
+ const lo = Math.floor(idx);
245
+ const hi = Math.ceil(idx);
246
+ if (lo < 0) return energy[0] || 0.5;
247
+ if (hi >= energy.length) return energy[energy.length - 1] || 0.5;
248
+ if (lo === hi) return energy[lo];
249
+ const f = idx - lo;
250
+ return energy[lo] * (1 - f) + energy[hi] * f;
251
+ }
252
+
253
+ /**
254
+ * Generate beat markers with per-beat energy
255
+ * @param {number[]} beats
256
+ * @param {number[]} energy
257
+ * @param {number} eps
258
+ * @returns {Array<{time: number, index: number, energy: number, isStrong: boolean, isDownbeat: boolean}>}
259
+ */
260
+ function generateBeatMarkers(beats, energy, eps) {
261
+ if (!beats || beats.length === 0) return [];
262
+
263
+ const evals = beats.map(t => getEnergyAtTime(t, energy, eps));
264
+ const avg = evals.reduce((a, b) => a + b, 0) / evals.length;
265
+ const strongThreshold = avg * 1.3;
266
+
267
+ return beats.map((time, index) => {
268
+ const e = getEnergyAtTime(time, energy, eps);
269
+ return {
270
+ time,
271
+ index,
272
+ energy: Math.round(e * 1000) / 1000,
273
+ isStrong: e > strongThreshold,
274
+ isDownbeat: index % 4 === 0,
275
+ isOffbeat: index % 2 === 1,
276
+ };
277
+ });
278
+ }
279
+
280
+ /**
281
+ * Detect high-resolution transitions using beat timestamps
282
+ * @param {number[]} beats
283
+ * @param {number[]} energy
284
+ * @param {number} eps
285
+ * @returns {Array<{time: number, type: string, magnitude: number, beatIndex: number}>}
286
+ */
287
+ function detectHiResTransitions(beats, energy, eps) {
288
+ if (!beats || beats.length < 2) return [];
289
+
290
+ const transitions = [];
291
+ const windowBeats = 4;
292
+ const threshold = 0.2;
293
+
294
+ for (let i = windowBeats; i < beats.length; i++) {
295
+ let prevSum = 0;
296
+ for (let j = i - windowBeats; j < i; j++) {
297
+ prevSum += getEnergyAtTime(beats[j], energy, eps);
298
+ }
299
+ const prevAvg = prevSum / windowBeats;
300
+
301
+ let currSum = 0;
302
+ const end = Math.min(i + windowBeats, beats.length);
303
+ for (let j = i; j < end; j++) {
304
+ currSum += getEnergyAtTime(beats[j], energy, eps);
305
+ }
306
+ const currAvg = currSum / (end - i);
307
+
308
+ const change = currAvg - prevAvg;
309
+ if (Math.abs(change) > threshold) {
310
+ transitions.push({
311
+ time: beats[i],
312
+ beatIndex: i,
313
+ type: change > 0 ? 'rise' : 'drop',
314
+ magnitude: Math.round(Math.abs(change) * 100) / 100,
315
+ });
316
+ }
317
+ }
318
+
319
+ const deduped = [];
320
+ for (const t of transitions) {
321
+ const last = deduped[deduped.length - 1];
322
+ if (!last || t.beatIndex - last.beatIndex > 2) deduped.push(t);
323
+ else if (t.magnitude > last.magnitude) deduped[deduped.length - 1] = t;
324
+ }
325
+ return deduped;
326
+ }
327
+
328
+ /**
329
+ * Generate complete effects skeleton from beat data
330
+ * @param {Object} beatData - Data from ai/beat-detect
331
+ * @param {Object} params - Handler params
332
+ * @returns {Object} Effects skeleton
333
+ */
334
+ function generateEffectsSkeleton(beatData, params) {
335
+ const {
336
+ energy = [],
337
+ peaks = [],
338
+ beats = [],
339
+ tempo = 120,
340
+ duration = 0,
341
+ quietZones = [],
342
+ strongOnsets = [],
343
+ } = beatData;
344
+
345
+ // Support both snake_case (librosa output) and camelCase
346
+ const rawQuietZones = beatData.quiet_zones || quietZones;
347
+ const rawStrongOnsets = beatData.strong_onsets || strongOnsets;
348
+ const rawEps = beatData.energy_per_second || params.energyPerSecond || 10;
349
+ const rawDownbeats = beatData.downbeats || [];
350
+
351
+ const eps = rawEps;
352
+
353
+ const intensityZones = detectIntensityZones(energy, eps);
354
+ const fadeZones = detectFadeZones(energy, eps, params.fadeWindowSize || 2.0);
355
+ const transitionAnchors = detectTransitionAnchors(energy, eps, params.transitionThreshold || 0.25);
356
+ const dropPoints = detectDropPoints(energy, eps, rawStrongOnsets, params.dropThreshold || 0.7);
357
+ const beatMarkers = generateBeatMarkers(beats, energy, eps);
358
+ const hiResTransitions = detectHiResTransitions(beats, energy, eps);
359
+
360
+ const normalizedQuietZones = rawQuietZones.map(z => ({
361
+ start: z.start,
362
+ end: z.end || (z.start + (z.duration || 0)),
363
+ duration: z.duration || (z.end ? z.end - z.start : 0),
364
+ }));
365
+
366
+ const downbeatAnchors = rawDownbeats.map(t => ({ time: t, type: 'downbeat' }));
367
+
368
+ return {
369
+ metadata: {
370
+ duration,
371
+ tempo,
372
+ totalEnergySamples: energy.length,
373
+ energyPerSecond: eps,
374
+ totalBeats: beats.length,
375
+ resolution: 'millisecond',
376
+ },
377
+ intensityZones,
378
+ fadeZones,
379
+ transitionAnchors,
380
+ hiResTransitions,
381
+ dropPoints,
382
+ quietZones: normalizedQuietZones,
383
+ downbeatAnchors,
384
+ beatMarkers,
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Annotate timeline segments with effects metadata
390
+ * @param {Array} segments - Timeline segments [{start, end, ...}]
391
+ * @param {Object} skeleton - Effects skeleton
392
+ * @returns {Array} Segments with .effects field
393
+ */
394
+ function annotateSegmentsWithEffects(segments, skeleton) {
395
+ return segments.map(seg => {
396
+ const { start, end } = seg;
397
+ const mid = (start + end) / 2;
398
+
399
+ const zone = skeleton.intensityZones.find(z => z.start <= mid && z.end >= mid);
400
+ const inFade = skeleton.fadeZones.some(z => z.start <= end && z.end >= start);
401
+ const drops = skeleton.dropPoints.filter(d => d.time >= start && d.time <= end);
402
+ const trans = skeleton.transitionAnchors.filter(a => a.time >= start && a.time <= end);
403
+
404
+ return {
405
+ ...seg,
406
+ effects: {
407
+ intensity: zone?.level || 'medium',
408
+ avgEnergy: zone?.avgEnergy || 0.5,
409
+ isFadeCandidate: inFade,
410
+ hasDrops: drops.length > 0,
411
+ dropCount: drops.length,
412
+ hasTransition: trans.length > 0,
413
+ transitionType: trans[0]?.type || null,
414
+ },
415
+ };
416
+ });
417
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * transform/json-parse — Parse JSON string to object
3
+ *
4
+ * Safely parses JSON input. Returns error instead of throwing.
5
+ *
6
+ * @module agi-graph/packs/transform/json-parse
7
+ */
8
+
9
+ export default {
10
+ type: 'transform/json-parse',
11
+ category: 'transform',
12
+ icon: 'data_object',
13
+
14
+ driver: {
15
+ description: 'Parse JSON string to object',
16
+ inputs: [
17
+ { name: 'input', type: 'string' },
18
+ ],
19
+ outputs: [
20
+ { name: 'result', type: 'any' },
21
+ { name: 'error', type: 'string' },
22
+ ],
23
+ params: {},
24
+ },
25
+
26
+ lifecycle: {
27
+ validate: (inputs) => {
28
+ if (inputs.input === undefined || inputs.input === null) return false;
29
+ return true;
30
+ },
31
+
32
+ cacheKey: (inputs) => `json:${inputs.input}`,
33
+
34
+ execute: async (inputs) => {
35
+ try {
36
+ const result = JSON.parse(inputs.input);
37
+ return { result, error: null };
38
+ } catch (err) {
39
+ return { result: null, error: err.message };
40
+ }
41
+ },
42
+ },
43
+ };