@x12i/memorix-service 3.0.5 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +8 -1
  2. package/dist/ai-services/client.d.ts +31 -0
  3. package/dist/ai-services/client.d.ts.map +1 -0
  4. package/dist/ai-services/client.js +104 -0
  5. package/dist/ai-services/client.js.map +1 -0
  6. package/dist/ai-services/config.d.ts +17 -0
  7. package/dist/ai-services/config.d.ts.map +1 -0
  8. package/dist/ai-services/config.js +44 -0
  9. package/dist/ai-services/config.js.map +1 -0
  10. package/dist/ai-services/normalize-output.d.ts +23 -0
  11. package/dist/ai-services/normalize-output.d.ts.map +1 -0
  12. package/dist/ai-services/normalize-output.js +148 -0
  13. package/dist/ai-services/normalize-output.js.map +1 -0
  14. package/dist/ai-services/skill-execution.d.ts +23 -0
  15. package/dist/ai-services/skill-execution.d.ts.map +1 -0
  16. package/dist/ai-services/skill-execution.js +54 -0
  17. package/dist/ai-services/skill-execution.js.map +1 -0
  18. package/dist/ai-services/types.d.ts +62 -0
  19. package/dist/ai-services/types.d.ts.map +1 -0
  20. package/dist/ai-services/types.js +3 -0
  21. package/dist/ai-services/types.js.map +1 -0
  22. package/dist/app.d.ts.map +1 -1
  23. package/dist/app.js +37 -6
  24. package/dist/app.js.map +1 -1
  25. package/dist/assist/ask.d.ts +35 -0
  26. package/dist/assist/ask.d.ts.map +1 -0
  27. package/dist/assist/ask.js +346 -0
  28. package/dist/assist/ask.js.map +1 -0
  29. package/dist/assist/facts.d.ts +5 -0
  30. package/dist/assist/facts.d.ts.map +1 -0
  31. package/dist/assist/facts.js +237 -0
  32. package/dist/assist/facts.js.map +1 -0
  33. package/dist/assist/merge-ask-proposal.d.ts +9 -0
  34. package/dist/assist/merge-ask-proposal.d.ts.map +1 -0
  35. package/dist/assist/merge-ask-proposal.js +57 -0
  36. package/dist/assist/merge-ask-proposal.js.map +1 -0
  37. package/dist/assist/types.d.ts +60 -0
  38. package/dist/assist/types.d.ts.map +1 -0
  39. package/dist/assist/types.js +57 -0
  40. package/dist/assist/types.js.map +1 -0
  41. package/dist/assist/validate.d.ts +4 -0
  42. package/dist/assist/validate.d.ts.map +1 -0
  43. package/dist/assist/validate.js +319 -0
  44. package/dist/assist/validate.js.map +1 -0
  45. package/dist/cli.js +29 -6
  46. package/dist/cli.js.map +1 -1
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/isolation.d.ts +1 -1
  52. package/dist/isolation.d.ts.map +1 -1
  53. package/dist/journey/collect.js +2 -2
  54. package/dist/journey/collect.js.map +1 -1
  55. package/dist/journey/coverage.js +3 -3
  56. package/dist/journey/coverage.js.map +1 -1
  57. package/dist/known-packs.d.ts +1 -1
  58. package/dist/known-packs.d.ts.map +1 -1
  59. package/dist/known-packs.js +1 -1
  60. package/dist/known-packs.js.map +1 -1
  61. package/dist/live.d.ts +2 -1
  62. package/dist/live.d.ts.map +1 -1
  63. package/dist/live.js +239 -2
  64. package/dist/live.js.map +1 -1
  65. package/dist/magit/magit-pack-service.d.ts +167 -0
  66. package/dist/magit/magit-pack-service.d.ts.map +1 -0
  67. package/dist/magit/magit-pack-service.js +699 -0
  68. package/dist/magit/magit-pack-service.js.map +1 -0
  69. package/dist/magit/pack-to-patchset.d.ts +45 -0
  70. package/dist/magit/pack-to-patchset.d.ts.map +1 -0
  71. package/dist/magit/pack-to-patchset.js +196 -0
  72. package/dist/magit/pack-to-patchset.js.map +1 -0
  73. package/dist/magit/package-catalog-bridge.d.ts +58 -0
  74. package/dist/magit/package-catalog-bridge.d.ts.map +1 -0
  75. package/dist/magit/package-catalog-bridge.js +220 -0
  76. package/dist/magit/package-catalog-bridge.js.map +1 -0
  77. package/dist/magit/proposal-to-pack.d.ts +22 -0
  78. package/dist/magit/proposal-to-pack.d.ts.map +1 -0
  79. package/dist/magit/proposal-to-pack.js +68 -0
  80. package/dist/magit/proposal-to-pack.js.map +1 -0
  81. package/dist/mongo-metadata-store.d.ts.map +1 -1
  82. package/dist/mongo-metadata-store.js +11 -3
  83. package/dist/mongo-metadata-store.js.map +1 -1
  84. package/dist/openapi/openapi.json +665 -1
  85. package/dist/org-sources.d.ts +14 -0
  86. package/dist/org-sources.d.ts.map +1 -0
  87. package/dist/org-sources.js +31 -0
  88. package/dist/org-sources.js.map +1 -0
  89. package/dist/pipeline-compile.d.ts +51 -0
  90. package/dist/pipeline-compile.d.ts.map +1 -0
  91. package/dist/pipeline-compile.js +203 -0
  92. package/dist/pipeline-compile.js.map +1 -0
  93. package/dist/platform.d.ts +22 -1
  94. package/dist/platform.d.ts.map +1 -1
  95. package/dist/platform.js +150 -21
  96. package/dist/platform.js.map +1 -1
  97. package/dist/routes/ai-services.d.ts +4 -0
  98. package/dist/routes/ai-services.d.ts.map +1 -0
  99. package/dist/routes/ai-services.js +239 -0
  100. package/dist/routes/ai-services.js.map +1 -0
  101. package/dist/routes/assist.d.ts +4 -0
  102. package/dist/routes/assist.d.ts.map +1 -0
  103. package/dist/routes/assist.js +75 -0
  104. package/dist/routes/assist.js.map +1 -0
  105. package/dist/routes/connector-invocations.js +2 -2
  106. package/dist/routes/data-journey.js +11 -11
  107. package/dist/routes/data-journey.js.map +1 -1
  108. package/dist/routes/intelligence.d.ts.map +1 -1
  109. package/dist/routes/intelligence.js +19 -1
  110. package/dist/routes/intelligence.js.map +1 -1
  111. package/dist/routes/memory.d.ts.map +1 -1
  112. package/dist/routes/memory.js +343 -41
  113. package/dist/routes/memory.js.map +1 -1
  114. package/dist/routes/metadata.d.ts.map +1 -1
  115. package/dist/routes/metadata.js +800 -8
  116. package/dist/routes/metadata.js.map +1 -1
  117. package/dist/routes/pipelines.d.ts.map +1 -1
  118. package/dist/routes/pipelines.js +28 -3
  119. package/dist/routes/pipelines.js.map +1 -1
  120. package/dist/routes/relationships.d.ts.map +1 -1
  121. package/dist/routes/relationships.js +29 -0
  122. package/dist/routes/relationships.js.map +1 -1
  123. package/dist/routes/simulation.d.ts +7 -0
  124. package/dist/routes/simulation.d.ts.map +1 -0
  125. package/dist/routes/simulation.js +22 -0
  126. package/dist/routes/simulation.js.map +1 -0
  127. package/dist/simulation.d.ts +26 -0
  128. package/dist/simulation.d.ts.map +1 -0
  129. package/dist/simulation.js +46 -0
  130. package/dist/simulation.js.map +1 -0
  131. package/openapi/openapi.json +665 -1
  132. package/package.json +19 -10
@@ -3,6 +3,7 @@ import { credentialHandleFromDelegation, createCredentialHandle, } from "@x12i/m
3
3
  import { resolveScope } from "../request-scope.js";
4
4
  import { ServiceError } from "../errors.js";
5
5
  import { forbidCallbackGrant, requireCallbackAccess } from "../access-gate.js";
6
+ import { asSourceDefinition } from "../org-sources.js";
6
7
  const CONNECTION_PROBE_MS = 2000;
7
8
  /** Module-level replay ledger — lives for the process lifetime. */
8
9
  const pushEnvelopeStore = createInMemoryPushEnvelopeStore({ maxSize: 50_000 });
@@ -59,18 +60,77 @@ async function probeSourceHealth(baseUrl) {
59
60
  return false;
60
61
  }
61
62
  }
62
- function resolveSourceDefinition(platform, agentIds, sourceId) {
63
- const def = platform.metadata.getDefinition(agentIds, "sources", sourceId);
64
- if (!def) {
65
- throw new ServiceError("NOT_FOUND", `source not found: ${sourceId}`, 404);
63
+ const STREAM_PROBE_PREVIEW_MAX = 32_768;
64
+ /** Expand `:param` segments from workRef / query; fail if any remain unresolved. */
65
+ function expandStreamEndpoint(endpoint, opts) {
66
+ const params = [...endpoint.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1]);
67
+ let path = endpoint;
68
+ for (const name of params) {
69
+ const fromQuery = opts.query?.[name];
70
+ let value = null;
71
+ if (typeof fromQuery === "string" && fromQuery.trim()) {
72
+ value = fromQuery.trim();
73
+ }
74
+ else if (typeof fromQuery === "number" && Number.isFinite(fromQuery)) {
75
+ value = String(fromQuery);
76
+ }
77
+ else if (opts.workRef?.trim()) {
78
+ if (name === "id" || params.length === 1) {
79
+ value = opts.workRef.trim();
80
+ }
81
+ }
82
+ if (!value) {
83
+ return {
84
+ error: `path parameter :${name} requires workRef or query.${name}`,
85
+ };
86
+ }
87
+ path = path.replace(new RegExp(`:${name}\\b`), encodeURIComponent(value));
88
+ }
89
+ if (path.includes(":")) {
90
+ return { error: `unresolved path parameters in ${endpoint}` };
91
+ }
92
+ return { path };
93
+ }
94
+ function redactProbeHeaders(headers) {
95
+ const out = {};
96
+ headers.forEach((value, key) => {
97
+ const lower = key.toLowerCase();
98
+ if (lower === "authorization" ||
99
+ lower === "cookie" ||
100
+ lower === "set-cookie" ||
101
+ lower.includes("secret") ||
102
+ lower.includes("token")) {
103
+ out[key] = "[redacted]";
104
+ return;
105
+ }
106
+ out[key] = value;
107
+ });
108
+ return out;
109
+ }
110
+ function connectorFrameworkFlag(entry) {
111
+ return entry.protocol === "memorix-connector/1";
112
+ }
113
+ async function resolveSourceDefinition(platform, scope, sourceId) {
114
+ const packDef = platform.metadata.getDefinition(scope.agentIds, "sources", sourceId);
115
+ if (packDef?.definition) {
116
+ return packDef.definition;
117
+ }
118
+ const orgDoc = await platform
119
+ .orgRuntimeSourcesFor(scope.orgId)
120
+ .get(sourceId);
121
+ if (orgDoc &&
122
+ (!orgDoc.agentIds.length ||
123
+ !scope.agentIds.length ||
124
+ scope.agentIds.some((a) => orgDoc.agentIds.includes(a)))) {
125
+ return orgDoc.definition;
66
126
  }
67
- return def.definition;
127
+ throw new ServiceError("NOT_FOUND", `source not found: ${sourceId}`, 404);
68
128
  }
69
129
  export async function registerMemoryRoutes(app, platform) {
70
130
  app.get("/api/memory/sources", async (req) => {
71
131
  const scope = resolveScope(req);
72
132
  const effective = platform.metadata.resolveEffective(scope.agentIds);
73
- const sources = (effective.effective ?? [])
133
+ const packSources = (effective.effective ?? [])
74
134
  .filter((e) => e.kind === "sources")
75
135
  .map((e) => {
76
136
  const d = e.definition;
@@ -78,6 +138,7 @@ export async function registerMemoryRoutes(app, platform) {
78
138
  return {
79
139
  id: e.id,
80
140
  ...safe,
141
+ registry: "pack",
81
142
  provenance: {
82
143
  definedByAgentId: e.provenance.definedByAgentId,
83
144
  inheritancePath: e.provenance.inheritancePath,
@@ -85,19 +146,108 @@ export async function registerMemoryRoutes(app, platform) {
85
146
  },
86
147
  };
87
148
  });
88
- return { scope, sources, note: "credentialRef only" };
149
+ const packIds = new Set(packSources.map((s) => s.id));
150
+ const orgRows = await platform
151
+ .orgRuntimeSourcesFor(scope.orgId)
152
+ .list({ agentIds: scope.agentIds });
153
+ const orgSources = orgRows
154
+ .filter((r) => !packIds.has(r.sourceId))
155
+ .map((r) => {
156
+ const d = r.definition;
157
+ const { credential: _c, credentials: _cs, secret: _s, ...safe } = d;
158
+ return {
159
+ id: r.sourceId,
160
+ ...safe,
161
+ registry: "org",
162
+ origin: r.origin,
163
+ provenance: {
164
+ definedByAgentId: r.agentIds[0] ?? null,
165
+ inheritancePath: r.agentIds,
166
+ selectedAgentId: r.agentIds[0] ?? null,
167
+ },
168
+ };
169
+ });
170
+ return {
171
+ scope,
172
+ sources: [...packSources, ...orgSources],
173
+ note: "credentialRef only; registry=pack|org",
174
+ };
175
+ });
176
+ app.get("/api/memory/org-sources", async (req) => {
177
+ const scope = resolveScope(req);
178
+ const q = req.query;
179
+ const sources = await platform.orgRuntimeSourcesFor(scope.orgId).list({
180
+ origin: q.origin?.trim() || undefined,
181
+ agentIds: scope.agentIds,
182
+ });
183
+ return { scope, sources };
184
+ });
185
+ app.put("/api/memory/org-sources/:sourceId", async (req) => {
186
+ const scope = resolveScope(req);
187
+ const sourceId = String(req.params.sourceId ?? "").trim();
188
+ if (!sourceId) {
189
+ throw new ServiceError("VALIDATION", "sourceId required", 400);
190
+ }
191
+ const body = (req.body ?? {});
192
+ if (!body.definition || typeof body.definition !== "object") {
193
+ throw new ServiceError("VALIDATION", "definition object required", 400);
194
+ }
195
+ const definition = asSourceDefinition(body.definition, sourceId);
196
+ const saved = await platform.orgRuntimeSourcesFor(scope.orgId).upsert({
197
+ sourceId,
198
+ definition,
199
+ agentIds: body.agentIds?.length ? body.agentIds : scope.agentIds,
200
+ origin: body.origin?.trim() || "knowx-folder",
201
+ });
202
+ return { scope, source: saved };
203
+ });
204
+ app.delete("/api/memory/org-sources/:sourceId", async (req) => {
205
+ const scope = resolveScope(req);
206
+ const sourceId = String(req.params.sourceId ?? "").trim();
207
+ if (!sourceId) {
208
+ throw new ServiceError("VALIDATION", "sourceId required", 400);
209
+ }
210
+ const deleted = await platform
211
+ .orgRuntimeSourcesFor(scope.orgId)
212
+ .delete(sourceId);
213
+ if (!deleted) {
214
+ throw new ServiceError("NOT_FOUND", `org source not found: ${sourceId}`, 404);
215
+ }
216
+ return { scope, deleted: true, sourceId };
217
+ });
218
+ app.post("/api/memory/org-sources/replace", async (req) => {
219
+ const scope = resolveScope(req);
220
+ const body = (req.body ?? {});
221
+ const origin = body.origin?.trim() || "knowx-folder";
222
+ if (!Array.isArray(body.sources)) {
223
+ throw new ServiceError("VALIDATION", "sources array required", 400);
224
+ }
225
+ const defs = body.sources.map((s, i) => {
226
+ const sourceId = String(s.sourceId ?? s.definition?.id ?? "").trim();
227
+ if (!sourceId) {
228
+ throw new ServiceError("VALIDATION", `sources[${i}].sourceId required`, 400);
229
+ }
230
+ if (!s.definition || typeof s.definition !== "object") {
231
+ throw new ServiceError("VALIDATION", `sources[${i}].definition required`, 400);
232
+ }
233
+ return {
234
+ sourceId,
235
+ definition: asSourceDefinition(s.definition, sourceId),
236
+ agentIds: s.agentIds?.length ? s.agentIds : scope.agentIds,
237
+ };
238
+ });
239
+ const result = await platform
240
+ .orgRuntimeSourcesFor(scope.orgId)
241
+ .replaceAllForOrigin(origin, defs);
242
+ return { scope, origin, ...result };
89
243
  });
90
244
  app.get("/api/memory/sources/:sourceId/connection", async (req) => {
91
245
  const scope = resolveScope(req);
92
246
  const sourceId = String(req.params.sourceId ?? "").trim();
93
247
  if (!sourceId)
94
248
  throw new ServiceError("VALIDATION", "sourceId required", 400);
95
- const effective = platform.metadata.resolveEffective(scope.agentIds);
96
- const entry = (effective.effective ?? []).find((e) => e.kind === "sources" && e.id === sourceId);
97
- if (!entry) {
98
- throw new ServiceError("NOT_FOUND", `source not found: ${sourceId}`, 404);
99
- }
100
- const definition = entry.definition;
249
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
250
+ const definition = source;
101
251
  const baseUrl = resolveSourceBaseUrl(definition);
102
252
  const checkedAt = new Date().toISOString();
103
253
  if (!baseUrl) {
@@ -120,22 +270,109 @@ export async function registerMemoryRoutes(app, platform) {
120
270
  checkedAt,
121
271
  };
122
272
  });
273
+ app.post("/api/memory/sources/:sourceId/streams/probe", async (req) => {
274
+ const scope = resolveScope(req);
275
+ const sourceId = String(req.params.sourceId ?? "").trim();
276
+ if (!sourceId)
277
+ throw new ServiceError("VALIDATION", "sourceId required", 400);
278
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
279
+ const definition = source;
280
+ const body = (req.body ?? {});
281
+ const endpoint = String(body.endpoint ?? "").trim();
282
+ if (!endpoint) {
283
+ throw new ServiceError("VALIDATION", "endpoint required", 400);
284
+ }
285
+ const baseUrl = resolveSourceBaseUrl(definition);
286
+ if (!baseUrl) {
287
+ throw new ServiceError("VALIDATION", "source has no configured base URL (baseUrlEnv / defaultBaseUrl)", 400);
288
+ }
289
+ const expanded = expandStreamEndpoint(endpoint, {
290
+ workRef: typeof body.workRef === "string" ? body.workRef : undefined,
291
+ query: body.query && typeof body.query === "object" ? body.query : undefined,
292
+ });
293
+ if ("error" in expanded) {
294
+ throw new ServiceError("VALIDATION", expanded.error, 400);
295
+ }
296
+ const url = new URL(expanded.path.replace(/^\//, ""), `${baseUrl}/`);
297
+ const query = body.query && typeof body.query === "object" ? body.query : undefined;
298
+ const pathParamNames = new Set([...endpoint.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)].map((m) => m[1]));
299
+ if (query) {
300
+ for (const [key, value] of Object.entries(query)) {
301
+ if (key.startsWith(":") || pathParamNames.has(key))
302
+ continue;
303
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
304
+ url.searchParams.set(key, String(value));
305
+ }
306
+ }
307
+ }
308
+ if (typeof body.limit === "number" && Number.isFinite(body.limit) && body.limit > 0) {
309
+ url.searchParams.set("limit", String(Math.floor(body.limit)));
310
+ }
311
+ const started = Date.now();
312
+ try {
313
+ const res = await fetch(url.toString(), {
314
+ method: "GET",
315
+ signal: AbortSignal.timeout(CONNECTION_PROBE_MS),
316
+ headers: { accept: "application/json, text/plain, */*" },
317
+ });
318
+ const durationMs = Date.now() - started;
319
+ const contentType = res.headers.get("content-type") ?? undefined;
320
+ const text = await res.text();
321
+ const truncated = text.length > STREAM_PROBE_PREVIEW_MAX;
322
+ const bodyPreview = truncated ? text.slice(0, STREAM_PROBE_PREVIEW_MAX) : text;
323
+ return {
324
+ scope,
325
+ sourceId,
326
+ streamId: typeof body.streamId === "string" ? body.streamId : undefined,
327
+ endpoint,
328
+ ok: res.ok,
329
+ status: res.status,
330
+ url: url.toString(),
331
+ durationMs,
332
+ contentType,
333
+ headers: redactProbeHeaders(res.headers),
334
+ bodyPreview,
335
+ truncated,
336
+ };
337
+ }
338
+ catch (err) {
339
+ const durationMs = Date.now() - started;
340
+ const msg = err instanceof Error ? err.message : String(err);
341
+ return {
342
+ scope,
343
+ sourceId,
344
+ streamId: typeof body.streamId === "string" ? body.streamId : undefined,
345
+ endpoint,
346
+ ok: false,
347
+ status: 0,
348
+ url: url.toString(),
349
+ durationMs,
350
+ contentType: undefined,
351
+ headers: {},
352
+ bodyPreview: "",
353
+ truncated: false,
354
+ error: msg,
355
+ };
356
+ }
357
+ });
123
358
  app.get("/api/memory/sources/:sourceId/checkpoints", async (req) => {
124
359
  requireCallbackAccess(req, "checkpoints");
125
360
  const scope = resolveScope(req);
126
361
  const sourceId = String(req.params.sourceId ?? "").trim();
127
362
  if (!sourceId)
128
363
  throw new ServiceError("VALIDATION", "sourceId required", 400);
129
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
364
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
130
365
  const ports = platform.portsFor(scope);
131
366
  const cursors = await ports.memoryRunStore.listCursors({ sourceId });
132
367
  const mode = req.query.mode === "context" || req.query.mode === "knowledge"
133
368
  ? req.query.mode
134
369
  : undefined;
135
370
  const includeTechnical = req.query.technical === "1" || req.query.technical === "true";
371
+ const workRef = req.query.workRef?.trim() || undefined;
136
372
  const checkpoints = listCheckpointSummariesForSource({
137
373
  source,
138
374
  mode,
375
+ workRef,
139
376
  cursors,
140
377
  includeTechnical,
141
378
  });
@@ -154,7 +391,7 @@ export async function registerMemoryRoutes(app, platform) {
154
391
  if (!sourceId || !checkpointKey) {
155
392
  throw new ServiceError("VALIDATION", "sourceId and checkpointKey required", 400);
156
393
  }
157
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
394
+ await resolveSourceDefinition(platform, scope, sourceId);
158
395
  const ports = platform.portsFor(scope);
159
396
  const doc = await ports.memoryRunStore.getCursor(checkpointKey);
160
397
  if (!doc || doc.sourceId !== sourceId) {
@@ -172,7 +409,7 @@ export async function registerMemoryRoutes(app, platform) {
172
409
  const sourceId = String(req.params.sourceId ?? "").trim();
173
410
  if (!sourceId)
174
411
  throw new ServiceError("VALIDATION", "sourceId required", 400);
175
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
412
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
176
413
  const body = (req.body ?? {});
177
414
  const ports = platform.portsFor(scope);
178
415
  const mode = body.mode ?? "knowledge";
@@ -215,7 +452,7 @@ export async function registerMemoryRoutes(app, platform) {
215
452
  const sourceId = String(req.params.sourceId ?? "").trim();
216
453
  if (!sourceId)
217
454
  throw new ServiceError("VALIDATION", "sourceId required", 400);
218
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
455
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
219
456
  const body = (req.body ?? {});
220
457
  if (!body.objectType) {
221
458
  throw new ServiceError("VALIDATION", "objectType required", 400);
@@ -324,7 +561,7 @@ export async function registerMemoryRoutes(app, platform) {
324
561
  const sourceId = String(req.params.sourceId ?? "").trim();
325
562
  if (!sourceId)
326
563
  throw new ServiceError("VALIDATION", "sourceId required", 400);
327
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
564
+ await resolveSourceDefinition(platform, scope, sourceId);
328
565
  const mode = req.query.mode === "context" ? "context" : "knowledge";
329
566
  const ports = platform.portsFor(scope);
330
567
  const schedule = await ports.memoryScheduleStore.getSchedule(sourceId, mode);
@@ -335,7 +572,7 @@ export async function registerMemoryRoutes(app, platform) {
335
572
  const sourceId = String(req.params.sourceId ?? "").trim();
336
573
  if (!sourceId)
337
574
  throw new ServiceError("VALIDATION", "sourceId required", 400);
338
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
575
+ await resolveSourceDefinition(platform, scope, sourceId);
339
576
  const body = (req.body ?? {});
340
577
  if (!body.interval) {
341
578
  throw new ServiceError("VALIDATION", "interval required", 400);
@@ -362,7 +599,7 @@ export async function registerMemoryRoutes(app, platform) {
362
599
  const sourceId = String(req.params.sourceId ?? "").trim();
363
600
  if (!sourceId)
364
601
  throw new ServiceError("VALIDATION", "sourceId required", 400);
365
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
602
+ await resolveSourceDefinition(platform, scope, sourceId);
366
603
  const mode = req.query.mode === "context" ? "context" : "knowledge";
367
604
  const ports = platform.portsFor(scope);
368
605
  const existing = await ports.memoryScheduleStore.getSchedule(sourceId, mode);
@@ -383,7 +620,7 @@ export async function registerMemoryRoutes(app, platform) {
383
620
  const sourceId = String(req.params.sourceId ?? "").trim();
384
621
  if (!sourceId)
385
622
  throw new ServiceError("VALIDATION", "sourceId required", 400);
386
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
623
+ await resolveSourceDefinition(platform, scope, sourceId);
387
624
  const mode = req.query.mode === "context" ? "context" : "knowledge";
388
625
  const ports = platform.portsFor(scope);
389
626
  const existing = await ports.memoryScheduleStore.getSchedule(sourceId, mode);
@@ -410,7 +647,7 @@ export async function registerMemoryRoutes(app, platform) {
410
647
  const sourceId = String(req.params.sourceId ?? "").trim();
411
648
  if (!sourceId)
412
649
  throw new ServiceError("VALIDATION", "sourceId required", 400);
413
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
650
+ await resolveSourceDefinition(platform, scope, sourceId);
414
651
  const mode = req.query.mode === "context" ? "context" : "knowledge";
415
652
  const ports = platform.portsFor(scope);
416
653
  const existing = await ports.memoryScheduleStore.getSchedule(sourceId, mode);
@@ -468,7 +705,7 @@ export async function registerMemoryRoutes(app, platform) {
468
705
  const sourceId = String(req.params.sourceId ?? "").trim();
469
706
  if (!sourceId)
470
707
  throw new ServiceError("VALIDATION", "sourceId required", 400);
471
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
708
+ await resolveSourceDefinition(platform, scope, sourceId);
472
709
  const mode = req.query.mode === "context" ? "context" : "knowledge";
473
710
  const ports = platform.portsFor(scope);
474
711
  const removed = await ports.memoryScheduleStore.deleteSchedule(sourceId, mode);
@@ -530,7 +767,7 @@ export async function registerMemoryRoutes(app, platform) {
530
767
  const sourceId = String(req.params.sourceId ?? "").trim();
531
768
  if (!sourceId)
532
769
  throw new ServiceError("VALIDATION", "sourceId required", 400);
533
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
770
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
534
771
  const body = (req.body ?? {});
535
772
  if (!body.objectType) {
536
773
  throw new ServiceError("VALIDATION", "objectType required", 400);
@@ -588,7 +825,7 @@ export async function registerMemoryRoutes(app, platform) {
588
825
  const sourceId = String(req.params.sourceId ?? "").trim();
589
826
  if (!sourceId)
590
827
  throw new ServiceError("VALIDATION", "sourceId required", 400);
591
- resolveSourceDefinition(platform, scope.agentIds, sourceId);
828
+ await resolveSourceDefinition(platform, scope, sourceId);
592
829
  const body = (req.body ?? {});
593
830
  // Delivery replay guard — short-circuit duplicate provider deliveries.
594
831
  const deliveryId = body.deliveryId?.trim() || null;
@@ -643,7 +880,7 @@ export async function registerMemoryRoutes(app, platform) {
643
880
  const sourceId = String(req.params.sourceId ?? "").trim();
644
881
  if (!sourceId)
645
882
  throw new ServiceError("VALIDATION", "sourceId required", 400);
646
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
883
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
647
884
  const connectorRef = source.connectorRef
648
885
  ?? source.kind;
649
886
  const connector = connectorRef
@@ -692,7 +929,7 @@ export async function registerMemoryRoutes(app, platform) {
692
929
  const sourceId = String(req.params.sourceId ?? "").trim();
693
930
  if (!sourceId)
694
931
  throw new ServiceError("VALIDATION", "sourceId required", 400);
695
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
932
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
696
933
  const connectorRef = source.connectorRef
697
934
  ?? source.kind;
698
935
  const connector = connectorRef
@@ -729,23 +966,88 @@ export async function registerMemoryRoutes(app, platform) {
729
966
  });
730
967
  app.get("/api/connectors", async (req) => {
731
968
  const scope = resolveScope(req);
732
- const frameworkConnectors = platform.connectorRegistry.list().map((doc) => ({
733
- id: doc.connectorId,
734
- version: doc.version,
735
- protocol: doc.protocol,
736
- enabled: doc.enabled,
737
- workflowHash: doc.workflowHash,
738
- }));
739
- const legacyConnectors = [
740
- { id: "rest-fixture", version: "0.0.0-legacy", protocol: "memorix-connector/1", enabled: true },
741
- { id: "opx-fixture", version: "0.0.0-legacy", protocol: "memorix-connector/1", enabled: true },
742
- { id: "knowx-fixture", version: "0.0.0-legacy", protocol: "memorix-connector/1", enabled: true },
969
+ const frameworkConnectors = platform.connectorRegistry.list().map((doc) => {
970
+ const manifest = (doc.manifest ?? {});
971
+ const caps = manifest.capabilities && typeof manifest.capabilities === "object"
972
+ ? manifest.capabilities
973
+ : undefined;
974
+ return {
975
+ id: doc.connectorId,
976
+ version: doc.version,
977
+ protocol: doc.protocol,
978
+ enabled: doc.enabled,
979
+ workflowHash: doc.workflowHash,
980
+ framework: connectorFrameworkFlag(doc),
981
+ displayName: typeof manifest.displayName === "string" ? manifest.displayName : undefined,
982
+ capabilities: caps
983
+ ? {
984
+ pull: caps.pull === true,
985
+ ...(caps.simulation === true ? { simulation: true } : {}),
986
+ ...(caps.providerPush === true ? { providerPush: true } : {}),
987
+ ...(caps.cursor === true ? { cursor: true } : {}),
988
+ ...(caps.watermark === true ? { watermark: true } : {}),
989
+ ...(caps.fullScanHash === true ? { fullScanHash: true } : {}),
990
+ ...(caps.polling === true ? { polling: true } : {}),
991
+ ...(caps.scheduling === true ? { scheduling: true } : {}),
992
+ ...(caps.largeContent === true ? { largeContent: true } : {}),
993
+ ...(caps.xml === true ? { xml: true } : {}),
994
+ ...(caps.validation === true ? { validation: true } : {}),
995
+ ...(caps.health === true ? { health: true } : {}),
996
+ ...(caps.remoteHosting === true ? { remoteHosting: true } : {}),
997
+ }
998
+ : { pull: true },
999
+ };
1000
+ });
1001
+ const builtinFramework = [
1002
+ {
1003
+ id: "opx-rest",
1004
+ version: "1.0.0",
1005
+ protocol: "memorix-connector/1",
1006
+ enabled: true,
1007
+ framework: true,
1008
+ displayName: "OpX REST",
1009
+ capabilities: { pull: true, cursor: true, simulation: true },
1010
+ },
1011
+ {
1012
+ id: "knowx-rest",
1013
+ version: "1.0.0",
1014
+ protocol: "memorix-connector/1",
1015
+ enabled: true,
1016
+ framework: true,
1017
+ displayName: "KnowX REST",
1018
+ capabilities: { pull: true, cursor: true, simulation: true },
1019
+ },
1020
+ {
1021
+ id: "knowx-folder",
1022
+ version: "1.0.0",
1023
+ protocol: "memorix-connector/1",
1024
+ enabled: true,
1025
+ framework: true,
1026
+ displayName: "KnowX folder",
1027
+ capabilities: { pull: true, simulation: true },
1028
+ },
1029
+ {
1030
+ id: "knowx-managed",
1031
+ version: "1.0.0",
1032
+ protocol: "memorix-connector/1",
1033
+ enabled: true,
1034
+ framework: true,
1035
+ displayName: "KnowX managed",
1036
+ capabilities: {
1037
+ pull: true,
1038
+ largeContent: true,
1039
+ remoteHosting: true,
1040
+ validation: true,
1041
+ health: true,
1042
+ simulation: true,
1043
+ },
1044
+ },
743
1045
  ];
744
- // Deduplicate: framework connectors take precedence over legacy stubs.
1046
+ // Deduplicate: registry connectors take precedence over builtins.
745
1047
  const frameworkIds = new Set(frameworkConnectors.map((c) => c.id));
746
1048
  const connectors = [
747
1049
  ...frameworkConnectors,
748
- ...legacyConnectors.filter((c) => !frameworkIds.has(c.id)),
1050
+ ...builtinFramework.filter((c) => !frameworkIds.has(c.id)),
749
1051
  ];
750
1052
  return { scope, connectors };
751
1053
  });
@@ -756,7 +1058,7 @@ export async function registerMemoryRoutes(app, platform) {
756
1058
  const sourceId = String(req.params.sourceId ?? "").trim();
757
1059
  if (!sourceId)
758
1060
  throw new ServiceError("VALIDATION", "sourceId required", 400);
759
- const source = resolveSourceDefinition(platform, scope.agentIds, sourceId);
1061
+ const source = await resolveSourceDefinition(platform, scope, sourceId);
760
1062
  const body = (req.body ?? {});
761
1063
  const action = body.action ?? "create";
762
1064
  const connectorRef = source.connectorRef ??