@cadenza.io/service 2.3.17 → 2.5.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/dist/index.js CHANGED
@@ -295,6 +295,72 @@ var DatabaseTask = class extends DeputyTask {
295
295
  var isNode = typeof process !== "undefined" && process.versions?.node != null;
296
296
  var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
297
297
 
298
+ // src/utils/inquiry.ts
299
+ var META_INTENT_PREFIX = "meta-";
300
+ var META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT = "meta-runtime-transport-diagnostics";
301
+ function isPlainObject(value) {
302
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
303
+ }
304
+ function deepMergeDeterministic(left, right) {
305
+ if (Array.isArray(left) && Array.isArray(right)) {
306
+ return [...left, ...right];
307
+ }
308
+ if (isPlainObject(left) && isPlainObject(right)) {
309
+ const merged = { ...left };
310
+ const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])).sort();
311
+ for (const key of keys) {
312
+ if (!(key in left)) {
313
+ merged[key] = right[key];
314
+ continue;
315
+ }
316
+ if (!(key in right)) {
317
+ merged[key] = left[key];
318
+ continue;
319
+ }
320
+ merged[key] = deepMergeDeterministic(left[key], right[key]);
321
+ }
322
+ return merged;
323
+ }
324
+ return right;
325
+ }
326
+ function mergeInquiryContexts(contexts) {
327
+ return contexts.reduce((acc, next) => deepMergeDeterministic(acc, next), {});
328
+ }
329
+ function isMetaIntentName(intentName) {
330
+ return intentName.startsWith(META_INTENT_PREFIX);
331
+ }
332
+ function shouldExecuteInquiryResponder(inquiry, responderIsMeta) {
333
+ if (!isMetaIntentName(inquiry)) {
334
+ return true;
335
+ }
336
+ return responderIsMeta;
337
+ }
338
+ function compareResponderDescriptors(left, right) {
339
+ if (left.serviceName !== right.serviceName) {
340
+ return left.serviceName.localeCompare(right.serviceName);
341
+ }
342
+ if (left.taskName !== right.taskName) {
343
+ return left.taskName.localeCompare(right.taskName);
344
+ }
345
+ if (left.taskVersion !== right.taskVersion) {
346
+ return left.taskVersion - right.taskVersion;
347
+ }
348
+ return left.localTaskName.localeCompare(right.localTaskName);
349
+ }
350
+ function summarizeResponderStatuses(statuses) {
351
+ let responded = 0;
352
+ let failed = 0;
353
+ let timedOut = 0;
354
+ let pending = 0;
355
+ for (const status of statuses) {
356
+ if (status.status === "fulfilled") responded++;
357
+ if (status.status === "failed") failed++;
358
+ if (status.status === "timed_out") timedOut++;
359
+ }
360
+ pending = Math.max(0, statuses.length - responded - failed - timedOut);
361
+ return { responded, failed, timedOut, pending };
362
+ }
363
+
298
364
  // src/registry/ServiceRegistry.ts
299
365
  var ServiceRegistry = class _ServiceRegistry {
300
366
  /**
@@ -310,11 +376,47 @@ var ServiceRegistry = class _ServiceRegistry {
310
376
  this.instances = /* @__PURE__ */ new Map();
311
377
  this.deputies = /* @__PURE__ */ new Map();
312
378
  this.remoteSignals = /* @__PURE__ */ new Map();
379
+ this.remoteIntents = /* @__PURE__ */ new Map();
380
+ this.remoteIntentDeputiesByKey = /* @__PURE__ */ new Map();
381
+ this.remoteIntentDeputiesByTask = /* @__PURE__ */ new Map();
313
382
  this.serviceName = null;
314
383
  this.serviceInstanceId = null;
315
384
  this.numberOfRunningGraphs = 0;
316
385
  this.useSocket = false;
317
386
  this.retryCount = 3;
387
+ CadenzaService.defineIntent({
388
+ name: META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
389
+ description: "Gather transport diagnostics across all services and communication clients.",
390
+ input: {
391
+ type: "object",
392
+ properties: {
393
+ detailLevel: {
394
+ type: "string",
395
+ constraints: {
396
+ oneOf: ["summary", "full"]
397
+ }
398
+ },
399
+ includeErrorHistory: {
400
+ type: "boolean"
401
+ },
402
+ errorHistoryLimit: {
403
+ type: "number",
404
+ constraints: {
405
+ min: 1,
406
+ max: 200
407
+ }
408
+ }
409
+ }
410
+ },
411
+ output: {
412
+ type: "object",
413
+ properties: {
414
+ transportDiagnostics: {
415
+ type: "object"
416
+ }
417
+ }
418
+ }
419
+ });
318
420
  this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
319
421
  "Handle Instance Update",
320
422
  (ctx, emit) => {
@@ -330,10 +432,10 @@ var ServiceRegistry = class _ServiceRegistry {
330
432
  } = serviceInstance;
331
433
  if (uuid4 === this.serviceInstanceId) return;
332
434
  if (deleted) {
333
- this.instances.get(serviceName)?.splice(
334
- this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1,
335
- 1
336
- );
435
+ const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1;
436
+ if (indexToDelete >= 0) {
437
+ this.instances.get(serviceName)?.splice(indexToDelete, 1);
438
+ }
337
439
  if (this.instances.get(serviceName)?.length === 0) {
338
440
  this.instances.delete(serviceName);
339
441
  } else if (this.instances.get(serviceName)?.filter((i) => i.address === address && i.port === port).length === 0) {
@@ -354,7 +456,7 @@ var ServiceRegistry = class _ServiceRegistry {
354
456
  if (this.serviceName === serviceName) {
355
457
  return false;
356
458
  }
357
- if (!isFrontend && this.deputies.has(serviceName) || this.remoteSignals.has(serviceName)) {
459
+ if (!isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
358
460
  const clientCreated = instances?.some(
359
461
  (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
360
462
  );
@@ -376,7 +478,7 @@ var ServiceRegistry = class _ServiceRegistry {
376
478
  communicationTypes
377
479
  });
378
480
  instances?.filter(
379
- (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
481
+ (i) => i.address === address && i.port === port && i.isActive
380
482
  ).forEach((i) => {
381
483
  i.clientCreated = true;
382
484
  });
@@ -404,7 +506,10 @@ var ServiceRegistry = class _ServiceRegistry {
404
506
  for (const serviceInstance of ctx.serviceInstances) {
405
507
  yield { serviceInstance };
406
508
  }
407
- }).doOn("meta.service_registry.registered_global_signals").then(this.handleInstanceUpdateTask);
509
+ }).doOn(
510
+ "meta.service_registry.registered_global_signals",
511
+ "meta.service_registry.registered_global_intents"
512
+ ).then(this.handleInstanceUpdateTask);
408
513
  this.handleGlobalSignalRegistrationTask = CadenzaService.createMetaTask(
409
514
  "Handle global Signal Registration",
410
515
  (ctx) => {
@@ -445,6 +550,32 @@ var ServiceRegistry = class _ServiceRegistry {
445
550
  },
446
551
  "Handles registration of remote signals"
447
552
  ).emits("meta.service_registry.registered_global_signals").doOn("global.meta.cadenza_db.gathered_sync_data");
553
+ this.handleGlobalIntentRegistrationTask = CadenzaService.createMetaTask(
554
+ "Handle global intent registration",
555
+ (ctx) => {
556
+ const intentToTaskMaps = this.normalizeIntentMaps(ctx);
557
+ const sorted = intentToTaskMaps.sort((a, b) => {
558
+ if (a.deleted && !b.deleted) return -1;
559
+ if (!a.deleted && b.deleted) return 1;
560
+ return 0;
561
+ });
562
+ for (const map of sorted) {
563
+ if (map.deleted) {
564
+ this.unregisterRemoteIntentDeputy(map);
565
+ continue;
566
+ }
567
+ CadenzaService.inquiryBroker.addIntent({
568
+ name: map.intentName
569
+ });
570
+ this.registerRemoteIntentDeputy(map);
571
+ }
572
+ return true;
573
+ },
574
+ "Handles registration of remote inquiry intent responders"
575
+ ).emits("meta.service_registry.registered_global_intents").doOn(
576
+ "global.meta.cadenza_db.gathered_sync_data",
577
+ "global.meta.graph_metadata.task_intent_associated"
578
+ );
448
579
  this.handleServiceNotRespondingTask = CadenzaService.createMetaTask(
449
580
  "Handle service not responding",
450
581
  (ctx, emit) => {
@@ -480,7 +611,12 @@ var ServiceRegistry = class _ServiceRegistry {
480
611
  return true;
481
612
  },
482
613
  "Handles service not responding"
483
- ).doOn("meta.fetch.handshake_failed", "meta.socket_client.disconnected").attachSignal("global.meta.service_registry.service_not_responding");
614
+ ).doOn(
615
+ "meta.fetch.handshake_failed",
616
+ "meta.fetch.handshake_failed.*",
617
+ "meta.socket_client.disconnected",
618
+ "meta.socket_client.disconnected.*"
619
+ ).attachSignal("global.meta.service_registry.service_not_responding");
484
620
  this.handleServiceHandshakeTask = CadenzaService.createMetaTask(
485
621
  "Handle service handshake",
486
622
  (ctx, emit) => {
@@ -507,7 +643,10 @@ var ServiceRegistry = class _ServiceRegistry {
507
643
  (i) => i.uuid !== serviceInstanceId && i.address === serviceAddress && i.port === servicePort
508
644
  );
509
645
  for (const i of instancesToDelete ?? []) {
510
- this.instances.get(serviceName)?.splice(this.instances.get(serviceName)?.indexOf(i) ?? -1, 1);
646
+ const indexToDelete = this.instances.get(serviceName)?.indexOf(i) ?? -1;
647
+ if (indexToDelete >= 0) {
648
+ this.instances.get(serviceName)?.splice(indexToDelete, 1);
649
+ }
511
650
  emit("global.meta.service_registry.deleted", {
512
651
  data: {
513
652
  isActive: false,
@@ -550,7 +689,10 @@ var ServiceRegistry = class _ServiceRegistry {
550
689
  });
551
690
  return joinedContext;
552
691
  }
553
- ).emits("meta.service_registry.initial_sync_complete").then(this.handleGlobalSignalRegistrationTask);
692
+ ).emits("meta.service_registry.initial_sync_complete").then(
693
+ this.handleGlobalSignalRegistrationTask,
694
+ this.handleGlobalIntentRegistrationTask
695
+ );
554
696
  this.fullSyncTask = CadenzaService.createMetaRoutine("Full sync", [
555
697
  CadenzaService.createCadenzaDBQueryTask("signal_to_task_map", {
556
698
  filter: {
@@ -558,6 +700,15 @@ var ServiceRegistry = class _ServiceRegistry {
558
700
  },
559
701
  fields: ["signal_name", "service_name", "deleted"]
560
702
  }).then(mergeSyncDataTask),
703
+ CadenzaService.createCadenzaDBQueryTask("intent_to_task_map", {
704
+ fields: [
705
+ "intent_name",
706
+ "task_name",
707
+ "task_version",
708
+ "service_name",
709
+ "deleted"
710
+ ]
711
+ }).then(mergeSyncDataTask),
561
712
  CadenzaService.createCadenzaDBQueryTask("service_instance", {
562
713
  filter: {
563
714
  deleted: false,
@@ -667,8 +818,9 @@ var ServiceRegistry = class _ServiceRegistry {
667
818
  }
668
819
  if (__broadcast || instances[0].isFrontend) {
669
820
  for (const instance of instances) {
821
+ const socketKey = instance.isFrontend ? instance.address : `${instance.address}_${instance.port}`;
670
822
  emit(
671
- `meta.service_registry.selected_instance_for_socket:${instance.address}`,
823
+ `meta.service_registry.selected_instance_for_socket:${socketKey}`,
672
824
  context
673
825
  );
674
826
  }
@@ -747,6 +899,25 @@ var ServiceRegistry = class _ServiceRegistry {
747
899
  __active: self?.isActive ?? false
748
900
  };
749
901
  }).doOn("meta.socket.status_check_requested");
902
+ this.collectTransportDiagnosticsTask = CadenzaService.createMetaTask(
903
+ "Collect transport diagnostics",
904
+ async (ctx) => {
905
+ const inquiryResult = await CadenzaService.inquire(
906
+ META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
907
+ {
908
+ detailLevel: ctx.detailLevel,
909
+ includeErrorHistory: ctx.includeErrorHistory,
910
+ errorHistoryLimit: ctx.errorHistoryLimit
911
+ },
912
+ ctx.inquiryOptions ?? ctx.__inquiryOptions ?? {}
913
+ );
914
+ return {
915
+ ...ctx,
916
+ ...inquiryResult
917
+ };
918
+ },
919
+ "Collects distributed transport diagnostics using inquiry responders."
920
+ ).doOn("meta.service_registry.transport_diagnostics_requested").emits("meta.service_registry.transport_diagnostics_collected").emitsOnFail("meta.service_registry.transport_diagnostics_failed");
750
921
  this.insertServiceTask = CadenzaService.createCadenzaDBInsertTask(
751
922
  "service",
752
923
  {
@@ -955,8 +1126,125 @@ var ServiceRegistry = class _ServiceRegistry {
955
1126
  if (!this._instance) this._instance = new _ServiceRegistry();
956
1127
  return this._instance;
957
1128
  }
1129
+ buildRemoteIntentDeputyKey(map) {
1130
+ return `${map.intentName}|${map.serviceName}|${map.taskName}|${map.taskVersion ?? 1}`;
1131
+ }
1132
+ normalizeIntentMaps(ctx) {
1133
+ if (Array.isArray(ctx.intentToTaskMaps)) {
1134
+ return ctx.intentToTaskMaps.map((m) => ({
1135
+ intentName: m.intentName ?? m.intent_name,
1136
+ serviceName: m.serviceName ?? m.service_name,
1137
+ taskName: m.taskName ?? m.task_name,
1138
+ taskVersion: m.taskVersion ?? m.task_version ?? 1,
1139
+ deleted: !!m.deleted
1140
+ })).filter((m) => m.intentName && m.serviceName && m.taskName);
1141
+ }
1142
+ const single = ctx.intentToTaskMap ?? ctx.data ?? (ctx.intentName ? ctx : void 0);
1143
+ if (!single) return [];
1144
+ const normalized = {
1145
+ intentName: single.intentName ?? single.intent_name,
1146
+ serviceName: single.serviceName ?? single.service_name,
1147
+ taskName: single.taskName ?? single.task_name,
1148
+ taskVersion: single.taskVersion ?? single.task_version ?? 1,
1149
+ deleted: !!single.deleted
1150
+ };
1151
+ if (!normalized.intentName || !normalized.serviceName || !normalized.taskName)
1152
+ return [];
1153
+ return [normalized];
1154
+ }
1155
+ registerRemoteIntentDeputy(map) {
1156
+ if (!this.serviceName || map.serviceName === this.serviceName) {
1157
+ return;
1158
+ }
1159
+ const key = this.buildRemoteIntentDeputyKey(map);
1160
+ if (this.remoteIntentDeputiesByKey.has(key)) {
1161
+ return;
1162
+ }
1163
+ const deputyTaskName = `Inquire ${map.intentName} via ${map.serviceName} (${map.taskName} v${map.taskVersion})`;
1164
+ const deputyTask = isMetaIntentName(map.intentName) ? CadenzaService.createMetaDeputyTask(map.taskName, map.serviceName, {
1165
+ register: false,
1166
+ isHidden: true,
1167
+ retryCount: 1,
1168
+ retryDelay: 50,
1169
+ retryDelayFactor: 1.2
1170
+ }) : CadenzaService.createDeputyTask(map.taskName, map.serviceName, {
1171
+ register: false,
1172
+ isHidden: true,
1173
+ retryCount: 1,
1174
+ retryDelay: 50,
1175
+ retryDelayFactor: 1.2
1176
+ });
1177
+ deputyTask.respondsTo(map.intentName);
1178
+ if (!this.remoteIntents.has(map.serviceName)) {
1179
+ this.remoteIntents.set(map.serviceName, /* @__PURE__ */ new Set());
1180
+ }
1181
+ this.remoteIntents.get(map.serviceName).add(map.intentName);
1182
+ const descriptor = {
1183
+ key,
1184
+ intentName: map.intentName,
1185
+ serviceName: map.serviceName,
1186
+ remoteTaskName: map.taskName,
1187
+ remoteTaskVersion: map.taskVersion,
1188
+ localTaskName: deputyTask.name || deputyTaskName,
1189
+ localTask: deputyTask
1190
+ };
1191
+ this.remoteIntentDeputiesByKey.set(key, descriptor);
1192
+ this.remoteIntentDeputiesByTask.set(deputyTask, descriptor);
1193
+ }
1194
+ unregisterRemoteIntentDeputy(map) {
1195
+ const key = this.buildRemoteIntentDeputyKey(map);
1196
+ const descriptor = this.remoteIntentDeputiesByKey.get(key);
1197
+ if (!descriptor) {
1198
+ return;
1199
+ }
1200
+ const task = descriptor.localTask;
1201
+ if (task) {
1202
+ CadenzaService.inquiryBroker.unsubscribe(descriptor.intentName, task);
1203
+ task.destroy();
1204
+ }
1205
+ this.remoteIntentDeputiesByTask.delete(descriptor.localTask);
1206
+ this.remoteIntentDeputiesByKey.delete(key);
1207
+ this.remoteIntents.get(descriptor.serviceName)?.delete(descriptor.intentName);
1208
+ if (!this.remoteIntents.get(descriptor.serviceName)?.size) {
1209
+ this.remoteIntents.delete(descriptor.serviceName);
1210
+ }
1211
+ const deputies = this.deputies.get(descriptor.serviceName);
1212
+ if (deputies) {
1213
+ this.deputies.set(
1214
+ descriptor.serviceName,
1215
+ deputies.filter((d) => d.localTaskName !== descriptor.localTaskName)
1216
+ );
1217
+ if (this.deputies.get(descriptor.serviceName)?.length === 0) {
1218
+ this.deputies.delete(descriptor.serviceName);
1219
+ }
1220
+ }
1221
+ }
1222
+ getInquiryResponderDescriptor(task) {
1223
+ const remote = this.remoteIntentDeputiesByTask.get(task);
1224
+ if (remote) {
1225
+ return {
1226
+ isRemote: true,
1227
+ serviceName: remote.serviceName,
1228
+ taskName: remote.remoteTaskName,
1229
+ taskVersion: remote.remoteTaskVersion,
1230
+ localTaskName: remote.localTaskName
1231
+ };
1232
+ }
1233
+ return {
1234
+ isRemote: false,
1235
+ serviceName: this.serviceName ?? "UnknownService",
1236
+ taskName: task.name,
1237
+ taskVersion: task.version,
1238
+ localTaskName: task.name
1239
+ };
1240
+ }
958
1241
  reset() {
959
1242
  this.instances.clear();
1243
+ this.deputies.clear();
1244
+ this.remoteSignals.clear();
1245
+ this.remoteIntents.clear();
1246
+ this.remoteIntentDeputiesByKey.clear();
1247
+ this.remoteIntentDeputiesByTask.clear();
960
1248
  }
961
1249
  };
962
1250
 
@@ -1073,6 +1361,8 @@ var RestController = class _RestController {
1073
1361
  * It initializes and configures the REST server tasks.
1074
1362
  */
1075
1363
  constructor() {
1364
+ this.fetchClientDiagnostics = /* @__PURE__ */ new Map();
1365
+ this.diagnosticsErrorHistoryLimit = 100;
1076
1366
  /**
1077
1367
  * Fetches data from the given URL with a specified timeout. This function performs
1078
1368
  * a fetch request with the ability to cancel the request if it exceeds the provided timeout duration.
@@ -1113,6 +1403,11 @@ var RestController = class _RestController {
1113
1403
  "meta.rest.delegation_requested",
1114
1404
  "meta.socket.delegation_requested"
1115
1405
  );
1406
+ CadenzaService.createMetaTask(
1407
+ "Collect fetch transport diagnostics",
1408
+ (ctx) => this.collectFetchTransportDiagnostics(ctx),
1409
+ "Responds to distributed transport diagnostics inquiries with REST/fetch client data."
1410
+ ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
1116
1411
  CadenzaService.createMetaRoutine(
1117
1412
  "RestServer",
1118
1413
  [
@@ -1443,6 +1738,13 @@ var RestController = class _RestController {
1443
1738
  const port = protocol === "https" ? 443 : servicePort;
1444
1739
  const URL = `${protocol}://${serviceAddress}:${port}`;
1445
1740
  const fetchId = `${serviceAddress}_${port}`;
1741
+ const fetchDiagnostics = this.ensureFetchClientDiagnostics(
1742
+ fetchId,
1743
+ serviceName,
1744
+ URL
1745
+ );
1746
+ fetchDiagnostics.destroyed = false;
1747
+ fetchDiagnostics.updatedAt = Date.now();
1446
1748
  if (CadenzaService.get(`Send Handshake to ${URL}`)) {
1447
1749
  console.error("Fetch client already exists", URL);
1448
1750
  return;
@@ -1464,6 +1766,10 @@ var RestController = class _RestController {
1464
1766
  );
1465
1767
  if (response.__status !== "success") {
1466
1768
  const error = response.__error ?? `Failed to connect to service ${serviceName} ${ctx2.serviceInstanceId}`;
1769
+ fetchDiagnostics.connected = false;
1770
+ fetchDiagnostics.lastHandshakeError = error;
1771
+ fetchDiagnostics.updatedAt = Date.now();
1772
+ this.recordFetchClientError(fetchId, serviceName, URL, error);
1467
1773
  CadenzaService.log(
1468
1774
  "Fetch handshake failed.",
1469
1775
  { error, serviceName, URL },
@@ -1473,6 +1779,11 @@ var RestController = class _RestController {
1473
1779
  return { ...ctx2, __error: error, errored: true };
1474
1780
  }
1475
1781
  ctx2.serviceInstanceId = response.__serviceInstanceId;
1782
+ fetchDiagnostics.connected = true;
1783
+ fetchDiagnostics.destroyed = false;
1784
+ fetchDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
1785
+ fetchDiagnostics.lastHandshakeError = null;
1786
+ fetchDiagnostics.updatedAt = Date.now();
1476
1787
  CadenzaService.log("Fetch client connected.", {
1477
1788
  response,
1478
1789
  serviceName,
@@ -1488,6 +1799,10 @@ var RestController = class _RestController {
1488
1799
  });
1489
1800
  }
1490
1801
  } catch (e) {
1802
+ fetchDiagnostics.connected = false;
1803
+ fetchDiagnostics.lastHandshakeError = this.getErrorMessage(e);
1804
+ fetchDiagnostics.updatedAt = Date.now();
1805
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1491
1806
  CadenzaService.log(
1492
1807
  "Error in fetch handshake",
1493
1808
  { error: e, serviceName, URL, ctx: ctx2 },
@@ -1509,6 +1824,8 @@ var RestController = class _RestController {
1509
1824
  if (ctx2.__remoteRoutineName === void 0) {
1510
1825
  return;
1511
1826
  }
1827
+ fetchDiagnostics.delegationRequests++;
1828
+ fetchDiagnostics.updatedAt = Date.now();
1512
1829
  let resultContext;
1513
1830
  try {
1514
1831
  resultContext = await this.fetchDataWithTimeout(
@@ -1522,8 +1839,21 @@ var RestController = class _RestController {
1522
1839
  },
1523
1840
  3e4
1524
1841
  );
1842
+ if (resultContext?.errored || resultContext?.failed) {
1843
+ fetchDiagnostics.delegationFailures++;
1844
+ fetchDiagnostics.updatedAt = Date.now();
1845
+ this.recordFetchClientError(
1846
+ fetchId,
1847
+ serviceName,
1848
+ URL,
1849
+ resultContext?.__error ?? resultContext?.error ?? "Delegation failed"
1850
+ );
1851
+ }
1525
1852
  } catch (e) {
1526
1853
  console.error("Error in delegation", e);
1854
+ fetchDiagnostics.delegationFailures++;
1855
+ fetchDiagnostics.updatedAt = Date.now();
1856
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1527
1857
  resultContext = {
1528
1858
  __error: `Error: ${e}`,
1529
1859
  errored: true,
@@ -1549,6 +1879,8 @@ var RestController = class _RestController {
1549
1879
  if (ctx2.__signalName === void 0) {
1550
1880
  return;
1551
1881
  }
1882
+ fetchDiagnostics.signalTransmissions++;
1883
+ fetchDiagnostics.updatedAt = Date.now();
1552
1884
  let response;
1553
1885
  try {
1554
1886
  response = await this.fetchDataWithTimeout(
@@ -1565,8 +1897,21 @@ var RestController = class _RestController {
1565
1897
  if (ctx2.__routineExecId) {
1566
1898
  emit(`meta.fetch.transmitted:${ctx2.__routineExecId}`, response);
1567
1899
  }
1900
+ if (response?.errored || response?.failed) {
1901
+ fetchDiagnostics.signalFailures++;
1902
+ fetchDiagnostics.updatedAt = Date.now();
1903
+ this.recordFetchClientError(
1904
+ fetchId,
1905
+ serviceName,
1906
+ URL,
1907
+ response?.__error ?? response?.error ?? "Signal transmission failed"
1908
+ );
1909
+ }
1568
1910
  } catch (e) {
1569
1911
  console.error("Error in transmission", e);
1912
+ fetchDiagnostics.signalFailures++;
1913
+ fetchDiagnostics.updatedAt = Date.now();
1914
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1570
1915
  response = {
1571
1916
  __error: `Error: ${e}`,
1572
1917
  errored: true,
@@ -1584,6 +1929,8 @@ var RestController = class _RestController {
1584
1929
  const statusTask = CadenzaService.createMetaTask(
1585
1930
  `Request status from ${URL}`,
1586
1931
  async (ctx2) => {
1932
+ fetchDiagnostics.statusChecks++;
1933
+ fetchDiagnostics.updatedAt = Date.now();
1587
1934
  let status;
1588
1935
  try {
1589
1936
  status = await this.fetchDataWithTimeout(
@@ -1593,7 +1940,20 @@ var RestController = class _RestController {
1593
1940
  },
1594
1941
  1e3
1595
1942
  );
1943
+ if (status?.errored || status?.failed) {
1944
+ fetchDiagnostics.statusFailures++;
1945
+ fetchDiagnostics.updatedAt = Date.now();
1946
+ this.recordFetchClientError(
1947
+ fetchId,
1948
+ serviceName,
1949
+ URL,
1950
+ status?.__error ?? status?.error ?? "Status check failed"
1951
+ );
1952
+ }
1596
1953
  } catch (e) {
1954
+ fetchDiagnostics.statusFailures++;
1955
+ fetchDiagnostics.updatedAt = Date.now();
1956
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1597
1957
  status = {
1598
1958
  __error: `Error: ${e}`,
1599
1959
  errored: true,
@@ -1605,6 +1965,9 @@ var RestController = class _RestController {
1605
1965
  "Requests status"
1606
1966
  ).doOn("meta.fetch.status_check_requested").emits("meta.fetch.status_checked").emitsOnFail("meta.fetch.status_check_failed");
1607
1967
  CadenzaService.createEphemeralMetaTask("Destroy fetch client", () => {
1968
+ fetchDiagnostics.connected = false;
1969
+ fetchDiagnostics.destroyed = true;
1970
+ fetchDiagnostics.updatedAt = Date.now();
1608
1971
  CadenzaService.log("Destroying fetch client", { URL, serviceName });
1609
1972
  handshakeTask.destroy();
1610
1973
  delegateTask.destroy();
@@ -1653,17 +2016,191 @@ var RestController = class _RestController {
1653
2016
  if (!this._instance) this._instance = new _RestController();
1654
2017
  return this._instance;
1655
2018
  }
2019
+ resolveTransportDiagnosticsOptions(ctx) {
2020
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
2021
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
2022
+ const requestedLimit = Number(ctx.errorHistoryLimit);
2023
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
2024
+ return {
2025
+ detailLevel,
2026
+ includeErrorHistory,
2027
+ errorHistoryLimit
2028
+ };
2029
+ }
2030
+ ensureFetchClientDiagnostics(fetchId, serviceName, url) {
2031
+ let state = this.fetchClientDiagnostics.get(fetchId);
2032
+ if (!state) {
2033
+ state = {
2034
+ fetchId,
2035
+ serviceName,
2036
+ url,
2037
+ connected: false,
2038
+ destroyed: false,
2039
+ lastHandshakeAt: null,
2040
+ lastHandshakeError: null,
2041
+ lastError: null,
2042
+ lastErrorAt: 0,
2043
+ errorHistory: [],
2044
+ delegationRequests: 0,
2045
+ delegationFailures: 0,
2046
+ signalTransmissions: 0,
2047
+ signalFailures: 0,
2048
+ statusChecks: 0,
2049
+ statusFailures: 0,
2050
+ updatedAt: Date.now()
2051
+ };
2052
+ this.fetchClientDiagnostics.set(fetchId, state);
2053
+ } else {
2054
+ state.serviceName = serviceName;
2055
+ state.url = url;
2056
+ }
2057
+ return state;
2058
+ }
2059
+ getErrorMessage(error) {
2060
+ if (error instanceof Error) {
2061
+ return error.message;
2062
+ }
2063
+ if (typeof error === "string") {
2064
+ return error;
2065
+ }
2066
+ try {
2067
+ return JSON.stringify(error);
2068
+ } catch {
2069
+ return String(error);
2070
+ }
2071
+ }
2072
+ recordFetchClientError(fetchId, serviceName, url, error) {
2073
+ const state = this.ensureFetchClientDiagnostics(fetchId, serviceName, url);
2074
+ const message = this.getErrorMessage(error);
2075
+ const now = Date.now();
2076
+ state.lastError = message;
2077
+ state.lastErrorAt = now;
2078
+ state.updatedAt = now;
2079
+ state.errorHistory.push({ at: new Date(now).toISOString(), message });
2080
+ if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
2081
+ state.errorHistory.splice(
2082
+ 0,
2083
+ state.errorHistory.length - this.diagnosticsErrorHistoryLimit
2084
+ );
2085
+ }
2086
+ }
2087
+ collectFetchTransportDiagnostics(ctx) {
2088
+ const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
2089
+ const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
2090
+ const states = Array.from(this.fetchClientDiagnostics.values()).sort(
2091
+ (a, b) => a.fetchId.localeCompare(b.fetchId)
2092
+ );
2093
+ const summary = {
2094
+ detailLevel,
2095
+ totalClients: states.length,
2096
+ connectedClients: states.filter((state) => state.connected).length,
2097
+ destroyedClients: states.filter((state) => state.destroyed).length,
2098
+ delegationRequests: states.reduce(
2099
+ (acc, state) => acc + state.delegationRequests,
2100
+ 0
2101
+ ),
2102
+ delegationFailures: states.reduce(
2103
+ (acc, state) => acc + state.delegationFailures,
2104
+ 0
2105
+ ),
2106
+ signalTransmissions: states.reduce(
2107
+ (acc, state) => acc + state.signalTransmissions,
2108
+ 0
2109
+ ),
2110
+ signalFailures: states.reduce((acc, state) => acc + state.signalFailures, 0),
2111
+ statusChecks: states.reduce((acc, state) => acc + state.statusChecks, 0),
2112
+ statusFailures: states.reduce((acc, state) => acc + state.statusFailures, 0),
2113
+ latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
2114
+ };
2115
+ if (detailLevel === "summary") {
2116
+ return {
2117
+ transportDiagnostics: {
2118
+ [serviceName]: {
2119
+ fetchClient: summary
2120
+ }
2121
+ }
2122
+ };
2123
+ }
2124
+ const clients = states.map((state) => {
2125
+ const details = {
2126
+ fetchId: state.fetchId,
2127
+ serviceName: state.serviceName,
2128
+ url: state.url,
2129
+ connected: state.connected,
2130
+ destroyed: state.destroyed,
2131
+ lastHandshakeAt: state.lastHandshakeAt,
2132
+ lastHandshakeError: state.lastHandshakeError,
2133
+ latestError: state.lastError,
2134
+ delegationRequests: state.delegationRequests,
2135
+ delegationFailures: state.delegationFailures,
2136
+ signalTransmissions: state.signalTransmissions,
2137
+ signalFailures: state.signalFailures,
2138
+ statusChecks: state.statusChecks,
2139
+ statusFailures: state.statusFailures
2140
+ };
2141
+ if (includeErrorHistory) {
2142
+ details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
2143
+ }
2144
+ return details;
2145
+ });
2146
+ return {
2147
+ transportDiagnostics: {
2148
+ [serviceName]: {
2149
+ fetchClient: {
2150
+ ...summary,
2151
+ clients
2152
+ }
2153
+ }
2154
+ }
2155
+ };
2156
+ }
1656
2157
  };
1657
2158
 
1658
2159
  // src/network/SocketController.ts
1659
2160
  var import_socket = require("socket.io");
1660
2161
  var import_rate_limiter_flexible2 = require("rate-limiter-flexible");
1661
2162
  var import_socket2 = require("socket.io-client");
1662
- var SocketController = class _SocketController {
1663
- static get instance() {
1664
- if (!this._instance) this._instance = new _SocketController();
1665
- return this._instance;
2163
+
2164
+ // src/network/socketClientUtils.ts
2165
+ var waitForSocketConnection = async (socket, timeoutMs, createError) => {
2166
+ if (!socket) {
2167
+ return { ok: false, error: createError("disconnected") };
1666
2168
  }
2169
+ if (socket.connected) {
2170
+ return { ok: true };
2171
+ }
2172
+ return new Promise((resolve) => {
2173
+ let timer = null;
2174
+ let settled = false;
2175
+ const cleanup = () => {
2176
+ if (timer) {
2177
+ clearTimeout(timer);
2178
+ timer = null;
2179
+ }
2180
+ socket.off("connect", onConnect);
2181
+ socket.off("connect_error", onConnectError);
2182
+ socket.off("disconnect", onDisconnect);
2183
+ };
2184
+ const settle = (outcome) => {
2185
+ if (settled) return;
2186
+ settled = true;
2187
+ cleanup();
2188
+ resolve(outcome);
2189
+ };
2190
+ const onConnect = () => settle({ ok: true });
2191
+ const onConnectError = (error) => settle({ ok: false, error: createError("connect_error", error) });
2192
+ const onDisconnect = () => settle({ ok: false, error: createError("disconnected") });
2193
+ socket.once("connect", onConnect);
2194
+ socket.once("connect_error", onConnectError);
2195
+ socket.once("disconnect", onDisconnect);
2196
+ timer = setTimeout(() => {
2197
+ settle({ ok: false, error: createError("connect_timeout") });
2198
+ }, timeoutMs);
2199
+ });
2200
+ };
2201
+
2202
+ // src/network/SocketController.ts
2203
+ var SocketController = class _SocketController {
1667
2204
  /**
1668
2205
  * Constructs the `SocketServer`, setting up a WebSocket server with specific configurations,
1669
2206
  * including connection state recovery, rate limiting, CORS handling, and custom event handling.
@@ -1682,6 +2219,13 @@ var SocketController = class _SocketController {
1682
2219
  * Initializes the `SocketServer` to be ready for WebSocket communication.
1683
2220
  */
1684
2221
  constructor() {
2222
+ this.socketClientDiagnostics = /* @__PURE__ */ new Map();
2223
+ this.diagnosticsErrorHistoryLimit = 100;
2224
+ CadenzaService.createMetaTask(
2225
+ "Collect socket transport diagnostics",
2226
+ (ctx) => this.collectSocketTransportDiagnostics(ctx),
2227
+ "Responds to distributed transport diagnostics inquiries with socket client data."
2228
+ ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
1685
2229
  CadenzaService.createMetaRoutine(
1686
2230
  "SocketServer",
1687
2231
  [
@@ -1909,6 +2453,13 @@ var SocketController = class _SocketController {
1909
2453
  const port = protocol === "https" ? 443 : servicePort;
1910
2454
  const URL = `${socketProtocol}://${serviceAddress}:${port}`;
1911
2455
  const fetchId = `${serviceAddress}_${port}`;
2456
+ const socketDiagnostics = this.ensureSocketClientDiagnostics(
2457
+ fetchId,
2458
+ serviceName,
2459
+ URL
2460
+ );
2461
+ socketDiagnostics.destroyed = false;
2462
+ socketDiagnostics.updatedAt = Date.now();
1912
2463
  let handshake = false;
1913
2464
  let errorCount = 0;
1914
2465
  const ERROR_LIMIT = 5;
@@ -1918,6 +2469,11 @@ var SocketController = class _SocketController {
1918
2469
  }
1919
2470
  const pendingDelegationIds = /* @__PURE__ */ new Set();
1920
2471
  const pendingTimers = /* @__PURE__ */ new Set();
2472
+ const syncPendingCounts = () => {
2473
+ socketDiagnostics.pendingDelegations = pendingDelegationIds.size;
2474
+ socketDiagnostics.pendingTimers = pendingTimers.size;
2475
+ socketDiagnostics.updatedAt = Date.now();
2476
+ };
1921
2477
  let handshakeTask = null;
1922
2478
  let emitWhenReady = null;
1923
2479
  let transmitTask = null;
@@ -1934,35 +2490,86 @@ var SocketController = class _SocketController {
1934
2490
  });
1935
2491
  emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
1936
2492
  return new Promise((resolve) => {
1937
- const tryEmit = () => {
1938
- if (!socket?.connected) {
1939
- socket?.once("connect", tryEmit);
2493
+ const resolveWithError = (errorMessage, fallbackError) => {
2494
+ resolve({
2495
+ ...data,
2496
+ errored: true,
2497
+ __error: errorMessage,
2498
+ error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
2499
+ socketId: socket?.id,
2500
+ serviceName,
2501
+ URL
2502
+ });
2503
+ };
2504
+ const tryEmit = async () => {
2505
+ const waitTimeoutMs = timeoutMs > 0 ? timeoutMs + 10 : 1e4;
2506
+ const waitResult = await waitForSocketConnection(
2507
+ socket,
2508
+ waitTimeoutMs,
2509
+ (reason, error) => {
2510
+ if (reason === "connect_timeout") {
2511
+ return `Socket connect timed out before '${event}'`;
2512
+ }
2513
+ if (reason === "connect_error") {
2514
+ const errMessage = error instanceof Error ? error.message : String(error);
2515
+ return `Socket connect error before '${event}': ${errMessage}`;
2516
+ }
2517
+ return `Socket disconnected before '${event}'`;
2518
+ }
2519
+ );
2520
+ if (!waitResult.ok) {
2521
+ CadenzaService.log(
2522
+ waitResult.error,
2523
+ { socketId: socket?.id, serviceName, URL, event },
2524
+ "error"
2525
+ );
2526
+ this.recordSocketClientError(
2527
+ fetchId,
2528
+ serviceName,
2529
+ URL,
2530
+ waitResult.error
2531
+ );
2532
+ resolveWithError(waitResult.error);
1940
2533
  return;
1941
2534
  }
1942
- let timer;
2535
+ let timer = null;
1943
2536
  if (timeoutMs !== 0) {
1944
2537
  timer = setTimeout(() => {
2538
+ if (timer) {
2539
+ pendingTimers.delete(timer);
2540
+ syncPendingCounts();
2541
+ timer = null;
2542
+ }
1945
2543
  CadenzaService.log(
1946
2544
  `Socket event '${event}' timed out`,
1947
2545
  { socketId: socket?.id, serviceName, URL },
1948
2546
  "error"
1949
2547
  );
1950
- resolve({
1951
- ...data,
1952
- errored: true,
1953
- __error: `Socket event '${event}' timed out`,
1954
- error: `Socket event '${event}' timed out`,
1955
- socketId: socket?.id,
2548
+ this.recordSocketClientError(
2549
+ fetchId,
1956
2550
  serviceName,
1957
- URL
1958
- });
2551
+ URL,
2552
+ `Socket event '${event}' timed out`
2553
+ );
2554
+ resolveWithError(`Socket event '${event}' timed out`);
1959
2555
  }, timeoutMs + 10);
1960
2556
  pendingTimers.add(timer);
2557
+ syncPendingCounts();
1961
2558
  }
1962
- socket.timeout(timeoutMs).emit(event, data, (err, response) => {
1963
- if (timer) clearTimeout(timer);
1964
- pendingTimers.delete(timer);
1965
- timer = null;
2559
+ const connectedSocket = socket;
2560
+ if (!connectedSocket) {
2561
+ resolveWithError(
2562
+ `Socket unavailable before emitting '${event}'`
2563
+ );
2564
+ return;
2565
+ }
2566
+ connectedSocket.timeout(timeoutMs).emit(event, data, (err, response) => {
2567
+ if (timer) {
2568
+ clearTimeout(timer);
2569
+ pendingTimers.delete(timer);
2570
+ syncPendingCounts();
2571
+ timer = null;
2572
+ }
1966
2573
  if (err) {
1967
2574
  CadenzaService.log(
1968
2575
  "Socket timeout.",
@@ -1974,6 +2581,12 @@ var SocketController = class _SocketController {
1974
2581
  },
1975
2582
  "warning"
1976
2583
  );
2584
+ this.recordSocketClientError(
2585
+ fetchId,
2586
+ serviceName,
2587
+ URL,
2588
+ err
2589
+ );
1977
2590
  response = {
1978
2591
  __error: `Timeout error: ${err}`,
1979
2592
  errored: true,
@@ -1985,15 +2598,15 @@ var SocketController = class _SocketController {
1985
2598
  resolve(response);
1986
2599
  });
1987
2600
  };
1988
- if (socket?.connected) {
1989
- tryEmit();
1990
- } else {
1991
- socket?.once("connect", tryEmit);
1992
- }
2601
+ void tryEmit();
1993
2602
  });
1994
2603
  };
1995
2604
  socket.on("connect", () => {
1996
2605
  if (handshake) return;
2606
+ socketDiagnostics.connected = true;
2607
+ socketDiagnostics.destroyed = false;
2608
+ socketDiagnostics.socketId = socket?.id ?? null;
2609
+ socketDiagnostics.updatedAt = Date.now();
1997
2610
  CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, ctx);
1998
2611
  });
1999
2612
  socket.on("delegation_progress", (ctx2) => {
@@ -2012,6 +2625,12 @@ var SocketController = class _SocketController {
2012
2625
  });
2013
2626
  socket.on("connect_error", (err) => {
2014
2627
  handshake = false;
2628
+ socketDiagnostics.connected = false;
2629
+ socketDiagnostics.handshake = false;
2630
+ socketDiagnostics.connectErrors++;
2631
+ socketDiagnostics.lastHandshakeError = err.message;
2632
+ socketDiagnostics.updatedAt = Date.now();
2633
+ this.recordSocketClientError(fetchId, serviceName, URL, err);
2015
2634
  CadenzaService.log(
2016
2635
  "Socket connect error",
2017
2636
  { error: err.message, serviceName, socketId: socket?.id, URL },
@@ -2020,9 +2639,16 @@ var SocketController = class _SocketController {
2020
2639
  CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
2021
2640
  });
2022
2641
  socket.on("reconnect_attempt", (attempt) => {
2642
+ socketDiagnostics.reconnectAttempts = Math.max(
2643
+ socketDiagnostics.reconnectAttempts,
2644
+ attempt
2645
+ );
2646
+ socketDiagnostics.updatedAt = Date.now();
2023
2647
  CadenzaService.log(`Reconnect attempt: ${attempt}`);
2024
2648
  });
2025
2649
  socket.on("reconnect", (attempt) => {
2650
+ socketDiagnostics.connected = true;
2651
+ socketDiagnostics.updatedAt = Date.now();
2026
2652
  CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
2027
2653
  socketId: socket?.id,
2028
2654
  URL,
@@ -2031,6 +2657,12 @@ var SocketController = class _SocketController {
2031
2657
  });
2032
2658
  socket.on("reconnect_error", (err) => {
2033
2659
  handshake = false;
2660
+ socketDiagnostics.connected = false;
2661
+ socketDiagnostics.handshake = false;
2662
+ socketDiagnostics.reconnectErrors++;
2663
+ socketDiagnostics.lastHandshakeError = err.message;
2664
+ socketDiagnostics.updatedAt = Date.now();
2665
+ this.recordSocketClientError(fetchId, serviceName, URL, err);
2034
2666
  CadenzaService.log(
2035
2667
  "Socket reconnect failed.",
2036
2668
  { error: err.message, serviceName, URL, socketId: socket?.id },
@@ -2039,6 +2671,9 @@ var SocketController = class _SocketController {
2039
2671
  });
2040
2672
  socket.on("error", (err) => {
2041
2673
  errorCount++;
2674
+ socketDiagnostics.socketErrors++;
2675
+ socketDiagnostics.updatedAt = Date.now();
2676
+ this.recordSocketClientError(fetchId, serviceName, URL, err);
2042
2677
  CadenzaService.log(
2043
2678
  "Socket error",
2044
2679
  { error: err, socketId: socket?.id, URL, serviceName },
@@ -2047,6 +2682,11 @@ var SocketController = class _SocketController {
2047
2682
  CadenzaService.emit("meta.socket_client.error", err);
2048
2683
  });
2049
2684
  socket.on("disconnect", () => {
2685
+ const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
2686
+ socketDiagnostics.connected = false;
2687
+ socketDiagnostics.handshake = false;
2688
+ socketDiagnostics.lastDisconnectAt = disconnectedAt;
2689
+ socketDiagnostics.updatedAt = Date.now();
2050
2690
  CadenzaService.log(
2051
2691
  "Socket disconnected.",
2052
2692
  { URL, serviceName, socketId: socket?.id },
@@ -2065,6 +2705,8 @@ var SocketController = class _SocketController {
2065
2705
  async (ctx2, emit) => {
2066
2706
  if (handshake) return;
2067
2707
  handshake = true;
2708
+ socketDiagnostics.handshake = true;
2709
+ socketDiagnostics.updatedAt = Date.now();
2068
2710
  await emitWhenReady?.(
2069
2711
  "handshake",
2070
2712
  {
@@ -2076,6 +2718,11 @@ var SocketController = class _SocketController {
2076
2718
  1e4,
2077
2719
  (result) => {
2078
2720
  if (result.status === "success") {
2721
+ socketDiagnostics.connected = true;
2722
+ socketDiagnostics.handshake = true;
2723
+ socketDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
2724
+ socketDiagnostics.lastHandshakeError = null;
2725
+ socketDiagnostics.updatedAt = Date.now();
2079
2726
  CadenzaService.log("Socket client connected", {
2080
2727
  result,
2081
2728
  serviceName,
@@ -2083,6 +2730,16 @@ var SocketController = class _SocketController {
2083
2730
  URL
2084
2731
  });
2085
2732
  } else {
2733
+ socketDiagnostics.connected = false;
2734
+ socketDiagnostics.handshake = false;
2735
+ socketDiagnostics.lastHandshakeError = result?.__error ?? result?.error ?? "Socket handshake failed";
2736
+ socketDiagnostics.updatedAt = Date.now();
2737
+ this.recordSocketClientError(
2738
+ fetchId,
2739
+ serviceName,
2740
+ URL,
2741
+ socketDiagnostics.lastHandshakeError
2742
+ );
2086
2743
  CadenzaService.log(
2087
2744
  "Socket handshake failed",
2088
2745
  { result, serviceName, socketId: socket?.id, URL },
@@ -2105,6 +2762,7 @@ var SocketController = class _SocketController {
2105
2762
  delete ctx2.__broadcast;
2106
2763
  const requestSentAt = Date.now();
2107
2764
  pendingDelegationIds.add(ctx2.__metadata.__deputyExecId);
2765
+ syncPendingCounts();
2108
2766
  emitWhenReady?.(
2109
2767
  "delegation",
2110
2768
  ctx2,
@@ -2122,6 +2780,15 @@ var SocketController = class _SocketController {
2122
2780
  }
2123
2781
  );
2124
2782
  pendingDelegationIds.delete(ctx2.__metadata.__deputyExecId);
2783
+ syncPendingCounts();
2784
+ if (resultContext?.errored || resultContext?.failed) {
2785
+ this.recordSocketClientError(
2786
+ fetchId,
2787
+ serviceName,
2788
+ URL,
2789
+ resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
2790
+ );
2791
+ }
2125
2792
  resolve(resultContext);
2126
2793
  }
2127
2794
  );
@@ -2157,6 +2824,10 @@ var SocketController = class _SocketController {
2157
2824
  `Shutdown SocketClient ${URL}`,
2158
2825
  (ctx2, emit) => {
2159
2826
  handshake = false;
2827
+ socketDiagnostics.connected = false;
2828
+ socketDiagnostics.handshake = false;
2829
+ socketDiagnostics.destroyed = true;
2830
+ socketDiagnostics.updatedAt = Date.now();
2160
2831
  CadenzaService.log("Shutting down socket client", { URL, serviceName });
2161
2832
  socket?.close();
2162
2833
  handshakeTask?.destroy();
@@ -2186,10 +2857,12 @@ var SocketController = class _SocketController {
2186
2857
  });
2187
2858
  }
2188
2859
  pendingDelegationIds.clear();
2860
+ syncPendingCounts();
2189
2861
  for (const timer of pendingTimers) {
2190
2862
  clearTimeout(timer);
2191
2863
  }
2192
2864
  pendingTimers.clear();
2865
+ syncPendingCounts();
2193
2866
  },
2194
2867
  "Shuts down the socket client"
2195
2868
  ).doOn(
@@ -2203,6 +2876,157 @@ var SocketController = class _SocketController {
2203
2876
  "Connects to a specified socket server"
2204
2877
  ).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
2205
2878
  }
2879
+ static get instance() {
2880
+ if (!this._instance) this._instance = new _SocketController();
2881
+ return this._instance;
2882
+ }
2883
+ resolveTransportDiagnosticsOptions(ctx) {
2884
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
2885
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
2886
+ const requestedLimit = Number(ctx.errorHistoryLimit);
2887
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
2888
+ return {
2889
+ detailLevel,
2890
+ includeErrorHistory,
2891
+ errorHistoryLimit
2892
+ };
2893
+ }
2894
+ ensureSocketClientDiagnostics(fetchId, serviceName, url) {
2895
+ let state = this.socketClientDiagnostics.get(fetchId);
2896
+ if (!state) {
2897
+ state = {
2898
+ fetchId,
2899
+ serviceName,
2900
+ url,
2901
+ socketId: null,
2902
+ connected: false,
2903
+ handshake: false,
2904
+ reconnectAttempts: 0,
2905
+ connectErrors: 0,
2906
+ reconnectErrors: 0,
2907
+ socketErrors: 0,
2908
+ pendingDelegations: 0,
2909
+ pendingTimers: 0,
2910
+ destroyed: false,
2911
+ lastHandshakeAt: null,
2912
+ lastHandshakeError: null,
2913
+ lastDisconnectAt: null,
2914
+ lastError: null,
2915
+ lastErrorAt: 0,
2916
+ errorHistory: [],
2917
+ updatedAt: Date.now()
2918
+ };
2919
+ this.socketClientDiagnostics.set(fetchId, state);
2920
+ } else {
2921
+ state.serviceName = serviceName;
2922
+ state.url = url;
2923
+ }
2924
+ return state;
2925
+ }
2926
+ getErrorMessage(error) {
2927
+ if (error instanceof Error) {
2928
+ return error.message;
2929
+ }
2930
+ if (typeof error === "string") {
2931
+ return error;
2932
+ }
2933
+ try {
2934
+ return JSON.stringify(error);
2935
+ } catch {
2936
+ return String(error);
2937
+ }
2938
+ }
2939
+ recordSocketClientError(fetchId, serviceName, url, error) {
2940
+ const state = this.ensureSocketClientDiagnostics(fetchId, serviceName, url);
2941
+ const message = this.getErrorMessage(error);
2942
+ const now = Date.now();
2943
+ state.lastError = message;
2944
+ state.lastErrorAt = now;
2945
+ state.updatedAt = now;
2946
+ state.errorHistory.push({
2947
+ at: new Date(now).toISOString(),
2948
+ message
2949
+ });
2950
+ if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
2951
+ state.errorHistory.splice(
2952
+ 0,
2953
+ state.errorHistory.length - this.diagnosticsErrorHistoryLimit
2954
+ );
2955
+ }
2956
+ }
2957
+ collectSocketTransportDiagnostics(ctx) {
2958
+ const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
2959
+ const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
2960
+ const states = Array.from(this.socketClientDiagnostics.values()).sort(
2961
+ (a, b) => a.fetchId.localeCompare(b.fetchId)
2962
+ );
2963
+ const summary = {
2964
+ detailLevel,
2965
+ totalClients: states.length,
2966
+ connectedClients: states.filter((state) => state.connected).length,
2967
+ activeHandshakes: states.filter((state) => state.handshake).length,
2968
+ pendingDelegations: states.reduce(
2969
+ (acc, state) => acc + state.pendingDelegations,
2970
+ 0
2971
+ ),
2972
+ pendingTimers: states.reduce((acc, state) => acc + state.pendingTimers, 0),
2973
+ reconnectAttempts: states.reduce(
2974
+ (acc, state) => acc + state.reconnectAttempts,
2975
+ 0
2976
+ ),
2977
+ connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
2978
+ reconnectErrors: states.reduce(
2979
+ (acc, state) => acc + state.reconnectErrors,
2980
+ 0
2981
+ ),
2982
+ socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
2983
+ latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
2984
+ };
2985
+ if (detailLevel === "summary") {
2986
+ return {
2987
+ transportDiagnostics: {
2988
+ [serviceName]: {
2989
+ socketClient: summary
2990
+ }
2991
+ }
2992
+ };
2993
+ }
2994
+ const clients = states.map((state) => {
2995
+ const details = {
2996
+ fetchId: state.fetchId,
2997
+ serviceName: state.serviceName,
2998
+ url: state.url,
2999
+ socketId: state.socketId,
3000
+ connected: state.connected,
3001
+ handshake: state.handshake,
3002
+ reconnectAttempts: state.reconnectAttempts,
3003
+ connectErrors: state.connectErrors,
3004
+ reconnectErrors: state.reconnectErrors,
3005
+ socketErrors: state.socketErrors,
3006
+ pendingDelegations: state.pendingDelegations,
3007
+ pendingTimers: state.pendingTimers,
3008
+ destroyed: state.destroyed,
3009
+ lastHandshakeAt: state.lastHandshakeAt,
3010
+ lastHandshakeError: state.lastHandshakeError,
3011
+ lastDisconnectAt: state.lastDisconnectAt,
3012
+ latestError: state.lastError
3013
+ };
3014
+ if (includeErrorHistory) {
3015
+ details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
3016
+ }
3017
+ return details;
3018
+ });
3019
+ return {
3020
+ transportDiagnostics: {
3021
+ [serviceName]: {
3022
+ socketClient: {
3023
+ ...summary,
3024
+ clients
3025
+ }
3026
+ }
3027
+ }
3028
+ };
3029
+ }
2206
3030
  };
2207
3031
 
2208
3032
  // src/utils/tools.ts
@@ -2307,12 +3131,6 @@ var GraphMetadataController = class _GraphMetadataController {
2307
3131
  data: {
2308
3132
  ...ctx.data,
2309
3133
  serviceName: CadenzaService.serviceRegistry.serviceName
2310
- // input_context_schema_id: ctx.data.inputContextSchema ? { // TODO
2311
- //
2312
- // } : null,
2313
- // output_context_schema_id: ctx.data.outputContextSchema ? {
2314
- //
2315
- // } : null,
2316
3134
  }
2317
3135
  };
2318
3136
  }).doOn("meta.task.created").emits("global.meta.graph_metadata.task_created");
@@ -2520,9 +3338,11 @@ var GraphMetadataController = class _GraphMetadataController {
2520
3338
  { concurrency: 100, isSubMeta: true }
2521
3339
  ).doOn("meta.node.mapped", "meta.node.detected_previous_task_execution").emits("global.meta.graph_metadata.relationship_executed");
2522
3340
  CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
3341
+ const intentName = ctx.data?.name;
2523
3342
  return {
2524
3343
  data: {
2525
- ...ctx.data
3344
+ ...ctx.data,
3345
+ isMeta: intentName ? isMetaIntentName(intentName) : false
2526
3346
  }
2527
3347
  };
2528
3348
  }).doOn("meta.inquiry_broker.added").emits("global.meta.graph_metadata.intent_created");
@@ -2554,6 +3374,52 @@ var SCHEMA_TYPES = [
2554
3374
  // src/database/DatabaseController.ts
2555
3375
  var import_pg = require("pg");
2556
3376
  var import_lodash_es = require("lodash-es");
3377
+ function resolveTableQueryIntents(serviceName, tableName, table, defaultInputSchema) {
3378
+ const resolvedServiceName = serviceName ?? "unknown-service";
3379
+ const defaultIntentName = `query-${resolvedServiceName}-${tableName}`;
3380
+ const defaultDescription = `Perform a query operation on the ${tableName} table`;
3381
+ const intents = [
3382
+ {
3383
+ name: defaultIntentName,
3384
+ description: defaultDescription,
3385
+ input: defaultInputSchema
3386
+ }
3387
+ ];
3388
+ const warnings = [];
3389
+ const names = /* @__PURE__ */ new Set([defaultIntentName]);
3390
+ for (const customIntent of table.customIntents?.query ?? []) {
3391
+ const name = typeof customIntent === "string" ? customIntent.trim() : customIntent.intent?.trim();
3392
+ if (!name) {
3393
+ warnings.push(`Skipped empty custom query intent for table '${tableName}'.`);
3394
+ continue;
3395
+ }
3396
+ if (name.length > 100) {
3397
+ warnings.push(
3398
+ `Skipped custom query intent '${name}' for table '${tableName}': name must be <= 100 characters.`
3399
+ );
3400
+ continue;
3401
+ }
3402
+ if (name.includes(" ") || name.includes(".") || name.includes("\\")) {
3403
+ warnings.push(
3404
+ `Skipped custom query intent '${name}' for table '${tableName}': name cannot contain spaces, dots or backslashes.`
3405
+ );
3406
+ continue;
3407
+ }
3408
+ if (names.has(name)) {
3409
+ warnings.push(
3410
+ `Skipped duplicate custom query intent '${name}' for table '${tableName}'.`
3411
+ );
3412
+ continue;
3413
+ }
3414
+ names.add(name);
3415
+ intents.push({
3416
+ name,
3417
+ description: typeof customIntent === "string" ? `Perform a query operation on the ${tableName} table` : customIntent.description ?? defaultDescription,
3418
+ input: typeof customIntent === "string" ? defaultInputSchema : customIntent.input ?? defaultInputSchema
3419
+ });
3420
+ }
3421
+ return { intents, warnings };
3422
+ }
2557
3423
  var DatabaseController = class _DatabaseController {
2558
3424
  /**
2559
3425
  * Constructor for initializing the `DatabaseService` class.
@@ -2695,6 +3561,20 @@ var DatabaseController = class _DatabaseController {
2695
3561
  }
2696
3562
  }
2697
3563
  }
3564
+ if (table.customIntents?.query) {
3565
+ if (!Array.isArray(table.customIntents.query)) {
3566
+ throw new Error(
3567
+ `Invalid customIntents.query for ${tableName}: expected array`
3568
+ );
3569
+ }
3570
+ for (const customIntent of table.customIntents.query) {
3571
+ if (typeof customIntent !== "string" && (typeof customIntent !== "object" || !customIntent || typeof customIntent.intent !== "string")) {
3572
+ throw new Error(
3573
+ `Invalid custom query intent on ${tableName}: expected string or object with intent`
3574
+ );
3575
+ }
3576
+ }
3577
+ }
2698
3578
  }
2699
3579
  }
2700
3580
  console.log("SCHEMA VALIDATED");
@@ -3621,9 +4501,9 @@ var DatabaseController = class _DatabaseController {
3621
4501
  createDatabaseTask(op, tableName, table, queryFunction, options) {
3622
4502
  const opAction = op === "query" ? "queried" : op === "insert" ? "inserted" : op === "update" ? "updated" : op === "delete" ? "deleted" : "";
3623
4503
  const defaultSignal = `global.${options.isMeta ? "meta." : ""}${tableName}.${opAction}`;
3624
- const tableNameFormatted = tableName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
3625
- const taskName = `db${op.charAt(0).toUpperCase() + op.slice(1)}${tableNameFormatted}`;
3626
- CadenzaService.createThrottledTask(
4504
+ const taskName = `${op.charAt(0).toUpperCase() + op.slice(1)} ${tableName}`;
4505
+ const schema = this.getInputSchema(op, tableName, table);
4506
+ const task = CadenzaService.createThrottledTask(
3627
4507
  taskName,
3628
4508
  async (context, emit) => {
3629
4509
  for (const action of Object.keys(table.customSignals?.triggers ?? {})) {
@@ -3718,17 +4598,8 @@ var DatabaseController = class _DatabaseController {
3718
4598
  {
3719
4599
  isMeta: options.isMeta,
3720
4600
  isSubMeta: options.isMeta,
3721
- validateInputContext: false,
3722
- // TODO
3723
- inputSchema: {
3724
- // TODO
3725
- type: "object",
3726
- properties: {
3727
- filter: {
3728
- type: "object"
3729
- }
3730
- }
3731
- }
4601
+ validateInputContext: options.securityProfile !== "low",
4602
+ inputSchema: schema
3732
4603
  }
3733
4604
  ).doOn(
3734
4605
  ...table.customSignals?.triggers?.[op]?.map((signal) => {
@@ -3739,8 +4610,397 @@ var DatabaseController = class _DatabaseController {
3739
4610
  return typeof signal === "string" ? signal : signal.signal;
3740
4611
  }) ?? []
3741
4612
  );
4613
+ if (op === "query") {
4614
+ const { intents, warnings } = resolveTableQueryIntents(
4615
+ CadenzaService.serviceRegistry?.serviceName,
4616
+ tableName,
4617
+ table,
4618
+ schema
4619
+ );
4620
+ for (const warning of warnings) {
4621
+ CadenzaService.log(
4622
+ "Skipped custom query intent registration.",
4623
+ {
4624
+ tableName,
4625
+ warning
4626
+ },
4627
+ "warning"
4628
+ );
4629
+ }
4630
+ for (const intent of intents) {
4631
+ CadenzaService.defineIntent({
4632
+ name: intent.name,
4633
+ description: intent.description,
4634
+ input: intent.input
4635
+ });
4636
+ }
4637
+ task.respondsTo(...intents.map((intent) => intent.name));
4638
+ }
4639
+ }
4640
+ getInputSchema(op, tableName, table) {
4641
+ const inputSchema = {
4642
+ type: "object",
4643
+ properties: {
4644
+ queryData: {
4645
+ type: "object",
4646
+ properties: {},
4647
+ strict: true
4648
+ }
4649
+ },
4650
+ strict: true
4651
+ };
4652
+ if (!inputSchema.properties) {
4653
+ return inputSchema;
4654
+ }
4655
+ inputSchema.properties.transaction = getTransactionSchema();
4656
+ inputSchema.properties.queryData.properties.transaction = inputSchema.properties.transaction;
4657
+ switch (op) {
4658
+ case "insert":
4659
+ inputSchema.properties.data = getInsertDataSchemaFromTable(
4660
+ table,
4661
+ tableName
4662
+ );
4663
+ inputSchema.properties.queryData.properties.data = inputSchema.properties.data;
4664
+ inputSchema.properties.batch = getQueryBatchSchemaFromTable();
4665
+ inputSchema.properties.queryData.properties.batch = inputSchema.properties.batch;
4666
+ inputSchema.properties.onConflict = getQueryOnConflictSchemaFromTable(
4667
+ table,
4668
+ tableName
4669
+ );
4670
+ inputSchema.properties.queryData.properties.onConflict = inputSchema.properties.onConflict;
4671
+ break;
4672
+ case "query":
4673
+ inputSchema.properties.filter = getQueryFilterSchemaFromTable(
4674
+ table,
4675
+ tableName
4676
+ );
4677
+ inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
4678
+ inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
4679
+ table,
4680
+ tableName
4681
+ );
4682
+ inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
4683
+ inputSchema.properties.joins = getQueryJoinsSchemaFromTable(
4684
+ table,
4685
+ tableName
4686
+ );
4687
+ inputSchema.properties.queryData.properties.joins = inputSchema.properties.joins;
4688
+ inputSchema.properties.sort = getQuerySortSchemaFromTable(
4689
+ table,
4690
+ tableName
4691
+ );
4692
+ inputSchema.properties.queryData.properties.sort = inputSchema.properties.sort;
4693
+ inputSchema.properties.limit = getQueryLimitSchemaFromTable();
4694
+ inputSchema.properties.queryData.properties.limit = inputSchema.properties.limit;
4695
+ inputSchema.properties.offset = getQueryOffsetSchemaFromTable();
4696
+ inputSchema.properties.queryData.properties.offset = inputSchema.properties.offset;
4697
+ break;
4698
+ case "update":
4699
+ inputSchema.properties.filter = getQueryFilterSchemaFromTable(
4700
+ table,
4701
+ tableName
4702
+ );
4703
+ inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
4704
+ inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
4705
+ table,
4706
+ tableName
4707
+ );
4708
+ inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
4709
+ break;
4710
+ case "delete":
4711
+ inputSchema.properties.filter = getQueryFilterSchemaFromTable(
4712
+ table,
4713
+ tableName
4714
+ );
4715
+ inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
4716
+ break;
4717
+ }
4718
+ return inputSchema;
3742
4719
  }
3743
4720
  };
4721
+ function getInsertDataSchemaFromTable(table, tableName) {
4722
+ const dataSchema = {
4723
+ type: "object",
4724
+ properties: {
4725
+ ...Object.fromEntries(
4726
+ Object.entries(table.fields).map((field) => {
4727
+ return [
4728
+ field[0],
4729
+ {
4730
+ value: {
4731
+ type: tableFieldTypeToSchemaType(field[1].type),
4732
+ description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
4733
+ },
4734
+ effect: {
4735
+ type: "string",
4736
+ constraints: {
4737
+ oneOf: ["increment", "decrement", "set"]
4738
+ }
4739
+ },
4740
+ subOperation: {
4741
+ type: "object",
4742
+ properties: {
4743
+ subOperation: {
4744
+ type: "string",
4745
+ enum: ["insert", "query"]
4746
+ },
4747
+ table: {
4748
+ type: "string"
4749
+ },
4750
+ data: {
4751
+ single: {
4752
+ type: "object"
4753
+ },
4754
+ batch: {
4755
+ type: "array",
4756
+ items: {
4757
+ type: "object"
4758
+ }
4759
+ }
4760
+ },
4761
+ filter: {
4762
+ type: "object"
4763
+ },
4764
+ fields: {
4765
+ type: "array",
4766
+ items: {
4767
+ type: "string"
4768
+ }
4769
+ },
4770
+ return: {
4771
+ type: "string"
4772
+ }
4773
+ },
4774
+ required: ["subOperation", "table"]
4775
+ }
4776
+ }
4777
+ ];
4778
+ })
4779
+ )
4780
+ },
4781
+ required: Object.entries(table.fields).filter((field) => field[1].required || field[1].primary).map((field) => field[0]),
4782
+ strict: true
4783
+ };
4784
+ return {
4785
+ single: dataSchema,
4786
+ batch: {
4787
+ type: "array",
4788
+ items: dataSchema
4789
+ }
4790
+ };
4791
+ }
4792
+ function getQueryFilterSchemaFromTable(table, tableName) {
4793
+ return {
4794
+ type: "object",
4795
+ properties: {
4796
+ ...Object.fromEntries(
4797
+ Object.entries(table.fields).map((field) => {
4798
+ return [
4799
+ field[0],
4800
+ {
4801
+ value: {
4802
+ type: tableFieldTypeToSchemaType(field[1].type),
4803
+ description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
4804
+ },
4805
+ in: {
4806
+ type: "array",
4807
+ items: {
4808
+ type: tableFieldTypeToSchemaType(field[1].type)
4809
+ }
4810
+ }
4811
+ }
4812
+ ];
4813
+ })
4814
+ )
4815
+ },
4816
+ strict: true,
4817
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4818
+ };
4819
+ }
4820
+ function getQueryFieldsSchemaFromTable(table, tableName) {
4821
+ return {
4822
+ type: "array",
4823
+ items: {
4824
+ type: "string",
4825
+ constraints: {
4826
+ oneOf: Object.keys(table.fields)
4827
+ }
4828
+ },
4829
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4830
+ };
4831
+ }
4832
+ function getQueryJoinsSchemaFromTable(table, tableName) {
4833
+ return {
4834
+ type: "object",
4835
+ properties: {
4836
+ ...Object.fromEntries(
4837
+ Object.entries(table.fields).map((field) => {
4838
+ return [
4839
+ field[0],
4840
+ {
4841
+ type: "object",
4842
+ properties: {
4843
+ on: {
4844
+ type: "string"
4845
+ },
4846
+ fields: {
4847
+ type: "array",
4848
+ items: {
4849
+ type: "string"
4850
+ }
4851
+ },
4852
+ filter: {
4853
+ type: "object"
4854
+ },
4855
+ returnAs: {
4856
+ type: "string",
4857
+ constraints: {
4858
+ oneOf: ["array", "object"]
4859
+ }
4860
+ },
4861
+ alias: {
4862
+ type: "string"
4863
+ },
4864
+ joins: {
4865
+ type: "object"
4866
+ }
4867
+ },
4868
+ required: ["on", "fields"],
4869
+ strict: true
4870
+ }
4871
+ ];
4872
+ })
4873
+ )
4874
+ },
4875
+ strict: true,
4876
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4877
+ };
4878
+ }
4879
+ function getQuerySortSchemaFromTable(table, tableName) {
4880
+ return {
4881
+ type: "object",
4882
+ properties: {
4883
+ ...Object.fromEntries(
4884
+ Object.entries(table.fields).map((field) => {
4885
+ return [
4886
+ field[0],
4887
+ {
4888
+ type: "string",
4889
+ constraints: {
4890
+ oneOf: ["asc", "desc"]
4891
+ }
4892
+ }
4893
+ ];
4894
+ })
4895
+ )
4896
+ },
4897
+ strict: true,
4898
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4899
+ };
4900
+ }
4901
+ function getQueryLimitSchemaFromTable() {
4902
+ return {
4903
+ type: "number",
4904
+ constraints: {
4905
+ min: 1
4906
+ },
4907
+ description: "Limit for query results"
4908
+ };
4909
+ }
4910
+ function getQueryOffsetSchemaFromTable() {
4911
+ return {
4912
+ type: "number",
4913
+ constraints: {
4914
+ min: 0
4915
+ },
4916
+ description: "Offset for query results"
4917
+ };
4918
+ }
4919
+ function getTransactionSchema() {
4920
+ return {
4921
+ type: "boolean",
4922
+ description: "Whether to run the query in a transaction"
4923
+ };
4924
+ }
4925
+ function getQueryBatchSchemaFromTable() {
4926
+ return {
4927
+ type: "boolean",
4928
+ description: "Whether to run the query in batch mode"
4929
+ };
4930
+ }
4931
+ function getQueryOnConflictSchemaFromTable(table, tableName) {
4932
+ return {
4933
+ type: "object",
4934
+ properties: {
4935
+ target: {
4936
+ type: "array",
4937
+ items: {
4938
+ type: "string",
4939
+ constraints: {
4940
+ oneOf: Object.keys(table.fields)
4941
+ }
4942
+ }
4943
+ },
4944
+ action: {
4945
+ type: "object",
4946
+ properties: {
4947
+ do: {
4948
+ type: "string",
4949
+ constraints: {
4950
+ oneOf: ["nothing", "update"]
4951
+ }
4952
+ },
4953
+ set: {
4954
+ type: "object",
4955
+ properties: {
4956
+ ...Object.fromEntries(
4957
+ Object.entries(table.fields).map((field) => {
4958
+ return [
4959
+ field[0],
4960
+ {
4961
+ type: tableFieldTypeToSchemaType(field[1].type),
4962
+ description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
4963
+ }
4964
+ ];
4965
+ })
4966
+ )
4967
+ }
4968
+ },
4969
+ where: {
4970
+ type: "string"
4971
+ }
4972
+ },
4973
+ required: ["do"]
4974
+ }
4975
+ },
4976
+ required: ["target", "action"],
4977
+ strict: true
4978
+ };
4979
+ }
4980
+ function tableFieldTypeToSchemaType(type) {
4981
+ switch (type) {
4982
+ case "varchar":
4983
+ case "text":
4984
+ case "jsonb":
4985
+ case "uuid":
4986
+ case "date":
4987
+ case "geo_point":
4988
+ case "bytea":
4989
+ return "string";
4990
+ case "int":
4991
+ case "bigint":
4992
+ case "decimal":
4993
+ case "timestamp":
4994
+ return "number";
4995
+ case "boolean":
4996
+ return "boolean";
4997
+ case "array":
4998
+ return "array";
4999
+ case "object":
5000
+ return "object";
5001
+ }
5002
+ return "any";
5003
+ }
3744
5004
 
3745
5005
  // src/Cadenza.ts
3746
5006
  var import_uuid3 = require("uuid");
@@ -4058,6 +5318,75 @@ var GraphSyncController = class _GraphSyncController {
4058
5318
  { concurrency: 30 }
4059
5319
  ) : CadenzaService.get("dbInsertSignalToTaskMap"))?.then(registerSignalTask)
4060
5320
  );
5321
+ const registerIntentTask = CadenzaService.createMetaTask(
5322
+ "Record intent registration",
5323
+ (ctx) => {
5324
+ if (!ctx.__syncing) {
5325
+ return;
5326
+ }
5327
+ CadenzaService.debounce("meta.sync_controller.synced_resource", {
5328
+ delayMs: 3e3
5329
+ });
5330
+ const task = CadenzaService.get(ctx.__taskName);
5331
+ task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
5332
+ task.__registeredIntents.add(ctx.__intent);
5333
+ }
5334
+ );
5335
+ this.registerIntentToTaskMapTask = CadenzaService.createMetaTask(
5336
+ "Split intents of task",
5337
+ function* (ctx) {
5338
+ const task = ctx.task;
5339
+ if (task.hidden || !task.register) return;
5340
+ task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
5341
+ task.__invalidMetaIntentWarnings = task.__invalidMetaIntentWarnings ?? /* @__PURE__ */ new Set();
5342
+ for (const intent of task.handlesIntents) {
5343
+ if (task.__registeredIntents.has(intent)) continue;
5344
+ if (isMetaIntentName(intent) && !task.isMeta) {
5345
+ if (!task.__invalidMetaIntentWarnings.has(intent)) {
5346
+ task.__invalidMetaIntentWarnings.add(intent);
5347
+ CadenzaService.log(
5348
+ "Skipping intent-to-task registration: non-meta task cannot handle meta intent.",
5349
+ {
5350
+ intent,
5351
+ taskName: task.name,
5352
+ taskVersion: task.version
5353
+ },
5354
+ "warning"
5355
+ );
5356
+ }
5357
+ continue;
5358
+ }
5359
+ yield {
5360
+ data: {
5361
+ intentName: intent,
5362
+ taskName: task.name,
5363
+ taskVersion: task.version,
5364
+ serviceName: CadenzaService.serviceRegistry.serviceName
5365
+ },
5366
+ __taskName: task.name,
5367
+ __intent: intent
5368
+ };
5369
+ }
5370
+ }
5371
+ ).then(
5372
+ (this.isCadenzaDBReady ? CadenzaService.createCadenzaDBInsertTask(
5373
+ "intent_to_task_map",
5374
+ {
5375
+ onConflict: {
5376
+ target: [
5377
+ "intent_name",
5378
+ "task_name",
5379
+ "task_version",
5380
+ "service_name"
5381
+ ],
5382
+ action: {
5383
+ do: "nothing"
5384
+ }
5385
+ }
5386
+ },
5387
+ { concurrency: 30 }
5388
+ ) : CadenzaService.get("dbInsertIntentToTaskMap"))?.then(registerIntentTask)
5389
+ );
4061
5390
  this.registerTaskMapTask = CadenzaService.createMetaTask(
4062
5391
  "Register task map to DB",
4063
5392
  function* (ctx) {
@@ -4180,6 +5509,7 @@ var GraphSyncController = class _GraphSyncController {
4180
5509
  CadenzaService.registry.doForEachTask.clone().doOn("meta.sync_controller.synced_tasks").then(
4181
5510
  this.registerTaskMapTask,
4182
5511
  this.registerSignalToTaskMapTask,
5512
+ this.registerIntentToTaskMapTask,
4183
5513
  this.registerDeputyRelationshipTask
4184
5514
  );
4185
5515
  CadenzaService.registry.getAllRoutines.clone().doOn("meta.sync_controller.synced_routines").then(this.splitTasksInRoutines);
@@ -4321,11 +5651,192 @@ var CadenzaService = class {
4321
5651
  import_core3.default.interval(signal, context, intervalMs, leading, startDateTime);
4322
5652
  }
4323
5653
  static defineIntent(intent) {
4324
- this.inquiryBroker?.intents.set(intent.name, intent);
5654
+ this.inquiryBroker?.addIntent(intent);
4325
5655
  return intent;
4326
5656
  }
4327
- static async inquire(inquiry, context, options) {
4328
- return this.inquiryBroker?.inquire(inquiry, context, options);
5657
+ static getInquiryResponderDescriptor(task) {
5658
+ return this.serviceRegistry.getInquiryResponderDescriptor(task);
5659
+ }
5660
+ static compareInquiryResponders(left, right) {
5661
+ return compareResponderDescriptors(left.descriptor, right.descriptor);
5662
+ }
5663
+ static buildInquirySummary(inquiry, startedAt, statuses, totalResponders) {
5664
+ const counts = summarizeResponderStatuses(statuses);
5665
+ const isMetaInquiry = isMetaIntentName(inquiry);
5666
+ const eligibleResponders = statuses.length;
5667
+ return {
5668
+ inquiry,
5669
+ isMetaInquiry,
5670
+ totalResponders,
5671
+ eligibleResponders,
5672
+ filteredOutResponders: Math.max(0, totalResponders - eligibleResponders),
5673
+ responded: counts.responded,
5674
+ failed: counts.failed,
5675
+ timedOut: counts.timedOut,
5676
+ pending: counts.pending,
5677
+ durationMs: Date.now() - startedAt,
5678
+ responders: statuses
5679
+ };
5680
+ }
5681
+ static async inquire(inquiry, context, options = {}) {
5682
+ this.bootstrap();
5683
+ const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
5684
+ const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
5685
+ task,
5686
+ descriptor: this.getInquiryResponderDescriptor(task)
5687
+ })) : [];
5688
+ const isMetaInquiry = isMetaIntentName(inquiry);
5689
+ const responders = allResponders.filter(({ task, descriptor }) => {
5690
+ const shouldExecute = shouldExecuteInquiryResponder(inquiry, task.isMeta);
5691
+ if (shouldExecute) {
5692
+ return true;
5693
+ }
5694
+ const warningKey = `${inquiry}|${descriptor.serviceName}|${descriptor.taskName}|${descriptor.taskVersion}|${descriptor.localTaskName}`;
5695
+ if (!this.warnedInvalidMetaIntentResponderKeys.has(warningKey)) {
5696
+ this.warnedInvalidMetaIntentResponderKeys.add(warningKey);
5697
+ this.log(
5698
+ "Skipping non-meta task for meta intent inquiry.",
5699
+ {
5700
+ inquiry,
5701
+ responder: descriptor
5702
+ },
5703
+ "warning",
5704
+ descriptor.serviceName
5705
+ );
5706
+ }
5707
+ return false;
5708
+ });
5709
+ if (responders.length === 0) {
5710
+ return {
5711
+ __inquiryMeta: {
5712
+ inquiry,
5713
+ isMetaInquiry,
5714
+ totalResponders: allResponders.length,
5715
+ eligibleResponders: 0,
5716
+ filteredOutResponders: allResponders.length,
5717
+ responded: 0,
5718
+ failed: 0,
5719
+ timedOut: 0,
5720
+ pending: 0,
5721
+ durationMs: 0,
5722
+ responders: []
5723
+ }
5724
+ };
5725
+ }
5726
+ responders.sort(this.compareInquiryResponders.bind(this));
5727
+ const overallTimeoutMs = options.overallTimeoutMs ?? options.timeout ?? 0;
5728
+ const requireComplete = options.requireComplete ?? false;
5729
+ const perResponderTimeoutMs = options.perResponderTimeoutMs;
5730
+ const startedAt = Date.now();
5731
+ const statuses = [];
5732
+ const statusByTask = /* @__PURE__ */ new Map();
5733
+ for (const responder of responders) {
5734
+ const status = {
5735
+ ...responder.descriptor,
5736
+ status: "timed_out",
5737
+ durationMs: 0
5738
+ };
5739
+ statuses.push(status);
5740
+ statusByTask.set(responder.task, status);
5741
+ }
5742
+ const resultsByTask = /* @__PURE__ */ new Map();
5743
+ const resolverTasks = [];
5744
+ const pending = new Set(responders.map((r) => r.task));
5745
+ const startTimeByTask = /* @__PURE__ */ new Map();
5746
+ this.emit("meta.inquiry_broker.inquire", { inquiry, context });
5747
+ return new Promise((resolve, reject) => {
5748
+ let finalized = false;
5749
+ let timeoutId;
5750
+ const finalize = (timedOut) => {
5751
+ if (finalized) return;
5752
+ finalized = true;
5753
+ if (timeoutId) {
5754
+ clearTimeout(timeoutId);
5755
+ timeoutId = void 0;
5756
+ }
5757
+ for (const resolverTask of resolverTasks) {
5758
+ resolverTask.destroy();
5759
+ }
5760
+ if (timedOut && pending.size > 0) {
5761
+ for (const task of pending) {
5762
+ const status = statusByTask.get(task);
5763
+ if (!status) continue;
5764
+ status.status = "timed_out";
5765
+ status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
5766
+ }
5767
+ }
5768
+ const fulfilledContexts = responders.filter((responder) => resultsByTask.has(responder.task)).map((responder) => resultsByTask.get(responder.task));
5769
+ const mergedContext = mergeInquiryContexts(fulfilledContexts);
5770
+ const inquiryMeta = this.buildInquirySummary(
5771
+ inquiry,
5772
+ startedAt,
5773
+ statuses,
5774
+ allResponders.length
5775
+ );
5776
+ const responseContext = {
5777
+ ...mergedContext,
5778
+ __inquiryMeta: inquiryMeta
5779
+ };
5780
+ if (requireComplete && (timedOut || inquiryMeta.failed > 0 || inquiryMeta.timedOut > 0 || inquiryMeta.pending > 0)) {
5781
+ reject({
5782
+ ...responseContext,
5783
+ __error: `Inquiry '${inquiry}' did not complete successfully`,
5784
+ errored: true
5785
+ });
5786
+ return;
5787
+ }
5788
+ resolve(responseContext);
5789
+ };
5790
+ if (overallTimeoutMs > 0) {
5791
+ timeoutId = setTimeout(() => finalize(true), overallTimeoutMs);
5792
+ }
5793
+ for (const responder of responders) {
5794
+ const { task, descriptor } = responder;
5795
+ const inquiryId = (0, import_uuid3.v4)();
5796
+ startTimeByTask.set(task, Date.now());
5797
+ const resolverTask = this.createEphemeralMetaTask(
5798
+ `Resolve inquiry ${inquiry} for ${descriptor.localTaskName}`,
5799
+ (resultCtx) => {
5800
+ if (finalized) {
5801
+ return;
5802
+ }
5803
+ pending.delete(task);
5804
+ const status = statusByTask.get(task);
5805
+ if (status) {
5806
+ status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
5807
+ if (resultCtx?.errored || resultCtx?.failed) {
5808
+ status.status = "failed";
5809
+ status.error = String(
5810
+ resultCtx?.__error ?? resultCtx?.error ?? "Inquiry responder failed"
5811
+ );
5812
+ } else {
5813
+ status.status = "fulfilled";
5814
+ resultsByTask.set(task, resultCtx);
5815
+ }
5816
+ }
5817
+ if (pending.size === 0) {
5818
+ finalize(false);
5819
+ }
5820
+ },
5821
+ "Resolves distributed inquiry responder result",
5822
+ { register: false }
5823
+ ).doOn(`meta.node.graph_completed:${inquiryId}`);
5824
+ resolverTasks.push(resolverTask);
5825
+ const executionContext = {
5826
+ ...context,
5827
+ __routineExecId: inquiryId,
5828
+ __isInquiry: true
5829
+ };
5830
+ if (perResponderTimeoutMs !== void 0) {
5831
+ executionContext.__timeout = perResponderTimeoutMs;
5832
+ }
5833
+ if (task.isMeta) {
5834
+ this.metaRunner?.run(task, executionContext);
5835
+ } else {
5836
+ this.runner?.run(task, executionContext);
5837
+ }
5838
+ }
5839
+ });
4329
5840
  }
4330
5841
  /**
4331
5842
  * Executes the given task or graph routine within the provided context using the configured runner.
@@ -4614,10 +6125,9 @@ var CadenzaService = class {
4614
6125
  this.bootstrap();
4615
6126
  this.validateName(tableName);
4616
6127
  this.validateName(operation);
4617
- const tableNameFormatted = tableName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
4618
6128
  const name = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName} in ${databaseServiceName ?? "default database service"}`;
4619
6129
  const description = `Executes a ${operation} on table ${tableName} in ${databaseServiceName ?? "default database service"}`;
4620
- const taskName = `db${operation.charAt(0).toUpperCase() + operation.slice(1)}${tableNameFormatted}`;
6130
+ const taskName = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName}`;
4621
6131
  options = {
4622
6132
  concurrency: 100,
4623
6133
  timeout: 0,
@@ -4879,7 +6389,7 @@ var CadenzaService = class {
4879
6389
  * This method is not supported in a browser environment and will log a warning if called in such an environment.
4880
6390
  *
4881
6391
  * @param {string} name - The name of the database service to be created.
4882
- * @param {SchemaDefinition} schema - The schema definition for the database service.
6392
+ * @param {DatabaseSchemaDefinition} schema - The schema definition for the database service.
4883
6393
  * @param {string} [description=""] - An optional description of the database service.
4884
6394
  * @param {ServerOptions & DatabaseOptions} [options={}] - Optional configuration settings for the database and server.
4885
6395
  * @return {void} This method does not return a value.
@@ -4943,7 +6453,7 @@ var CadenzaService = class {
4943
6453
  * Creates a meta database service with the specified configuration.
4944
6454
  *
4945
6455
  * @param {string} name - The name of the database service to be created.
4946
- * @param {SchemaDefinition} schema - The schema definition for the database.
6456
+ * @param {DatabaseSchemaDefinition} schema - The schema definition for the database.
4947
6457
  * @param {string} [description=""] - An optional description of the database service.
4948
6458
  * @param {ServerOptions & DatabaseOptions} [options={}] - Optional server and database configuration options. The `isMeta` flag will be automatically set to true.
4949
6459
  * @return {void} - This method does not return a value.
@@ -5363,6 +6873,7 @@ var CadenzaService = class {
5363
6873
  };
5364
6874
  CadenzaService.isBootstrapped = false;
5365
6875
  CadenzaService.serviceCreated = false;
6876
+ CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
5366
6877
 
5367
6878
  // src/index.ts
5368
6879
  var import_core4 = require("@cadenza.io/core");