@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.mjs CHANGED
@@ -247,6 +247,72 @@ var DatabaseTask = class extends DeputyTask {
247
247
  var isNode = typeof process !== "undefined" && process.versions?.node != null;
248
248
  var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
249
249
 
250
+ // src/utils/inquiry.ts
251
+ var META_INTENT_PREFIX = "meta-";
252
+ var META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT = "meta-runtime-transport-diagnostics";
253
+ function isPlainObject(value) {
254
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
255
+ }
256
+ function deepMergeDeterministic(left, right) {
257
+ if (Array.isArray(left) && Array.isArray(right)) {
258
+ return [...left, ...right];
259
+ }
260
+ if (isPlainObject(left) && isPlainObject(right)) {
261
+ const merged = { ...left };
262
+ const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])).sort();
263
+ for (const key of keys) {
264
+ if (!(key in left)) {
265
+ merged[key] = right[key];
266
+ continue;
267
+ }
268
+ if (!(key in right)) {
269
+ merged[key] = left[key];
270
+ continue;
271
+ }
272
+ merged[key] = deepMergeDeterministic(left[key], right[key]);
273
+ }
274
+ return merged;
275
+ }
276
+ return right;
277
+ }
278
+ function mergeInquiryContexts(contexts) {
279
+ return contexts.reduce((acc, next) => deepMergeDeterministic(acc, next), {});
280
+ }
281
+ function isMetaIntentName(intentName) {
282
+ return intentName.startsWith(META_INTENT_PREFIX);
283
+ }
284
+ function shouldExecuteInquiryResponder(inquiry, responderIsMeta) {
285
+ if (!isMetaIntentName(inquiry)) {
286
+ return true;
287
+ }
288
+ return responderIsMeta;
289
+ }
290
+ function compareResponderDescriptors(left, right) {
291
+ if (left.serviceName !== right.serviceName) {
292
+ return left.serviceName.localeCompare(right.serviceName);
293
+ }
294
+ if (left.taskName !== right.taskName) {
295
+ return left.taskName.localeCompare(right.taskName);
296
+ }
297
+ if (left.taskVersion !== right.taskVersion) {
298
+ return left.taskVersion - right.taskVersion;
299
+ }
300
+ return left.localTaskName.localeCompare(right.localTaskName);
301
+ }
302
+ function summarizeResponderStatuses(statuses) {
303
+ let responded = 0;
304
+ let failed = 0;
305
+ let timedOut = 0;
306
+ let pending = 0;
307
+ for (const status of statuses) {
308
+ if (status.status === "fulfilled") responded++;
309
+ if (status.status === "failed") failed++;
310
+ if (status.status === "timed_out") timedOut++;
311
+ }
312
+ pending = Math.max(0, statuses.length - responded - failed - timedOut);
313
+ return { responded, failed, timedOut, pending };
314
+ }
315
+
250
316
  // src/registry/ServiceRegistry.ts
251
317
  var ServiceRegistry = class _ServiceRegistry {
252
318
  /**
@@ -262,11 +328,47 @@ var ServiceRegistry = class _ServiceRegistry {
262
328
  this.instances = /* @__PURE__ */ new Map();
263
329
  this.deputies = /* @__PURE__ */ new Map();
264
330
  this.remoteSignals = /* @__PURE__ */ new Map();
331
+ this.remoteIntents = /* @__PURE__ */ new Map();
332
+ this.remoteIntentDeputiesByKey = /* @__PURE__ */ new Map();
333
+ this.remoteIntentDeputiesByTask = /* @__PURE__ */ new Map();
265
334
  this.serviceName = null;
266
335
  this.serviceInstanceId = null;
267
336
  this.numberOfRunningGraphs = 0;
268
337
  this.useSocket = false;
269
338
  this.retryCount = 3;
339
+ CadenzaService.defineIntent({
340
+ name: META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
341
+ description: "Gather transport diagnostics across all services and communication clients.",
342
+ input: {
343
+ type: "object",
344
+ properties: {
345
+ detailLevel: {
346
+ type: "string",
347
+ constraints: {
348
+ oneOf: ["summary", "full"]
349
+ }
350
+ },
351
+ includeErrorHistory: {
352
+ type: "boolean"
353
+ },
354
+ errorHistoryLimit: {
355
+ type: "number",
356
+ constraints: {
357
+ min: 1,
358
+ max: 200
359
+ }
360
+ }
361
+ }
362
+ },
363
+ output: {
364
+ type: "object",
365
+ properties: {
366
+ transportDiagnostics: {
367
+ type: "object"
368
+ }
369
+ }
370
+ }
371
+ });
270
372
  this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
271
373
  "Handle Instance Update",
272
374
  (ctx, emit) => {
@@ -282,10 +384,10 @@ var ServiceRegistry = class _ServiceRegistry {
282
384
  } = serviceInstance;
283
385
  if (uuid4 === this.serviceInstanceId) return;
284
386
  if (deleted) {
285
- this.instances.get(serviceName)?.splice(
286
- this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1,
287
- 1
288
- );
387
+ const indexToDelete = this.instances.get(serviceName)?.findIndex((i) => i.uuid === uuid4) ?? -1;
388
+ if (indexToDelete >= 0) {
389
+ this.instances.get(serviceName)?.splice(indexToDelete, 1);
390
+ }
289
391
  if (this.instances.get(serviceName)?.length === 0) {
290
392
  this.instances.delete(serviceName);
291
393
  } else if (this.instances.get(serviceName)?.filter((i) => i.address === address && i.port === port).length === 0) {
@@ -306,7 +408,7 @@ var ServiceRegistry = class _ServiceRegistry {
306
408
  if (this.serviceName === serviceName) {
307
409
  return false;
308
410
  }
309
- if (!isFrontend && this.deputies.has(serviceName) || this.remoteSignals.has(serviceName)) {
411
+ if (!isFrontend && (this.deputies.has(serviceName) || this.remoteIntents.has(serviceName)) || this.remoteSignals.has(serviceName)) {
310
412
  const clientCreated = instances?.some(
311
413
  (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
312
414
  );
@@ -328,7 +430,7 @@ var ServiceRegistry = class _ServiceRegistry {
328
430
  communicationTypes
329
431
  });
330
432
  instances?.filter(
331
- (i) => i.address === address && i.port === port && i.clientCreated && i.isActive
433
+ (i) => i.address === address && i.port === port && i.isActive
332
434
  ).forEach((i) => {
333
435
  i.clientCreated = true;
334
436
  });
@@ -356,7 +458,10 @@ var ServiceRegistry = class _ServiceRegistry {
356
458
  for (const serviceInstance of ctx.serviceInstances) {
357
459
  yield { serviceInstance };
358
460
  }
359
- }).doOn("meta.service_registry.registered_global_signals").then(this.handleInstanceUpdateTask);
461
+ }).doOn(
462
+ "meta.service_registry.registered_global_signals",
463
+ "meta.service_registry.registered_global_intents"
464
+ ).then(this.handleInstanceUpdateTask);
360
465
  this.handleGlobalSignalRegistrationTask = CadenzaService.createMetaTask(
361
466
  "Handle global Signal Registration",
362
467
  (ctx) => {
@@ -397,6 +502,32 @@ var ServiceRegistry = class _ServiceRegistry {
397
502
  },
398
503
  "Handles registration of remote signals"
399
504
  ).emits("meta.service_registry.registered_global_signals").doOn("global.meta.cadenza_db.gathered_sync_data");
505
+ this.handleGlobalIntentRegistrationTask = CadenzaService.createMetaTask(
506
+ "Handle global intent registration",
507
+ (ctx) => {
508
+ const intentToTaskMaps = this.normalizeIntentMaps(ctx);
509
+ const sorted = intentToTaskMaps.sort((a, b) => {
510
+ if (a.deleted && !b.deleted) return -1;
511
+ if (!a.deleted && b.deleted) return 1;
512
+ return 0;
513
+ });
514
+ for (const map of sorted) {
515
+ if (map.deleted) {
516
+ this.unregisterRemoteIntentDeputy(map);
517
+ continue;
518
+ }
519
+ CadenzaService.inquiryBroker.addIntent({
520
+ name: map.intentName
521
+ });
522
+ this.registerRemoteIntentDeputy(map);
523
+ }
524
+ return true;
525
+ },
526
+ "Handles registration of remote inquiry intent responders"
527
+ ).emits("meta.service_registry.registered_global_intents").doOn(
528
+ "global.meta.cadenza_db.gathered_sync_data",
529
+ "global.meta.graph_metadata.task_intent_associated"
530
+ );
400
531
  this.handleServiceNotRespondingTask = CadenzaService.createMetaTask(
401
532
  "Handle service not responding",
402
533
  (ctx, emit) => {
@@ -432,7 +563,12 @@ var ServiceRegistry = class _ServiceRegistry {
432
563
  return true;
433
564
  },
434
565
  "Handles service not responding"
435
- ).doOn("meta.fetch.handshake_failed", "meta.socket_client.disconnected").attachSignal("global.meta.service_registry.service_not_responding");
566
+ ).doOn(
567
+ "meta.fetch.handshake_failed",
568
+ "meta.fetch.handshake_failed.*",
569
+ "meta.socket_client.disconnected",
570
+ "meta.socket_client.disconnected.*"
571
+ ).attachSignal("global.meta.service_registry.service_not_responding");
436
572
  this.handleServiceHandshakeTask = CadenzaService.createMetaTask(
437
573
  "Handle service handshake",
438
574
  (ctx, emit) => {
@@ -459,7 +595,10 @@ var ServiceRegistry = class _ServiceRegistry {
459
595
  (i) => i.uuid !== serviceInstanceId && i.address === serviceAddress && i.port === servicePort
460
596
  );
461
597
  for (const i of instancesToDelete ?? []) {
462
- this.instances.get(serviceName)?.splice(this.instances.get(serviceName)?.indexOf(i) ?? -1, 1);
598
+ const indexToDelete = this.instances.get(serviceName)?.indexOf(i) ?? -1;
599
+ if (indexToDelete >= 0) {
600
+ this.instances.get(serviceName)?.splice(indexToDelete, 1);
601
+ }
463
602
  emit("global.meta.service_registry.deleted", {
464
603
  data: {
465
604
  isActive: false,
@@ -502,7 +641,10 @@ var ServiceRegistry = class _ServiceRegistry {
502
641
  });
503
642
  return joinedContext;
504
643
  }
505
- ).emits("meta.service_registry.initial_sync_complete").then(this.handleGlobalSignalRegistrationTask);
644
+ ).emits("meta.service_registry.initial_sync_complete").then(
645
+ this.handleGlobalSignalRegistrationTask,
646
+ this.handleGlobalIntentRegistrationTask
647
+ );
506
648
  this.fullSyncTask = CadenzaService.createMetaRoutine("Full sync", [
507
649
  CadenzaService.createCadenzaDBQueryTask("signal_to_task_map", {
508
650
  filter: {
@@ -510,6 +652,15 @@ var ServiceRegistry = class _ServiceRegistry {
510
652
  },
511
653
  fields: ["signal_name", "service_name", "deleted"]
512
654
  }).then(mergeSyncDataTask),
655
+ CadenzaService.createCadenzaDBQueryTask("intent_to_task_map", {
656
+ fields: [
657
+ "intent_name",
658
+ "task_name",
659
+ "task_version",
660
+ "service_name",
661
+ "deleted"
662
+ ]
663
+ }).then(mergeSyncDataTask),
513
664
  CadenzaService.createCadenzaDBQueryTask("service_instance", {
514
665
  filter: {
515
666
  deleted: false,
@@ -619,8 +770,9 @@ var ServiceRegistry = class _ServiceRegistry {
619
770
  }
620
771
  if (__broadcast || instances[0].isFrontend) {
621
772
  for (const instance of instances) {
773
+ const socketKey = instance.isFrontend ? instance.address : `${instance.address}_${instance.port}`;
622
774
  emit(
623
- `meta.service_registry.selected_instance_for_socket:${instance.address}`,
775
+ `meta.service_registry.selected_instance_for_socket:${socketKey}`,
624
776
  context
625
777
  );
626
778
  }
@@ -699,6 +851,25 @@ var ServiceRegistry = class _ServiceRegistry {
699
851
  __active: self?.isActive ?? false
700
852
  };
701
853
  }).doOn("meta.socket.status_check_requested");
854
+ this.collectTransportDiagnosticsTask = CadenzaService.createMetaTask(
855
+ "Collect transport diagnostics",
856
+ async (ctx) => {
857
+ const inquiryResult = await CadenzaService.inquire(
858
+ META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT,
859
+ {
860
+ detailLevel: ctx.detailLevel,
861
+ includeErrorHistory: ctx.includeErrorHistory,
862
+ errorHistoryLimit: ctx.errorHistoryLimit
863
+ },
864
+ ctx.inquiryOptions ?? ctx.__inquiryOptions ?? {}
865
+ );
866
+ return {
867
+ ...ctx,
868
+ ...inquiryResult
869
+ };
870
+ },
871
+ "Collects distributed transport diagnostics using inquiry responders."
872
+ ).doOn("meta.service_registry.transport_diagnostics_requested").emits("meta.service_registry.transport_diagnostics_collected").emitsOnFail("meta.service_registry.transport_diagnostics_failed");
702
873
  this.insertServiceTask = CadenzaService.createCadenzaDBInsertTask(
703
874
  "service",
704
875
  {
@@ -907,8 +1078,125 @@ var ServiceRegistry = class _ServiceRegistry {
907
1078
  if (!this._instance) this._instance = new _ServiceRegistry();
908
1079
  return this._instance;
909
1080
  }
1081
+ buildRemoteIntentDeputyKey(map) {
1082
+ return `${map.intentName}|${map.serviceName}|${map.taskName}|${map.taskVersion ?? 1}`;
1083
+ }
1084
+ normalizeIntentMaps(ctx) {
1085
+ if (Array.isArray(ctx.intentToTaskMaps)) {
1086
+ return ctx.intentToTaskMaps.map((m) => ({
1087
+ intentName: m.intentName ?? m.intent_name,
1088
+ serviceName: m.serviceName ?? m.service_name,
1089
+ taskName: m.taskName ?? m.task_name,
1090
+ taskVersion: m.taskVersion ?? m.task_version ?? 1,
1091
+ deleted: !!m.deleted
1092
+ })).filter((m) => m.intentName && m.serviceName && m.taskName);
1093
+ }
1094
+ const single = ctx.intentToTaskMap ?? ctx.data ?? (ctx.intentName ? ctx : void 0);
1095
+ if (!single) return [];
1096
+ const normalized = {
1097
+ intentName: single.intentName ?? single.intent_name,
1098
+ serviceName: single.serviceName ?? single.service_name,
1099
+ taskName: single.taskName ?? single.task_name,
1100
+ taskVersion: single.taskVersion ?? single.task_version ?? 1,
1101
+ deleted: !!single.deleted
1102
+ };
1103
+ if (!normalized.intentName || !normalized.serviceName || !normalized.taskName)
1104
+ return [];
1105
+ return [normalized];
1106
+ }
1107
+ registerRemoteIntentDeputy(map) {
1108
+ if (!this.serviceName || map.serviceName === this.serviceName) {
1109
+ return;
1110
+ }
1111
+ const key = this.buildRemoteIntentDeputyKey(map);
1112
+ if (this.remoteIntentDeputiesByKey.has(key)) {
1113
+ return;
1114
+ }
1115
+ const deputyTaskName = `Inquire ${map.intentName} via ${map.serviceName} (${map.taskName} v${map.taskVersion})`;
1116
+ const deputyTask = isMetaIntentName(map.intentName) ? CadenzaService.createMetaDeputyTask(map.taskName, map.serviceName, {
1117
+ register: false,
1118
+ isHidden: true,
1119
+ retryCount: 1,
1120
+ retryDelay: 50,
1121
+ retryDelayFactor: 1.2
1122
+ }) : CadenzaService.createDeputyTask(map.taskName, map.serviceName, {
1123
+ register: false,
1124
+ isHidden: true,
1125
+ retryCount: 1,
1126
+ retryDelay: 50,
1127
+ retryDelayFactor: 1.2
1128
+ });
1129
+ deputyTask.respondsTo(map.intentName);
1130
+ if (!this.remoteIntents.has(map.serviceName)) {
1131
+ this.remoteIntents.set(map.serviceName, /* @__PURE__ */ new Set());
1132
+ }
1133
+ this.remoteIntents.get(map.serviceName).add(map.intentName);
1134
+ const descriptor = {
1135
+ key,
1136
+ intentName: map.intentName,
1137
+ serviceName: map.serviceName,
1138
+ remoteTaskName: map.taskName,
1139
+ remoteTaskVersion: map.taskVersion,
1140
+ localTaskName: deputyTask.name || deputyTaskName,
1141
+ localTask: deputyTask
1142
+ };
1143
+ this.remoteIntentDeputiesByKey.set(key, descriptor);
1144
+ this.remoteIntentDeputiesByTask.set(deputyTask, descriptor);
1145
+ }
1146
+ unregisterRemoteIntentDeputy(map) {
1147
+ const key = this.buildRemoteIntentDeputyKey(map);
1148
+ const descriptor = this.remoteIntentDeputiesByKey.get(key);
1149
+ if (!descriptor) {
1150
+ return;
1151
+ }
1152
+ const task = descriptor.localTask;
1153
+ if (task) {
1154
+ CadenzaService.inquiryBroker.unsubscribe(descriptor.intentName, task);
1155
+ task.destroy();
1156
+ }
1157
+ this.remoteIntentDeputiesByTask.delete(descriptor.localTask);
1158
+ this.remoteIntentDeputiesByKey.delete(key);
1159
+ this.remoteIntents.get(descriptor.serviceName)?.delete(descriptor.intentName);
1160
+ if (!this.remoteIntents.get(descriptor.serviceName)?.size) {
1161
+ this.remoteIntents.delete(descriptor.serviceName);
1162
+ }
1163
+ const deputies = this.deputies.get(descriptor.serviceName);
1164
+ if (deputies) {
1165
+ this.deputies.set(
1166
+ descriptor.serviceName,
1167
+ deputies.filter((d) => d.localTaskName !== descriptor.localTaskName)
1168
+ );
1169
+ if (this.deputies.get(descriptor.serviceName)?.length === 0) {
1170
+ this.deputies.delete(descriptor.serviceName);
1171
+ }
1172
+ }
1173
+ }
1174
+ getInquiryResponderDescriptor(task) {
1175
+ const remote = this.remoteIntentDeputiesByTask.get(task);
1176
+ if (remote) {
1177
+ return {
1178
+ isRemote: true,
1179
+ serviceName: remote.serviceName,
1180
+ taskName: remote.remoteTaskName,
1181
+ taskVersion: remote.remoteTaskVersion,
1182
+ localTaskName: remote.localTaskName
1183
+ };
1184
+ }
1185
+ return {
1186
+ isRemote: false,
1187
+ serviceName: this.serviceName ?? "UnknownService",
1188
+ taskName: task.name,
1189
+ taskVersion: task.version,
1190
+ localTaskName: task.name
1191
+ };
1192
+ }
910
1193
  reset() {
911
1194
  this.instances.clear();
1195
+ this.deputies.clear();
1196
+ this.remoteSignals.clear();
1197
+ this.remoteIntents.clear();
1198
+ this.remoteIntentDeputiesByKey.clear();
1199
+ this.remoteIntentDeputiesByTask.clear();
912
1200
  }
913
1201
  };
914
1202
 
@@ -1025,6 +1313,8 @@ var RestController = class _RestController {
1025
1313
  * It initializes and configures the REST server tasks.
1026
1314
  */
1027
1315
  constructor() {
1316
+ this.fetchClientDiagnostics = /* @__PURE__ */ new Map();
1317
+ this.diagnosticsErrorHistoryLimit = 100;
1028
1318
  /**
1029
1319
  * Fetches data from the given URL with a specified timeout. This function performs
1030
1320
  * a fetch request with the ability to cancel the request if it exceeds the provided timeout duration.
@@ -1065,6 +1355,11 @@ var RestController = class _RestController {
1065
1355
  "meta.rest.delegation_requested",
1066
1356
  "meta.socket.delegation_requested"
1067
1357
  );
1358
+ CadenzaService.createMetaTask(
1359
+ "Collect fetch transport diagnostics",
1360
+ (ctx) => this.collectFetchTransportDiagnostics(ctx),
1361
+ "Responds to distributed transport diagnostics inquiries with REST/fetch client data."
1362
+ ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
1068
1363
  CadenzaService.createMetaRoutine(
1069
1364
  "RestServer",
1070
1365
  [
@@ -1395,6 +1690,13 @@ var RestController = class _RestController {
1395
1690
  const port = protocol === "https" ? 443 : servicePort;
1396
1691
  const URL = `${protocol}://${serviceAddress}:${port}`;
1397
1692
  const fetchId = `${serviceAddress}_${port}`;
1693
+ const fetchDiagnostics = this.ensureFetchClientDiagnostics(
1694
+ fetchId,
1695
+ serviceName,
1696
+ URL
1697
+ );
1698
+ fetchDiagnostics.destroyed = false;
1699
+ fetchDiagnostics.updatedAt = Date.now();
1398
1700
  if (CadenzaService.get(`Send Handshake to ${URL}`)) {
1399
1701
  console.error("Fetch client already exists", URL);
1400
1702
  return;
@@ -1416,6 +1718,10 @@ var RestController = class _RestController {
1416
1718
  );
1417
1719
  if (response.__status !== "success") {
1418
1720
  const error = response.__error ?? `Failed to connect to service ${serviceName} ${ctx2.serviceInstanceId}`;
1721
+ fetchDiagnostics.connected = false;
1722
+ fetchDiagnostics.lastHandshakeError = error;
1723
+ fetchDiagnostics.updatedAt = Date.now();
1724
+ this.recordFetchClientError(fetchId, serviceName, URL, error);
1419
1725
  CadenzaService.log(
1420
1726
  "Fetch handshake failed.",
1421
1727
  { error, serviceName, URL },
@@ -1425,6 +1731,11 @@ var RestController = class _RestController {
1425
1731
  return { ...ctx2, __error: error, errored: true };
1426
1732
  }
1427
1733
  ctx2.serviceInstanceId = response.__serviceInstanceId;
1734
+ fetchDiagnostics.connected = true;
1735
+ fetchDiagnostics.destroyed = false;
1736
+ fetchDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
1737
+ fetchDiagnostics.lastHandshakeError = null;
1738
+ fetchDiagnostics.updatedAt = Date.now();
1428
1739
  CadenzaService.log("Fetch client connected.", {
1429
1740
  response,
1430
1741
  serviceName,
@@ -1440,6 +1751,10 @@ var RestController = class _RestController {
1440
1751
  });
1441
1752
  }
1442
1753
  } catch (e) {
1754
+ fetchDiagnostics.connected = false;
1755
+ fetchDiagnostics.lastHandshakeError = this.getErrorMessage(e);
1756
+ fetchDiagnostics.updatedAt = Date.now();
1757
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1443
1758
  CadenzaService.log(
1444
1759
  "Error in fetch handshake",
1445
1760
  { error: e, serviceName, URL, ctx: ctx2 },
@@ -1461,6 +1776,8 @@ var RestController = class _RestController {
1461
1776
  if (ctx2.__remoteRoutineName === void 0) {
1462
1777
  return;
1463
1778
  }
1779
+ fetchDiagnostics.delegationRequests++;
1780
+ fetchDiagnostics.updatedAt = Date.now();
1464
1781
  let resultContext;
1465
1782
  try {
1466
1783
  resultContext = await this.fetchDataWithTimeout(
@@ -1474,8 +1791,21 @@ var RestController = class _RestController {
1474
1791
  },
1475
1792
  3e4
1476
1793
  );
1794
+ if (resultContext?.errored || resultContext?.failed) {
1795
+ fetchDiagnostics.delegationFailures++;
1796
+ fetchDiagnostics.updatedAt = Date.now();
1797
+ this.recordFetchClientError(
1798
+ fetchId,
1799
+ serviceName,
1800
+ URL,
1801
+ resultContext?.__error ?? resultContext?.error ?? "Delegation failed"
1802
+ );
1803
+ }
1477
1804
  } catch (e) {
1478
1805
  console.error("Error in delegation", e);
1806
+ fetchDiagnostics.delegationFailures++;
1807
+ fetchDiagnostics.updatedAt = Date.now();
1808
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1479
1809
  resultContext = {
1480
1810
  __error: `Error: ${e}`,
1481
1811
  errored: true,
@@ -1501,6 +1831,8 @@ var RestController = class _RestController {
1501
1831
  if (ctx2.__signalName === void 0) {
1502
1832
  return;
1503
1833
  }
1834
+ fetchDiagnostics.signalTransmissions++;
1835
+ fetchDiagnostics.updatedAt = Date.now();
1504
1836
  let response;
1505
1837
  try {
1506
1838
  response = await this.fetchDataWithTimeout(
@@ -1517,8 +1849,21 @@ var RestController = class _RestController {
1517
1849
  if (ctx2.__routineExecId) {
1518
1850
  emit(`meta.fetch.transmitted:${ctx2.__routineExecId}`, response);
1519
1851
  }
1852
+ if (response?.errored || response?.failed) {
1853
+ fetchDiagnostics.signalFailures++;
1854
+ fetchDiagnostics.updatedAt = Date.now();
1855
+ this.recordFetchClientError(
1856
+ fetchId,
1857
+ serviceName,
1858
+ URL,
1859
+ response?.__error ?? response?.error ?? "Signal transmission failed"
1860
+ );
1861
+ }
1520
1862
  } catch (e) {
1521
1863
  console.error("Error in transmission", e);
1864
+ fetchDiagnostics.signalFailures++;
1865
+ fetchDiagnostics.updatedAt = Date.now();
1866
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1522
1867
  response = {
1523
1868
  __error: `Error: ${e}`,
1524
1869
  errored: true,
@@ -1536,6 +1881,8 @@ var RestController = class _RestController {
1536
1881
  const statusTask = CadenzaService.createMetaTask(
1537
1882
  `Request status from ${URL}`,
1538
1883
  async (ctx2) => {
1884
+ fetchDiagnostics.statusChecks++;
1885
+ fetchDiagnostics.updatedAt = Date.now();
1539
1886
  let status;
1540
1887
  try {
1541
1888
  status = await this.fetchDataWithTimeout(
@@ -1545,7 +1892,20 @@ var RestController = class _RestController {
1545
1892
  },
1546
1893
  1e3
1547
1894
  );
1895
+ if (status?.errored || status?.failed) {
1896
+ fetchDiagnostics.statusFailures++;
1897
+ fetchDiagnostics.updatedAt = Date.now();
1898
+ this.recordFetchClientError(
1899
+ fetchId,
1900
+ serviceName,
1901
+ URL,
1902
+ status?.__error ?? status?.error ?? "Status check failed"
1903
+ );
1904
+ }
1548
1905
  } catch (e) {
1906
+ fetchDiagnostics.statusFailures++;
1907
+ fetchDiagnostics.updatedAt = Date.now();
1908
+ this.recordFetchClientError(fetchId, serviceName, URL, e);
1549
1909
  status = {
1550
1910
  __error: `Error: ${e}`,
1551
1911
  errored: true,
@@ -1557,6 +1917,9 @@ var RestController = class _RestController {
1557
1917
  "Requests status"
1558
1918
  ).doOn("meta.fetch.status_check_requested").emits("meta.fetch.status_checked").emitsOnFail("meta.fetch.status_check_failed");
1559
1919
  CadenzaService.createEphemeralMetaTask("Destroy fetch client", () => {
1920
+ fetchDiagnostics.connected = false;
1921
+ fetchDiagnostics.destroyed = true;
1922
+ fetchDiagnostics.updatedAt = Date.now();
1560
1923
  CadenzaService.log("Destroying fetch client", { URL, serviceName });
1561
1924
  handshakeTask.destroy();
1562
1925
  delegateTask.destroy();
@@ -1605,17 +1968,191 @@ var RestController = class _RestController {
1605
1968
  if (!this._instance) this._instance = new _RestController();
1606
1969
  return this._instance;
1607
1970
  }
1971
+ resolveTransportDiagnosticsOptions(ctx) {
1972
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
1973
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
1974
+ const requestedLimit = Number(ctx.errorHistoryLimit);
1975
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
1976
+ return {
1977
+ detailLevel,
1978
+ includeErrorHistory,
1979
+ errorHistoryLimit
1980
+ };
1981
+ }
1982
+ ensureFetchClientDiagnostics(fetchId, serviceName, url) {
1983
+ let state = this.fetchClientDiagnostics.get(fetchId);
1984
+ if (!state) {
1985
+ state = {
1986
+ fetchId,
1987
+ serviceName,
1988
+ url,
1989
+ connected: false,
1990
+ destroyed: false,
1991
+ lastHandshakeAt: null,
1992
+ lastHandshakeError: null,
1993
+ lastError: null,
1994
+ lastErrorAt: 0,
1995
+ errorHistory: [],
1996
+ delegationRequests: 0,
1997
+ delegationFailures: 0,
1998
+ signalTransmissions: 0,
1999
+ signalFailures: 0,
2000
+ statusChecks: 0,
2001
+ statusFailures: 0,
2002
+ updatedAt: Date.now()
2003
+ };
2004
+ this.fetchClientDiagnostics.set(fetchId, state);
2005
+ } else {
2006
+ state.serviceName = serviceName;
2007
+ state.url = url;
2008
+ }
2009
+ return state;
2010
+ }
2011
+ getErrorMessage(error) {
2012
+ if (error instanceof Error) {
2013
+ return error.message;
2014
+ }
2015
+ if (typeof error === "string") {
2016
+ return error;
2017
+ }
2018
+ try {
2019
+ return JSON.stringify(error);
2020
+ } catch {
2021
+ return String(error);
2022
+ }
2023
+ }
2024
+ recordFetchClientError(fetchId, serviceName, url, error) {
2025
+ const state = this.ensureFetchClientDiagnostics(fetchId, serviceName, url);
2026
+ const message = this.getErrorMessage(error);
2027
+ const now = Date.now();
2028
+ state.lastError = message;
2029
+ state.lastErrorAt = now;
2030
+ state.updatedAt = now;
2031
+ state.errorHistory.push({ at: new Date(now).toISOString(), message });
2032
+ if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
2033
+ state.errorHistory.splice(
2034
+ 0,
2035
+ state.errorHistory.length - this.diagnosticsErrorHistoryLimit
2036
+ );
2037
+ }
2038
+ }
2039
+ collectFetchTransportDiagnostics(ctx) {
2040
+ const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
2041
+ const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
2042
+ const states = Array.from(this.fetchClientDiagnostics.values()).sort(
2043
+ (a, b) => a.fetchId.localeCompare(b.fetchId)
2044
+ );
2045
+ const summary = {
2046
+ detailLevel,
2047
+ totalClients: states.length,
2048
+ connectedClients: states.filter((state) => state.connected).length,
2049
+ destroyedClients: states.filter((state) => state.destroyed).length,
2050
+ delegationRequests: states.reduce(
2051
+ (acc, state) => acc + state.delegationRequests,
2052
+ 0
2053
+ ),
2054
+ delegationFailures: states.reduce(
2055
+ (acc, state) => acc + state.delegationFailures,
2056
+ 0
2057
+ ),
2058
+ signalTransmissions: states.reduce(
2059
+ (acc, state) => acc + state.signalTransmissions,
2060
+ 0
2061
+ ),
2062
+ signalFailures: states.reduce((acc, state) => acc + state.signalFailures, 0),
2063
+ statusChecks: states.reduce((acc, state) => acc + state.statusChecks, 0),
2064
+ statusFailures: states.reduce((acc, state) => acc + state.statusFailures, 0),
2065
+ latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
2066
+ };
2067
+ if (detailLevel === "summary") {
2068
+ return {
2069
+ transportDiagnostics: {
2070
+ [serviceName]: {
2071
+ fetchClient: summary
2072
+ }
2073
+ }
2074
+ };
2075
+ }
2076
+ const clients = states.map((state) => {
2077
+ const details = {
2078
+ fetchId: state.fetchId,
2079
+ serviceName: state.serviceName,
2080
+ url: state.url,
2081
+ connected: state.connected,
2082
+ destroyed: state.destroyed,
2083
+ lastHandshakeAt: state.lastHandshakeAt,
2084
+ lastHandshakeError: state.lastHandshakeError,
2085
+ latestError: state.lastError,
2086
+ delegationRequests: state.delegationRequests,
2087
+ delegationFailures: state.delegationFailures,
2088
+ signalTransmissions: state.signalTransmissions,
2089
+ signalFailures: state.signalFailures,
2090
+ statusChecks: state.statusChecks,
2091
+ statusFailures: state.statusFailures
2092
+ };
2093
+ if (includeErrorHistory) {
2094
+ details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
2095
+ }
2096
+ return details;
2097
+ });
2098
+ return {
2099
+ transportDiagnostics: {
2100
+ [serviceName]: {
2101
+ fetchClient: {
2102
+ ...summary,
2103
+ clients
2104
+ }
2105
+ }
2106
+ }
2107
+ };
2108
+ }
1608
2109
  };
1609
2110
 
1610
2111
  // src/network/SocketController.ts
1611
2112
  import { Server } from "socket.io";
1612
2113
  import { RateLimiterMemory as RateLimiterMemory2 } from "rate-limiter-flexible";
1613
2114
  import { io } from "socket.io-client";
1614
- var SocketController = class _SocketController {
1615
- static get instance() {
1616
- if (!this._instance) this._instance = new _SocketController();
1617
- return this._instance;
2115
+
2116
+ // src/network/socketClientUtils.ts
2117
+ var waitForSocketConnection = async (socket, timeoutMs, createError) => {
2118
+ if (!socket) {
2119
+ return { ok: false, error: createError("disconnected") };
1618
2120
  }
2121
+ if (socket.connected) {
2122
+ return { ok: true };
2123
+ }
2124
+ return new Promise((resolve) => {
2125
+ let timer = null;
2126
+ let settled = false;
2127
+ const cleanup = () => {
2128
+ if (timer) {
2129
+ clearTimeout(timer);
2130
+ timer = null;
2131
+ }
2132
+ socket.off("connect", onConnect);
2133
+ socket.off("connect_error", onConnectError);
2134
+ socket.off("disconnect", onDisconnect);
2135
+ };
2136
+ const settle = (outcome) => {
2137
+ if (settled) return;
2138
+ settled = true;
2139
+ cleanup();
2140
+ resolve(outcome);
2141
+ };
2142
+ const onConnect = () => settle({ ok: true });
2143
+ const onConnectError = (error) => settle({ ok: false, error: createError("connect_error", error) });
2144
+ const onDisconnect = () => settle({ ok: false, error: createError("disconnected") });
2145
+ socket.once("connect", onConnect);
2146
+ socket.once("connect_error", onConnectError);
2147
+ socket.once("disconnect", onDisconnect);
2148
+ timer = setTimeout(() => {
2149
+ settle({ ok: false, error: createError("connect_timeout") });
2150
+ }, timeoutMs);
2151
+ });
2152
+ };
2153
+
2154
+ // src/network/SocketController.ts
2155
+ var SocketController = class _SocketController {
1619
2156
  /**
1620
2157
  * Constructs the `SocketServer`, setting up a WebSocket server with specific configurations,
1621
2158
  * including connection state recovery, rate limiting, CORS handling, and custom event handling.
@@ -1634,6 +2171,13 @@ var SocketController = class _SocketController {
1634
2171
  * Initializes the `SocketServer` to be ready for WebSocket communication.
1635
2172
  */
1636
2173
  constructor() {
2174
+ this.socketClientDiagnostics = /* @__PURE__ */ new Map();
2175
+ this.diagnosticsErrorHistoryLimit = 100;
2176
+ CadenzaService.createMetaTask(
2177
+ "Collect socket transport diagnostics",
2178
+ (ctx) => this.collectSocketTransportDiagnostics(ctx),
2179
+ "Responds to distributed transport diagnostics inquiries with socket client data."
2180
+ ).respondsTo(META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT);
1637
2181
  CadenzaService.createMetaRoutine(
1638
2182
  "SocketServer",
1639
2183
  [
@@ -1861,6 +2405,13 @@ var SocketController = class _SocketController {
1861
2405
  const port = protocol === "https" ? 443 : servicePort;
1862
2406
  const URL = `${socketProtocol}://${serviceAddress}:${port}`;
1863
2407
  const fetchId = `${serviceAddress}_${port}`;
2408
+ const socketDiagnostics = this.ensureSocketClientDiagnostics(
2409
+ fetchId,
2410
+ serviceName,
2411
+ URL
2412
+ );
2413
+ socketDiagnostics.destroyed = false;
2414
+ socketDiagnostics.updatedAt = Date.now();
1864
2415
  let handshake = false;
1865
2416
  let errorCount = 0;
1866
2417
  const ERROR_LIMIT = 5;
@@ -1870,6 +2421,11 @@ var SocketController = class _SocketController {
1870
2421
  }
1871
2422
  const pendingDelegationIds = /* @__PURE__ */ new Set();
1872
2423
  const pendingTimers = /* @__PURE__ */ new Set();
2424
+ const syncPendingCounts = () => {
2425
+ socketDiagnostics.pendingDelegations = pendingDelegationIds.size;
2426
+ socketDiagnostics.pendingTimers = pendingTimers.size;
2427
+ socketDiagnostics.updatedAt = Date.now();
2428
+ };
1873
2429
  let handshakeTask = null;
1874
2430
  let emitWhenReady = null;
1875
2431
  let transmitTask = null;
@@ -1886,35 +2442,86 @@ var SocketController = class _SocketController {
1886
2442
  });
1887
2443
  emitWhenReady = (event, data, timeoutMs = 6e4, ack) => {
1888
2444
  return new Promise((resolve) => {
1889
- const tryEmit = () => {
1890
- if (!socket?.connected) {
1891
- socket?.once("connect", tryEmit);
2445
+ const resolveWithError = (errorMessage, fallbackError) => {
2446
+ resolve({
2447
+ ...data,
2448
+ errored: true,
2449
+ __error: errorMessage,
2450
+ error: fallbackError instanceof Error ? fallbackError.message : errorMessage,
2451
+ socketId: socket?.id,
2452
+ serviceName,
2453
+ URL
2454
+ });
2455
+ };
2456
+ const tryEmit = async () => {
2457
+ const waitTimeoutMs = timeoutMs > 0 ? timeoutMs + 10 : 1e4;
2458
+ const waitResult = await waitForSocketConnection(
2459
+ socket,
2460
+ waitTimeoutMs,
2461
+ (reason, error) => {
2462
+ if (reason === "connect_timeout") {
2463
+ return `Socket connect timed out before '${event}'`;
2464
+ }
2465
+ if (reason === "connect_error") {
2466
+ const errMessage = error instanceof Error ? error.message : String(error);
2467
+ return `Socket connect error before '${event}': ${errMessage}`;
2468
+ }
2469
+ return `Socket disconnected before '${event}'`;
2470
+ }
2471
+ );
2472
+ if (!waitResult.ok) {
2473
+ CadenzaService.log(
2474
+ waitResult.error,
2475
+ { socketId: socket?.id, serviceName, URL, event },
2476
+ "error"
2477
+ );
2478
+ this.recordSocketClientError(
2479
+ fetchId,
2480
+ serviceName,
2481
+ URL,
2482
+ waitResult.error
2483
+ );
2484
+ resolveWithError(waitResult.error);
1892
2485
  return;
1893
2486
  }
1894
- let timer;
2487
+ let timer = null;
1895
2488
  if (timeoutMs !== 0) {
1896
2489
  timer = setTimeout(() => {
2490
+ if (timer) {
2491
+ pendingTimers.delete(timer);
2492
+ syncPendingCounts();
2493
+ timer = null;
2494
+ }
1897
2495
  CadenzaService.log(
1898
2496
  `Socket event '${event}' timed out`,
1899
2497
  { socketId: socket?.id, serviceName, URL },
1900
2498
  "error"
1901
2499
  );
1902
- resolve({
1903
- ...data,
1904
- errored: true,
1905
- __error: `Socket event '${event}' timed out`,
1906
- error: `Socket event '${event}' timed out`,
1907
- socketId: socket?.id,
2500
+ this.recordSocketClientError(
2501
+ fetchId,
1908
2502
  serviceName,
1909
- URL
1910
- });
2503
+ URL,
2504
+ `Socket event '${event}' timed out`
2505
+ );
2506
+ resolveWithError(`Socket event '${event}' timed out`);
1911
2507
  }, timeoutMs + 10);
1912
2508
  pendingTimers.add(timer);
2509
+ syncPendingCounts();
1913
2510
  }
1914
- socket.timeout(timeoutMs).emit(event, data, (err, response) => {
1915
- if (timer) clearTimeout(timer);
1916
- pendingTimers.delete(timer);
1917
- timer = null;
2511
+ const connectedSocket = socket;
2512
+ if (!connectedSocket) {
2513
+ resolveWithError(
2514
+ `Socket unavailable before emitting '${event}'`
2515
+ );
2516
+ return;
2517
+ }
2518
+ connectedSocket.timeout(timeoutMs).emit(event, data, (err, response) => {
2519
+ if (timer) {
2520
+ clearTimeout(timer);
2521
+ pendingTimers.delete(timer);
2522
+ syncPendingCounts();
2523
+ timer = null;
2524
+ }
1918
2525
  if (err) {
1919
2526
  CadenzaService.log(
1920
2527
  "Socket timeout.",
@@ -1926,6 +2533,12 @@ var SocketController = class _SocketController {
1926
2533
  },
1927
2534
  "warning"
1928
2535
  );
2536
+ this.recordSocketClientError(
2537
+ fetchId,
2538
+ serviceName,
2539
+ URL,
2540
+ err
2541
+ );
1929
2542
  response = {
1930
2543
  __error: `Timeout error: ${err}`,
1931
2544
  errored: true,
@@ -1937,15 +2550,15 @@ var SocketController = class _SocketController {
1937
2550
  resolve(response);
1938
2551
  });
1939
2552
  };
1940
- if (socket?.connected) {
1941
- tryEmit();
1942
- } else {
1943
- socket?.once("connect", tryEmit);
1944
- }
2553
+ void tryEmit();
1945
2554
  });
1946
2555
  };
1947
2556
  socket.on("connect", () => {
1948
2557
  if (handshake) return;
2558
+ socketDiagnostics.connected = true;
2559
+ socketDiagnostics.destroyed = false;
2560
+ socketDiagnostics.socketId = socket?.id ?? null;
2561
+ socketDiagnostics.updatedAt = Date.now();
1949
2562
  CadenzaService.emit(`meta.socket_client.connected:${fetchId}`, ctx);
1950
2563
  });
1951
2564
  socket.on("delegation_progress", (ctx2) => {
@@ -1964,6 +2577,12 @@ var SocketController = class _SocketController {
1964
2577
  });
1965
2578
  socket.on("connect_error", (err) => {
1966
2579
  handshake = false;
2580
+ socketDiagnostics.connected = false;
2581
+ socketDiagnostics.handshake = false;
2582
+ socketDiagnostics.connectErrors++;
2583
+ socketDiagnostics.lastHandshakeError = err.message;
2584
+ socketDiagnostics.updatedAt = Date.now();
2585
+ this.recordSocketClientError(fetchId, serviceName, URL, err);
1967
2586
  CadenzaService.log(
1968
2587
  "Socket connect error",
1969
2588
  { error: err.message, serviceName, socketId: socket?.id, URL },
@@ -1972,9 +2591,16 @@ var SocketController = class _SocketController {
1972
2591
  CadenzaService.emit(`meta.socket_client.connect_error:${fetchId}`, err);
1973
2592
  });
1974
2593
  socket.on("reconnect_attempt", (attempt) => {
2594
+ socketDiagnostics.reconnectAttempts = Math.max(
2595
+ socketDiagnostics.reconnectAttempts,
2596
+ attempt
2597
+ );
2598
+ socketDiagnostics.updatedAt = Date.now();
1975
2599
  CadenzaService.log(`Reconnect attempt: ${attempt}`);
1976
2600
  });
1977
2601
  socket.on("reconnect", (attempt) => {
2602
+ socketDiagnostics.connected = true;
2603
+ socketDiagnostics.updatedAt = Date.now();
1978
2604
  CadenzaService.log(`Socket reconnected after ${attempt} tries`, {
1979
2605
  socketId: socket?.id,
1980
2606
  URL,
@@ -1983,6 +2609,12 @@ var SocketController = class _SocketController {
1983
2609
  });
1984
2610
  socket.on("reconnect_error", (err) => {
1985
2611
  handshake = false;
2612
+ socketDiagnostics.connected = false;
2613
+ socketDiagnostics.handshake = false;
2614
+ socketDiagnostics.reconnectErrors++;
2615
+ socketDiagnostics.lastHandshakeError = err.message;
2616
+ socketDiagnostics.updatedAt = Date.now();
2617
+ this.recordSocketClientError(fetchId, serviceName, URL, err);
1986
2618
  CadenzaService.log(
1987
2619
  "Socket reconnect failed.",
1988
2620
  { error: err.message, serviceName, URL, socketId: socket?.id },
@@ -1991,6 +2623,9 @@ var SocketController = class _SocketController {
1991
2623
  });
1992
2624
  socket.on("error", (err) => {
1993
2625
  errorCount++;
2626
+ socketDiagnostics.socketErrors++;
2627
+ socketDiagnostics.updatedAt = Date.now();
2628
+ this.recordSocketClientError(fetchId, serviceName, URL, err);
1994
2629
  CadenzaService.log(
1995
2630
  "Socket error",
1996
2631
  { error: err, socketId: socket?.id, URL, serviceName },
@@ -1999,6 +2634,11 @@ var SocketController = class _SocketController {
1999
2634
  CadenzaService.emit("meta.socket_client.error", err);
2000
2635
  });
2001
2636
  socket.on("disconnect", () => {
2637
+ const disconnectedAt = (/* @__PURE__ */ new Date()).toISOString();
2638
+ socketDiagnostics.connected = false;
2639
+ socketDiagnostics.handshake = false;
2640
+ socketDiagnostics.lastDisconnectAt = disconnectedAt;
2641
+ socketDiagnostics.updatedAt = Date.now();
2002
2642
  CadenzaService.log(
2003
2643
  "Socket disconnected.",
2004
2644
  { URL, serviceName, socketId: socket?.id },
@@ -2017,6 +2657,8 @@ var SocketController = class _SocketController {
2017
2657
  async (ctx2, emit) => {
2018
2658
  if (handshake) return;
2019
2659
  handshake = true;
2660
+ socketDiagnostics.handshake = true;
2661
+ socketDiagnostics.updatedAt = Date.now();
2020
2662
  await emitWhenReady?.(
2021
2663
  "handshake",
2022
2664
  {
@@ -2028,6 +2670,11 @@ var SocketController = class _SocketController {
2028
2670
  1e4,
2029
2671
  (result) => {
2030
2672
  if (result.status === "success") {
2673
+ socketDiagnostics.connected = true;
2674
+ socketDiagnostics.handshake = true;
2675
+ socketDiagnostics.lastHandshakeAt = (/* @__PURE__ */ new Date()).toISOString();
2676
+ socketDiagnostics.lastHandshakeError = null;
2677
+ socketDiagnostics.updatedAt = Date.now();
2031
2678
  CadenzaService.log("Socket client connected", {
2032
2679
  result,
2033
2680
  serviceName,
@@ -2035,6 +2682,16 @@ var SocketController = class _SocketController {
2035
2682
  URL
2036
2683
  });
2037
2684
  } else {
2685
+ socketDiagnostics.connected = false;
2686
+ socketDiagnostics.handshake = false;
2687
+ socketDiagnostics.lastHandshakeError = result?.__error ?? result?.error ?? "Socket handshake failed";
2688
+ socketDiagnostics.updatedAt = Date.now();
2689
+ this.recordSocketClientError(
2690
+ fetchId,
2691
+ serviceName,
2692
+ URL,
2693
+ socketDiagnostics.lastHandshakeError
2694
+ );
2038
2695
  CadenzaService.log(
2039
2696
  "Socket handshake failed",
2040
2697
  { result, serviceName, socketId: socket?.id, URL },
@@ -2057,6 +2714,7 @@ var SocketController = class _SocketController {
2057
2714
  delete ctx2.__broadcast;
2058
2715
  const requestSentAt = Date.now();
2059
2716
  pendingDelegationIds.add(ctx2.__metadata.__deputyExecId);
2717
+ syncPendingCounts();
2060
2718
  emitWhenReady?.(
2061
2719
  "delegation",
2062
2720
  ctx2,
@@ -2074,6 +2732,15 @@ var SocketController = class _SocketController {
2074
2732
  }
2075
2733
  );
2076
2734
  pendingDelegationIds.delete(ctx2.__metadata.__deputyExecId);
2735
+ syncPendingCounts();
2736
+ if (resultContext?.errored || resultContext?.failed) {
2737
+ this.recordSocketClientError(
2738
+ fetchId,
2739
+ serviceName,
2740
+ URL,
2741
+ resultContext?.__error ?? resultContext?.error ?? "Socket delegation failed"
2742
+ );
2743
+ }
2077
2744
  resolve(resultContext);
2078
2745
  }
2079
2746
  );
@@ -2109,6 +2776,10 @@ var SocketController = class _SocketController {
2109
2776
  `Shutdown SocketClient ${URL}`,
2110
2777
  (ctx2, emit) => {
2111
2778
  handshake = false;
2779
+ socketDiagnostics.connected = false;
2780
+ socketDiagnostics.handshake = false;
2781
+ socketDiagnostics.destroyed = true;
2782
+ socketDiagnostics.updatedAt = Date.now();
2112
2783
  CadenzaService.log("Shutting down socket client", { URL, serviceName });
2113
2784
  socket?.close();
2114
2785
  handshakeTask?.destroy();
@@ -2138,10 +2809,12 @@ var SocketController = class _SocketController {
2138
2809
  });
2139
2810
  }
2140
2811
  pendingDelegationIds.clear();
2812
+ syncPendingCounts();
2141
2813
  for (const timer of pendingTimers) {
2142
2814
  clearTimeout(timer);
2143
2815
  }
2144
2816
  pendingTimers.clear();
2817
+ syncPendingCounts();
2145
2818
  },
2146
2819
  "Shuts down the socket client"
2147
2820
  ).doOn(
@@ -2155,6 +2828,157 @@ var SocketController = class _SocketController {
2155
2828
  "Connects to a specified socket server"
2156
2829
  ).doOn("meta.fetch.handshake_complete").emitsOnFail("meta.socket_client.connect_failed");
2157
2830
  }
2831
+ static get instance() {
2832
+ if (!this._instance) this._instance = new _SocketController();
2833
+ return this._instance;
2834
+ }
2835
+ resolveTransportDiagnosticsOptions(ctx) {
2836
+ const detailLevel = ctx.detailLevel === "full" ? "full" : "summary";
2837
+ const includeErrorHistory = Boolean(ctx.includeErrorHistory);
2838
+ const requestedLimit = Number(ctx.errorHistoryLimit);
2839
+ const errorHistoryLimit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(200, Math.trunc(requestedLimit))) : 10;
2840
+ return {
2841
+ detailLevel,
2842
+ includeErrorHistory,
2843
+ errorHistoryLimit
2844
+ };
2845
+ }
2846
+ ensureSocketClientDiagnostics(fetchId, serviceName, url) {
2847
+ let state = this.socketClientDiagnostics.get(fetchId);
2848
+ if (!state) {
2849
+ state = {
2850
+ fetchId,
2851
+ serviceName,
2852
+ url,
2853
+ socketId: null,
2854
+ connected: false,
2855
+ handshake: false,
2856
+ reconnectAttempts: 0,
2857
+ connectErrors: 0,
2858
+ reconnectErrors: 0,
2859
+ socketErrors: 0,
2860
+ pendingDelegations: 0,
2861
+ pendingTimers: 0,
2862
+ destroyed: false,
2863
+ lastHandshakeAt: null,
2864
+ lastHandshakeError: null,
2865
+ lastDisconnectAt: null,
2866
+ lastError: null,
2867
+ lastErrorAt: 0,
2868
+ errorHistory: [],
2869
+ updatedAt: Date.now()
2870
+ };
2871
+ this.socketClientDiagnostics.set(fetchId, state);
2872
+ } else {
2873
+ state.serviceName = serviceName;
2874
+ state.url = url;
2875
+ }
2876
+ return state;
2877
+ }
2878
+ getErrorMessage(error) {
2879
+ if (error instanceof Error) {
2880
+ return error.message;
2881
+ }
2882
+ if (typeof error === "string") {
2883
+ return error;
2884
+ }
2885
+ try {
2886
+ return JSON.stringify(error);
2887
+ } catch {
2888
+ return String(error);
2889
+ }
2890
+ }
2891
+ recordSocketClientError(fetchId, serviceName, url, error) {
2892
+ const state = this.ensureSocketClientDiagnostics(fetchId, serviceName, url);
2893
+ const message = this.getErrorMessage(error);
2894
+ const now = Date.now();
2895
+ state.lastError = message;
2896
+ state.lastErrorAt = now;
2897
+ state.updatedAt = now;
2898
+ state.errorHistory.push({
2899
+ at: new Date(now).toISOString(),
2900
+ message
2901
+ });
2902
+ if (state.errorHistory.length > this.diagnosticsErrorHistoryLimit) {
2903
+ state.errorHistory.splice(
2904
+ 0,
2905
+ state.errorHistory.length - this.diagnosticsErrorHistoryLimit
2906
+ );
2907
+ }
2908
+ }
2909
+ collectSocketTransportDiagnostics(ctx) {
2910
+ const { detailLevel, includeErrorHistory, errorHistoryLimit } = this.resolveTransportDiagnosticsOptions(ctx);
2911
+ const serviceName = CadenzaService.serviceRegistry.serviceName ?? "UnknownService";
2912
+ const states = Array.from(this.socketClientDiagnostics.values()).sort(
2913
+ (a, b) => a.fetchId.localeCompare(b.fetchId)
2914
+ );
2915
+ const summary = {
2916
+ detailLevel,
2917
+ totalClients: states.length,
2918
+ connectedClients: states.filter((state) => state.connected).length,
2919
+ activeHandshakes: states.filter((state) => state.handshake).length,
2920
+ pendingDelegations: states.reduce(
2921
+ (acc, state) => acc + state.pendingDelegations,
2922
+ 0
2923
+ ),
2924
+ pendingTimers: states.reduce((acc, state) => acc + state.pendingTimers, 0),
2925
+ reconnectAttempts: states.reduce(
2926
+ (acc, state) => acc + state.reconnectAttempts,
2927
+ 0
2928
+ ),
2929
+ connectErrors: states.reduce((acc, state) => acc + state.connectErrors, 0),
2930
+ reconnectErrors: states.reduce(
2931
+ (acc, state) => acc + state.reconnectErrors,
2932
+ 0
2933
+ ),
2934
+ socketErrors: states.reduce((acc, state) => acc + state.socketErrors, 0),
2935
+ latestError: states.slice().sort((a, b) => b.lastErrorAt - a.lastErrorAt).find((state) => state.lastError)?.lastError ?? null
2936
+ };
2937
+ if (detailLevel === "summary") {
2938
+ return {
2939
+ transportDiagnostics: {
2940
+ [serviceName]: {
2941
+ socketClient: summary
2942
+ }
2943
+ }
2944
+ };
2945
+ }
2946
+ const clients = states.map((state) => {
2947
+ const details = {
2948
+ fetchId: state.fetchId,
2949
+ serviceName: state.serviceName,
2950
+ url: state.url,
2951
+ socketId: state.socketId,
2952
+ connected: state.connected,
2953
+ handshake: state.handshake,
2954
+ reconnectAttempts: state.reconnectAttempts,
2955
+ connectErrors: state.connectErrors,
2956
+ reconnectErrors: state.reconnectErrors,
2957
+ socketErrors: state.socketErrors,
2958
+ pendingDelegations: state.pendingDelegations,
2959
+ pendingTimers: state.pendingTimers,
2960
+ destroyed: state.destroyed,
2961
+ lastHandshakeAt: state.lastHandshakeAt,
2962
+ lastHandshakeError: state.lastHandshakeError,
2963
+ lastDisconnectAt: state.lastDisconnectAt,
2964
+ latestError: state.lastError
2965
+ };
2966
+ if (includeErrorHistory) {
2967
+ details.errorHistory = state.errorHistory.slice(-errorHistoryLimit);
2968
+ }
2969
+ return details;
2970
+ });
2971
+ return {
2972
+ transportDiagnostics: {
2973
+ [serviceName]: {
2974
+ socketClient: {
2975
+ ...summary,
2976
+ clients
2977
+ }
2978
+ }
2979
+ }
2980
+ };
2981
+ }
2158
2982
  };
2159
2983
 
2160
2984
  // src/utils/tools.ts
@@ -2259,12 +3083,6 @@ var GraphMetadataController = class _GraphMetadataController {
2259
3083
  data: {
2260
3084
  ...ctx.data,
2261
3085
  serviceName: CadenzaService.serviceRegistry.serviceName
2262
- // input_context_schema_id: ctx.data.inputContextSchema ? { // TODO
2263
- //
2264
- // } : null,
2265
- // output_context_schema_id: ctx.data.outputContextSchema ? {
2266
- //
2267
- // } : null,
2268
3086
  }
2269
3087
  };
2270
3088
  }).doOn("meta.task.created").emits("global.meta.graph_metadata.task_created");
@@ -2472,9 +3290,11 @@ var GraphMetadataController = class _GraphMetadataController {
2472
3290
  { concurrency: 100, isSubMeta: true }
2473
3291
  ).doOn("meta.node.mapped", "meta.node.detected_previous_task_execution").emits("global.meta.graph_metadata.relationship_executed");
2474
3292
  CadenzaService.createMetaTask("Handle Intent Creation", (ctx) => {
3293
+ const intentName = ctx.data?.name;
2475
3294
  return {
2476
3295
  data: {
2477
- ...ctx.data
3296
+ ...ctx.data,
3297
+ isMeta: intentName ? isMetaIntentName(intentName) : false
2478
3298
  }
2479
3299
  };
2480
3300
  }).doOn("meta.inquiry_broker.added").emits("global.meta.graph_metadata.intent_created");
@@ -2506,6 +3326,52 @@ var SCHEMA_TYPES = [
2506
3326
  // src/database/DatabaseController.ts
2507
3327
  import { Pool } from "pg";
2508
3328
  import { camelCase, snakeCase } from "lodash-es";
3329
+ function resolveTableQueryIntents(serviceName, tableName, table, defaultInputSchema) {
3330
+ const resolvedServiceName = serviceName ?? "unknown-service";
3331
+ const defaultIntentName = `query-${resolvedServiceName}-${tableName}`;
3332
+ const defaultDescription = `Perform a query operation on the ${tableName} table`;
3333
+ const intents = [
3334
+ {
3335
+ name: defaultIntentName,
3336
+ description: defaultDescription,
3337
+ input: defaultInputSchema
3338
+ }
3339
+ ];
3340
+ const warnings = [];
3341
+ const names = /* @__PURE__ */ new Set([defaultIntentName]);
3342
+ for (const customIntent of table.customIntents?.query ?? []) {
3343
+ const name = typeof customIntent === "string" ? customIntent.trim() : customIntent.intent?.trim();
3344
+ if (!name) {
3345
+ warnings.push(`Skipped empty custom query intent for table '${tableName}'.`);
3346
+ continue;
3347
+ }
3348
+ if (name.length > 100) {
3349
+ warnings.push(
3350
+ `Skipped custom query intent '${name}' for table '${tableName}': name must be <= 100 characters.`
3351
+ );
3352
+ continue;
3353
+ }
3354
+ if (name.includes(" ") || name.includes(".") || name.includes("\\")) {
3355
+ warnings.push(
3356
+ `Skipped custom query intent '${name}' for table '${tableName}': name cannot contain spaces, dots or backslashes.`
3357
+ );
3358
+ continue;
3359
+ }
3360
+ if (names.has(name)) {
3361
+ warnings.push(
3362
+ `Skipped duplicate custom query intent '${name}' for table '${tableName}'.`
3363
+ );
3364
+ continue;
3365
+ }
3366
+ names.add(name);
3367
+ intents.push({
3368
+ name,
3369
+ description: typeof customIntent === "string" ? `Perform a query operation on the ${tableName} table` : customIntent.description ?? defaultDescription,
3370
+ input: typeof customIntent === "string" ? defaultInputSchema : customIntent.input ?? defaultInputSchema
3371
+ });
3372
+ }
3373
+ return { intents, warnings };
3374
+ }
2509
3375
  var DatabaseController = class _DatabaseController {
2510
3376
  /**
2511
3377
  * Constructor for initializing the `DatabaseService` class.
@@ -2647,6 +3513,20 @@ var DatabaseController = class _DatabaseController {
2647
3513
  }
2648
3514
  }
2649
3515
  }
3516
+ if (table.customIntents?.query) {
3517
+ if (!Array.isArray(table.customIntents.query)) {
3518
+ throw new Error(
3519
+ `Invalid customIntents.query for ${tableName}: expected array`
3520
+ );
3521
+ }
3522
+ for (const customIntent of table.customIntents.query) {
3523
+ if (typeof customIntent !== "string" && (typeof customIntent !== "object" || !customIntent || typeof customIntent.intent !== "string")) {
3524
+ throw new Error(
3525
+ `Invalid custom query intent on ${tableName}: expected string or object with intent`
3526
+ );
3527
+ }
3528
+ }
3529
+ }
2650
3530
  }
2651
3531
  }
2652
3532
  console.log("SCHEMA VALIDATED");
@@ -3573,9 +4453,9 @@ var DatabaseController = class _DatabaseController {
3573
4453
  createDatabaseTask(op, tableName, table, queryFunction, options) {
3574
4454
  const opAction = op === "query" ? "queried" : op === "insert" ? "inserted" : op === "update" ? "updated" : op === "delete" ? "deleted" : "";
3575
4455
  const defaultSignal = `global.${options.isMeta ? "meta." : ""}${tableName}.${opAction}`;
3576
- const tableNameFormatted = tableName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
3577
- const taskName = `db${op.charAt(0).toUpperCase() + op.slice(1)}${tableNameFormatted}`;
3578
- CadenzaService.createThrottledTask(
4456
+ const taskName = `${op.charAt(0).toUpperCase() + op.slice(1)} ${tableName}`;
4457
+ const schema = this.getInputSchema(op, tableName, table);
4458
+ const task = CadenzaService.createThrottledTask(
3579
4459
  taskName,
3580
4460
  async (context, emit) => {
3581
4461
  for (const action of Object.keys(table.customSignals?.triggers ?? {})) {
@@ -3670,17 +4550,8 @@ var DatabaseController = class _DatabaseController {
3670
4550
  {
3671
4551
  isMeta: options.isMeta,
3672
4552
  isSubMeta: options.isMeta,
3673
- validateInputContext: false,
3674
- // TODO
3675
- inputSchema: {
3676
- // TODO
3677
- type: "object",
3678
- properties: {
3679
- filter: {
3680
- type: "object"
3681
- }
3682
- }
3683
- }
4553
+ validateInputContext: options.securityProfile !== "low",
4554
+ inputSchema: schema
3684
4555
  }
3685
4556
  ).doOn(
3686
4557
  ...table.customSignals?.triggers?.[op]?.map((signal) => {
@@ -3691,8 +4562,397 @@ var DatabaseController = class _DatabaseController {
3691
4562
  return typeof signal === "string" ? signal : signal.signal;
3692
4563
  }) ?? []
3693
4564
  );
4565
+ if (op === "query") {
4566
+ const { intents, warnings } = resolveTableQueryIntents(
4567
+ CadenzaService.serviceRegistry?.serviceName,
4568
+ tableName,
4569
+ table,
4570
+ schema
4571
+ );
4572
+ for (const warning of warnings) {
4573
+ CadenzaService.log(
4574
+ "Skipped custom query intent registration.",
4575
+ {
4576
+ tableName,
4577
+ warning
4578
+ },
4579
+ "warning"
4580
+ );
4581
+ }
4582
+ for (const intent of intents) {
4583
+ CadenzaService.defineIntent({
4584
+ name: intent.name,
4585
+ description: intent.description,
4586
+ input: intent.input
4587
+ });
4588
+ }
4589
+ task.respondsTo(...intents.map((intent) => intent.name));
4590
+ }
4591
+ }
4592
+ getInputSchema(op, tableName, table) {
4593
+ const inputSchema = {
4594
+ type: "object",
4595
+ properties: {
4596
+ queryData: {
4597
+ type: "object",
4598
+ properties: {},
4599
+ strict: true
4600
+ }
4601
+ },
4602
+ strict: true
4603
+ };
4604
+ if (!inputSchema.properties) {
4605
+ return inputSchema;
4606
+ }
4607
+ inputSchema.properties.transaction = getTransactionSchema();
4608
+ inputSchema.properties.queryData.properties.transaction = inputSchema.properties.transaction;
4609
+ switch (op) {
4610
+ case "insert":
4611
+ inputSchema.properties.data = getInsertDataSchemaFromTable(
4612
+ table,
4613
+ tableName
4614
+ );
4615
+ inputSchema.properties.queryData.properties.data = inputSchema.properties.data;
4616
+ inputSchema.properties.batch = getQueryBatchSchemaFromTable();
4617
+ inputSchema.properties.queryData.properties.batch = inputSchema.properties.batch;
4618
+ inputSchema.properties.onConflict = getQueryOnConflictSchemaFromTable(
4619
+ table,
4620
+ tableName
4621
+ );
4622
+ inputSchema.properties.queryData.properties.onConflict = inputSchema.properties.onConflict;
4623
+ break;
4624
+ case "query":
4625
+ inputSchema.properties.filter = getQueryFilterSchemaFromTable(
4626
+ table,
4627
+ tableName
4628
+ );
4629
+ inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
4630
+ inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
4631
+ table,
4632
+ tableName
4633
+ );
4634
+ inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
4635
+ inputSchema.properties.joins = getQueryJoinsSchemaFromTable(
4636
+ table,
4637
+ tableName
4638
+ );
4639
+ inputSchema.properties.queryData.properties.joins = inputSchema.properties.joins;
4640
+ inputSchema.properties.sort = getQuerySortSchemaFromTable(
4641
+ table,
4642
+ tableName
4643
+ );
4644
+ inputSchema.properties.queryData.properties.sort = inputSchema.properties.sort;
4645
+ inputSchema.properties.limit = getQueryLimitSchemaFromTable();
4646
+ inputSchema.properties.queryData.properties.limit = inputSchema.properties.limit;
4647
+ inputSchema.properties.offset = getQueryOffsetSchemaFromTable();
4648
+ inputSchema.properties.queryData.properties.offset = inputSchema.properties.offset;
4649
+ break;
4650
+ case "update":
4651
+ inputSchema.properties.filter = getQueryFilterSchemaFromTable(
4652
+ table,
4653
+ tableName
4654
+ );
4655
+ inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
4656
+ inputSchema.properties.fields = getQueryFieldsSchemaFromTable(
4657
+ table,
4658
+ tableName
4659
+ );
4660
+ inputSchema.properties.queryData.properties.fields = inputSchema.properties.fields;
4661
+ break;
4662
+ case "delete":
4663
+ inputSchema.properties.filter = getQueryFilterSchemaFromTable(
4664
+ table,
4665
+ tableName
4666
+ );
4667
+ inputSchema.properties.queryData.properties.filter = inputSchema.properties.filter;
4668
+ break;
4669
+ }
4670
+ return inputSchema;
3694
4671
  }
3695
4672
  };
4673
+ function getInsertDataSchemaFromTable(table, tableName) {
4674
+ const dataSchema = {
4675
+ type: "object",
4676
+ properties: {
4677
+ ...Object.fromEntries(
4678
+ Object.entries(table.fields).map((field) => {
4679
+ return [
4680
+ field[0],
4681
+ {
4682
+ value: {
4683
+ type: tableFieldTypeToSchemaType(field[1].type),
4684
+ description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
4685
+ },
4686
+ effect: {
4687
+ type: "string",
4688
+ constraints: {
4689
+ oneOf: ["increment", "decrement", "set"]
4690
+ }
4691
+ },
4692
+ subOperation: {
4693
+ type: "object",
4694
+ properties: {
4695
+ subOperation: {
4696
+ type: "string",
4697
+ enum: ["insert", "query"]
4698
+ },
4699
+ table: {
4700
+ type: "string"
4701
+ },
4702
+ data: {
4703
+ single: {
4704
+ type: "object"
4705
+ },
4706
+ batch: {
4707
+ type: "array",
4708
+ items: {
4709
+ type: "object"
4710
+ }
4711
+ }
4712
+ },
4713
+ filter: {
4714
+ type: "object"
4715
+ },
4716
+ fields: {
4717
+ type: "array",
4718
+ items: {
4719
+ type: "string"
4720
+ }
4721
+ },
4722
+ return: {
4723
+ type: "string"
4724
+ }
4725
+ },
4726
+ required: ["subOperation", "table"]
4727
+ }
4728
+ }
4729
+ ];
4730
+ })
4731
+ )
4732
+ },
4733
+ required: Object.entries(table.fields).filter((field) => field[1].required || field[1].primary).map((field) => field[0]),
4734
+ strict: true
4735
+ };
4736
+ return {
4737
+ single: dataSchema,
4738
+ batch: {
4739
+ type: "array",
4740
+ items: dataSchema
4741
+ }
4742
+ };
4743
+ }
4744
+ function getQueryFilterSchemaFromTable(table, tableName) {
4745
+ return {
4746
+ type: "object",
4747
+ properties: {
4748
+ ...Object.fromEntries(
4749
+ Object.entries(table.fields).map((field) => {
4750
+ return [
4751
+ field[0],
4752
+ {
4753
+ value: {
4754
+ type: tableFieldTypeToSchemaType(field[1].type),
4755
+ description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
4756
+ },
4757
+ in: {
4758
+ type: "array",
4759
+ items: {
4760
+ type: tableFieldTypeToSchemaType(field[1].type)
4761
+ }
4762
+ }
4763
+ }
4764
+ ];
4765
+ })
4766
+ )
4767
+ },
4768
+ strict: true,
4769
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4770
+ };
4771
+ }
4772
+ function getQueryFieldsSchemaFromTable(table, tableName) {
4773
+ return {
4774
+ type: "array",
4775
+ items: {
4776
+ type: "string",
4777
+ constraints: {
4778
+ oneOf: Object.keys(table.fields)
4779
+ }
4780
+ },
4781
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4782
+ };
4783
+ }
4784
+ function getQueryJoinsSchemaFromTable(table, tableName) {
4785
+ return {
4786
+ type: "object",
4787
+ properties: {
4788
+ ...Object.fromEntries(
4789
+ Object.entries(table.fields).map((field) => {
4790
+ return [
4791
+ field[0],
4792
+ {
4793
+ type: "object",
4794
+ properties: {
4795
+ on: {
4796
+ type: "string"
4797
+ },
4798
+ fields: {
4799
+ type: "array",
4800
+ items: {
4801
+ type: "string"
4802
+ }
4803
+ },
4804
+ filter: {
4805
+ type: "object"
4806
+ },
4807
+ returnAs: {
4808
+ type: "string",
4809
+ constraints: {
4810
+ oneOf: ["array", "object"]
4811
+ }
4812
+ },
4813
+ alias: {
4814
+ type: "string"
4815
+ },
4816
+ joins: {
4817
+ type: "object"
4818
+ }
4819
+ },
4820
+ required: ["on", "fields"],
4821
+ strict: true
4822
+ }
4823
+ ];
4824
+ })
4825
+ )
4826
+ },
4827
+ strict: true,
4828
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4829
+ };
4830
+ }
4831
+ function getQuerySortSchemaFromTable(table, tableName) {
4832
+ return {
4833
+ type: "object",
4834
+ properties: {
4835
+ ...Object.fromEntries(
4836
+ Object.entries(table.fields).map((field) => {
4837
+ return [
4838
+ field[0],
4839
+ {
4840
+ type: "string",
4841
+ constraints: {
4842
+ oneOf: ["asc", "desc"]
4843
+ }
4844
+ }
4845
+ ];
4846
+ })
4847
+ )
4848
+ },
4849
+ strict: true,
4850
+ description: `Inferred from table '${tableName}' on database service ${CadenzaService.serviceRegistry?.serviceName ?? "unknown-service"}.`
4851
+ };
4852
+ }
4853
+ function getQueryLimitSchemaFromTable() {
4854
+ return {
4855
+ type: "number",
4856
+ constraints: {
4857
+ min: 1
4858
+ },
4859
+ description: "Limit for query results"
4860
+ };
4861
+ }
4862
+ function getQueryOffsetSchemaFromTable() {
4863
+ return {
4864
+ type: "number",
4865
+ constraints: {
4866
+ min: 0
4867
+ },
4868
+ description: "Offset for query results"
4869
+ };
4870
+ }
4871
+ function getTransactionSchema() {
4872
+ return {
4873
+ type: "boolean",
4874
+ description: "Whether to run the query in a transaction"
4875
+ };
4876
+ }
4877
+ function getQueryBatchSchemaFromTable() {
4878
+ return {
4879
+ type: "boolean",
4880
+ description: "Whether to run the query in batch mode"
4881
+ };
4882
+ }
4883
+ function getQueryOnConflictSchemaFromTable(table, tableName) {
4884
+ return {
4885
+ type: "object",
4886
+ properties: {
4887
+ target: {
4888
+ type: "array",
4889
+ items: {
4890
+ type: "string",
4891
+ constraints: {
4892
+ oneOf: Object.keys(table.fields)
4893
+ }
4894
+ }
4895
+ },
4896
+ action: {
4897
+ type: "object",
4898
+ properties: {
4899
+ do: {
4900
+ type: "string",
4901
+ constraints: {
4902
+ oneOf: ["nothing", "update"]
4903
+ }
4904
+ },
4905
+ set: {
4906
+ type: "object",
4907
+ properties: {
4908
+ ...Object.fromEntries(
4909
+ Object.entries(table.fields).map((field) => {
4910
+ return [
4911
+ field[0],
4912
+ {
4913
+ type: tableFieldTypeToSchemaType(field[1].type),
4914
+ description: `Inferred from field '${field[0]}' of type [${field[1].type}] on table ${tableName}.`
4915
+ }
4916
+ ];
4917
+ })
4918
+ )
4919
+ }
4920
+ },
4921
+ where: {
4922
+ type: "string"
4923
+ }
4924
+ },
4925
+ required: ["do"]
4926
+ }
4927
+ },
4928
+ required: ["target", "action"],
4929
+ strict: true
4930
+ };
4931
+ }
4932
+ function tableFieldTypeToSchemaType(type) {
4933
+ switch (type) {
4934
+ case "varchar":
4935
+ case "text":
4936
+ case "jsonb":
4937
+ case "uuid":
4938
+ case "date":
4939
+ case "geo_point":
4940
+ case "bytea":
4941
+ return "string";
4942
+ case "int":
4943
+ case "bigint":
4944
+ case "decimal":
4945
+ case "timestamp":
4946
+ return "number";
4947
+ case "boolean":
4948
+ return "boolean";
4949
+ case "array":
4950
+ return "array";
4951
+ case "object":
4952
+ return "object";
4953
+ }
4954
+ return "any";
4955
+ }
3696
4956
 
3697
4957
  // src/Cadenza.ts
3698
4958
  import { v4 as uuid3 } from "uuid";
@@ -4010,6 +5270,75 @@ var GraphSyncController = class _GraphSyncController {
4010
5270
  { concurrency: 30 }
4011
5271
  ) : CadenzaService.get("dbInsertSignalToTaskMap"))?.then(registerSignalTask)
4012
5272
  );
5273
+ const registerIntentTask = CadenzaService.createMetaTask(
5274
+ "Record intent registration",
5275
+ (ctx) => {
5276
+ if (!ctx.__syncing) {
5277
+ return;
5278
+ }
5279
+ CadenzaService.debounce("meta.sync_controller.synced_resource", {
5280
+ delayMs: 3e3
5281
+ });
5282
+ const task = CadenzaService.get(ctx.__taskName);
5283
+ task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
5284
+ task.__registeredIntents.add(ctx.__intent);
5285
+ }
5286
+ );
5287
+ this.registerIntentToTaskMapTask = CadenzaService.createMetaTask(
5288
+ "Split intents of task",
5289
+ function* (ctx) {
5290
+ const task = ctx.task;
5291
+ if (task.hidden || !task.register) return;
5292
+ task.__registeredIntents = task.__registeredIntents ?? /* @__PURE__ */ new Set();
5293
+ task.__invalidMetaIntentWarnings = task.__invalidMetaIntentWarnings ?? /* @__PURE__ */ new Set();
5294
+ for (const intent of task.handlesIntents) {
5295
+ if (task.__registeredIntents.has(intent)) continue;
5296
+ if (isMetaIntentName(intent) && !task.isMeta) {
5297
+ if (!task.__invalidMetaIntentWarnings.has(intent)) {
5298
+ task.__invalidMetaIntentWarnings.add(intent);
5299
+ CadenzaService.log(
5300
+ "Skipping intent-to-task registration: non-meta task cannot handle meta intent.",
5301
+ {
5302
+ intent,
5303
+ taskName: task.name,
5304
+ taskVersion: task.version
5305
+ },
5306
+ "warning"
5307
+ );
5308
+ }
5309
+ continue;
5310
+ }
5311
+ yield {
5312
+ data: {
5313
+ intentName: intent,
5314
+ taskName: task.name,
5315
+ taskVersion: task.version,
5316
+ serviceName: CadenzaService.serviceRegistry.serviceName
5317
+ },
5318
+ __taskName: task.name,
5319
+ __intent: intent
5320
+ };
5321
+ }
5322
+ }
5323
+ ).then(
5324
+ (this.isCadenzaDBReady ? CadenzaService.createCadenzaDBInsertTask(
5325
+ "intent_to_task_map",
5326
+ {
5327
+ onConflict: {
5328
+ target: [
5329
+ "intent_name",
5330
+ "task_name",
5331
+ "task_version",
5332
+ "service_name"
5333
+ ],
5334
+ action: {
5335
+ do: "nothing"
5336
+ }
5337
+ }
5338
+ },
5339
+ { concurrency: 30 }
5340
+ ) : CadenzaService.get("dbInsertIntentToTaskMap"))?.then(registerIntentTask)
5341
+ );
4013
5342
  this.registerTaskMapTask = CadenzaService.createMetaTask(
4014
5343
  "Register task map to DB",
4015
5344
  function* (ctx) {
@@ -4132,6 +5461,7 @@ var GraphSyncController = class _GraphSyncController {
4132
5461
  CadenzaService.registry.doForEachTask.clone().doOn("meta.sync_controller.synced_tasks").then(
4133
5462
  this.registerTaskMapTask,
4134
5463
  this.registerSignalToTaskMapTask,
5464
+ this.registerIntentToTaskMapTask,
4135
5465
  this.registerDeputyRelationshipTask
4136
5466
  );
4137
5467
  CadenzaService.registry.getAllRoutines.clone().doOn("meta.sync_controller.synced_routines").then(this.splitTasksInRoutines);
@@ -4273,11 +5603,192 @@ var CadenzaService = class {
4273
5603
  Cadenza.interval(signal, context, intervalMs, leading, startDateTime);
4274
5604
  }
4275
5605
  static defineIntent(intent) {
4276
- this.inquiryBroker?.intents.set(intent.name, intent);
5606
+ this.inquiryBroker?.addIntent(intent);
4277
5607
  return intent;
4278
5608
  }
4279
- static async inquire(inquiry, context, options) {
4280
- return this.inquiryBroker?.inquire(inquiry, context, options);
5609
+ static getInquiryResponderDescriptor(task) {
5610
+ return this.serviceRegistry.getInquiryResponderDescriptor(task);
5611
+ }
5612
+ static compareInquiryResponders(left, right) {
5613
+ return compareResponderDescriptors(left.descriptor, right.descriptor);
5614
+ }
5615
+ static buildInquirySummary(inquiry, startedAt, statuses, totalResponders) {
5616
+ const counts = summarizeResponderStatuses(statuses);
5617
+ const isMetaInquiry = isMetaIntentName(inquiry);
5618
+ const eligibleResponders = statuses.length;
5619
+ return {
5620
+ inquiry,
5621
+ isMetaInquiry,
5622
+ totalResponders,
5623
+ eligibleResponders,
5624
+ filteredOutResponders: Math.max(0, totalResponders - eligibleResponders),
5625
+ responded: counts.responded,
5626
+ failed: counts.failed,
5627
+ timedOut: counts.timedOut,
5628
+ pending: counts.pending,
5629
+ durationMs: Date.now() - startedAt,
5630
+ responders: statuses
5631
+ };
5632
+ }
5633
+ static async inquire(inquiry, context, options = {}) {
5634
+ this.bootstrap();
5635
+ const observer = this.inquiryBroker?.inquiryObservers.get(inquiry);
5636
+ const allResponders = observer ? Array.from(observer.tasks).map((task) => ({
5637
+ task,
5638
+ descriptor: this.getInquiryResponderDescriptor(task)
5639
+ })) : [];
5640
+ const isMetaInquiry = isMetaIntentName(inquiry);
5641
+ const responders = allResponders.filter(({ task, descriptor }) => {
5642
+ const shouldExecute = shouldExecuteInquiryResponder(inquiry, task.isMeta);
5643
+ if (shouldExecute) {
5644
+ return true;
5645
+ }
5646
+ const warningKey = `${inquiry}|${descriptor.serviceName}|${descriptor.taskName}|${descriptor.taskVersion}|${descriptor.localTaskName}`;
5647
+ if (!this.warnedInvalidMetaIntentResponderKeys.has(warningKey)) {
5648
+ this.warnedInvalidMetaIntentResponderKeys.add(warningKey);
5649
+ this.log(
5650
+ "Skipping non-meta task for meta intent inquiry.",
5651
+ {
5652
+ inquiry,
5653
+ responder: descriptor
5654
+ },
5655
+ "warning",
5656
+ descriptor.serviceName
5657
+ );
5658
+ }
5659
+ return false;
5660
+ });
5661
+ if (responders.length === 0) {
5662
+ return {
5663
+ __inquiryMeta: {
5664
+ inquiry,
5665
+ isMetaInquiry,
5666
+ totalResponders: allResponders.length,
5667
+ eligibleResponders: 0,
5668
+ filteredOutResponders: allResponders.length,
5669
+ responded: 0,
5670
+ failed: 0,
5671
+ timedOut: 0,
5672
+ pending: 0,
5673
+ durationMs: 0,
5674
+ responders: []
5675
+ }
5676
+ };
5677
+ }
5678
+ responders.sort(this.compareInquiryResponders.bind(this));
5679
+ const overallTimeoutMs = options.overallTimeoutMs ?? options.timeout ?? 0;
5680
+ const requireComplete = options.requireComplete ?? false;
5681
+ const perResponderTimeoutMs = options.perResponderTimeoutMs;
5682
+ const startedAt = Date.now();
5683
+ const statuses = [];
5684
+ const statusByTask = /* @__PURE__ */ new Map();
5685
+ for (const responder of responders) {
5686
+ const status = {
5687
+ ...responder.descriptor,
5688
+ status: "timed_out",
5689
+ durationMs: 0
5690
+ };
5691
+ statuses.push(status);
5692
+ statusByTask.set(responder.task, status);
5693
+ }
5694
+ const resultsByTask = /* @__PURE__ */ new Map();
5695
+ const resolverTasks = [];
5696
+ const pending = new Set(responders.map((r) => r.task));
5697
+ const startTimeByTask = /* @__PURE__ */ new Map();
5698
+ this.emit("meta.inquiry_broker.inquire", { inquiry, context });
5699
+ return new Promise((resolve, reject) => {
5700
+ let finalized = false;
5701
+ let timeoutId;
5702
+ const finalize = (timedOut) => {
5703
+ if (finalized) return;
5704
+ finalized = true;
5705
+ if (timeoutId) {
5706
+ clearTimeout(timeoutId);
5707
+ timeoutId = void 0;
5708
+ }
5709
+ for (const resolverTask of resolverTasks) {
5710
+ resolverTask.destroy();
5711
+ }
5712
+ if (timedOut && pending.size > 0) {
5713
+ for (const task of pending) {
5714
+ const status = statusByTask.get(task);
5715
+ if (!status) continue;
5716
+ status.status = "timed_out";
5717
+ status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
5718
+ }
5719
+ }
5720
+ const fulfilledContexts = responders.filter((responder) => resultsByTask.has(responder.task)).map((responder) => resultsByTask.get(responder.task));
5721
+ const mergedContext = mergeInquiryContexts(fulfilledContexts);
5722
+ const inquiryMeta = this.buildInquirySummary(
5723
+ inquiry,
5724
+ startedAt,
5725
+ statuses,
5726
+ allResponders.length
5727
+ );
5728
+ const responseContext = {
5729
+ ...mergedContext,
5730
+ __inquiryMeta: inquiryMeta
5731
+ };
5732
+ if (requireComplete && (timedOut || inquiryMeta.failed > 0 || inquiryMeta.timedOut > 0 || inquiryMeta.pending > 0)) {
5733
+ reject({
5734
+ ...responseContext,
5735
+ __error: `Inquiry '${inquiry}' did not complete successfully`,
5736
+ errored: true
5737
+ });
5738
+ return;
5739
+ }
5740
+ resolve(responseContext);
5741
+ };
5742
+ if (overallTimeoutMs > 0) {
5743
+ timeoutId = setTimeout(() => finalize(true), overallTimeoutMs);
5744
+ }
5745
+ for (const responder of responders) {
5746
+ const { task, descriptor } = responder;
5747
+ const inquiryId = uuid3();
5748
+ startTimeByTask.set(task, Date.now());
5749
+ const resolverTask = this.createEphemeralMetaTask(
5750
+ `Resolve inquiry ${inquiry} for ${descriptor.localTaskName}`,
5751
+ (resultCtx) => {
5752
+ if (finalized) {
5753
+ return;
5754
+ }
5755
+ pending.delete(task);
5756
+ const status = statusByTask.get(task);
5757
+ if (status) {
5758
+ status.durationMs = Date.now() - (startTimeByTask.get(task) ?? startedAt);
5759
+ if (resultCtx?.errored || resultCtx?.failed) {
5760
+ status.status = "failed";
5761
+ status.error = String(
5762
+ resultCtx?.__error ?? resultCtx?.error ?? "Inquiry responder failed"
5763
+ );
5764
+ } else {
5765
+ status.status = "fulfilled";
5766
+ resultsByTask.set(task, resultCtx);
5767
+ }
5768
+ }
5769
+ if (pending.size === 0) {
5770
+ finalize(false);
5771
+ }
5772
+ },
5773
+ "Resolves distributed inquiry responder result",
5774
+ { register: false }
5775
+ ).doOn(`meta.node.graph_completed:${inquiryId}`);
5776
+ resolverTasks.push(resolverTask);
5777
+ const executionContext = {
5778
+ ...context,
5779
+ __routineExecId: inquiryId,
5780
+ __isInquiry: true
5781
+ };
5782
+ if (perResponderTimeoutMs !== void 0) {
5783
+ executionContext.__timeout = perResponderTimeoutMs;
5784
+ }
5785
+ if (task.isMeta) {
5786
+ this.metaRunner?.run(task, executionContext);
5787
+ } else {
5788
+ this.runner?.run(task, executionContext);
5789
+ }
5790
+ }
5791
+ });
4281
5792
  }
4282
5793
  /**
4283
5794
  * Executes the given task or graph routine within the provided context using the configured runner.
@@ -4566,10 +6077,9 @@ var CadenzaService = class {
4566
6077
  this.bootstrap();
4567
6078
  this.validateName(tableName);
4568
6079
  this.validateName(operation);
4569
- const tableNameFormatted = tableName.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
4570
6080
  const name = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName} in ${databaseServiceName ?? "default database service"}`;
4571
6081
  const description = `Executes a ${operation} on table ${tableName} in ${databaseServiceName ?? "default database service"}`;
4572
- const taskName = `db${operation.charAt(0).toUpperCase() + operation.slice(1)}${tableNameFormatted}`;
6082
+ const taskName = `${operation.charAt(0).toUpperCase() + operation.slice(1)} ${tableName}`;
4573
6083
  options = {
4574
6084
  concurrency: 100,
4575
6085
  timeout: 0,
@@ -4831,7 +6341,7 @@ var CadenzaService = class {
4831
6341
  * This method is not supported in a browser environment and will log a warning if called in such an environment.
4832
6342
  *
4833
6343
  * @param {string} name - The name of the database service to be created.
4834
- * @param {SchemaDefinition} schema - The schema definition for the database service.
6344
+ * @param {DatabaseSchemaDefinition} schema - The schema definition for the database service.
4835
6345
  * @param {string} [description=""] - An optional description of the database service.
4836
6346
  * @param {ServerOptions & DatabaseOptions} [options={}] - Optional configuration settings for the database and server.
4837
6347
  * @return {void} This method does not return a value.
@@ -4895,7 +6405,7 @@ var CadenzaService = class {
4895
6405
  * Creates a meta database service with the specified configuration.
4896
6406
  *
4897
6407
  * @param {string} name - The name of the database service to be created.
4898
- * @param {SchemaDefinition} schema - The schema definition for the database.
6408
+ * @param {DatabaseSchemaDefinition} schema - The schema definition for the database.
4899
6409
  * @param {string} [description=""] - An optional description of the database service.
4900
6410
  * @param {ServerOptions & DatabaseOptions} [options={}] - Optional server and database configuration options. The `isMeta` flag will be automatically set to true.
4901
6411
  * @return {void} - This method does not return a value.
@@ -5315,6 +6825,7 @@ var CadenzaService = class {
5315
6825
  };
5316
6826
  CadenzaService.isBootstrapped = false;
5317
6827
  CadenzaService.serviceCreated = false;
6828
+ CadenzaService.warnedInvalidMetaIntentResponderKeys = /* @__PURE__ */ new Set();
5318
6829
 
5319
6830
  // src/index.ts
5320
6831
  import {