agentlang 0.10.8 → 0.11.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 (121) hide show
  1. package/README.md +60 -0
  2. package/out/api/http.d.ts.map +1 -1
  3. package/out/api/http.js +64 -39
  4. package/out/api/http.js.map +1 -1
  5. package/out/cli/main.d.ts +1 -1
  6. package/out/cli/main.d.ts.map +1 -1
  7. package/out/cli/main.js +7 -3
  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/main.cjs +504 -504
  12. package/out/language/main.cjs.map +3 -3
  13. package/out/runtime/auth/cognito.d.ts.map +1 -1
  14. package/out/runtime/auth/cognito.js +129 -64
  15. package/out/runtime/auth/cognito.js.map +1 -1
  16. package/out/runtime/defs.d.ts +22 -9
  17. package/out/runtime/defs.d.ts.map +1 -1
  18. package/out/runtime/defs.js +44 -9
  19. package/out/runtime/defs.js.map +1 -1
  20. package/out/runtime/document-retriever.d.ts +24 -0
  21. package/out/runtime/document-retriever.d.ts.map +1 -0
  22. package/out/runtime/document-retriever.js +258 -0
  23. package/out/runtime/document-retriever.js.map +1 -0
  24. package/out/runtime/errors/coded-error.d.ts +8 -0
  25. package/out/runtime/errors/coded-error.d.ts.map +1 -0
  26. package/out/runtime/errors/coded-error.js +13 -0
  27. package/out/runtime/errors/coded-error.js.map +1 -0
  28. package/out/runtime/errors/http-error.d.ts +25 -0
  29. package/out/runtime/errors/http-error.d.ts.map +1 -0
  30. package/out/runtime/errors/http-error.js +169 -0
  31. package/out/runtime/errors/http-error.js.map +1 -0
  32. package/out/runtime/excel-resolver.d.ts +4 -0
  33. package/out/runtime/excel-resolver.d.ts.map +1 -0
  34. package/out/runtime/excel-resolver.js +96 -0
  35. package/out/runtime/excel-resolver.js.map +1 -0
  36. package/out/runtime/excel.d.ts +25 -0
  37. package/out/runtime/excel.d.ts.map +1 -0
  38. package/out/runtime/excel.js +127 -0
  39. package/out/runtime/excel.js.map +1 -0
  40. package/out/runtime/exec-graph-cache.d.ts +5 -0
  41. package/out/runtime/exec-graph-cache.d.ts.map +1 -0
  42. package/out/runtime/exec-graph-cache.js +9 -0
  43. package/out/runtime/exec-graph-cache.js.map +1 -0
  44. package/out/runtime/exec-graph.d.ts.map +1 -1
  45. package/out/runtime/exec-graph.js +2 -1
  46. package/out/runtime/exec-graph.js.map +1 -1
  47. package/out/runtime/interpreter.d.ts.map +1 -1
  48. package/out/runtime/interpreter.js +30 -27
  49. package/out/runtime/interpreter.js.map +1 -1
  50. package/out/runtime/loader.d.ts.map +1 -1
  51. package/out/runtime/loader.js +5 -1
  52. package/out/runtime/loader.js.map +1 -1
  53. package/out/runtime/logger.d.ts +6 -0
  54. package/out/runtime/logger.d.ts.map +1 -1
  55. package/out/runtime/logger.js +21 -0
  56. package/out/runtime/logger.js.map +1 -1
  57. package/out/runtime/module.d.ts.map +1 -1
  58. package/out/runtime/module.js +14 -13
  59. package/out/runtime/module.js.map +1 -1
  60. package/out/runtime/modules/ai.d.ts +2 -1
  61. package/out/runtime/modules/ai.d.ts.map +1 -1
  62. package/out/runtime/modules/ai.js +7 -2
  63. package/out/runtime/modules/ai.js.map +1 -1
  64. package/out/runtime/modules/auth.d.ts.map +1 -1
  65. package/out/runtime/modules/auth.js +44 -16
  66. package/out/runtime/modules/auth.js.map +1 -1
  67. package/out/runtime/modules/core.d.ts +6 -0
  68. package/out/runtime/modules/core.d.ts.map +1 -1
  69. package/out/runtime/modules/core.js +20 -0
  70. package/out/runtime/modules/core.js.map +1 -1
  71. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  72. package/out/runtime/resolvers/sqldb/database.js +76 -39
  73. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  74. package/out/runtime/resolvers/sqldb/db-errors.d.ts +6 -0
  75. package/out/runtime/resolvers/sqldb/db-errors.d.ts.map +1 -0
  76. package/out/runtime/resolvers/sqldb/db-errors.js +100 -0
  77. package/out/runtime/resolvers/sqldb/db-errors.js.map +1 -0
  78. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  79. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  80. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  81. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  82. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  83. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  84. package/out/runtime/resolvers/vector/types.js +2 -0
  85. package/out/runtime/resolvers/vector/types.js.map +1 -0
  86. package/out/runtime/state.d.ts +3 -0
  87. package/out/runtime/state.d.ts.map +1 -1
  88. package/out/runtime/state.js +7 -0
  89. package/out/runtime/state.js.map +1 -1
  90. package/out/setupClassic.d.ts +98 -0
  91. package/out/setupClassic.d.ts.map +1 -0
  92. package/out/setupClassic.js +38 -0
  93. package/out/setupClassic.js.map +1 -0
  94. package/out/setupCommon.d.ts +2 -0
  95. package/out/setupCommon.d.ts.map +1 -0
  96. package/out/setupCommon.js +33 -0
  97. package/out/setupCommon.js.map +1 -0
  98. package/out/setupExtended.d.ts +40 -0
  99. package/out/setupExtended.d.ts.map +1 -0
  100. package/out/setupExtended.js +67 -0
  101. package/out/setupExtended.js.map +1 -0
  102. package/package.json +19 -18
  103. package/src/api/http.ts +71 -37
  104. package/src/cli/main.ts +12 -4
  105. package/src/runtime/auth/cognito.ts +187 -65
  106. package/src/runtime/defs.ts +51 -18
  107. package/src/runtime/errors/coded-error.ts +18 -0
  108. package/src/runtime/errors/http-error.ts +197 -0
  109. package/src/runtime/exec-graph-cache.ts +12 -0
  110. package/src/runtime/exec-graph.ts +2 -2
  111. package/src/runtime/interpreter.ts +73 -28
  112. package/src/runtime/loader.ts +5 -0
  113. package/src/runtime/logger.ts +27 -0
  114. package/src/runtime/module.ts +45 -13
  115. package/src/runtime/modules/ai.ts +11 -2
  116. package/src/runtime/modules/auth.ts +45 -18
  117. package/src/runtime/modules/core.ts +26 -0
  118. package/src/runtime/resolvers/sqldb/database.ts +88 -37
  119. package/src/runtime/resolvers/sqldb/db-errors.ts +113 -0
  120. package/src/runtime/state.ts +7 -0
  121. package/src/xlsx.d.ts +17 -0
@@ -0,0 +1,18 @@
1
+ /** Stable bucket for errors without an explicit code. */
2
+ export const AL_RUNTIME_UNHANDLED = 'AL_RUNTIME_UNHANDLED';
3
+
4
+ export type CodedError = Error & { agentlangCode: string };
5
+
6
+ export function createCodedError(message: string, code: string): CodedError {
7
+ const e = new Error(message) as CodedError;
8
+ e.agentlangCode = code;
9
+ return e;
10
+ }
11
+
12
+ export function isCodedError(err: unknown): err is CodedError {
13
+ return (
14
+ err instanceof Error &&
15
+ typeof (err as CodedError).agentlangCode === 'string' &&
16
+ (err as CodedError).agentlangCode.length > 0
17
+ );
18
+ }
@@ -0,0 +1,197 @@
1
+ import { AgentCancelledException } from '../modules/ai.js';
2
+ import { lookupCustomErrorMessage } from '../modules/core.js';
3
+ import {
4
+ BadRequestError,
5
+ CodeMismatchError,
6
+ ExpiredCodeError,
7
+ InvalidParameterError,
8
+ PasswordResetRequiredError,
9
+ TooManyRequestsError,
10
+ UnauthorisedError,
11
+ UserNotConfirmedError,
12
+ UserNotFoundError,
13
+ } from '../defs.js';
14
+ import { errorFileLogger, logger } from '../logger.js';
15
+ import { AppConfig } from '../state.js';
16
+ import { AL_RUNTIME_UNHANDLED, isCodedError } from './coded-error.js';
17
+
18
+ /** Registry of Tier-1 HTTP-facing error codes (documentation / consistency). */
19
+ export const AgentlangErrorCodes = {
20
+ AL_RUNTIME_UNHANDLED,
21
+ AL_HTTP_AUTH_REQUIRED: 'AL_HTTP_AUTH_REQUIRED',
22
+ AL_HTTP_HANDLER_EXCEPTION: 'AL_HTTP_HANDLER_EXCEPTION',
23
+ } as const;
24
+
25
+ /** `target` value of an override that applies to any entity for a given code. */
26
+ const UNIVERSAL_TARGET = '*';
27
+
28
+ /** Replace `{{code}}` and `{{message}}` with runtime values (iterates until stable). */
29
+ export function applyErrorMessageTemplate(
30
+ template: string,
31
+ code: string,
32
+ originalMessage: string
33
+ ): string {
34
+ let out = template;
35
+ const maxPasses = 10;
36
+ for (let i = 0; i < maxPasses; i++) {
37
+ const next = out.replace(/\{\{code\}\}/g, code).replace(/\{\{message\}\}/g, originalMessage);
38
+ if (next === out) break;
39
+ out = next;
40
+ }
41
+ return out;
42
+ }
43
+
44
+ function customMessagesEnabled(): boolean {
45
+ return AppConfig?.customErrorMessages?.enabled === true;
46
+ }
47
+
48
+ /**
49
+ * When custom error messages are enabled, append the error to a dedicated
50
+ * `logs/errors-<DATE>.log` file as a single line: timestamp (added by the logger),
51
+ * error code, and error message. No-op when custom messages are disabled.
52
+ */
53
+ export function recordCustomError(code: string, message: string): void {
54
+ if (!customMessagesEnabled()) {
55
+ return;
56
+ }
57
+ try {
58
+ errorFileLogger?.error(`${code} ${message}`);
59
+ } catch (e) {
60
+ logger.warn(`Failed to write error to errors log file: ${e}`);
61
+ }
62
+ }
63
+
64
+ export function getErrorCode(err: unknown): string {
65
+ if (isCodedError(err)) {
66
+ return err.agentlangCode;
67
+ }
68
+ if (err instanceof UnauthorisedError) {
69
+ return err.agentlangCode;
70
+ }
71
+ if (err instanceof BadRequestError) {
72
+ return err.agentlangCode;
73
+ }
74
+ if (err instanceof UserNotFoundError) {
75
+ return err.agentlangCode;
76
+ }
77
+ if (err instanceof UserNotConfirmedError) {
78
+ return err.agentlangCode;
79
+ }
80
+ if (err instanceof PasswordResetRequiredError) {
81
+ return err.agentlangCode;
82
+ }
83
+ if (err instanceof TooManyRequestsError) {
84
+ return err.agentlangCode;
85
+ }
86
+ if (err instanceof InvalidParameterError) {
87
+ return err.agentlangCode;
88
+ }
89
+ if (err instanceof ExpiredCodeError) {
90
+ return err.agentlangCode;
91
+ }
92
+ if (err instanceof CodeMismatchError) {
93
+ return err.agentlangCode;
94
+ }
95
+ if (err instanceof AgentCancelledException) {
96
+ return err.agentlangCode;
97
+ }
98
+ return AL_RUNTIME_UNHANDLED;
99
+ }
100
+
101
+ /**
102
+ * Resolve the message for an entity-route error from the `agentlang/errorMessage`
103
+ * system entity. An entity-specific override (`module/Entry`) wins over a universal
104
+ * (`*`) one for the same code; falls back to `defaultMessage` when neither exists or
105
+ * when custom messages are disabled.
106
+ */
107
+ export async function resolveEntityErrorMessage(
108
+ moduleName: string,
109
+ entryName: string,
110
+ code: string,
111
+ defaultMessage: string
112
+ ): Promise<string> {
113
+ if (!customMessagesEnabled()) {
114
+ return defaultMessage;
115
+ }
116
+ let template: string | undefined;
117
+ try {
118
+ const entityKey = `${moduleName}/${entryName}`;
119
+ template =
120
+ (await lookupCustomErrorMessage(entityKey, code)) ??
121
+ (await lookupCustomErrorMessage(UNIVERSAL_TARGET, code));
122
+ } catch (e) {
123
+ logger.warn(`Failed to resolve custom error message for code ${code}: ${e}`);
124
+ return defaultMessage;
125
+ }
126
+ if (template === undefined) {
127
+ return defaultMessage;
128
+ }
129
+ return applyErrorMessageTemplate(template, code, defaultMessage);
130
+ }
131
+
132
+ export function httpStatusFromError(err: unknown): number {
133
+ const ec = getErrorCode(err);
134
+ if (ec === 'AL_DB_UNIQUE_VIOLATION') {
135
+ return 409;
136
+ }
137
+ if (
138
+ ec === 'AL_DB_FOREIGN_KEY_VIOLATION' ||
139
+ ec === 'AL_DB_NOT_NULL_VIOLATION' ||
140
+ ec === 'AL_DB_CHECK_VIOLATION' ||
141
+ ec === 'AL_DB_INVALID_TYPE' ||
142
+ ec === 'AL_MOD_TYPE_MISMATCH' ||
143
+ ec === 'AL_MOD_INVALID_ATTR'
144
+ ) {
145
+ return 400;
146
+ }
147
+ if (
148
+ ec === 'AL_DB_DEADLOCK' ||
149
+ ec === 'AL_DB_LOCK_WAIT_TIMEOUT' ||
150
+ ec === 'AL_DB_SERIALIZATION_FAILURE'
151
+ ) {
152
+ return 503;
153
+ }
154
+ if (ec === 'AL_DB_SYNTAX_ERROR' || ec === 'AL_DB_QUERY_FAILED') {
155
+ return 500;
156
+ }
157
+ if (err instanceof UserNotFoundError) {
158
+ return 404;
159
+ }
160
+ if (err instanceof UnauthorisedError) {
161
+ return 401;
162
+ }
163
+ if (err instanceof TooManyRequestsError) {
164
+ return 429;
165
+ }
166
+ if (err instanceof BadRequestError || err instanceof InvalidParameterError) {
167
+ return 400;
168
+ }
169
+ if (err instanceof ExpiredCodeError || err instanceof CodeMismatchError) {
170
+ return 400;
171
+ }
172
+ if (err instanceof UserNotConfirmedError || err instanceof PasswordResetRequiredError) {
173
+ return 403;
174
+ }
175
+ if (err instanceof Error && err.message) {
176
+ if (
177
+ err.message.includes('temporarily unavailable') ||
178
+ err.message.includes('service error') ||
179
+ err.message.includes('configuration error')
180
+ ) {
181
+ return 503;
182
+ }
183
+ if (err.message.includes('contact support')) {
184
+ return 500;
185
+ }
186
+ }
187
+ return 500;
188
+ }
189
+
190
+ export function logEntityRouteError(reason: unknown, agentlangCode: string): void {
191
+ if (reason instanceof Error) {
192
+ const stack = reason.stack ? `\n${reason.stack}` : '';
193
+ logger.error(`[${agentlangCode}] ${reason.name}: ${reason.message}${stack}`);
194
+ } else {
195
+ logger.error(`[${agentlangCode}] ${String(reason)}`);
196
+ }
197
+ }
@@ -0,0 +1,12 @@
1
+ import type { ExecGraph } from './defs.js';
2
+
3
+ const graphCache = new Map<string, ExecGraph>();
4
+
5
+ /** Drop cached graphs so workflow/agent structure changes after module reload are picked up. */
6
+ export function clearExecutionGraphCache(): void {
7
+ graphCache.clear();
8
+ }
9
+
10
+ export function getExecutionGraphCache(): Map<string, ExecGraph> {
11
+ return graphCache;
12
+ }
@@ -56,10 +56,10 @@ import {
56
56
  makeFqName,
57
57
  nameToPath,
58
58
  } from './util.js';
59
-
60
- const GraphCache = new Map<string, ExecGraph>();
59
+ import { getExecutionGraphCache } from './exec-graph-cache.js';
61
60
 
62
61
  export async function generateExecutionGraph(eventName: string): Promise<ExecGraph | undefined> {
62
+ const GraphCache = getExecutionGraphCache();
63
63
  const cg = GraphCache.get(eventName);
64
64
  if (cg) return cg;
65
65
  const wf = getWorkflowForEvent(eventName);
@@ -34,6 +34,7 @@ import {
34
34
  import {
35
35
  Agent,
36
36
  defineAgentEvent,
37
+ eventAgentName,
37
38
  Event,
38
39
  getOneOfRef,
39
40
  getRelationship,
@@ -127,6 +128,7 @@ import { detailedDiff } from 'deep-object-diff';
127
128
  import { callMcpTool, mcpClientNameFromToolEvent } from './mcpclient.js';
128
129
  import { isNodeEnv } from '../utils/runtime.js';
129
130
  import Handlebars from 'handlebars';
131
+ import { createCodedError } from './errors/coded-error.js';
130
132
 
131
133
  export type Result = any;
132
134
 
@@ -445,7 +447,11 @@ export class Environment extends Instance {
445
447
 
446
448
  setActiveEvent(eventInst: Instance | undefined): Environment {
447
449
  if (eventInst) {
448
- if (!isEventInstance(eventInst)) throw new Error(`Not an event instance - ${eventInst.name}`);
450
+ if (!isEventInstance(eventInst))
451
+ throw createCodedError(
452
+ `Not an event instance - ${eventInst.name}`,
453
+ 'AL_INT_NOT_EVENT_INSTANCE'
454
+ );
449
455
  this.bindInstance(eventInst);
450
456
  this.activeModule = eventInst.moduleName;
451
457
  this.activeEventInstance = eventInst;
@@ -531,7 +537,7 @@ export class Environment extends Instance {
531
537
  if (this.suspensionId) {
532
538
  return this.suspensionId;
533
539
  } else {
534
- throw new Error('SuspensionId is not set');
540
+ throw createCodedError('SuspensionId is not set', 'AL_INT_SUSPENSION_ID_NOT_SET');
535
541
  }
536
542
  }
537
543
 
@@ -681,7 +687,7 @@ export class Environment extends Instance {
681
687
  this.activeTransactions.set(n, txnId);
682
688
  return txnId;
683
689
  } else {
684
- throw new Error(`Failed to start transaction for ${n}`);
690
+ throw createCodedError(`Failed to start transaction for ${n}`, 'AL_INT_TXN_START_FAILED');
685
691
  }
686
692
  }
687
693
  }
@@ -773,7 +779,7 @@ export class Environment extends Instance {
773
779
  popHandlers(): CatchHandlers {
774
780
  const r = this.activeCatchHandlers.pop();
775
781
  if (r === undefined) {
776
- throw new Error(`No more handlers to pop`);
782
+ throw createCodedError(`No more handlers to pop`, 'AL_INT_NO_HANDLERS_TO_POP');
777
783
  }
778
784
  return r;
779
785
  }
@@ -999,7 +1005,7 @@ export let evaluate = async function (
999
1005
  return null;
1000
1006
  }
1001
1007
  } else {
1002
- throw new Error('Not an event - ' + eventInstance.name);
1008
+ throw createCodedError('Not an event - ' + eventInstance.name, 'AL_INT_NOT_AN_EVENT');
1003
1009
  }
1004
1010
  } catch (err) {
1005
1011
  if (env && env.hasHandlers()) {
@@ -1096,7 +1102,10 @@ async function evaluateAsyncPattern(
1096
1102
  if (s.$cstNode) {
1097
1103
  return s.$cstNode.text;
1098
1104
  } else {
1099
- throw new Error('failed to extract code for suspension statement');
1105
+ throw createCodedError(
1106
+ 'failed to extract code for suspension statement',
1107
+ 'AL_INT_SUSPENSION_CODE_EXTRACT'
1108
+ );
1100
1109
  }
1101
1110
  }),
1102
1111
  env
@@ -1386,7 +1395,7 @@ export async function evaluatePattern(
1386
1395
 
1387
1396
  async function evaluateThrowError(throwErr: ThrowError, env: Environment) {
1388
1397
  await evaluateExpression(throwErr.reason, env);
1389
- throw new Error(env.getLastResult());
1398
+ throw createCodedError(String(env.getLastResult()), 'AL_INT_USER_THROW');
1390
1399
  }
1391
1400
 
1392
1401
  async function evaluateFullTextSearch(fts: FullTextSearch, env: Environment): Promise<void> {
@@ -1396,7 +1405,10 @@ async function evaluateFullTextSearch(fts: FullTextSearch, env: Environment): Pr
1396
1405
  if (inst) {
1397
1406
  n = makeFqName(inst.moduleName, n);
1398
1407
  } else {
1399
- throw new Error(`Fully qualified name required for full-text-search in ${n}`);
1408
+ throw createCodedError(
1409
+ `Fully qualified name required for full-text-search in ${n}`,
1410
+ 'AL_INT_FTS_FQN_REQUIRED'
1411
+ );
1400
1412
  }
1401
1413
  }
1402
1414
  const path = nameToPath(n);
@@ -1406,7 +1418,10 @@ async function evaluateFullTextSearch(fts: FullTextSearch, env: Environment): Pr
1406
1418
  await evaluateLiteral(fts.query, env);
1407
1419
  const q = env.getLastResult();
1408
1420
  if (!isString(q)) {
1409
- throw new Error(`Full text search query must be a string - ${q}`);
1421
+ throw createCodedError(
1422
+ `Full text search query must be a string - ${q}`,
1423
+ 'AL_INT_FTS_QUERY_STRING'
1424
+ );
1410
1425
  }
1411
1426
  let options: Map<string, any> | undefined;
1412
1427
  if (fts.options) {
@@ -1526,7 +1541,10 @@ async function patternToInstance(
1526
1541
  let aname: string = a.name;
1527
1542
  if (aname.endsWith(QuerySuffix)) {
1528
1543
  if (isQueryAll) {
1529
- throw new Error(`Cannot specifiy query attribute ${aname} here`);
1544
+ throw createCodedError(
1545
+ `Cannot specifiy query attribute ${aname} here`,
1546
+ 'AL_INT_QUERY_ATTR_FORBIDDEN'
1547
+ );
1530
1548
  }
1531
1549
  if (qattrs === undefined) qattrs = newInstanceAttributes();
1532
1550
  if (qattrVals === undefined) qattrVals = newInstanceAttributes();
@@ -1569,11 +1587,15 @@ async function instanceFromSource(crud: CrudMap, env: Environment): Promise<Inst
1569
1587
  const m = nparts.hasModule() ? nparts.getModuleName() : env.getActiveModuleName();
1570
1588
  return makeInstance(m, n, attrs);
1571
1589
  } else {
1572
- throw new Error(`Failed to initialize instance of ${crud.name}, expected a map after @from.`);
1590
+ throw createCodedError(
1591
+ `Failed to initialize instance of ${crud.name}, expected a map after @from.`,
1592
+ 'AL_INT_CRUD_INIT_MAP_EXPECTED'
1593
+ );
1573
1594
  }
1574
1595
  } else {
1575
- throw new Error(
1576
- `Cannot create instance of ${crud.name}, CRUD pattern does not specify a source map.`
1596
+ throw createCodedError(
1597
+ `Cannot create instance of ${crud.name}, CRUD pattern does not specify a source map.`,
1598
+ 'AL_INT_CRUD_NO_SOURCE_MAP'
1577
1599
  );
1578
1600
  }
1579
1601
  }
@@ -1588,7 +1610,7 @@ async function maybeValidateOneOfRefs(inst: Instance, env: Environment) {
1588
1610
  const attrSpec = inst.record.schema.get(n);
1589
1611
  if (!attrSpec) continue;
1590
1612
  const r = getOneOfRef(attrSpec);
1591
- if (!r) throw new Error(`Failed to fetch one-of-ref for ${n}`);
1613
+ if (!r) throw createCodedError(`Failed to fetch one-of-ref for ${n}`, 'AL_INT_ONEOF_REF_FETCH');
1592
1614
  if (r) {
1593
1615
  const parts = r.split('.');
1594
1616
  const insts = await lookupOneOfVals(parts[0], env);
@@ -1601,7 +1623,7 @@ async function maybeValidateOneOfRefs(inst: Instance, env: Environment) {
1601
1623
  return i.lookup(parts[1]) == v;
1602
1624
  })
1603
1625
  ) {
1604
- throw new Error(`Invalid enum-value ${v} for ${n}`);
1626
+ throw createCodedError(`Invalid enum-value ${v} for ${n}`, 'AL_INT_INVALID_ENUM');
1605
1627
  }
1606
1628
  }
1607
1629
  }
@@ -1655,12 +1677,16 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
1655
1677
  }
1656
1678
  if (qopts.into) {
1657
1679
  if (attrs.size > 0) {
1658
- throw new Error(
1659
- `Query pattern for ${entryName} with 'into' clause cannot be used to update attributes`
1680
+ throw createCodedError(
1681
+ `Query pattern for ${entryName} with 'into' clause cannot be used to update attributes`,
1682
+ 'AL_INT_INTO_WITH_ATTRS'
1660
1683
  );
1661
1684
  }
1662
1685
  if (qattrs === undefined && !isQueryAll) {
1663
- throw new Error(`Pattern for ${entryName} with 'into' clause must be a query`);
1686
+ throw createCodedError(
1687
+ `Pattern for ${entryName} with 'into' clause must be a query`,
1688
+ 'AL_INT_INTO_MUST_BE_QUERY'
1689
+ );
1664
1690
  }
1665
1691
  if (qopts.joins && qopts.joins.length > 0) {
1666
1692
  await evaluateJoinQuery(qopts.joins, qopts.into, qopts.where, inst, distinct, env);
@@ -1913,7 +1939,10 @@ async function handleDocEvent(inst: Instance, env: Environment): Promise<void> {
1913
1939
  const url = inst.lookup('url');
1914
1940
  if (typeof url === 'string' && url.startsWith('s3://')) {
1915
1941
  if (!isNodeEnv) {
1916
- throw new Error('Document fetching is only available in Node.js environment');
1942
+ throw createCodedError(
1943
+ 'Document fetching is only available in Node.js environment',
1944
+ 'AL_INT_DOC_FETCH_NODE_ONLY'
1945
+ );
1917
1946
  }
1918
1947
  const title = inst.lookup('title');
1919
1948
  const retrievalConfig = inst.lookup('retrievalConfig');
@@ -2212,7 +2241,10 @@ async function walkJoinQueryPattern(
2212
2241
  });
2213
2242
  return joinsSpec;
2214
2243
  } else {
2215
- throw new Error(`Expected a query for relationship ${rp.name}`);
2244
+ throw createCodedError(
2245
+ `Expected a query for relationship ${rp.name}`,
2246
+ 'AL_INT_RELATIONSHIP_QUERY_EXPECTED'
2247
+ );
2216
2248
  }
2217
2249
  }
2218
2250
 
@@ -2337,10 +2369,13 @@ async function agentInvoke(agent: AgentInstance, msg: string, env: Environment):
2337
2369
  }
2338
2370
  }
2339
2371
  } else {
2340
- throw new Error(`Agent ${agent.name} failed to generate a response`);
2372
+ throw createCodedError(
2373
+ `Agent ${agent.name} failed to generate a response`,
2374
+ 'AL_INT_AGENT_NO_RESPONSE'
2375
+ );
2341
2376
  }
2342
2377
  if (agentInternalError !== undefined) {
2343
- throw new Error(agentInternalError);
2378
+ throw createCodedError(agentInternalError, 'AL_INT_AGENT_INTERNAL');
2344
2379
  }
2345
2380
  }
2346
2381
 
@@ -2348,7 +2383,8 @@ export async function handleAgentInvocation(
2348
2383
  agentEventInst: Instance,
2349
2384
  env: Environment
2350
2385
  ): Promise<void> {
2351
- const agent: AgentInstance = await findAgentByName(agentEventInst.name, env);
2386
+ const agentLookupName = eventAgentName(agentEventInst) ?? agentEventInst.name;
2387
+ const agent: AgentInstance = await findAgentByName(agentLookupName, env);
2352
2388
  if (agent.role) {
2353
2389
  env.setAssumedRole(agent.role);
2354
2390
  }
@@ -2467,7 +2503,10 @@ async function iterateOnFlow(
2467
2503
  while (step != 'DONE' && !executedSteps.has(step)) {
2468
2504
  await checkCancelled(iterId);
2469
2505
  if (stepc > MaxFlowSteps) {
2470
- throw new Error(`Flow execution exceeded maximum steps limit`);
2506
+ throw createCodedError(
2507
+ `Flow execution exceeded maximum steps limit`,
2508
+ 'AL_INT_FLOW_MAX_STEPS'
2509
+ );
2471
2510
  }
2472
2511
  executedSteps.add(step);
2473
2512
  ++stepc;
@@ -2529,7 +2568,10 @@ async function iterateOnFlow(
2529
2568
  ++fullFlowRetries;
2530
2569
  continue;
2531
2570
  } else {
2532
- throw new Error(reason);
2571
+ throw createCodedError(
2572
+ typeof reason === 'string' ? reason : String(reason),
2573
+ 'AL_INT_FLOW_FATAL'
2574
+ );
2533
2575
  }
2534
2576
  } finally {
2535
2577
  env.decrementMonitor().revokeLastResult().setMonitorFlowResult();
@@ -2737,7 +2779,7 @@ export async function evaluateExpression(expr: Expr, env: Environment): Promise<
2737
2779
  });
2738
2780
  break;
2739
2781
  default:
2740
- throw new Error(`Unrecognized binary operator: ${expr.op}`);
2782
+ throw createCodedError(`Unrecognized binary operator: ${expr.op}`, 'AL_INT_BAD_BINARY_OP');
2741
2783
  }
2742
2784
  } else if (isNegExpr(expr)) {
2743
2785
  await evaluateExpression(expr.ne, env);
@@ -2815,7 +2857,10 @@ async function followReference(env: Environment, s: string): Promise<Result> {
2815
2857
  async function dereferencePath(path: string, env: Environment): Promise<Result> {
2816
2858
  const fqName = fqNameFromPath(path);
2817
2859
  if (fqName === undefined) {
2818
- throw new Error(`Failed to deduce entry-name from path - ${path}`);
2860
+ throw createCodedError(
2861
+ `Failed to deduce entry-name from path - ${path}`,
2862
+ 'AL_INT_DEDUCE_ENTRY_NAME'
2863
+ );
2819
2864
  }
2820
2865
  const newEnv = new Environment('path-deref', env);
2821
2866
  await parseAndEvaluateStatement(
@@ -2923,7 +2968,7 @@ async function runPrePostEvents(
2923
2968
  if (env.hasHandlers()) {
2924
2969
  throw reason;
2925
2970
  } else {
2926
- throw new Error(`${prefix}: ${reason}`);
2971
+ throw createCodedError(`${prefix}: ${reason}`, 'AL_INT_PREPOST_EVENT_FAILED');
2927
2972
  }
2928
2973
  };
2929
2974
  if (trigInfo.async) {
@@ -80,6 +80,7 @@ import {
80
80
  asStringLiteralsMap,
81
81
  escapeSpecialChars,
82
82
  findRbacSchema,
83
+ isCoreModule,
83
84
  isFqName,
84
85
  makeFqName,
85
86
  maybeExtends,
@@ -105,6 +106,7 @@ import {
105
106
  parseStatement,
106
107
  parseWorkflow,
107
108
  } from '../language/parser.js';
109
+ import { clearExecutionGraphCache } from './exec-graph-cache.js';
108
110
  import { logger } from './logger.js';
109
111
  import {
110
112
  Environment,
@@ -1305,6 +1307,9 @@ export async function internModule(
1305
1307
  moduleFileName?: string
1306
1308
  ): Promise<Module> {
1307
1309
  const mn = module.name;
1310
+ if (!isCoreModule(mn)) {
1311
+ clearExecutionGraphCache();
1312
+ }
1308
1313
  const r = addModule(mn);
1309
1314
  // Process imports sequentially to ensure all JS modules are loaded before definitions
1310
1315
  for (const imp of module.imports as Import[]) {
@@ -17,6 +17,13 @@ if (isNodeEnv) {
17
17
 
18
18
  export let logger: any;
19
19
 
20
+ /**
21
+ * Dedicated logger that records each error to `logs/errors-<DATE>.log`, one line
22
+ * per error. Used when custom error messages are enabled (see `recordCustomError`
23
+ * in errors/http-error.ts).
24
+ */
25
+ export let errorFileLogger: any;
26
+
20
27
  function getLogLevel(): string {
21
28
  if (isNodeEnv && process.env && process.env.DEBUG) {
22
29
  return 'debug';
@@ -46,6 +53,25 @@ export function initializeLogger() {
46
53
  ),
47
54
  transports: [fileTransport],
48
55
  });
56
+
57
+ const errorFileTransport = new DailyRotateFile({
58
+ level: 'error',
59
+ filename: 'logs/errors-%DATE%.log',
60
+ datePattern: 'YYYY-MM-DD',
61
+ maxSize: '20m',
62
+ maxFiles: '30d',
63
+ });
64
+
65
+ errorFileLogger = winston.createLogger({
66
+ level: 'error',
67
+ format: winston.format.combine(
68
+ winston.format.timestamp(),
69
+ winston.format.printf(({ timestamp, message }: any) => {
70
+ return `[${timestamp}] ${message}`;
71
+ })
72
+ ),
73
+ transports: [errorFileTransport],
74
+ });
49
75
  } else {
50
76
  function mkLogger(tag: string): Function {
51
77
  return (msg: string) => {
@@ -58,6 +84,7 @@ export function initializeLogger() {
58
84
  warn: mkLogger('WARN'),
59
85
  error: mkLogger('ERROR'),
60
86
  };
87
+ errorFileLogger = { error: mkLogger('ERROR') };
61
88
  }
62
89
  }
63
90