agentlang 0.10.2 → 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 (145) 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 +171 -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 +51 -1
  19. package/out/language/generated/ast.d.ts.map +1 -1
  20. package/out/language/generated/ast.js +40 -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 +286 -190
  24. package/out/language/generated/grammar.js.map +1 -1
  25. package/out/language/main.cjs +828 -694
  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 +30 -97
  30. package/out/language/parser.js.map +1 -1
  31. package/out/language/syntax.d.ts +2 -0
  32. package/out/language/syntax.d.ts.map +1 -1
  33. package/out/language/syntax.js +6 -0
  34. package/out/language/syntax.js.map +1 -1
  35. package/out/runtime/api.d.ts.map +1 -1
  36. package/out/runtime/api.js +22 -0
  37. package/out/runtime/api.js.map +1 -1
  38. package/out/runtime/defs.d.ts +1 -0
  39. package/out/runtime/defs.d.ts.map +1 -1
  40. package/out/runtime/defs.js +2 -1
  41. package/out/runtime/defs.js.map +1 -1
  42. package/out/runtime/document-retriever.d.ts +24 -0
  43. package/out/runtime/document-retriever.d.ts.map +1 -0
  44. package/out/runtime/document-retriever.js +258 -0
  45. package/out/runtime/document-retriever.js.map +1 -0
  46. package/out/runtime/embeddings/chunker.d.ts +18 -0
  47. package/out/runtime/embeddings/chunker.d.ts.map +1 -1
  48. package/out/runtime/embeddings/chunker.js +47 -15
  49. package/out/runtime/embeddings/chunker.js.map +1 -1
  50. package/out/runtime/embeddings/openai.d.ts.map +1 -1
  51. package/out/runtime/embeddings/openai.js +22 -9
  52. package/out/runtime/embeddings/openai.js.map +1 -1
  53. package/out/runtime/embeddings/provider.d.ts +1 -0
  54. package/out/runtime/embeddings/provider.d.ts.map +1 -1
  55. package/out/runtime/embeddings/provider.js +20 -1
  56. package/out/runtime/embeddings/provider.js.map +1 -1
  57. package/out/runtime/integration-client.d.ts +21 -0
  58. package/out/runtime/integration-client.d.ts.map +1 -0
  59. package/out/runtime/integration-client.js +112 -0
  60. package/out/runtime/integration-client.js.map +1 -0
  61. package/out/runtime/integrations.d.ts.map +1 -1
  62. package/out/runtime/integrations.js +20 -9
  63. package/out/runtime/integrations.js.map +1 -1
  64. package/out/runtime/interpreter.d.ts +1 -0
  65. package/out/runtime/interpreter.d.ts.map +1 -1
  66. package/out/runtime/interpreter.js +152 -17
  67. package/out/runtime/interpreter.js.map +1 -1
  68. package/out/runtime/loader.d.ts.map +1 -1
  69. package/out/runtime/loader.js +70 -7
  70. package/out/runtime/loader.js.map +1 -1
  71. package/out/runtime/logger.d.ts.map +1 -1
  72. package/out/runtime/logger.js +8 -1
  73. package/out/runtime/logger.js.map +1 -1
  74. package/out/runtime/module.d.ts +10 -0
  75. package/out/runtime/module.d.ts.map +1 -1
  76. package/out/runtime/module.js +68 -3
  77. package/out/runtime/module.js.map +1 -1
  78. package/out/runtime/modules/ai.d.ts +9 -2
  79. package/out/runtime/modules/ai.d.ts.map +1 -1
  80. package/out/runtime/modules/ai.js +219 -67
  81. package/out/runtime/modules/ai.js.map +1 -1
  82. package/out/runtime/resolvers/interface.d.ts +4 -0
  83. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  84. package/out/runtime/resolvers/interface.js +14 -1
  85. package/out/runtime/resolvers/interface.js.map +1 -1
  86. package/out/runtime/resolvers/sqldb/database.d.ts +2 -0
  87. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  88. package/out/runtime/resolvers/sqldb/database.js +142 -126
  89. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  90. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  91. package/out/runtime/resolvers/sqldb/dbutil.js +8 -0
  92. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  93. package/out/runtime/resolvers/sqldb/impl.d.ts +1 -0
  94. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  95. package/out/runtime/resolvers/sqldb/impl.js +7 -0
  96. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  97. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  98. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  99. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  100. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  101. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  102. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  103. package/out/runtime/resolvers/vector/types.js +2 -0
  104. package/out/runtime/resolvers/vector/types.js.map +1 -0
  105. package/out/runtime/services/documentFetcher.d.ts.map +1 -1
  106. package/out/runtime/services/documentFetcher.js +21 -6
  107. package/out/runtime/services/documentFetcher.js.map +1 -1
  108. package/out/runtime/state.d.ts +19 -1
  109. package/out/runtime/state.d.ts.map +1 -1
  110. package/out/runtime/state.js +36 -1
  111. package/out/runtime/state.js.map +1 -1
  112. package/out/syntaxes/agentlang.monarch.js +1 -1
  113. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  114. package/package.json +19 -19
  115. package/src/api/http.ts +197 -37
  116. package/src/cli/main.ts +3 -0
  117. package/src/language/agentlang-validator.ts +3 -0
  118. package/src/language/agentlang.langium +3 -1
  119. package/src/language/error-reporter.ts +1028 -0
  120. package/src/language/generated/ast.ts +62 -0
  121. package/src/language/generated/grammar.ts +286 -190
  122. package/src/language/parser.ts +31 -100
  123. package/src/language/syntax.ts +8 -0
  124. package/src/runtime/api.ts +31 -0
  125. package/src/runtime/defs.ts +2 -1
  126. package/src/runtime/document-retriever.ts +311 -0
  127. package/src/runtime/embeddings/chunker.ts +52 -14
  128. package/src/runtime/embeddings/openai.ts +27 -9
  129. package/src/runtime/embeddings/provider.ts +22 -1
  130. package/src/runtime/integration-client.ts +158 -0
  131. package/src/runtime/integrations.ts +20 -11
  132. package/src/runtime/interpreter.ts +142 -12
  133. package/src/runtime/loader.ts +83 -5
  134. package/src/runtime/logger.ts +12 -1
  135. package/src/runtime/module.ts +78 -3
  136. package/src/runtime/modules/ai.ts +263 -76
  137. package/src/runtime/resolvers/interface.ts +19 -1
  138. package/src/runtime/resolvers/sqldb/database.ts +158 -130
  139. package/src/runtime/resolvers/sqldb/dbutil.ts +8 -0
  140. package/src/runtime/resolvers/sqldb/impl.ts +8 -0
  141. package/src/runtime/resolvers/vector/lancedb-store.ts +187 -0
  142. package/src/runtime/resolvers/vector/types.ts +39 -0
  143. package/src/runtime/services/documentFetcher.ts +21 -6
  144. package/src/runtime/state.ts +40 -1
  145. package/src/syntaxes/agentlang.monarch.ts +1 -1
@@ -1,6 +1,7 @@
1
1
  import { OpenAIEmbeddings } from '@langchain/openai';
2
2
  import { EmbeddingProvider, EmbeddingProviderConfig } from './provider.js';
3
3
  import { getLocalEnv } from '../auth/defs.js';
4
+ import { logger } from '../logger.js';
4
5
 
5
6
  export interface OpenAIEmbeddingConfig extends EmbeddingProviderConfig {
6
7
  model?: string;
@@ -14,6 +15,9 @@ export class OpenAIEmbeddingProvider extends EmbeddingProvider {
14
15
  constructor(config?: EmbeddingProviderConfig) {
15
16
  super(config || {});
16
17
  this.openaiConfig = (this.config as OpenAIEmbeddingConfig) || {};
18
+ logger.debug(
19
+ `[OPENAI-EMBEDDING] Provider created with model: ${this.openaiConfig.model || 'default'}`
20
+ );
17
21
  }
18
22
 
19
23
  protected createEmbeddings(): OpenAIEmbeddings {
@@ -21,26 +25,40 @@ export class OpenAIEmbeddingProvider extends EmbeddingProvider {
21
25
  apiKey: this.resolveApiKey(),
22
26
  };
23
27
 
24
- if (this.openaiConfig.model) {
25
- config.model = this.openaiConfig.model;
28
+ // Use this.config directly since this.openaiConfig is not initialized yet
29
+ // during parent constructor (super() calls createEmbeddings before child init)
30
+ const openaiConfig = (this.config as OpenAIEmbeddingConfig) || {};
31
+
32
+ if (openaiConfig?.model) {
33
+ config.model = openaiConfig.model;
26
34
  }
27
35
 
28
- if (this.openaiConfig.dimensions) {
29
- config.dimensions = this.openaiConfig.dimensions;
36
+ if (openaiConfig?.dimensions) {
37
+ config.dimensions = openaiConfig.dimensions;
30
38
  }
31
39
 
32
- if (this.openaiConfig.maxRetries !== undefined) {
33
- config.maxRetries = this.openaiConfig.maxRetries;
40
+ if (openaiConfig?.maxRetries !== undefined) {
41
+ config.maxRetries = openaiConfig.maxRetries;
34
42
  }
35
43
 
36
44
  return new OpenAIEmbeddings(config);
37
45
  }
38
46
 
39
47
  protected resolveApiKey(): string {
40
- if (this.openaiConfig.apiKey) {
41
- return this.openaiConfig.apiKey;
48
+ // Use this.config directly since this.openaiConfig may not be initialized yet
49
+ // during constructor (createEmbeddings is called during super())
50
+ const config = this.openaiConfig || (this.config as OpenAIEmbeddingConfig) || {};
51
+ if (config.apiKey) {
52
+ return config.apiKey;
53
+ }
54
+ const envKey = process.env.AGENTLANG_OPENAI_KEY || getLocalEnv('AGENTLANG_OPENAI_KEY');
55
+ if (envKey) {
56
+ return envKey;
42
57
  }
43
- return process.env.AGENTLANG_OPENAI_KEY || getLocalEnv('AGENTLANG_OPENAI_KEY') || '';
58
+ logger.warn(
59
+ `[OPENAI-EMBEDDING] No API key found! Set AGENTLANG_OPENAI_KEY environment variable.`
60
+ );
61
+ return '';
44
62
  }
45
63
 
46
64
  getProviderName(): string {
@@ -1,4 +1,5 @@
1
1
  import { Embeddings } from '@langchain/core/embeddings';
2
+ import { logger } from '../logger.js';
2
3
 
3
4
  export interface EmbeddingProviderConfig {
4
5
  chunkSize?: number;
@@ -23,7 +24,27 @@ export abstract class EmbeddingProvider {
23
24
  abstract getProviderName(): string;
24
25
 
25
26
  async embedText(text: string): Promise<number[]> {
26
- return await this.embeddings.embedQuery(text);
27
+ logger.debug(`[EMBEDDING-PROVIDER] embedText called (${text.length} chars)`);
28
+ const startTime = Date.now();
29
+ const result = await this.embeddings.embedQuery(text);
30
+ const duration = Date.now() - startTime;
31
+ logger.debug(
32
+ `[EMBEDDING-PROVIDER] embedText completed in ${duration}ms (${result.length} dimensions)`
33
+ );
34
+ return result;
35
+ }
36
+
37
+ async embedTexts(texts: string[]): Promise<number[][]> {
38
+ if (texts.length === 0) return [];
39
+ if (texts.length === 1) return [await this.embedText(texts[0])];
40
+ logger.debug(`[EMBEDDING-PROVIDER] embedTexts called (${texts.length} texts)`);
41
+ const startTime = Date.now();
42
+ const results = await this.embeddings.embedDocuments(texts);
43
+ const duration = Date.now() - startTime;
44
+ logger.debug(
45
+ `[EMBEDDING-PROVIDER] embedTexts completed in ${duration}ms (${texts.length} texts, ${results[0]?.length || 0} dimensions)`
46
+ );
47
+ return results;
27
48
  }
28
49
 
29
50
  getConfig(): EmbeddingProviderConfig {
@@ -0,0 +1,158 @@
1
+ let host: string | undefined;
2
+ let headers: Record<string, string> | undefined;
3
+
4
+ export function configureIntegrationClient(h: string, hdrs?: Record<string, string>): void {
5
+ host = h;
6
+ headers = hdrs;
7
+ }
8
+
9
+ export function isIntegrationClientConfigured(): boolean {
10
+ return host !== undefined;
11
+ }
12
+
13
+ export async function getIntegrationAuthHeaders(
14
+ integrationName: string
15
+ ): Promise<Record<string, string>> {
16
+ if (!host) {
17
+ throw new Error('Integration client not configured — call configureIntegrationClient() first');
18
+ }
19
+
20
+ const url = `${host}/integmanager.auth/authHeaders/${encodeURIComponent(integrationName)}`;
21
+ const response = await fetch(url, {
22
+ method: 'GET',
23
+ headers: { 'Content-Type': 'application/json', ...headers },
24
+ });
25
+
26
+ if (!response.ok) {
27
+ throw new Error(
28
+ `Failed to get auth headers for integration "${integrationName}": ${response.status} ${response.statusText}`
29
+ );
30
+ }
31
+
32
+ const data = await response.json();
33
+ // The response is either the entity directly or wrapped in an array
34
+ const result = Array.isArray(data) ? data[0] : data;
35
+ return result?.headers ?? {};
36
+ }
37
+
38
+ export async function refreshIntegrationAuth(integrationName: string): Promise<void> {
39
+ if (!host) {
40
+ throw new Error('Integration client not configured — call configureIntegrationClient() first');
41
+ }
42
+
43
+ const url = `${host}/integmanager.auth/authRefresh`;
44
+ const response = await fetch(url, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json', ...headers },
47
+ body: JSON.stringify({ integrationName }),
48
+ });
49
+
50
+ if (!response.ok) {
51
+ throw new Error(
52
+ `Failed to refresh auth for integration "${integrationName}": ${response.status} ${response.statusText}`
53
+ );
54
+ }
55
+ }
56
+
57
+ export async function integrationAuthFetch(
58
+ integrationName: string,
59
+ url: string | URL,
60
+ options: RequestInit = {}
61
+ ): Promise<Response> {
62
+ const authHeaders = await getIntegrationAuthHeaders(integrationName);
63
+ const mergedHeaders = { ...authHeaders, ...((options.headers as Record<string, string>) || {}) };
64
+ return fetch(url, { ...options, headers: mergedHeaders });
65
+ }
66
+
67
+ // --- OAuth consent flow helpers ---
68
+
69
+ export async function getOAuthAuthorizeUrl(
70
+ integrationName: string,
71
+ redirectUri: string
72
+ ): Promise<{ authorizationUrl: string; state: string }> {
73
+ if (!host) {
74
+ throw new Error('Integration client not configured — call configureIntegrationClient() first');
75
+ }
76
+
77
+ const params = new URLSearchParams({
78
+ action: 'authorize',
79
+ integrationName,
80
+ redirectUri,
81
+ });
82
+ const url = `${host}/integmanager.auth/oauthFlow?${params.toString()}`;
83
+ const response = await fetch(url, {
84
+ method: 'GET',
85
+ headers: { 'Content-Type': 'application/json', ...headers },
86
+ });
87
+
88
+ if (!response.ok) {
89
+ throw new Error(
90
+ `Failed to get OAuth authorize URL for "${integrationName}": ${response.status} ${response.statusText}`
91
+ );
92
+ }
93
+
94
+ const data = await response.json();
95
+ const result = Array.isArray(data) ? data[0] : data;
96
+ return { authorizationUrl: result.authorizationUrl, state: result.state };
97
+ }
98
+
99
+ export async function exchangeOAuthCode(
100
+ integrationName: string,
101
+ code: string,
102
+ state: string
103
+ ): Promise<{ accessToken: string; refreshToken: string; expiresIn: number; tokenType: string }> {
104
+ if (!host) {
105
+ throw new Error('Integration client not configured — call configureIntegrationClient() first');
106
+ }
107
+
108
+ const url = `${host}/integmanager.auth/oauthFlow`;
109
+ const response = await fetch(url, {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json', ...headers },
112
+ body: JSON.stringify({ action: 'exchange', integrationName, code, state }),
113
+ });
114
+
115
+ if (!response.ok) {
116
+ throw new Error(
117
+ `Failed to exchange OAuth code for "${integrationName}": ${response.status} ${response.statusText}`
118
+ );
119
+ }
120
+
121
+ const data = await response.json();
122
+ const result = Array.isArray(data) ? data[0] : data;
123
+ return {
124
+ accessToken: result.accessToken,
125
+ refreshToken: result.refreshToken,
126
+ expiresIn: result.expiresIn,
127
+ tokenType: result.tokenType,
128
+ };
129
+ }
130
+
131
+ export async function getIntegrationAccessToken(
132
+ integrationName: string
133
+ ): Promise<{ accessToken: string; expiresIn: number; tokenType: string }> {
134
+ if (!host) {
135
+ throw new Error('Integration client not configured — call configureIntegrationClient() first');
136
+ }
137
+
138
+ const params = new URLSearchParams({ integrationName });
139
+ const url = `${host}/integmanager.auth/oauthToken?${params.toString()}`;
140
+ const response = await fetch(url, {
141
+ method: 'GET',
142
+ headers: { 'Content-Type': 'application/json', ...headers },
143
+ });
144
+
145
+ if (!response.ok) {
146
+ throw new Error(
147
+ `Failed to get access token for "${integrationName}": ${response.status} ${response.statusText}`
148
+ );
149
+ }
150
+
151
+ const data = await response.json();
152
+ const result = Array.isArray(data) ? data[0] : data;
153
+ return {
154
+ accessToken: result.accessToken,
155
+ expiresIn: result.expiresIn,
156
+ tokenType: result.tokenType,
157
+ };
158
+ }
@@ -1,7 +1,6 @@
1
- import { Instance } from './module.js';
2
1
  import { isString } from './util.js';
3
2
 
4
- const Integrations = new Map<string, Instance>();
3
+ const Integrations = new Map<string, any>();
5
4
 
6
5
  const IntegManagerModel = 'integmanager.core';
7
6
 
@@ -16,7 +15,8 @@ export async function prepareIntegrations(
16
15
  const keys = [...integConfig.keys()];
17
16
  for (let i = 0; i < keys.length; ++i) {
18
17
  const configName = keys[i];
19
- const configPath = integConfig.get(configName);
18
+ const entry = integConfig.get(configName);
19
+ const configPath = typeof entry === 'string' ? entry : entry?.config;
20
20
  if (configPath) {
21
21
  const apiUrl = mkApiUrl(integManagerHost, configPath);
22
22
  try {
@@ -35,9 +35,6 @@ export async function prepareIntegrations(
35
35
  const data = await response.json();
36
36
  if (data.length > 0) {
37
37
  const inst: any = data[0].config;
38
- if (inst.type == 'custom' && isString(inst.parameter)) {
39
- inst.parameter = new Map(Object.entries(JSON.parse(inst.parameter)));
40
- }
41
38
  Integrations.set(configName, inst);
42
39
  } else {
43
40
  console.error(`Integration not found for ${configPath}`);
@@ -84,14 +81,26 @@ function mkApiUrl(integManagerHost: string, configPath: string): string {
84
81
  const parts = configPath.split('/');
85
82
  const integId = parts[0];
86
83
  const configId = parts[1];
87
- return `${integManagerHost}/${IntegManagerModel}/integration/${integId}/integrationConfig/config/${configId}`;
84
+ return `${integManagerHost}/${IntegManagerModel}/integration/${integId}/integrationConfig/config/${configId}?tree=true`;
88
85
  }
89
86
 
90
87
  export function getIntegrationConfig(name: string, configName: string): any {
91
88
  const config: any = Integrations.get(name);
92
- if (config) {
93
- return config.parameter.get(configName);
94
- } else {
95
- return undefined;
89
+ if (!config) return undefined;
90
+ if (config.parameter == null) return undefined;
91
+
92
+ if (config.parameter instanceof Map) {
93
+ return Object.fromEntries(config.parameter).get(configName);
94
+ }
95
+ if (isString(config.parameter)) {
96
+ try {
97
+ return JSON.parse(config.parameter)[configName];
98
+ } catch {
99
+ return undefined;
100
+ }
101
+ }
102
+ if (typeof config.parameter === 'object') {
103
+ return config.parameter[configName];
96
104
  }
105
+ return undefined;
97
106
  }
@@ -47,6 +47,7 @@ import {
47
47
  isEntityInstance,
48
48
  isEventInstance,
49
49
  isInstanceOfType,
50
+ getAllBetweenRelationshipsForEntity,
50
51
  isOneToOneBetweenRelationship,
51
52
  isTimer,
52
53
  makeInstance,
@@ -65,6 +66,7 @@ import {
65
66
  DefaultModuleName,
66
67
  escapeFqName,
67
68
  escapeQueryName,
69
+ firstAliasSpec,
68
70
  fqNameFromPath,
69
71
  isCoreModule,
70
72
  isFqName,
@@ -77,6 +79,7 @@ import {
77
79
  preprocessRawConfig,
78
80
  QuerySuffix,
79
81
  restoreSpecialChars,
82
+ splitFqName,
80
83
  splitRefs,
81
84
  } from './util.js';
82
85
  import { getResolver, getResolverNameForPath } from './resolvers/registry.js';
@@ -88,9 +91,12 @@ import {
88
91
  } from '../language/parser.js';
89
92
  import { ActiveSessionInfo, AdminSession, AdminUserId } from './auth/defs.js';
90
93
  import {
94
+ AgentCancelledException,
91
95
  AgentEntityName,
92
96
  AgentFqName,
93
97
  AgentInstance,
98
+ checkCancelled,
99
+ clearCancellation,
94
100
  findAgentByName,
95
101
  normalizeGeneratedCode,
96
102
  saveFlowStepResult,
@@ -1133,10 +1139,11 @@ export async function evaluateStatement(stmt: Statement, env: Environment): Prom
1133
1139
  handlersPushed = env.pushHandlers(handlers);
1134
1140
  }
1135
1141
  await evaluatePattern(stmt.pattern, env);
1142
+ let skipOuterAlias = false;
1136
1143
  if (hasHints) {
1137
- await maybeHandleEmpty(hints, env);
1144
+ skipOuterAlias = await maybeHandleEmpty(hints, env);
1138
1145
  }
1139
- if (hasHints) {
1146
+ if (hasHints && !skipOuterAlias) {
1140
1147
  maybeBindStatementResultToAlias(hints, env);
1141
1148
  }
1142
1149
  await maybeHandleNotFound(handlers, env);
@@ -1165,7 +1172,7 @@ async function maybeHandleNotFound(handlers: CatchHandlers | undefined, env: Env
1165
1172
  }
1166
1173
  }
1167
1174
 
1168
- async function maybeHandleEmpty(hints: RuntimeHint[], env: Environment) {
1175
+ async function maybeHandleEmpty(hints: RuntimeHint[], env: Environment): Promise<boolean> {
1169
1176
  const lastResult: Result = env.getLastResult();
1170
1177
  if (
1171
1178
  lastResult === null ||
@@ -1177,10 +1184,17 @@ async function maybeHandleEmpty(hints: RuntimeHint[], env: Environment) {
1177
1184
  const newEnv = new Environment('empty-env', env).unsetEventExecutor();
1178
1185
  await evaluateStatement(rh.emptySpec.stmt, newEnv);
1179
1186
  env.setLastResult(newEnv.getLastResult());
1187
+ // If inner statement has its own @as, propagate binding to parent env
1188
+ const innerAlias = firstAliasSpec(rh.emptySpec.stmt);
1189
+ if (innerAlias) {
1190
+ maybeBindStatementResultToAlias(rh.emptySpec.stmt.hints, env);
1191
+ return true; // signal: skip outer @as
1192
+ }
1180
1193
  break;
1181
1194
  }
1182
1195
  }
1183
1196
  }
1197
+ return false;
1184
1198
  }
1185
1199
 
1186
1200
  async function maybeHandleError(
@@ -1571,6 +1585,12 @@ function maybeSetQueryClauses(inst: Instance, qopts: ExtractedQueryOptions) {
1571
1585
  if (qopts.orderByClause) {
1572
1586
  inst.setOrderBy(qopts.orderByClause.colNames, qopts.orderByClause.order === '@desc');
1573
1587
  }
1588
+ if (qopts.limitClause) {
1589
+ inst.setLimit(qopts.limitClause.value);
1590
+ }
1591
+ if (qopts.offsetClause) {
1592
+ inst.setOffset(qopts.offsetClause.value);
1593
+ }
1574
1594
  }
1575
1595
 
1576
1596
  async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
@@ -1624,7 +1644,7 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
1624
1644
  }
1625
1645
  const res: Resolver = await getResolverForPath(entryName, moduleName, env);
1626
1646
  let r: Instance | undefined;
1627
- await computeExprAttributes(inst, undefined, undefined, env);
1647
+ await computeExprAttributes(inst, crud.body?.attributes, inst.attributes, env);
1628
1648
  await setMetaAttributes(inst.attributes, env);
1629
1649
  if (env.isInUpsertMode()) {
1630
1650
  await runPreUpdateEvents(inst, env);
@@ -1913,24 +1933,98 @@ async function computeExprAttributes(
1913
1933
  updatedAttrs?.forEach((v: any, k: string) => {
1914
1934
  if (v !== undefined) newEnv.bind(k, v);
1915
1935
  });
1936
+ // Bind related instances from between-relationships so that @expr references
1937
+ // like DeptEmployee.Department.BudgetMultiplier can be resolved via followReference.
1938
+ const entityFqName = inst.getFqName();
1939
+ const fqParts = splitFqName(entityFqName);
1940
+ const rels = getAllBetweenRelationshipsForEntity(fqParts[0], fqParts[1]);
1941
+ for (const rel of rels) {
1942
+ const isFirst = rel.isFirstNodeName(entityFqName);
1943
+ // Only bind if this entity is on a valid side for single-entity resolution
1944
+ if (rel.isManyToMany()) continue;
1945
+ if (rel.isOneToMany() && isFirst) continue;
1946
+ // Determine the target entity on the other side
1947
+ const targetNode = isFirst ? rel.node2 : rel.node1;
1948
+ const targetAlias = targetNode.alias;
1949
+ let connectedInst: Instance | undefined;
1950
+ // Fast path: check if the environment carries between-rel info for this relationship
1951
+ const betRelInfo = env.getBetweenRelInfo();
1952
+ if (betRelInfo && betRelInfo.relationship.name === rel.name) {
1953
+ connectedInst = betRelInfo.connectedInstance;
1954
+ } else if (inst.lookup(PathAttributeName)) {
1955
+ // Slow path: query the connected instance through the resolver.
1956
+ // Only possible if the current instance has been persisted (has a __path__).
1957
+ try {
1958
+ const targetFqName = targetNode.path.asFqName();
1959
+ const targetParts = splitFqName(targetFqName);
1960
+ const res: Resolver = await getResolverForPath(targetParts[1], targetParts[0], env);
1961
+ const queryInst = Instance.EmptyInstance(targetParts[1], targetParts[0]);
1962
+ const connected: Instance[] = await res.queryConnectedInstances(rel, inst, queryInst);
1963
+ if (connected && connected.length > 0) {
1964
+ connectedInst = connected[0];
1965
+ }
1966
+ } catch (reason: any) {
1967
+ logger.debug(
1968
+ `Relationship query failed (e.g. not yet linked) - skip binding - ${reason}`
1969
+ );
1970
+ }
1971
+ }
1972
+ if (connectedInst) {
1973
+ const relMap = new Map<string, Instance>();
1974
+ relMap.set(targetAlias, connectedInst);
1975
+ newEnv.bind(rel.name, relMap);
1976
+ }
1977
+ }
1978
+ // Build a map of user-provided values for @expr attributes so that
1979
+ // overrides are applied inline during expression evaluation, allowing
1980
+ // dependent expressions to see the user's value immediately.
1981
+ let userExprOverrides: Map<string, Expr> | undefined;
1982
+ if (exprAttrs && origAttrs) {
1983
+ for (let i = 0; i < origAttrs.length; ++i) {
1984
+ const a: SetAttribute = origAttrs[i];
1985
+ const n = a.name;
1986
+ if (exprAttrs.has(n) && !n.endsWith(QuerySuffix) && a.value !== undefined) {
1987
+ if (userExprOverrides === undefined) {
1988
+ userExprOverrides = new Map();
1989
+ }
1990
+ userExprOverrides.set(n, a.value);
1991
+ }
1992
+ }
1993
+ }
1916
1994
  if (exprAttrs) {
1917
1995
  const ks = [...exprAttrs.keys()];
1918
1996
  for (let i = 0; i < ks.length; ++i) {
1919
1997
  const n = ks[i];
1920
- const expr: Expr | undefined = exprAttrs.get(n);
1921
- if (expr) {
1922
- await evaluateExpression(expr, newEnv);
1923
- const v: Result = newEnv.getLastResult();
1924
- newEnv.bind(n, v);
1925
- inst.attributes.set(n, v);
1926
- updatedAttrs?.set(n, v);
1998
+ const userValue = userExprOverrides?.get(n);
1999
+ if (userValue !== undefined) {
2000
+ // User explicitly provided a value for this @expr attribute - use it
2001
+ await evaluateExpression(userValue, newEnv);
2002
+ } else {
2003
+ // No user override - evaluate the @expr expression
2004
+ const expr: Expr | undefined = exprAttrs.get(n);
2005
+ if (expr) {
2006
+ await evaluateExpression(expr, newEnv);
2007
+ } else {
2008
+ continue;
2009
+ }
1927
2010
  }
2011
+ const v: Result = newEnv.getLastResult();
2012
+ newEnv.bind(n, v);
2013
+ inst.attributes.set(n, v);
2014
+ updatedAttrs?.set(n, v);
1928
2015
  }
1929
2016
  }
1930
- if (origAttrs && updatedAttrs) {
2017
+ // Re-evaluate non-@expr attribute expressions from origAttrs in the context
2018
+ // of the queried instance. This handles workflow update expressions like
2019
+ // `balance balance + (balance * interestRate) + makeDeposit.amount` where
2020
+ // the local attribute references must resolve from the existing instance.
2021
+ // Only runs on the update path (where updatedAttrs is a separate map from
2022
+ // inst.attributes) to avoid double-evaluating expressions on create.
2023
+ if (origAttrs && updatedAttrs && updatedAttrs !== inst.attributes) {
1931
2024
  for (let i = 0; i < origAttrs.length; ++i) {
1932
2025
  const a: SetAttribute = origAttrs[i];
1933
2026
  const n = a.name;
2027
+ if (exprAttrs?.has(n)) continue;
1934
2028
  if (!n.endsWith(QuerySuffix) && updatedAttrs.has(n) && a.value !== undefined) {
1935
2029
  await evaluateExpression(a.value, newEnv);
1936
2030
  const v: Result = newEnv.getLastResult();
@@ -2085,6 +2179,8 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
2085
2179
  console.debug(invokeDebugMsg);
2086
2180
  //
2087
2181
 
2182
+ const agentChatId = env.getAgentChatId() || env.getActiveChatId() || '';
2183
+ await clearCancellation(agentChatId);
2088
2184
  const monitoringEnabled = isMonitoringEnabled();
2089
2185
 
2090
2186
  await agent.invoke(msg, env);
@@ -2101,6 +2197,7 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
2101
2197
  }
2102
2198
  let retries = 0;
2103
2199
  while (true) {
2200
+ await checkCancelled(agentChatId);
2104
2201
  try {
2105
2202
  let rs: string = result ? normalizeGeneratedCode(result) : '';
2106
2203
  if (agent.tools) {
@@ -2162,6 +2259,7 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
2162
2259
  } else {
2163
2260
  let retries = 0;
2164
2261
  while (true) {
2262
+ await checkCancelled(agentChatId);
2165
2263
  try {
2166
2264
  result = normalizeGeneratedCode(result);
2167
2265
  const obj = agent.maybeValidateJsonResponse(result);
@@ -2287,9 +2385,11 @@ async function iterateOnFlow(
2287
2385
  rootAgent.disableSession();
2288
2386
  const chatId = env.getActiveEventInstance()?.lookup('chatId');
2289
2387
  const iterId = chatId || crypto.randomUUID();
2388
+ await clearCancellation(iterId);
2290
2389
  let step = '';
2291
2390
  let fullFlowRetries = 0;
2292
2391
  while (true) {
2392
+ await checkCancelled(iterId);
2293
2393
  try {
2294
2394
  const initContext = msg;
2295
2395
  const s = `Now consider the following flowchart and return the next step:\n${flow}\n
@@ -2312,6 +2412,7 @@ async function iterateOnFlow(
2312
2412
  env.flagMonitorEntryAsFlow().incrementMonitor();
2313
2413
  }
2314
2414
  while (step != 'DONE' && !executedSteps.has(step)) {
2415
+ await checkCancelled(iterId);
2315
2416
  if (stepc > MaxFlowSteps) {
2316
2417
  throw new Error(`Flow execution exceeded maximum steps limit`);
2317
2418
  }
@@ -2366,6 +2467,9 @@ async function iterateOnFlow(
2366
2467
  needAgentProcessing = preprocResult.needAgentProcessing;
2367
2468
  }
2368
2469
  } catch (reason: any) {
2470
+ if (reason instanceof AgentCancelledException) {
2471
+ throw reason;
2472
+ }
2369
2473
  if (fullFlowRetries < MaxFlowRetries) {
2370
2474
  msg = `The previous attempt failed at step ${step} with the error ${reason}. Restart the flow the appropriate step
2371
2475
  (maybe even from the first step) and try to fix the issue.`;
@@ -2598,6 +2702,32 @@ export async function evaluateExpression(expr: Expr, env: Environment): Promise<
2598
2702
  env.setLastResult(result);
2599
2703
  }
2600
2704
 
2705
+ export function extractRefsFromExpr(expr: Expr): string[] {
2706
+ const refs: string[] = [];
2707
+ function walk(e: Expr) {
2708
+ if (isBinExpr(e)) {
2709
+ walk(e.e1);
2710
+ walk(e.e2);
2711
+ } else if (isLiteral(e)) {
2712
+ if (e.ref) refs.push(e.ref);
2713
+ if (e.fnCall) {
2714
+ for (const arg of e.fnCall.args) walk(arg);
2715
+ }
2716
+ if (e.asyncFnCall) {
2717
+ for (const arg of e.asyncFnCall.fnCall.args) walk(arg);
2718
+ }
2719
+ } else if (isGroup(e)) {
2720
+ walk(e.ge);
2721
+ } else if (isNegExpr(e)) {
2722
+ walk(e.ne);
2723
+ } else if (isNotExpr(e)) {
2724
+ walk(e.ne);
2725
+ }
2726
+ }
2727
+ walk(expr);
2728
+ return refs;
2729
+ }
2730
+
2601
2731
  async function getRef(r: string, src: any, env: Environment): Promise<Result> {
2602
2732
  if (Instance.IsInstance(src)) return src.lookup(r);
2603
2733
  else if (src instanceof Map) return src.get(r);