agentlang 0.10.1 → 0.10.3

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 (178) hide show
  1. package/README.md +7 -14
  2. package/out/api/http.d.ts +4 -0
  3. package/out/api/http.d.ts.map +1 -1
  4. package/out/api/http.js +307 -26
  5. package/out/api/http.js.map +1 -1
  6. package/out/cli/main.d.ts.map +1 -1
  7. package/out/cli/main.js +3 -0
  8. package/out/cli/main.js.map +1 -1
  9. package/out/extension/main.cjs +250 -250
  10. package/out/extension/main.cjs.map +2 -2
  11. package/out/language/agentlang-validator.d.ts.map +1 -1
  12. package/out/language/agentlang-validator.js +4 -0
  13. package/out/language/agentlang-validator.js.map +1 -1
  14. package/out/language/error-reporter.d.ts +53 -0
  15. package/out/language/error-reporter.d.ts.map +1 -0
  16. package/out/language/error-reporter.js +879 -0
  17. package/out/language/error-reporter.js.map +1 -0
  18. package/out/language/generated/ast.d.ts +77 -2
  19. package/out/language/generated/ast.d.ts.map +1 -1
  20. package/out/language/generated/ast.js +60 -0
  21. package/out/language/generated/ast.js.map +1 -1
  22. package/out/language/generated/grammar.d.ts.map +1 -1
  23. package/out/language/generated/grammar.js +342 -206
  24. package/out/language/generated/grammar.js.map +1 -1
  25. package/out/language/main.cjs +901 -710
  26. package/out/language/main.cjs.map +3 -3
  27. package/out/language/parser.d.ts +4 -2
  28. package/out/language/parser.d.ts.map +1 -1
  29. package/out/language/parser.js +58 -99
  30. package/out/language/parser.js.map +1 -1
  31. package/out/language/syntax.d.ts +16 -0
  32. package/out/language/syntax.d.ts.map +1 -1
  33. package/out/language/syntax.js +66 -27
  34. package/out/language/syntax.js.map +1 -1
  35. package/out/runtime/api.d.ts +2 -0
  36. package/out/runtime/api.d.ts.map +1 -1
  37. package/out/runtime/api.js +25 -0
  38. package/out/runtime/api.js.map +1 -1
  39. package/out/runtime/datefns.d.ts +34 -0
  40. package/out/runtime/datefns.d.ts.map +1 -0
  41. package/out/runtime/datefns.js +82 -0
  42. package/out/runtime/datefns.js.map +1 -0
  43. package/out/runtime/defs.d.ts +1 -0
  44. package/out/runtime/defs.d.ts.map +1 -1
  45. package/out/runtime/defs.js +2 -1
  46. package/out/runtime/defs.js.map +1 -1
  47. package/out/runtime/document-retriever.d.ts +24 -0
  48. package/out/runtime/document-retriever.d.ts.map +1 -0
  49. package/out/runtime/document-retriever.js +258 -0
  50. package/out/runtime/document-retriever.js.map +1 -0
  51. package/out/runtime/embeddings/chunker.d.ts +18 -0
  52. package/out/runtime/embeddings/chunker.d.ts.map +1 -1
  53. package/out/runtime/embeddings/chunker.js +47 -15
  54. package/out/runtime/embeddings/chunker.js.map +1 -1
  55. package/out/runtime/embeddings/openai.d.ts.map +1 -1
  56. package/out/runtime/embeddings/openai.js +22 -9
  57. package/out/runtime/embeddings/openai.js.map +1 -1
  58. package/out/runtime/embeddings/provider.d.ts +1 -0
  59. package/out/runtime/embeddings/provider.d.ts.map +1 -1
  60. package/out/runtime/embeddings/provider.js +20 -1
  61. package/out/runtime/embeddings/provider.js.map +1 -1
  62. package/out/runtime/exec-graph.d.ts.map +1 -1
  63. package/out/runtime/exec-graph.js +22 -3
  64. package/out/runtime/exec-graph.js.map +1 -1
  65. package/out/runtime/integration-client.d.ts +21 -0
  66. package/out/runtime/integration-client.d.ts.map +1 -0
  67. package/out/runtime/integration-client.js +112 -0
  68. package/out/runtime/integration-client.js.map +1 -0
  69. package/out/runtime/integrations.d.ts.map +1 -1
  70. package/out/runtime/integrations.js +20 -9
  71. package/out/runtime/integrations.js.map +1 -1
  72. package/out/runtime/interpreter.d.ts +10 -0
  73. package/out/runtime/interpreter.d.ts.map +1 -1
  74. package/out/runtime/interpreter.js +221 -22
  75. package/out/runtime/interpreter.js.map +1 -1
  76. package/out/runtime/loader.d.ts.map +1 -1
  77. package/out/runtime/loader.js +70 -7
  78. package/out/runtime/loader.js.map +1 -1
  79. package/out/runtime/logger.d.ts.map +1 -1
  80. package/out/runtime/logger.js +8 -1
  81. package/out/runtime/logger.js.map +1 -1
  82. package/out/runtime/module.d.ts +18 -0
  83. package/out/runtime/module.d.ts.map +1 -1
  84. package/out/runtime/module.js +91 -3
  85. package/out/runtime/module.js.map +1 -1
  86. package/out/runtime/modules/ai.d.ts +16 -5
  87. package/out/runtime/modules/ai.d.ts.map +1 -1
  88. package/out/runtime/modules/ai.js +286 -88
  89. package/out/runtime/modules/ai.js.map +1 -1
  90. package/out/runtime/modules/core.d.ts.map +1 -1
  91. package/out/runtime/modules/core.js +5 -1
  92. package/out/runtime/modules/core.js.map +1 -1
  93. package/out/runtime/monitor.d.ts +6 -0
  94. package/out/runtime/monitor.d.ts.map +1 -1
  95. package/out/runtime/monitor.js +21 -1
  96. package/out/runtime/monitor.js.map +1 -1
  97. package/out/runtime/relgraph.d.ts.map +1 -1
  98. package/out/runtime/relgraph.js +7 -3
  99. package/out/runtime/relgraph.js.map +1 -1
  100. package/out/runtime/resolvers/interface.d.ts +7 -2
  101. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  102. package/out/runtime/resolvers/interface.js +17 -3
  103. package/out/runtime/resolvers/interface.js.map +1 -1
  104. package/out/runtime/resolvers/sqldb/database.d.ts +2 -0
  105. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  106. package/out/runtime/resolvers/sqldb/database.js +142 -126
  107. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  108. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  109. package/out/runtime/resolvers/sqldb/dbutil.js +25 -4
  110. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  111. package/out/runtime/resolvers/sqldb/impl.d.ts +2 -1
  112. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  113. package/out/runtime/resolvers/sqldb/impl.js +24 -7
  114. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  115. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  116. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  117. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  118. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  119. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  120. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  121. package/out/runtime/resolvers/vector/types.js +2 -0
  122. package/out/runtime/resolvers/vector/types.js.map +1 -0
  123. package/out/runtime/services/documentFetcher.d.ts.map +1 -1
  124. package/out/runtime/services/documentFetcher.js +21 -6
  125. package/out/runtime/services/documentFetcher.js.map +1 -1
  126. package/out/runtime/state.d.ts +19 -1
  127. package/out/runtime/state.d.ts.map +1 -1
  128. package/out/runtime/state.js +36 -1
  129. package/out/runtime/state.js.map +1 -1
  130. package/out/runtime/util.d.ts +3 -2
  131. package/out/runtime/util.d.ts.map +1 -1
  132. package/out/runtime/util.js +13 -2
  133. package/out/runtime/util.js.map +1 -1
  134. package/out/syntaxes/agentlang.monarch.js +1 -1
  135. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  136. package/out/test-harness.d.ts +36 -0
  137. package/out/test-harness.d.ts.map +1 -0
  138. package/out/test-harness.js +341 -0
  139. package/out/test-harness.js.map +1 -0
  140. package/package.json +22 -19
  141. package/src/api/http.ts +336 -38
  142. package/src/cli/main.ts +3 -0
  143. package/src/language/agentlang-validator.ts +3 -0
  144. package/src/language/agentlang.langium +6 -2
  145. package/src/language/error-reporter.ts +1028 -0
  146. package/src/language/generated/ast.ts +94 -1
  147. package/src/language/generated/grammar.ts +342 -206
  148. package/src/language/parser.ts +64 -101
  149. package/src/language/syntax.ts +79 -24
  150. package/src/runtime/api.ts +36 -0
  151. package/src/runtime/datefns.ts +112 -0
  152. package/src/runtime/defs.ts +2 -1
  153. package/src/runtime/document-retriever.ts +311 -0
  154. package/src/runtime/embeddings/chunker.ts +52 -14
  155. package/src/runtime/embeddings/openai.ts +27 -9
  156. package/src/runtime/embeddings/provider.ts +22 -1
  157. package/src/runtime/exec-graph.ts +23 -2
  158. package/src/runtime/integration-client.ts +158 -0
  159. package/src/runtime/integrations.ts +20 -11
  160. package/src/runtime/interpreter.ts +221 -15
  161. package/src/runtime/loader.ts +83 -5
  162. package/src/runtime/logger.ts +12 -1
  163. package/src/runtime/module.ts +104 -3
  164. package/src/runtime/modules/ai.ts +341 -107
  165. package/src/runtime/modules/core.ts +5 -1
  166. package/src/runtime/monitor.ts +27 -1
  167. package/src/runtime/relgraph.ts +7 -3
  168. package/src/runtime/resolvers/interface.ts +23 -3
  169. package/src/runtime/resolvers/sqldb/database.ts +158 -130
  170. package/src/runtime/resolvers/sqldb/dbutil.ts +28 -6
  171. package/src/runtime/resolvers/sqldb/impl.ts +25 -7
  172. package/src/runtime/resolvers/vector/lancedb-store.ts +187 -0
  173. package/src/runtime/resolvers/vector/types.ts +39 -0
  174. package/src/runtime/services/documentFetcher.ts +21 -6
  175. package/src/runtime/state.ts +40 -1
  176. package/src/runtime/util.ts +19 -2
  177. package/src/syntaxes/agentlang.monarch.ts +1 -1
  178. package/src/test-harness.ts +423 -0
@@ -26,6 +26,7 @@ import {
26
26
  asJSONSchema,
27
27
  Decision,
28
28
  fetchModule,
29
+ getAllDocumentsForTopics,
29
30
  getDecision,
30
31
  getGlobalRetry,
31
32
  Instance,
@@ -37,7 +38,9 @@ import {
37
38
  newInstanceAttributes,
38
39
  objectToInstanceAttributes,
39
40
  Record,
41
+ registerTopic as registerTopicInRegistry,
40
42
  resolveDocumentAliases,
43
+ resolveTopicNames,
41
44
  Retry,
42
45
  } from '../module.js';
43
46
  import { provider } from '../agents/registry.js';
@@ -70,11 +73,11 @@ import {
70
73
  } from '../agents/common.js';
71
74
  import { logger } from '../logger.js';
72
75
  import { FlowStep } from '../agents/flows.js';
73
- import Handlebars from 'handlebars';
74
76
  import { Statement } from '../../language/generated/ast.js';
75
- import { isMonitoringEnabled, TtlCache } from '../state.js';
77
+ import { getKnowledgeGraphConfig, isMonitoringEnabled, TtlCache } from '../state.js';
76
78
  import { isNodeEnv } from '../../utils/runtime.js';
77
79
  import { getFileSystem } from '../../utils/fs-utils.js';
80
+ import { getDocumentRetriever } from '../document-retriever.js';
78
81
 
79
82
  export const CoreAIModuleName = makeCoreModuleName('ai');
80
83
  export const AgentEntityName = 'Agent';
@@ -83,6 +86,50 @@ export const AgentLearnerType = 'learner';
83
86
 
84
87
  const AgentEvalType = 'eval';
85
88
 
89
+ // --- Agent cancellation infrastructure ---
90
+
91
+ export class AgentCancelledException extends Error {
92
+ constructor(chatId: string) {
93
+ super(`Agent cancelled for chatId: ${chatId}`);
94
+ this.name = 'AgentCancelledException';
95
+ }
96
+ }
97
+
98
+ export async function cancelAgent(chatId: string): Promise<string> {
99
+ const t = `${CoreAIModuleName}/${CancelledAgentEntity}`;
100
+ await parseAndEvaluateStatement(`{${t} {chatId "${chatId}"}, @upsert}`);
101
+ return `Cancellation requested for chatId: ${chatId}`;
102
+ }
103
+
104
+ export async function checkCancelled(chatId: string): Promise<void> {
105
+ if (!chatId) return;
106
+ try {
107
+ const results: Instance[] = await parseAndEvaluateStatement(
108
+ `{${CoreAIModuleName}/${CancelledAgentEntity} {chatId? "${chatId}"}}`
109
+ );
110
+ if (results && results.length > 0) {
111
+ await parseAndEvaluateStatement(
112
+ `purge {${CoreAIModuleName}/${CancelledAgentEntity} {chatId? "${chatId}"}}`
113
+ );
114
+ throw new AgentCancelledException(chatId);
115
+ }
116
+ } catch (err: any) {
117
+ if (err instanceof AgentCancelledException) throw err;
118
+ logger.debug(`checkCancelled DB error for ${chatId}: ${err}`);
119
+ }
120
+ }
121
+
122
+ export async function clearCancellation(chatId: string): Promise<void> {
123
+ if (!chatId) return;
124
+ try {
125
+ await parseAndEvaluateStatement(
126
+ `purge {${CoreAIModuleName}/${CancelledAgentEntity} {chatId? "${chatId}"}}`
127
+ );
128
+ } catch (err: any) {
129
+ logger.debug(`clearCancellation DB error for ${chatId}: ${err}`);
130
+ }
131
+ }
132
+
86
133
  export default `module ${CoreAIModuleName}
87
134
 
88
135
  import "./modules/ai.js" @as ai
@@ -101,12 +148,14 @@ entity ${AgentEntityName} {
101
148
  stateless Boolean @default(false),
102
149
  instruction String @optional,
103
150
  tools String @optional, // comma-separated list of tool names
104
- documents String @optional, // comma-separated list of document names
151
+ documents String @optional, // list of document names
152
+ topics String @optional, // list of topic names
105
153
  channels String @optional, // comma-separated list of channel names
106
154
  role String @optional,
107
155
  flows String @optional,
108
156
  validate String @optional,
109
157
  retry String @optional,
158
+ saveResponseAs String @optional,
110
159
  llm String
111
160
  }
112
161
 
@@ -147,6 +196,15 @@ workflow processDoc {
147
196
  await ai.fetchAndCreateDocument(doc.title, doc.url, doc.retrievalConfig, doc.embeddingConfig)
148
197
  }
149
198
 
199
+ event topic {
200
+ name String,
201
+ documents String @optional // comma-separated or array of document titles
202
+ }
203
+
204
+ workflow processTopic {
205
+ await ai.registerTopic(topic.name, topic.documents)
206
+ }
207
+
150
208
  entity Directive {
151
209
  id UUID @id @default(uuid()),
152
210
  agentFqName String @indexed,
@@ -202,6 +260,10 @@ entity AgentFlowStep {
202
260
  suspensionId String
203
261
  }
204
262
 
263
+ entity CancelledAgent {
264
+ chatId String @id
265
+ }
266
+
205
267
  @public event restartFlow {
206
268
  chatId String,
207
269
  step String,
@@ -214,6 +276,14 @@ workflow restartFlow {
214
276
  {${DefaultModuleName}/restartSuspension {id fs.suspensionId, data restartFlow.userInput}}
215
277
  }
216
278
  }
279
+
280
+ @public event cancelAgent {
281
+ chatId String
282
+ }
283
+
284
+ workflow cancelAgent {
285
+ await ai.cancelAgent(cancelAgent.chatId)
286
+ }
217
287
  `;
218
288
 
219
289
  enum AgentCacheType {
@@ -317,12 +387,14 @@ export class AgentInstance {
317
387
  type: string = 'chat';
318
388
  tools: string | undefined;
319
389
  documents: string | undefined;
390
+ topics: string | undefined;
320
391
  channels: string | undefined;
321
392
  runWorkflows: boolean = true;
322
393
  role: string | undefined;
323
394
  flows: string | undefined;
324
395
  validate: string | undefined;
325
396
  retry: string | undefined;
397
+ saveResponseAs: string | undefined;
326
398
  stateless: boolean = false;
327
399
  private toolsArray: string[] | undefined = undefined;
328
400
  private hasModuleTools = false;
@@ -645,7 +717,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
645
717
  const spad = env.getScratchPad();
646
718
  if (spad !== undefined && Object.keys(spad).length > 0) {
647
719
  if (finalInstruction.indexOf('{{') > 0) {
648
- return AgentInstance.maybeRewriteTemplatePatterns(spad, finalInstruction, env);
720
+ return env.maybeRewriteTemplatePatterns(finalInstruction, spad);
649
721
  } else {
650
722
  const ctx = JSON.stringify(spad);
651
723
  return `${finalInstruction}\nSome additional context:\n${ctx}`;
@@ -657,6 +729,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
657
729
  }
658
730
 
659
731
  private static UserTag = 'user';
732
+ private static FileTag = 'file';
660
733
 
661
734
  private async getFullInstructions(
662
735
  env: Environment,
@@ -670,13 +743,21 @@ Only return a pure JSON object with no extra text, annotations etc.`;
670
743
  }
671
744
  }
672
745
 
673
- private static maybeRewriteTemplatePatterns(
674
- scratchPad: any,
675
- instruction: string,
676
- env: Environment
677
- ): string {
678
- const templ = Handlebars.compile(env.rewriteTemplateMappings(instruction));
679
- return templ(scratchPad);
746
+ private async maybeFillInFileContents(instructions: string, env: Environment): Promise<string> {
747
+ const chatId = env.getActiveChatId();
748
+ const FC = '$FC';
749
+ if (chatId && instructions.indexOf(`<${AgentInstance.FileTag}>`) > 0) {
750
+ const ext = extractAndRemoveAllXmlTaggedText(instructions, AgentInstance.FileTag, FC);
751
+ if (ext.extracted) {
752
+ instructions = ext.updatedText;
753
+ for (let i = 0; i < ext.extracted.length; ++i) {
754
+ const fileName = ext.extracted[i];
755
+ const contents = await loadLocalAgentResult(chatId, fileName);
756
+ if (contents) instructions = instructions.replace(FC, contents);
757
+ }
758
+ }
759
+ }
760
+ return instructions;
680
761
  }
681
762
 
682
763
  maybeValidateJsonResponse(response: string | undefined): object | undefined {
@@ -812,6 +893,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
812
893
  const p = await findProviderForLLM(this.llm, env);
813
894
  const agentName = this.name;
814
895
  const chatId = env.getAgentChatId() || agentName;
896
+ await checkCancelled(chatId);
815
897
  let isplnr = this.isPlanner();
816
898
  const isflow = !isplnr && this.isFlowExecutor();
817
899
  const iseval = !isplnr && !isflow && this.isEvaluator();
@@ -875,7 +957,8 @@ Only return a pure JSON object with no extra text, annotations etc.`;
875
957
  }
876
958
  let tmpMsg = message;
877
959
  if (extractedText?.extracted) {
878
- tmpMsg = `${tmpMsg}\n${extractedText.extracted.join('\n')}`;
960
+ const s = await this.maybeFillInFileContents(extractedText.extracted.join('\n'), env);
961
+ tmpMsg = `${tmpMsg}\n${s}`;
879
962
  }
880
963
  const hmsg = await this.maybeAddRelevantDocuments(
881
964
  this.maybeAddFlowContext(tmpMsg, env),
@@ -909,7 +992,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
909
992
  let response: AIResponse = await p.invoke(msgs, externalToolSpecs);
910
993
  const v = this.getValidationEvent();
911
994
  if (v) {
912
- response = await this.handleValidation(response, v, msgs, p);
995
+ response = await this.handleValidation(response, v, msgs, p, chatId);
913
996
  }
914
997
  if (!iseval)
915
998
  await AgentInstance.maybeEvaluateResponse(
@@ -927,6 +1010,13 @@ Only return a pure JSON object with no extra text, annotations etc.`;
927
1010
  await saveAgentChatSession(chatId, msgs, env);
928
1011
  }
929
1012
  if (monitoringEnabled) env.setMonitorEntryLlmResponse(response.content);
1013
+ if (monitoringEnabled && response.sysMsg.usage_metadata) {
1014
+ const u = response.sysMsg.usage_metadata;
1015
+ env.setMonitorEntryLlmTokenUsage(u.input_tokens, u.output_tokens, u.total_tokens);
1016
+ }
1017
+ if (this.saveResponseAs) {
1018
+ await saveAgentResponse(this.saveResponseAs, response.content, env);
1019
+ }
930
1020
  env.setLastResult(response.content);
931
1021
  } catch (err: any) {
932
1022
  logger.error(`Error while invoking ${agentName} - ${err}`);
@@ -987,7 +1077,8 @@ Only return a pure JSON object with no extra text, annotations etc.`;
987
1077
  response: AIResponse,
988
1078
  validationEventName: string,
989
1079
  msgs: BaseMessage[],
990
- provider: AgentServiceProvider
1080
+ provider: AgentServiceProvider,
1081
+ chatId?: string
991
1082
  ): Promise<AIResponse> {
992
1083
  let r: Instance = await this.invokeValidator(response, validationEventName);
993
1084
  const status = r.lookup('status');
@@ -999,6 +1090,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
999
1090
  let attempt = 0;
1000
1091
  let delay = this.retryObj.getNextDelayMs(attempt);
1001
1092
  while (delay) {
1093
+ if (chatId) await checkCancelled(chatId);
1002
1094
  msgs.push(assistantMessage(resp.content));
1003
1095
  const vs = JSON.stringify(r.asSerializableObject());
1004
1096
  msgs.push(
@@ -1050,93 +1142,139 @@ Only return a pure JSON object with no extra text, annotations etc.`;
1050
1142
  return result;
1051
1143
  }
1052
1144
 
1053
- private async maybeAddRelevantDocuments(message: string, env: Environment): Promise<string> {
1054
- if (this.documents && this.documents.length > 0) {
1055
- try {
1056
- const docNames = this.documents.split(',').map(d => d.trim());
1057
- const docTitles = resolveDocumentAliases(docNames);
1145
+ private async maybeAddRelevantDocuments(message: string, _env: Environment): Promise<string> {
1146
+ const hasDocuments = this.documents && this.documents.length > 0;
1147
+ const hasTopics = this.topics && this.topics.length > 0;
1058
1148
 
1059
- const searchQuery = message;
1149
+ if (!hasDocuments && !hasTopics) {
1150
+ return message;
1151
+ }
1060
1152
 
1061
- try {
1062
- const semanticResult: any[] = await parseHelper(
1063
- `{${CoreAIModuleName}/Document {content? "${searchQuery.replace(/"/g, '\\"')}"}}`,
1064
- env
1065
- );
1153
+ try {
1154
+ let containerTags: string[] = [];
1155
+ let topicDocumentTitles: string[] = [];
1066
1156
 
1067
- if (semanticResult && semanticResult.length > 0) {
1068
- const docs: Instance[] = [];
1069
- for (const doc of semanticResult) {
1070
- const docTitle = doc.lookup ? doc.lookup('title') : doc.title;
1071
- if (AgentInstance.docTitlesMatch(docTitle, docTitles)) {
1072
- docs.push(
1073
- doc instanceof Instance
1074
- ? doc
1075
- : Instance.newWithAttributes(doc, new Map(Object.entries(doc)))
1076
- );
1077
- }
1078
- }
1157
+ if (hasTopics) {
1158
+ const topicNames = resolveTopicNames(this.topics!);
1159
+ containerTags = topicNames;
1160
+ topicDocumentTitles = getAllDocumentsForTopics(topicNames);
1161
+ }
1079
1162
 
1080
- if (docs.length > 0) {
1081
- return message.concat('\n\nRelevant context from documents:\n').concat(
1082
- docs
1083
- .map((v: Instance) => {
1084
- return `Document: ${v.lookup('title')}\n${v.lookup('content') as string}`;
1085
- })
1086
- .join('\n\n---\n\n')
1087
- );
1088
- }
1089
- }
1090
- } catch (semanticErr) {
1091
- logger.debug(
1092
- `Semantic search is not available, falling back to title-based filtering: ${semanticErr}`
1093
- );
1094
- }
1163
+ if (containerTags.length === 0) {
1164
+ containerTags = [this.getFqName()];
1165
+ }
1095
1166
 
1096
- const result: any[] = await parseHelper(`{${CoreAIModuleName}/Document? {}}`, env);
1097
- if (result && result.length > 0) {
1098
- const docs: Instance[] = [];
1099
- for (let i = 0; i < result.length; ++i) {
1100
- const v: any = result[i];
1101
- const docTitle: string | undefined = AgentInstance.getDocumentTitle(v);
1102
-
1103
- if (docTitle && docTitles.includes(docTitle)) {
1104
- if (v instanceof Instance) {
1105
- docs.push(v);
1106
- }
1107
- }
1108
- }
1167
+ let documentTitles: string[] = [];
1168
+ let documentRefs: string[] = [];
1109
1169
 
1110
- if (docs.length > 0) {
1111
- return message.concat('\n\nRelevant context from documents:\n').concat(
1112
- docs
1113
- .map((v: Instance) => {
1114
- return v.lookup('content') as string;
1115
- })
1116
- .join('\n\n')
1117
- );
1170
+ if (hasDocuments) {
1171
+ const documentEntries = this.documents!.split(',')
1172
+ .map(d => d.trim())
1173
+ .filter(Boolean);
1174
+ documentTitles = resolveDocumentAliases(documentEntries);
1175
+ documentRefs = documentEntries.filter(d => d.startsWith('document-service://'));
1176
+ }
1177
+
1178
+ const allDocumentTitles = [...new Set([...topicDocumentTitles, ...documentTitles])];
1179
+
1180
+ const kgConfig = getKnowledgeGraphConfig();
1181
+ const serviceUrl = kgConfig?.serviceUrl?.trim() || process.env.KNOWLEDGE_SERVICE_URL || null;
1182
+
1183
+ if (serviceUrl) {
1184
+ return await this.queryRemoteKnowledgeService(
1185
+ message,
1186
+ serviceUrl,
1187
+ containerTags,
1188
+ allDocumentTitles,
1189
+ documentRefs
1190
+ );
1191
+ }
1192
+
1193
+ // Local mode: embed documents into pgvector/lancedb and query
1194
+ const retriever = getDocumentRetriever();
1195
+ const aiModule = isModule(DefaultModuleName + '.ai')
1196
+ ? fetchModule(DefaultModuleName + '.ai')
1197
+ : null;
1198
+ if (aiModule) {
1199
+ for (const title of allDocumentTitles) {
1200
+ const url = aiModule.getDocument(title);
1201
+ if (url) {
1202
+ await retriever.processDocument(title, url);
1118
1203
  }
1119
1204
  }
1120
- } catch (err) {
1121
- logger.debug(`Error retrieving documents: ${err}`);
1122
1205
  }
1206
+
1207
+ const contextString = await retriever.query(
1208
+ message,
1209
+ allDocumentTitles.length > 0 ? allDocumentTitles : undefined,
1210
+ 10
1211
+ );
1212
+
1213
+ if (contextString.trim().length > 0) {
1214
+ return message.concat('\n\nRelevant context from documents:\n').concat(contextString);
1215
+ }
1216
+ } catch (err) {
1217
+ logger.debug(`[KNOWLEDGE] Knowledge retrieval failed: ${err}`);
1123
1218
  }
1219
+
1124
1220
  return message;
1125
1221
  }
1126
1222
 
1127
- private static docTitlesMatch(title: string | undefined, docNames: string[]): boolean {
1128
- return title !== undefined && docNames.includes(title);
1129
- }
1223
+ private async queryRemoteKnowledgeService(
1224
+ message: string,
1225
+ serviceUrl: string,
1226
+ containerTags: string[],
1227
+ documentTitles: string[],
1228
+ documentRefs: string[]
1229
+ ): Promise<string> {
1230
+ const options = {
1231
+ chunkLimit: 10,
1232
+ entityLimit: 20,
1233
+ includeChunks: true,
1234
+ includeEntities: true,
1235
+ includeEdges: true,
1236
+ documentTitles: documentTitles.length > 0 ? documentTitles : undefined,
1237
+ documentRefs: documentRefs.length > 0 ? documentRefs : undefined,
1238
+ };
1130
1239
 
1131
- private static getDocumentTitle(doc: any): string | undefined {
1132
- if (typeof doc.lookup === 'function') {
1133
- return doc.lookup('title') as string | undefined;
1134
- } else if (doc.attributes) {
1135
- return doc.attributes.get('title') as string | undefined;
1136
- } else if (doc.title) {
1137
- return doc.title;
1240
+ let response = await fetch(`${serviceUrl}/api/knowledge/query`, {
1241
+ method: 'POST',
1242
+ headers: { 'Content-Type': 'application/json' },
1243
+ body: JSON.stringify({ query: message, containerTags, ...options }),
1244
+ });
1245
+
1246
+ if (!response.ok) {
1247
+ response = await fetch(`${serviceUrl}/knowledge.core/ApiKnowledgeQuery`, {
1248
+ method: 'POST',
1249
+ headers: { 'Content-Type': 'application/json' },
1250
+ body: JSON.stringify({
1251
+ queryText: message,
1252
+ containerTagsJson: JSON.stringify(containerTags),
1253
+ documentTitlesJson: JSON.stringify(options.documentTitles || []),
1254
+ documentRefsJson: JSON.stringify(options.documentRefs || []),
1255
+ optionsJson: JSON.stringify({
1256
+ includeChunks: options.includeChunks,
1257
+ includeEntities: options.includeEntities,
1258
+ includeEdges: options.includeEdges,
1259
+ chunkLimit: options.chunkLimit,
1260
+ entityLimit: options.entityLimit,
1261
+ }),
1262
+ }),
1263
+ });
1138
1264
  }
1139
- return undefined;
1265
+
1266
+ if (response.ok) {
1267
+ const rawPayload: any = await response.json();
1268
+ const first = Array.isArray(rawPayload) ? rawPayload[0] : rawPayload;
1269
+ const payload =
1270
+ first && typeof first === 'object' && first.KnowledgeQuery ? first.KnowledgeQuery : first;
1271
+ const contextString = payload?.contextString || '';
1272
+ if (contextString.trim().length > 0) {
1273
+ return message.concat('\n\nRelevant context from documents:\n').concat(contextString);
1274
+ }
1275
+ }
1276
+
1277
+ return message;
1140
1278
  }
1141
1279
 
1142
1280
  private static ToolsCache = new Map<string, string>();
@@ -1443,20 +1581,15 @@ export async function processAgentLearning(
1443
1581
  const LocalAgentgFlow = true;
1444
1582
  const LocalFlowStepsRootDirName = 'flows';
1445
1583
  const AgentFlowStep = 'AgentFlowStep';
1584
+ const CancelledAgentEntity = 'CancelledAgent';
1446
1585
 
1447
1586
  export async function saveFlowStepResultLocally(
1448
1587
  chatId: string,
1449
1588
  step: string,
1450
1589
  result: string,
1451
- suspensionId: string
1452
- ): Promise<Instance | undefined> {
1590
+ suspensionId?: string | undefined
1591
+ ): Promise<string> {
1453
1592
  const fs = await getFileSystem();
1454
- const attrs = newInstanceAttributes()
1455
- .set('chatId', chatId)
1456
- .set('step', step)
1457
- .set('result', result)
1458
- .set('suspensionId', suspensionId);
1459
- const inst = makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1460
1593
  const rootDirName = LocalFlowStepsRootDirName;
1461
1594
  if (!(await fs.exists(rootDirName))) {
1462
1595
  await fs.mkdir(rootDirName);
@@ -1469,8 +1602,18 @@ export async function saveFlowStepResultLocally(
1469
1602
  if (await fs.exists(fileName)) {
1470
1603
  await fs.unlink(fileName);
1471
1604
  }
1472
- await fs.writeFile(fileName, JSON.stringify(inst.attributesAsObject(true)));
1473
- return inst;
1605
+ let s = result;
1606
+ if (suspensionId) {
1607
+ const attrs = newInstanceAttributes()
1608
+ .set('chatId', chatId)
1609
+ .set('step', step)
1610
+ .set('result', result)
1611
+ .set('suspensionId', suspensionId);
1612
+ const inst = makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1613
+ s = JSON.stringify(inst.attributesAsObject(true));
1614
+ }
1615
+ await fs.writeFile(fileName, s);
1616
+ return s;
1474
1617
  }
1475
1618
 
1476
1619
  export async function saveFlowStepResult(
@@ -1479,9 +1622,10 @@ export async function saveFlowStepResult(
1479
1622
  result: string,
1480
1623
  suspensionId: string,
1481
1624
  env: Environment
1482
- ): Promise<Instance | undefined> {
1625
+ ): Promise<boolean> {
1483
1626
  if (LocalAgentgFlow) {
1484
- return await saveFlowStepResultLocally(chatId, step, result, suspensionId);
1627
+ await saveFlowStepResultLocally(chatId, step, result, suspensionId);
1628
+ return true;
1485
1629
  } else {
1486
1630
  const t = `${CoreAIModuleName}/${AgentFlowStep}`;
1487
1631
  try {
@@ -1495,11 +1639,11 @@ export async function saveFlowStepResult(
1495
1639
  undefined,
1496
1640
  env
1497
1641
  );
1498
- if (isInstanceOfType(inst, t)) return inst;
1499
- else return undefined;
1642
+ if (isInstanceOfType(inst, t)) return true;
1643
+ else return false;
1500
1644
  } catch (reason: any) {
1501
1645
  logger.error(`failed to save flow result for step ${step} - ${reason}`);
1502
- return undefined;
1646
+ return false;
1503
1647
  }
1504
1648
  }
1505
1649
  }
@@ -1537,10 +1681,10 @@ export async function loadFlowStepResults(chatId: string): Promise<Instance[]> {
1537
1681
  }
1538
1682
  }
1539
1683
 
1540
- export async function loadLocalFlowStep(
1684
+ export async function loadLocalAgentResult(
1541
1685
  chatId: string,
1542
1686
  step: string
1543
- ): Promise<Instance | undefined> {
1687
+ ): Promise<string | undefined> {
1544
1688
  const fs = await getFileSystem();
1545
1689
  const dirName = `${LocalFlowStepsRootDirName}/${chatId}`;
1546
1690
  if (await fs.exists(dirName)) {
@@ -1548,10 +1692,7 @@ export async function loadLocalFlowStep(
1548
1692
  for (let i = 0; i < fileNames.length; ++i) {
1549
1693
  const fileName = fileNames[i];
1550
1694
  if (fileName === step) {
1551
- const attrs = objectToInstanceAttributes(
1552
- JSON.parse(await fs.readFile(`${dirName}/${fileName}`))
1553
- );
1554
- return makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1695
+ return await fs.readFile(`${dirName}/${fileName}`);
1555
1696
  }
1556
1697
  }
1557
1698
  return undefined;
@@ -1560,6 +1701,19 @@ export async function loadLocalFlowStep(
1560
1701
  }
1561
1702
  }
1562
1703
 
1704
+ export async function loadLocalFlowStep(
1705
+ chatId: string,
1706
+ step: string
1707
+ ): Promise<Instance | undefined> {
1708
+ const contents = await loadLocalAgentResult(chatId, step);
1709
+ if (contents) {
1710
+ const attrs = objectToInstanceAttributes(JSON.parse(contents));
1711
+ return makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1712
+ } else {
1713
+ return undefined;
1714
+ }
1715
+ }
1716
+
1563
1717
  export async function loadFlowStep(chatId: string, step: string): Promise<Instance | undefined> {
1564
1718
  if (LocalAgentgFlow) {
1565
1719
  return await loadLocalFlowStep(chatId, step);
@@ -1580,6 +1734,72 @@ export async function loadFlowStep(chatId: string, step: string): Promise<Instan
1580
1734
  }
1581
1735
  }
1582
1736
 
1737
+ export async function registerTopic(
1738
+ name: string,
1739
+ documents: string | undefined,
1740
+ _env: Environment
1741
+ ): Promise<any> {
1742
+ // Register in local registry
1743
+ registerTopicInRegistry(name, documents ?? undefined);
1744
+
1745
+ // Sync to knowledge service if configured
1746
+ const kgConfig = getKnowledgeGraphConfig();
1747
+ const serviceUrl = kgConfig?.serviceUrl?.trim() || process.env.KNOWLEDGE_SERVICE_URL || null;
1748
+
1749
+ if (serviceUrl) {
1750
+ try {
1751
+ // Parse document list
1752
+ const docList = documents
1753
+ ? documents
1754
+ .split(',')
1755
+ .map(d => d.trim())
1756
+ .filter(Boolean)
1757
+ : [];
1758
+
1759
+ // Create or update topic in knowledge service
1760
+ let response = await fetch(`${serviceUrl}/api/knowledge/topics`, {
1761
+ method: 'POST',
1762
+ headers: {
1763
+ 'Content-Type': 'application/json',
1764
+ },
1765
+ body: JSON.stringify({
1766
+ name,
1767
+ description: `Topic ${name} with ${docList.length} documents`,
1768
+ documentTitles: docList,
1769
+ }),
1770
+ });
1771
+
1772
+ if (!response.ok) {
1773
+ // Fallback to Agentlang entity path for deployed knowledge-service
1774
+ response = await fetch(`${serviceUrl}/knowledge.core/Topic`, {
1775
+ method: 'POST',
1776
+ headers: {
1777
+ 'Content-Type': 'application/json',
1778
+ },
1779
+ body: JSON.stringify({
1780
+ name,
1781
+ description: `Topic ${name} with ${docList.length} documents`,
1782
+ type: 'manual',
1783
+ documentCount: docList.length,
1784
+ }),
1785
+ });
1786
+ }
1787
+
1788
+ if (!response.ok) {
1789
+ const errorText = await response.text();
1790
+ logger.debug(`[KNOWLEDGE] Failed to sync topic to service: ${errorText}`);
1791
+ } else {
1792
+ logger.debug(`[KNOWLEDGE] Synced topic "${name}" to knowledge service`);
1793
+ }
1794
+ } catch (err) {
1795
+ // Don't fail topic registration if service sync fails
1796
+ logger.debug(`[KNOWLEDGE] Error syncing topic to service: ${err}`);
1797
+ }
1798
+ }
1799
+
1800
+ return { topic: { name, documents: documents ?? '' } };
1801
+ }
1802
+
1583
1803
  export async function fetchAndCreateDocument(
1584
1804
  title: string,
1585
1805
  url: string,
@@ -1639,3 +1859,17 @@ export async function fetchAndCreateDocument(
1639
1859
  throw new Error(`Failed to fetch document: ${title} from ${url}`);
1640
1860
  }
1641
1861
  }
1862
+
1863
+ export async function saveAgentResponse(
1864
+ fileName: string,
1865
+ response: string,
1866
+ env: Environment
1867
+ ): Promise<string> {
1868
+ const chatId = env.getActiveChatId();
1869
+ if (chatId) {
1870
+ await saveFlowStepResultLocally(chatId, fileName, response);
1871
+ } else {
1872
+ logger.warn(`No chatId set, ${fileName} was not saved.`);
1873
+ }
1874
+ return response;
1875
+ }
@@ -749,5 +749,9 @@ export function migrationDowns(inst: Instance): string[] | undefined {
749
749
 
750
750
  export async function doRawQuery(q: any): Promise<any> {
751
751
  const qs = objectToQueryPattern(q);
752
- return await parseAndEvaluateStatement(qs);
752
+ const result = await parseAndEvaluateStatement(qs);
753
+ return {
754
+ query: qs,
755
+ result: result.map((res: any) => (res.attributes ? Object.fromEntries(res.attributes) : res)),
756
+ };
753
757
  }