botql 1.0.2 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/botql.browser.js CHANGED
@@ -19,648 +19,357 @@ var BotQL = (() => {
19
19
  var require_Parser = __commonJS({
20
20
  "Parser.js"(exports, module) {
21
21
  "use strict";
22
- var TokenType = {
23
- KEYWORD: "KEYWORD",
24
- STRING: "STRING",
25
- NUMBER: "NUMBER",
26
- IDENT: "IDENT",
27
- SYMBOL: "SYMBOL",
28
- EOF: "EOF"
29
- };
30
- var KEYWORDS = /* @__PURE__ */ new Set([
31
- "CREATE",
32
- "BOT",
33
- "PLATFORM",
34
- "CONNECT",
35
- "TABLE",
36
- "PREVENT",
37
- "DEFAULT",
38
- "ON",
39
- "START",
40
- "MESSAGE",
41
- "FROM",
42
- "WHEN",
43
- "CONTAINS",
44
- "OR",
45
- "OTHERWISE",
46
- "KEYWORDS",
47
- "THINK",
48
- "WAITING",
49
- "REPLY",
50
- "TO",
51
- "FORWARD",
52
- "PARSE",
53
- "SEND",
54
- "INSERT",
55
- "INTO",
56
- "VALUES",
57
- "UPDATE",
58
- "SET",
59
- "WHERE",
60
- "RUN",
61
- "IMPORT",
62
- "AS",
63
- "RESPONSE"
64
- ]);
22
+ var TokenType = { KEYWORD: "KEYWORD", STRING: "STRING", NUMBER: "NUMBER", IDENT: "IDENT", SYMBOL: "SYMBOL", EOF: "EOF" };
23
+ var KEYWORDS = /* @__PURE__ */ new Set(["CREATE", "BOT", "PLATFORM", "CONNECT", "TABLE", "PREVENT", "DEFAULT", "ON", "START", "MESSAGE", "FROM", "WHEN", "CONTAINS", "OR", "OTHERWISE", "KEYWORDS", "THINK", "WAITING", "REPLY", "TO", "FORWARD", "PARSE", "SEND", "INSERT", "INTO", "VALUES", "UPDATE", "SET", "WHERE", "RUN", "IMPORT", "AS", "RESPONSE"]);
65
24
  var Token = class {
66
- constructor(type, value, line) {
67
- this.type = type;
68
- this.value = value;
69
- this.line = line;
25
+ constructor(e, t, s, r, i) {
26
+ this.type = e, this.value = t, this.line = s, this.start = r, this.end = i;
27
+ }
28
+ };
29
+ var BotQLSyntaxError = class extends Error {
30
+ constructor(e, { line: t = null, start: s = 0, end: r = null, near: i = null } = {}) {
31
+ super(e), this.name = "BotQLSyntaxError", this.line = t, this.start = s, this.end = null === r ? s + 1 : r, this.near = i;
70
32
  }
71
33
  };
72
34
  var Tokenizer = class {
73
- constructor(source) {
74
- this.source = source;
75
- this.pos = 0;
76
- this.line = 1;
77
- this.tokens = [];
35
+ constructor(e) {
36
+ this.source = e, this.pos = 0, this.line = 1, this.tokens = [];
78
37
  }
79
- error(msg) {
80
- throw new Error(`syntax error: ${msg}`);
38
+ error(e, t, s) {
39
+ let r = void 0 !== t ? t : this.pos;
40
+ throw new BotQLSyntaxError(`syntax error: ${e}`, { line: this.line, start: r, end: s });
81
41
  }
82
- peekChar(offset = 0) {
83
- return this.source[this.pos + offset];
42
+ peekChar(e = 0) {
43
+ return this.source[this.pos + e];
84
44
  }
85
45
  tokenize() {
86
- while (this.pos < this.source.length) {
87
- const c = this.peekChar();
88
- if (c === "\n") {
89
- this.line++;
90
- this.pos++;
46
+ for (; this.pos < this.source.length; ) {
47
+ let e = this.peekChar(), t = this.pos;
48
+ if ("\n" === e) {
49
+ this.line++, this.pos++;
91
50
  continue;
92
51
  }
93
- if (/\s/.test(c)) {
52
+ if (/\s/.test(e)) {
94
53
  this.pos++;
95
54
  continue;
96
55
  }
97
- if (c === "-" && this.peekChar(1) === "-") {
98
- while (this.pos < this.source.length && this.peekChar() !== "\n") this.pos++;
99
- continue;
100
- }
101
- if (c === '"' || c === "'") {
102
- this.tokens.push(this.readString(c));
103
- continue;
104
- }
105
- if (/[0-9]/.test(c)) {
106
- this.tokens.push(this.readNumber());
107
- continue;
108
- }
109
- if ("{}(),.=+*;".includes(c)) {
110
- this.tokens.push(new Token(TokenType.SYMBOL, c, this.line));
111
- this.pos++;
56
+ if ("-" === e && "-" === this.peekChar(1)) {
57
+ for (; this.pos < this.source.length && "\n" !== this.peekChar(); ) this.pos++;
112
58
  continue;
113
59
  }
114
- if (/[A-Za-z_]/.test(c)) {
115
- const word = this.readWord();
116
- this.tokens.push(word);
117
- continue;
118
- }
119
- this.error(`unexpected character: "${c}"`);
60
+ let s;
61
+ '"' === e || "'" === e ? s = this.readString(e) : /[0-9]/.test(e) ? s = this.readNumber() : "{}(),.=+*;".includes(e) ? (s = new Token(TokenType.SYMBOL, e, this.line), this.pos++) : /[A-Za-z_]/.test(e) ? s = this.readWord() : this.error(`unexpected character: "${e}"`, t, t + 1), s.start = t, s.end = this.pos, this.tokens.push(s);
120
62
  }
121
- this.tokens.push(new Token(TokenType.EOF, null, this.line));
122
- return this.tokens;
63
+ let r = this.source.length, i = new Token(TokenType.EOF, null, this.line, r, r);
64
+ return this.tokens.push(i), this.tokens;
123
65
  }
124
- readString(quote) {
125
- const startLine = this.line;
66
+ readString(e) {
67
+ let t = this.line, s = this.pos;
126
68
  this.pos++;
127
- let value = "";
128
- while (this.pos < this.source.length && this.peekChar() !== quote) {
129
- if (this.peekChar() === "\\") {
130
- this.pos++;
131
- value += this.peekChar();
132
- this.pos++;
69
+ let r = "";
70
+ for (; this.pos < this.source.length && this.peekChar() !== e; ) {
71
+ if ("\\" === this.peekChar()) {
72
+ this.pos++, r += this.peekChar(), this.pos++;
133
73
  continue;
134
74
  }
135
- if (this.peekChar() === "\n") this.line++;
136
- value += this.peekChar();
137
- this.pos++;
138
- }
139
- if (this.peekChar() !== quote) {
140
- this.error("unterminated string");
75
+ "\n" === this.peekChar() && this.line++, r += this.peekChar(), this.pos++;
141
76
  }
142
- this.pos++;
143
- return new Token(TokenType.STRING, value, startLine);
77
+ return this.peekChar() !== e && this.error("unterminated string", s, this.pos), this.pos++, new Token(TokenType.STRING, r, t);
144
78
  }
145
79
  readNumber() {
146
- const startLine = this.line;
147
- let value = "";
148
- while (this.pos < this.source.length && /[0-9.]/.test(this.peekChar())) {
149
- value += this.peekChar();
150
- this.pos++;
151
- }
152
- return new Token(TokenType.NUMBER, Number(value), startLine);
80
+ let e = this.line, t = "";
81
+ for (; this.pos < this.source.length && /[0-9.]/.test(this.peekChar()); ) t += this.peekChar(), this.pos++;
82
+ return new Token(TokenType.NUMBER, Number(t), e);
153
83
  }
154
84
  readWord() {
155
- const startLine = this.line;
156
- let value = "";
157
- while (this.pos < this.source.length && /[A-Za-z0-9_]/.test(this.peekChar())) {
158
- value += this.peekChar();
159
- this.pos++;
160
- }
161
- const upper = value.toUpperCase();
162
- if (KEYWORDS.has(upper) && value === upper) {
163
- return new Token(TokenType.KEYWORD, upper, startLine);
164
- }
165
- return new Token(TokenType.IDENT, value, startLine);
85
+ let e = this.line, t = "";
86
+ for (; this.pos < this.source.length && /[A-Za-z0-9_]/.test(this.peekChar()); ) t += this.peekChar(), this.pos++;
87
+ let s = t.toUpperCase();
88
+ return KEYWORDS.has(s) && t === s ? new Token(TokenType.KEYWORD, s, e) : new Token(TokenType.IDENT, t, e);
166
89
  }
167
90
  };
168
91
  var Parser = class {
169
- constructor(source) {
170
- this.tokens = new Tokenizer(source).tokenize();
171
- this.pos = 0;
92
+ constructor(e) {
93
+ this.source = e, this.tokens = new Tokenizer(e).tokenize(), this.pos = 0;
172
94
  }
173
- error(msg) {
174
- const tok = this.current();
175
- throw new Error(`syntax error: ${msg}, near "${tok ? tok.value : "EOF"}"`);
95
+ error(e) {
96
+ let t = this.current(), s = t && t.type !== TokenType.EOF ? t.value : "EOF", r = t ? t.start : this.source.length, i = t ? t.end : this.source.length;
97
+ throw new BotQLSyntaxError(`syntax error: ${e}, near "${s}"`, { line: t ? t.line : null, start: r, end: i, near: s });
176
98
  }
177
99
  current() {
178
100
  return this.tokens[this.pos];
179
101
  }
180
- at(type, value) {
181
- const tok = this.current();
182
- if (tok.type !== type) return false;
183
- if (value !== void 0 && tok.value !== value) return false;
184
- return true;
102
+ at(e, t) {
103
+ let s = this.current();
104
+ return s.type === e && (void 0 === t || s.value === t);
185
105
  }
186
- atKeyword(...values) {
187
- return this.at(TokenType.KEYWORD) && values.includes(this.current().value);
106
+ atKeyword(...e) {
107
+ return this.at(TokenType.KEYWORD) && e.includes(this.current().value);
188
108
  }
189
- // Casa por valor, independente do token ser KEYWORD ou IDENT.
190
- // Necessário para palavras como "SIGNAL", que tanto introduz um
191
- // evento (ON SIGNAL FROM) como é usada como identificador (SIGNAL.PAIR).
192
- atWord(...values) {
193
- const tok = this.current();
194
- return (tok.type === TokenType.KEYWORD || tok.type === TokenType.IDENT) && values.includes(tok.value);
109
+ atWord(...e) {
110
+ let t = this.current();
111
+ return (t.type === TokenType.KEYWORD || t.type === TokenType.IDENT) && e.includes(t.value);
195
112
  }
196
113
  advance() {
197
- const tok = this.current();
198
- if (tok.type !== TokenType.EOF) this.pos++;
199
- return tok;
114
+ let e = this.current();
115
+ return e.type !== TokenType.EOF && this.pos++, e;
200
116
  }
201
- expect(type, value) {
202
- if (!this.at(type, value)) {
203
- this.error(`expected ${value || type}`);
204
- }
205
- return this.advance();
117
+ expect(e, t) {
118
+ return this.at(e, t) || this.error(`expected ${t || e}`), this.advance();
206
119
  }
207
- expectKeyword(value) {
208
- return this.expect(TokenType.KEYWORD, value);
120
+ expectKeyword(e) {
121
+ return this.expect(TokenType.KEYWORD, e);
209
122
  }
210
123
  isAtEnd() {
211
124
  return this.at(TokenType.EOF);
212
125
  }
213
- // ---- Programa ----
214
126
  parseProgram() {
215
- const statements = [];
216
- while (!this.isAtEnd()) {
217
- statements.push(this.parseStatement());
218
- }
219
- return { type: "Program", body: statements };
127
+ let e = [];
128
+ for (; !this.isAtEnd(); ) e.push(this.parseStatement());
129
+ return { type: "Program", body: e };
220
130
  }
221
- // ---- Bloco { ... } ou statement único ----
222
- // Regra da linguagem: bloco com mais de uma ação usa chaves,
223
- // uma única ação não precisa.
224
131
  parseBody() {
225
132
  if (this.at(TokenType.SYMBOL, "{")) {
226
133
  this.advance();
227
- const statements = [];
228
- while (!this.at(TokenType.SYMBOL, "}")) {
229
- if (this.isAtEnd()) this.error('unclosed block "{"');
230
- statements.push(this.parseStatement());
231
- }
232
- this.advance();
233
- return statements;
134
+ let e = [];
135
+ for (; !this.at(TokenType.SYMBOL, "}"); ) this.isAtEnd() && this.error('unclosed block "{"'), e.push(this.parseStatement());
136
+ return this.advance(), e;
234
137
  }
235
138
  return [this.parseStatement()];
236
139
  }
237
- // ---- Dispatch de statement ----
238
140
  parseStatement() {
239
- if (this.atKeyword("CREATE")) return this.parseCreate();
240
- if (this.atKeyword("PLATFORM")) return this.parsePlatform();
241
- if (this.atKeyword("CONNECT")) return this.parseConnect();
242
- if (this.atKeyword("ON")) return this.parseOn();
243
- if (this.atKeyword("WHEN")) return this.parseWhen();
244
- if (this.atKeyword("OTHERWISE")) return this.parseOtherwise();
245
- if (this.atKeyword("REPLY")) return this.parseReply();
246
- if (this.atKeyword("THINK")) return this.parseThink();
247
- if (this.atKeyword("FORWARD")) return this.parseForward();
248
- if (this.atKeyword("PARSE")) return this.parseParseSignal();
249
- if (this.atKeyword("SEND")) return this.parseSend();
250
- if (this.atKeyword("INSERT")) return this.parseInsert();
251
- if (this.atKeyword("UPDATE")) return this.parseUpdate();
252
- if (this.atKeyword("RUN")) return this.parseRunBot();
253
- if (this.atKeyword("IMPORT")) return this.parseImport();
254
- this.error("unexpected statement");
255
- }
256
- // ---- CREATE BOT / CREATE TABLE ----
141
+ return this.atKeyword("CREATE") ? this.parseCreate() : this.atKeyword("PLATFORM") ? this.parsePlatform() : this.atKeyword("CONNECT") ? this.parseConnect() : this.atKeyword("ON") ? this.parseOn() : this.atKeyword("WHEN") ? this.parseWhen() : this.atKeyword("OTHERWISE") ? this.parseOtherwise() : this.atKeyword("REPLY") ? this.parseReply() : this.atKeyword("THINK") ? this.parseThink() : this.atKeyword("WAITING") ? this.parseWaitingStatement() : this.atKeyword("FORWARD") ? this.parseForward() : this.atKeyword("PARSE") ? this.parseParseSignal() : this.atKeyword("SEND") ? this.parseSend() : this.atKeyword("INSERT") ? this.parseInsert() : this.atKeyword("UPDATE") ? this.parseUpdate() : this.atKeyword("RUN") ? this.parseRunBot() : this.atKeyword("IMPORT") ? this.parseImport() : void this.error("unexpected statement");
142
+ }
257
143
  parseCreate() {
258
- this.expectKeyword("CREATE");
259
- if (this.atKeyword("BOT")) {
144
+ if (this.expectKeyword("CREATE"), this.atKeyword("BOT")) {
260
145
  this.advance();
261
- const name = this.expect(TokenType.STRING).value;
262
- return { type: "CreateBot", name };
146
+ let e = this.expect(TokenType.STRING).value;
147
+ return { type: "CreateBot", name: e };
263
148
  }
264
149
  if (this.atKeyword("TABLE")) {
265
150
  this.advance();
266
- const name = this.expect(TokenType.IDENT).value;
267
- const columns = this.parseColumnList();
268
- const preventDefault = this.tryParsePreventDefault();
269
- const defaultMessage = this.tryParseDefaultMessage();
270
- return { type: "CreateTable", name, columns, preventDefault, defaultMessage };
151
+ let t = this.expect(TokenType.IDENT).value, s = this.parseColumnList(), r = this.tryParsePreventDefault(), i = this.tryParseDefaultMessage();
152
+ return { type: "CreateTable", name: t, columns: s, preventDefault: r, defaultMessage: i };
271
153
  }
272
154
  this.error("expected BOT or TABLE after CREATE");
273
155
  }
274
156
  parseColumnList() {
275
157
  this.expect(TokenType.SYMBOL, "(");
276
- const columns = [];
277
- while (!this.at(TokenType.SYMBOL, ")")) {
278
- const colName = this.expect(TokenType.IDENT).value;
279
- const constraints = [];
280
- let colType = null;
281
- while (this.at(TokenType.IDENT) && !this.at(TokenType.SYMBOL, ",") && !this.at(TokenType.SYMBOL, ")")) {
282
- const word = this.advance().value;
283
- if (!colType) colType = word;
284
- else constraints.push(word);
158
+ let e = [];
159
+ for (; !this.at(TokenType.SYMBOL, ")"); ) {
160
+ let t = this.expect(TokenType.IDENT).value, s = [], r = null;
161
+ for (; this.at(TokenType.IDENT) && !this.at(TokenType.SYMBOL, ",") && !this.at(TokenType.SYMBOL, ")"); ) {
162
+ let i = this.advance().value;
163
+ r ? s.push(i) : r = i;
285
164
  }
286
- columns.push({ name: colName, columnType: colType, constraints });
287
- if (this.at(TokenType.SYMBOL, ",")) this.advance();
165
+ e.push({ name: t, columnType: r, constraints: s }), this.at(TokenType.SYMBOL, ",") && this.advance();
288
166
  }
289
- this.expect(TokenType.SYMBOL, ")");
290
- return columns;
167
+ return this.expect(TokenType.SYMBOL, ")"), e;
291
168
  }
292
169
  tryParsePreventDefault() {
293
- if (this.atKeyword("PREVENT")) {
294
- this.advance();
295
- this.expectKeyword("DEFAULT");
296
- return true;
297
- }
298
- return false;
170
+ return !!this.atKeyword("PREVENT") && (this.advance(), this.expectKeyword("DEFAULT"), true);
299
171
  }
300
- // ---- DEFAULT MESSAGE "texto" ----
301
- // ---- DEFAULT MESSAGE (ficheiro.txt, N) ----
302
- //
303
- // Modificador do CREATE TABLE, ao lado de PREVENT DEFAULT: saudação
304
- // automática enviada só na primeira mensagem de cada client/sender
305
- // (ver botql.js, receiveMessage). Aceita texto direto ou a mesma forma
306
- // de ficheiro indexado do REPLY — por isso reaproveita isReplyFileForm
307
- // para o lookahead. "DEFAULT" aqui não é o mesmo "DEFAULT" de "PREVENT
308
- // DEFAULT" — só é reconhecido como início de DEFAULT MESSAGE quando NÃO
309
- // vem logo a seguir a PREVENT (tryParsePreventDefault já consumiu esse
310
- // caso antes de chegarmos aqui).
311
172
  tryParseDefaultMessage() {
312
173
  if (!this.atKeyword("DEFAULT")) return null;
313
- const next = this.tokens[this.pos + 1];
314
- if (!next || next.type !== TokenType.KEYWORD || next.value !== "MESSAGE") return null;
315
- this.advance();
316
- this.advance();
317
- if (this.isReplyFileForm()) {
174
+ let e = this.tokens[this.pos + 1];
175
+ if (!e || e.type !== TokenType.KEYWORD || "MESSAGE" !== e.value) return null;
176
+ if (this.advance(), this.advance(), this.isReplyFileForm()) {
318
177
  this.expect(TokenType.SYMBOL, "(");
319
- const file = this.parseFileName();
178
+ let t = this.parseFileName();
320
179
  this.expect(TokenType.SYMBOL, ",");
321
- const index = this.parseExpression();
322
- this.expect(TokenType.SYMBOL, ")");
323
- return { file, index };
180
+ let s = this.parseExpression();
181
+ return this.expect(TokenType.SYMBOL, ")"), { file: t, index: s };
324
182
  }
325
- const value = this.parseExpression();
326
- return { value };
183
+ let r = this.parseExpression();
184
+ return { value: r };
327
185
  }
328
- // ---- PLATFORM ----
329
186
  parsePlatform() {
330
187
  this.expectKeyword("PLATFORM");
331
- const value = this.advance().value;
332
- return { type: "Platform", value };
333
- }
334
- // ---- CONNECT SERVIÇO TIPO "credencial" ----
335
- // ---- CONNECT RESPONSE alias ----
336
- //
337
- // A segunda forma liga o bot a uma IA usando um valor já importado (o
338
- // alias de um IMPORT {..., N} AS alias) — só estabelece a ligação,
339
- // não dispara nada sozinho (quem dispara é Response()/Response(alias),
340
- // dentro de uma ação). RESPONSE é keyword fixa aqui, por isso da para
341
- // distinguir logo no primeiro token, sem lookahead: a forma normal
342
- // nunca começa por essa palavra.
188
+ let e = this.advance().value;
189
+ return { type: "Platform", value: e };
190
+ }
343
191
  parseConnect() {
344
- this.expectKeyword("CONNECT");
345
- if (this.atKeyword("RESPONSE")) {
192
+ if (this.expectKeyword("CONNECT"), this.atKeyword("RESPONSE")) {
346
193
  this.advance();
347
- const alias = this.expect(TokenType.IDENT).value;
348
- return { type: "ConnectResponse", alias };
194
+ let e = this.expect(TokenType.IDENT).value;
195
+ return { type: "ConnectResponse", alias: e };
349
196
  }
350
- const service = this.advance().value;
351
- const kind = this.advance().value;
352
- const credential = this.expect(TokenType.STRING).value;
353
- return { type: "Connect", service, kind, credential };
197
+ let t = this.advance().value, s = this.advance().value, r = this.expect(TokenType.STRING).value;
198
+ return { type: "Connect", service: t, kind: s, credential: r };
354
199
  }
355
- // ---- ON START | ON MESSAGE | ON SIGNAL FROM "..." ----
356
200
  parseOn() {
357
- this.expectKeyword("ON");
358
- if (this.atKeyword("START")) {
201
+ if (this.expectKeyword("ON"), this.atKeyword("START")) {
359
202
  this.advance();
360
- const body = this.parseBody();
361
- return { type: "On", event: "START", body };
203
+ let e = this.parseBody();
204
+ return { type: "On", event: "START", body: e };
362
205
  }
363
206
  if (this.atKeyword("MESSAGE")) {
364
207
  this.advance();
365
- const body = this.parseBody();
366
- return { type: "On", event: "MESSAGE", body };
208
+ let t = this.parseBody();
209
+ return { type: "On", event: "MESSAGE", body: t };
367
210
  }
368
211
  if (this.atWord("SIGNAL")) {
369
- this.advance();
370
- this.expectKeyword("FROM");
371
- const source = this.expect(TokenType.STRING).value;
372
- const body = this.parseBody();
373
- return { type: "On", event: "SIGNAL", source, body };
212
+ this.advance(), this.expectKeyword("FROM");
213
+ let s = this.expect(TokenType.STRING).value, r = this.parseBody();
214
+ return { type: "On", event: "SIGNAL", source: s, body: r };
374
215
  }
375
216
  this.error("unexpected event after ON");
376
217
  }
377
- // ---- WHEN CONTAINS "..." [OR CONTAINS "..."]* ----
378
218
  parseWhen() {
379
219
  this.expectKeyword("WHEN");
380
- const conditions = [this.parseCondition()];
381
- while (this.atKeyword("OR")) {
382
- this.advance();
383
- conditions.push(this.parseCondition());
384
- }
385
- const body = this.parseBody();
386
- return { type: "When", conditions, body };
220
+ let e = [this.parseCondition()];
221
+ for (; this.atKeyword("OR"); ) this.advance(), e.push(this.parseCondition());
222
+ let t = this.parseBody();
223
+ return { type: "When", conditions: e, body: t };
387
224
  }
388
225
  parseCondition() {
389
- this.expectKeyword("CONTAINS");
390
- if (this.at(TokenType.SYMBOL, "(")) {
391
- const values = this.parseStringList();
392
- return { op: "CONTAINS_ANY", values };
226
+ if (this.expectKeyword("CONTAINS"), this.at(TokenType.SYMBOL, "(")) {
227
+ let e = this.parseStringList();
228
+ return { op: "CONTAINS_ANY", values: e };
393
229
  }
394
230
  if (this.atKeyword("KEYWORDS")) {
395
- this.advance();
396
- this.expect(TokenType.SYMBOL, "(");
397
- const path = this.parseFileName();
398
- this.expect(TokenType.SYMBOL, ")");
399
- return { op: "CONTAINS_KEYWORDS_FILE", path };
231
+ this.advance(), this.expect(TokenType.SYMBOL, "(");
232
+ let t = this.parseFileName();
233
+ return this.expect(TokenType.SYMBOL, ")"), { op: "CONTAINS_KEYWORDS_FILE", path: t };
400
234
  }
401
- const value = this.expect(TokenType.STRING).value;
402
- return { op: "CONTAINS", value };
235
+ let s = this.expect(TokenType.STRING).value;
236
+ return { op: "CONTAINS", value: s };
403
237
  }
404
- // Lista de strings entre parênteses: ("a", "b", "c")
405
238
  parseStringList() {
406
239
  this.expect(TokenType.SYMBOL, "(");
407
- const values = [];
408
- while (!this.at(TokenType.SYMBOL, ")")) {
409
- values.push(this.expect(TokenType.STRING).value);
410
- if (this.at(TokenType.SYMBOL, ",")) this.advance();
411
- }
412
- this.expect(TokenType.SYMBOL, ")");
413
- return values;
240
+ let e = [];
241
+ for (; !this.at(TokenType.SYMBOL, ")"); ) e.push(this.expect(TokenType.STRING).value), this.at(TokenType.SYMBOL, ",") && this.advance();
242
+ return this.expect(TokenType.SYMBOL, ")"), e;
414
243
  }
415
- // Nome de ficheiro sem aspas, ex: saudacoes.txt ou lista_bot.sql.
416
- // O tokenizer separa "saudacoes" (IDENT) e ".txt" em IDENT + SYMBOL('.')
417
- // + IDENT, por isso remontamos aqui em vez de pedir um STRING.
418
244
  parseFileName() {
419
- let name = this.expect(TokenType.IDENT).value;
420
- while (this.at(TokenType.SYMBOL, ".")) {
421
- this.advance();
422
- name += "." + this.expect(TokenType.IDENT).value;
423
- }
424
- return name;
245
+ let e = this.expect(TokenType.IDENT).value;
246
+ for (; this.at(TokenType.SYMBOL, "."); ) this.advance(), e += "." + this.expect(TokenType.IDENT).value;
247
+ return e;
425
248
  }
426
- // ---- OTHERWISE ----
427
249
  parseOtherwise() {
428
250
  this.expectKeyword("OTHERWISE");
429
- const body = this.parseBody();
430
- return { type: "Otherwise", body };
431
- }
432
- // ---- REPLY [TO alvo] expressão ----
433
- // ---- REPLY [TO alvo] (ficheiro.txt, N) ----
434
- //
435
- // A segunda forma referencia uma entrada numerada de um ficheiro de
436
- // respostas (uma linha "N- texto" por entrada). Distingue-se da
437
- // expressão comum porque começa por "(" seguido de um nome de
438
- // ficheiro (IDENT + "." + IDENT), e não por uma STRING/NUMBER/IDENT
439
- // isolado — daí o lookahead antes de decidir qual ramo seguir.
440
- // ---- THINK(ficheiro.txt) ----
441
- // ---- THINK(ficheiro.txt) OR REPLY (fallback.txt, N) ----
442
- // ---- THINK(ficheiro.txt) OR REPLY "texto fixo" ----
443
- //
444
- // Diferente de REPLY: THINK não responde com um texto já mapeado, faz
445
- // retrieval local sobre um ficheiro de conhecimento (ver RAG.js) e só
446
- // responde se achar um bloco com confiança suficiente. Antes de
447
- // procurar, sinaliza onThinking (para a plataforma poder mostrar
448
- // "Pensando..." enquanto isso, já que a busca — em ficheiros grandes —
449
- // não é instantânea como um REPLY comum).
450
- //
451
- // O "OR REPLY ..." é o fallback: só corre se THINK não encontrar nada
452
- // com confiança suficiente. Fica preso ao mesmo statement (não é um
453
- // segundo statement solto no corpo do WHEN) porque só faz sentido
454
- // junto — um REPLY fallback sem THINK antes seria só um REPLY normal.
251
+ let e = this.parseBody();
252
+ return { type: "Otherwise", body: e };
253
+ }
455
254
  parseThink() {
456
- this.expectKeyword("THINK");
457
- this.expect(TokenType.SYMBOL, "(");
458
- const file = this.parseFileName();
255
+ this.expectKeyword("THINK"), this.expect(TokenType.SYMBOL, "(");
256
+ let e = this.parseFileName();
459
257
  this.expect(TokenType.SYMBOL, ")");
460
- const waiting = this.tryParseWaiting();
461
- let fallback = null;
462
- if (this.atKeyword("OR")) {
463
- this.advance();
464
- fallback = this.parseReply();
465
- }
466
- return { type: "Think", file, waiting, fallback };
467
- }
468
- // ---- WAITING() | WAITING(ficheiro.txt) | WAITING(ficheiro.txt, N) ----
469
- //
470
- // Modificador do THINK, sempre logo a seguir a ele, antes do OR (ver
471
- // parseThink acima). Os dois argumentos são opcionais e independentes:
472
- // sem nenhum, o interpretador usa texto e tempo mínimo por omissão
473
- // ("Pensando...", 3 segundos) — ver botql.js, resolveWaitingConfig.
258
+ let t = this.tryParseWaiting(), s = null;
259
+ return this.atKeyword("OR") && (this.advance(), s = this.parseReply()), { type: "Think", file: e, waiting: t, fallback: s };
260
+ }
474
261
  tryParseWaiting() {
475
262
  if (!this.atKeyword("WAITING")) return null;
476
- this.advance();
477
- this.expect(TokenType.SYMBOL, "(");
478
- let file = null;
479
- let seconds = null;
480
- if (!this.at(TokenType.SYMBOL, ")")) {
481
- file = this.parseFileName();
482
- if (this.at(TokenType.SYMBOL, ",")) {
483
- this.advance();
484
- seconds = this.parseExpression();
485
- }
486
- }
487
- this.expect(TokenType.SYMBOL, ")");
488
- return { file, seconds };
263
+ this.advance(), this.expect(TokenType.SYMBOL, "(");
264
+ let e = null, t = null, s = null;
265
+ return !this.at(TokenType.SYMBOL, ")") && (this.at(TokenType.STRING) ? t = this.advance().value : e = this.parseFileName(), this.at(TokenType.SYMBOL, ",") && (this.advance(), s = this.parseExpression())), this.expect(TokenType.SYMBOL, ")"), { file: e, text: t, seconds: s };
266
+ }
267
+ parseWaitingStatement() {
268
+ let e = this.tryParseWaiting();
269
+ return { type: "Waiting", file: e.file, text: e.text, seconds: e.seconds };
489
270
  }
490
271
  parseReply() {
491
272
  this.expectKeyword("REPLY");
492
- let target = null;
493
- if (this.atKeyword("TO")) {
494
- this.advance();
495
- target = this.advance().value;
496
- }
497
- if (this.isReplyFileForm()) {
273
+ let e = null;
274
+ if (this.atKeyword("TO") && (this.advance(), e = this.advance().value), this.isReplyFileForm()) {
498
275
  this.expect(TokenType.SYMBOL, "(");
499
- const file = this.parseFileName();
276
+ let t = this.parseFileName();
500
277
  this.expect(TokenType.SYMBOL, ",");
501
- const index = this.parseExpression();
502
- this.expect(TokenType.SYMBOL, ")");
503
- return { type: "Reply", target, file, index };
278
+ let s = this.parseExpression();
279
+ return this.expect(TokenType.SYMBOL, ")"), { type: "Reply", target: e, file: t, index: s };
504
280
  }
505
- const value = this.parseExpression();
506
- return { type: "Reply", target, value };
281
+ let r = this.parseExpression();
282
+ return { type: "Reply", target: e, value: r };
507
283
  }
508
- // Lookahead: "(" IDENT "." IDENT "," ... — só a forma de ficheiro
509
- // tem "." logo a seguir ao primeiro identificador dentro dos
510
- // parênteses. Uma chamada de função normal, ex: (NOW()), nunca
511
- // bate aqui.
512
284
  isReplyFileForm() {
513
285
  if (!this.at(TokenType.SYMBOL, "(")) return false;
514
- const next = this.tokens[this.pos + 1];
515
- const afterNext = this.tokens[this.pos + 2];
516
- return !!next && next.type === TokenType.IDENT && !!afterNext && afterNext.type === TokenType.SYMBOL && afterNext.value === ".";
286
+ let e = this.tokens[this.pos + 1], t = this.tokens[this.pos + 2];
287
+ return !!e && e.type === TokenType.IDENT && !!t && t.type === TokenType.SYMBOL && "." === t.value;
517
288
  }
518
- // ---- FORWARD TO "contacto" ----
519
289
  parseForward() {
520
- this.expectKeyword("FORWARD");
521
- this.expectKeyword("TO");
522
- const target = this.parseExpression();
523
- return { type: "ForwardTo", target };
290
+ this.expectKeyword("FORWARD"), this.expectKeyword("TO");
291
+ let e = this.parseExpression();
292
+ return { type: "ForwardTo", target: e };
524
293
  }
525
- // ---- PARSE SIGNAL ----
526
294
  parseParseSignal() {
527
- this.expectKeyword("PARSE");
528
- if (!this.atWord("SIGNAL")) this.error("expected SIGNAL after PARSE");
529
- this.advance();
530
- return { type: "ParseSignal" };
295
+ return this.expectKeyword("PARSE"), this.atWord("SIGNAL") || this.error("expected SIGNAL after PARSE"), this.advance(), { type: "ParseSignal" };
531
296
  }
532
- // ---- SEND TO destino ----
533
297
  parseSend() {
534
- this.expectKeyword("SEND");
535
- this.expectKeyword("TO");
536
- const target = this.advance().value;
537
- return { type: "SendTo", target };
298
+ this.expectKeyword("SEND"), this.expectKeyword("TO");
299
+ let e = this.advance().value;
300
+ return { type: "SendTo", target: e };
538
301
  }
539
- // ---- INSERT INTO tabela [(cols)] [VALUES (vals)] ----
540
302
  parseInsert() {
541
- this.expectKeyword("INSERT");
542
- this.expectKeyword("INTO");
543
- const table = this.expect(TokenType.IDENT).value;
544
- let columns = null;
545
- if (this.at(TokenType.SYMBOL, "(")) {
546
- columns = this.parseExpressionList();
547
- }
548
- let values = null;
549
- if (this.atKeyword("VALUES")) {
550
- this.advance();
551
- values = this.parseExpressionList();
552
- }
553
- return { type: "Insert", table, columns, values };
303
+ this.expectKeyword("INSERT"), this.expectKeyword("INTO");
304
+ let e = this.expect(TokenType.IDENT).value, t = null;
305
+ this.at(TokenType.SYMBOL, "(") && (t = this.parseExpressionList());
306
+ let s = null;
307
+ return this.atKeyword("VALUES") && (this.advance(), s = this.parseExpressionList()), { type: "Insert", table: e, columns: t, values: s };
554
308
  }
555
309
  parseExpressionList() {
556
310
  this.expect(TokenType.SYMBOL, "(");
557
- const items = [];
558
- while (!this.at(TokenType.SYMBOL, ")")) {
559
- items.push(this.parseExpression());
560
- if (this.at(TokenType.SYMBOL, ",")) this.advance();
561
- }
562
- this.expect(TokenType.SYMBOL, ")");
563
- return items;
311
+ let e = [];
312
+ for (; !this.at(TokenType.SYMBOL, ")"); ) e.push(this.parseExpression()), this.at(TokenType.SYMBOL, ",") && this.advance();
313
+ return this.expect(TokenType.SYMBOL, ")"), e;
564
314
  }
565
- // ---- UPDATE tabela SET col = expr WHERE expr ----
566
315
  parseUpdate() {
567
316
  this.expectKeyword("UPDATE");
568
- const table = this.expect(TokenType.IDENT).value;
317
+ let e = this.expect(TokenType.IDENT).value;
569
318
  this.expectKeyword("SET");
570
- const column = this.expect(TokenType.IDENT).value;
319
+ let t = this.expect(TokenType.IDENT).value;
571
320
  this.expect(TokenType.SYMBOL, "=");
572
- const value = this.parseExpression();
573
- let where = null;
574
- if (this.atKeyword("WHERE")) {
575
- this.advance();
576
- where = this.parseComparison();
577
- }
578
- return { type: "Update", table, set: { column, value }, where };
579
- }
580
- // ---- IMPORT {ficheiro.sql} ----
581
- // ---- IMPORT {ficheiro.txt, N} ----
582
- // ---- IMPORT {ficheiro.txt, N} AS alias ----
583
- //
584
- // A segunda forma importa só a entrada N de um ficheiro de valores
585
- // (o mesmo formato "N- texto" usado por REPLY (ficheiro, N)), em vez
586
- // do ficheiro inteiro. O índice é opcional, tal como em REPLY.
587
- //
588
- // AS alias só faz sentido junto com o índice (dá nome ao valor lido,
589
- // para CONNECT RESPONSE/Response(...) e qualquer outra expressão
590
- // referenciarem por esse nome em vez do nome generico "env"). Sem AS,
591
- // mantém-se o comportamento original: o valor cai em env.NOME_FICHEIRO.
321
+ let s = this.parseExpression(), r = null;
322
+ return this.atKeyword("WHERE") && (this.advance(), r = this.parseComparison()), { type: "Update", table: e, set: { column: t, value: s }, where: r };
323
+ }
592
324
  parseImport() {
593
- this.expectKeyword("IMPORT");
594
- this.expect(TokenType.SYMBOL, "{");
595
- const path = this.parseFileName();
596
- let index = null;
597
- if (this.at(TokenType.SYMBOL, ",")) {
598
- this.advance();
599
- index = this.parseExpression();
600
- }
601
- this.expect(TokenType.SYMBOL, "}");
602
- let alias = null;
603
- if (this.atKeyword("AS")) {
604
- this.advance();
605
- alias = this.expect(TokenType.IDENT).value;
606
- }
607
- return { type: "Import", path, index, alias };
325
+ this.expectKeyword("IMPORT"), this.expect(TokenType.SYMBOL, "{");
326
+ let e = this.parseFileName(), t = null;
327
+ this.at(TokenType.SYMBOL, ",") && (this.advance(), t = this.parseExpression()), this.expect(TokenType.SYMBOL, "}");
328
+ let s = null;
329
+ return this.atKeyword("AS") && (this.advance(), s = this.expect(TokenType.IDENT).value), { type: "Import", path: e, index: t, alias: s };
608
330
  }
609
- // ---- RUN BOT ----
610
331
  parseRunBot() {
611
- this.expectKeyword("RUN");
612
- this.expectKeyword("BOT");
613
- return { type: "RunBot" };
332
+ return this.expectKeyword("RUN"), this.expectKeyword("BOT"), { type: "RunBot" };
614
333
  }
615
- // ---- Expressões: literais, identificadores, chamadas, acesso a
616
- // propriedade (obj.prop) e concatenação com "+" ----
617
334
  parseExpression() {
618
- let node = this.parsePrimary();
619
- while (this.at(TokenType.SYMBOL, "+")) {
335
+ let e = this.parsePrimary();
336
+ for (; this.at(TokenType.SYMBOL, "+"); ) {
620
337
  this.advance();
621
- const right = this.parsePrimary();
622
- node = { type: "Binary", op: "+", left: node, right };
338
+ let t = this.parsePrimary();
339
+ e = { type: "Binary", op: "+", left: e, right: t };
623
340
  }
624
- return node;
341
+ return e;
625
342
  }
626
- // Igualdade, usada em WHERE (ex: id = LAST_INSERT_ID())
627
343
  parseComparison() {
628
- const left = this.parseExpression();
344
+ let e = this.parseExpression();
629
345
  if (this.at(TokenType.SYMBOL, "=")) {
630
346
  this.advance();
631
- const right = this.parseExpression();
632
- return { type: "Binary", op: "=", left, right };
347
+ let t = this.parseExpression();
348
+ return { type: "Binary", op: "=", left: e, right: t };
633
349
  }
634
- return left;
350
+ return e;
635
351
  }
636
352
  parsePrimary() {
637
- const tok = this.current();
638
- if (tok.type === TokenType.STRING) {
639
- this.advance();
640
- return { type: "Literal", value: tok.value };
641
- }
642
- if (tok.type === TokenType.NUMBER) {
643
- this.advance();
644
- return { type: "Literal", value: tok.value };
645
- }
646
- if (tok.type === TokenType.IDENT) {
353
+ let e = this.current();
354
+ if (e.type === TokenType.STRING || e.type === TokenType.NUMBER) return this.advance(), { type: "Literal", value: e.value };
355
+ if (e.type === TokenType.IDENT) {
647
356
  this.advance();
648
- let node = { type: "Identifier", name: tok.value };
357
+ let t = { type: "Identifier", name: e.value };
649
358
  if (this.at(TokenType.SYMBOL, "(")) {
650
- const args = this.parseExpressionList();
651
- node = { type: "Call", callee: node.name, args };
359
+ let s = this.parseExpressionList();
360
+ t = { type: "Call", callee: t.name, args: s };
652
361
  }
653
- while (this.at(TokenType.SYMBOL, ".")) {
362
+ for (; this.at(TokenType.SYMBOL, "."); ) {
654
363
  this.advance();
655
- const prop = this.expect(TokenType.IDENT).value;
656
- node = { type: "Member", object: node, property: prop };
364
+ let r = this.expect(TokenType.IDENT).value;
365
+ t = { type: "Member", object: t, property: r };
657
366
  }
658
- return node;
367
+ return t;
659
368
  }
660
369
  this.error("invalid expression");
661
370
  }
662
371
  };
663
- module.exports = { Parser, Tokenizer, TokenType, KEYWORDS };
372
+ module.exports = { Parser, Tokenizer, TokenType, KEYWORDS, BotQLSyntaxError };
664
373
  }
665
374
  });
666
375
 
@@ -668,97 +377,72 @@ var BotQL = (() => {
668
377
  var require_Database = __commonJS({
669
378
  "Database.js"(exports, module) {
670
379
  "use strict";
671
- var CONTEXT_SCHEMA = [
672
- { name: "id", columnType: "INT", constraints: ["PRIMARY", "KEY", "AUTO_INCREMENT"] },
673
- { name: "client", columnType: "TEXT", constraints: [] },
674
- { name: "message", columnType: "TEXT", constraints: [] },
675
- { name: "reply", columnType: "TEXT", constraints: [] },
676
- { name: "created_at", columnType: "DATETIME", constraints: [] }
677
- ];
380
+ var CONTEXT_SCHEMA = [{ name: "id", columnType: "INT", constraints: ["PRIMARY", "KEY", "AUTO_INCREMENT"] }, { name: "client", columnType: "TEXT", constraints: [] }, { name: "message", columnType: "TEXT", constraints: [] }, { name: "reply", columnType: "TEXT", constraints: [] }, { name: "created_at", columnType: "DATETIME", constraints: [] }];
678
381
  var MemoryDatabase = class {
679
382
  constructor() {
680
- this.tables = /* @__PURE__ */ new Map();
681
- this.autoIncrement = /* @__PURE__ */ new Map();
682
- }
683
- createTable(name, columns, preventDefault) {
684
- const resolvedColumns = name === "Context" && columns.length === 0 ? CONTEXT_SCHEMA : columns;
685
- if (this.tables.has(name)) {
686
- if (preventDefault) return;
687
- throw new Error(`runtime error: table "${name}" already exists`);
688
- }
689
- this.tables.set(name, { columns: resolvedColumns, rows: [] });
690
- this.autoIncrement.set(name, 0);
691
- }
692
- insert(table, columnNames, values) {
693
- const t = this.tables.get(table);
694
- if (!t) throw new Error(`runtime error: table "${table}" does not exist`);
695
- const nextId = this.autoIncrement.get(table) + 1;
696
- this.autoIncrement.set(table, nextId);
697
- const row = { id: nextId };
698
- columnNames.forEach((col, i) => {
699
- row[col] = values[i];
700
- });
701
- t.rows.push(row);
702
- return nextId;
703
- }
704
- update(table, column, value, whereColumn, whereValue) {
705
- const t = this.tables.get(table);
706
- if (!t) throw new Error(`runtime error: table "${table}" does not exist`);
707
- const row = whereColumn ? t.rows.find((r) => r[whereColumn] === whereValue) : t.rows[t.rows.length - 1];
708
- if (row) row[column] = value;
709
- return row || null;
710
- }
711
- getRows(table) {
712
- const t = this.tables.get(table);
383
+ this.tables = /* @__PURE__ */ new Map(), this.autoIncrement = /* @__PURE__ */ new Map();
384
+ }
385
+ createTable(e, t, r) {
386
+ let s = "Context" === e && 0 === t.length ? CONTEXT_SCHEMA : t;
387
+ if (this.tables.has(e)) {
388
+ if (r) return;
389
+ throw Error(`runtime error: table "${e}" already exists`);
390
+ }
391
+ this.tables.set(e, { columns: s, rows: [] }), this.autoIncrement.set(e, 0);
392
+ }
393
+ insert(e, t, r) {
394
+ let s = this.tables.get(e);
395
+ if (!s) throw Error(`runtime error: table "${e}" does not exist`);
396
+ let n = this.autoIncrement.get(e) + 1;
397
+ this.autoIncrement.set(e, n);
398
+ let i = { id: n };
399
+ return t.forEach((e2, t2) => {
400
+ i[e2] = r[t2];
401
+ }), s.rows.push(i), n;
402
+ }
403
+ update(e, t, r, s, n) {
404
+ let i = this.tables.get(e);
405
+ if (!i) throw Error(`runtime error: table "${e}" does not exist`);
406
+ let a = s ? i.rows.find((e2) => e2[s] === n) : i.rows[i.rows.length - 1];
407
+ return a && (a[t] = r), a || null;
408
+ }
409
+ getRows(e) {
410
+ let t = this.tables.get(e);
713
411
  return t ? t.rows.slice() : [];
714
412
  }
715
413
  };
716
- function mapColumnType(columnType) {
717
- const type = (columnType || "").toUpperCase();
718
- if (type === "INT" || type === "INTEGER") return "INTEGER";
719
- if (type === "DATETIME" || type === "DATE") return "TEXT";
720
- return "TEXT";
414
+ function mapColumnType(e) {
415
+ let t = (e || "").toUpperCase();
416
+ return "INT" === t || "INTEGER" === t ? "INTEGER" : "TEXT";
721
417
  }
722
- function buildColumnDef(col) {
723
- const type = mapColumnType(col.columnType);
724
- const isPrimary = col.constraints.includes("PRIMARY") && col.constraints.includes("KEY");
725
- const isAutoIncrement = col.constraints.includes("AUTO_INCREMENT");
726
- let def = `${col.name} ${type}`;
727
- if (isPrimary) def += " PRIMARY KEY";
728
- if (isPrimary && isAutoIncrement) def += " AUTOINCREMENT";
729
- return def;
418
+ function buildColumnDef(e) {
419
+ let t = mapColumnType(e.columnType), r = e.constraints.includes("PRIMARY") && e.constraints.includes("KEY"), s = e.constraints.includes("AUTO_INCREMENT"), n = `${e.name} ${t}`;
420
+ return r && (n += " PRIMARY KEY"), r && s && (n += " AUTOINCREMENT"), n;
730
421
  }
731
422
  var SQLiteDatabase = class {
732
- /**
733
- * @param {string} filePath Caminho do ficheiro .sqlite, ou ":memory:" para um banco temporário.
734
- */
735
- constructor(filePath = ":memory:") {
736
- const { DatabaseSync } = __require("node:sqlite");
737
- this.driver = new DatabaseSync(filePath);
738
- }
739
- createTable(name, columns, preventDefault) {
740
- const resolvedColumns = name === "Context" && columns.length === 0 ? CONTEXT_SCHEMA : columns;
741
- const columnDefs = resolvedColumns.map(buildColumnDef).join(", ");
742
- const ifNotExists = preventDefault ? "IF NOT EXISTS " : "";
743
- this.driver.exec(`CREATE TABLE ${ifNotExists}${name} (${columnDefs})`);
744
- }
745
- insert(table, columnNames, values) {
746
- const placeholders = columnNames.map(() => "?").join(", ");
747
- const sql = `INSERT INTO ${table} (${columnNames.join(", ")}) VALUES (${placeholders})`;
748
- const info = this.driver.prepare(sql).run(...values);
749
- return Number(info.lastInsertRowid);
750
- }
751
- update(table, column, value, whereColumn, whereValue) {
752
- if (whereColumn) {
753
- const sql = `UPDATE ${table} SET ${column} = ? WHERE ${whereColumn} = ?`;
754
- this.driver.prepare(sql).run(value, whereValue);
423
+ constructor(e = ":memory:") {
424
+ let { DatabaseSync: t } = __require("node:sqlite");
425
+ this.driver = new t(e);
426
+ }
427
+ createTable(e, t, r) {
428
+ let s = "Context" === e && 0 === t.length ? CONTEXT_SCHEMA : t, n = s.map(buildColumnDef).join(", ");
429
+ this.driver.exec(`CREATE TABLE ${r ? "IF NOT EXISTS " : ""}${e} (${n})`);
430
+ }
431
+ insert(e, t, r) {
432
+ let s = t.map(() => "?").join(", "), n = `INSERT INTO ${e} (${t.join(", ")}) VALUES (${s})`, i = this.driver.prepare(n).run(...r);
433
+ return Number(i.lastInsertRowid);
434
+ }
435
+ update(e, t, r, s, n) {
436
+ if (s) {
437
+ let i = `UPDATE ${e} SET ${t} = ? WHERE ${s} = ?`;
438
+ this.driver.prepare(i).run(r, n);
755
439
  } else {
756
- const sql = `UPDATE ${table} SET ${column} = ? WHERE id = (SELECT MAX(id) FROM ${table})`;
757
- this.driver.prepare(sql).run(value);
440
+ let a = `UPDATE ${e} SET ${t} = ? WHERE id = (SELECT MAX(id) FROM ${e})`;
441
+ this.driver.prepare(a).run(r);
758
442
  }
759
443
  }
760
- getRows(table) {
761
- return this.driver.prepare(`SELECT * FROM ${table}`).all();
444
+ getRows(e) {
445
+ return this.driver.prepare(`SELECT * FROM ${e}`).all();
762
446
  }
763
447
  close() {
764
448
  this.driver.close();
@@ -841,505 +525,221 @@ var BotQL = (() => {
841
525
  var require_RAG = __commonJS({
842
526
  "RAG.js"(exports, module) {
843
527
  "use strict";
844
- var STOPWORDS = /* @__PURE__ */ new Set([
845
- "a",
846
- "o",
847
- "as",
848
- "os",
849
- "de",
850
- "da",
851
- "do",
852
- "das",
853
- "dos",
854
- "e",
855
- "ou",
856
- "que",
857
- "um",
858
- "uma",
859
- "uns",
860
- "umas",
861
- "em",
862
- "no",
863
- "na",
864
- "nos",
865
- "nas",
866
- "por",
867
- "para",
868
- "com",
869
- "sem",
870
- "se",
871
- "foi",
872
- "ser",
873
- "sao",
874
- "esta",
875
- "estao",
876
- "ao",
877
- "aos",
878
- "mas",
879
- "como",
880
- "tem",
881
- "ter",
882
- "nao",
883
- "sim",
884
- "meu",
885
- "minha",
886
- "seu",
887
- "sua",
888
- "eu",
889
- "tu",
890
- "ele",
891
- "ela",
892
- "nos",
893
- "vos",
894
- "eles",
895
- "elas",
896
- "isso",
897
- "isto",
898
- "aquilo",
899
- "quando",
900
- "onde",
901
- "porque",
902
- "qual",
903
- "quais",
904
- "muito",
905
- "muita",
906
- "ja",
907
- "so"
908
- ]);
909
- function normalizar(texto) {
910
- return texto.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
528
+ var STOPWORDS = /* @__PURE__ */ new Set(["a", "o", "as", "os", "de", "da", "do", "das", "dos", "e", "ou", "que", "um", "uma", "uns", "umas", "em", "no", "na", "nos", "nas", "por", "para", "com", "sem", "se", "foi", "ser", "sao", "esta", "estao", "ao", "aos", "mas", "como", "tem", "ter", "nao", "sim", "meu", "minha", "seu", "sua", "eu", "tu", "ele", "ela", "nos", "vos", "eles", "elas", "isso", "isto", "aquilo", "quando", "onde", "porque", "qual", "quais", "muito", "muita", "ja", "so"]);
529
+ function normalizar(e) {
530
+ return e.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
911
531
  }
912
532
  var SUFIXOS_ADJETIVO_ADVERBIO = ["issimamente", "issimo", "issima", "mente"];
913
533
  var SUFIXOS_NOMINALIZACAO = ["acoes", "acao", "imentos", "imento", "idades", "idade"];
914
- function stem(palavra) {
915
- let p = palavra;
916
- for (const suf of SUFIXOS_ADJETIVO_ADVERBIO) {
917
- if (p.length > suf.length + 3 && p.endsWith(suf)) {
918
- p = p.slice(0, -suf.length);
919
- break;
920
- }
534
+ function stem(e) {
535
+ let t = e;
536
+ for (let i of SUFIXOS_ADJETIVO_ADVERBIO) if (t.length > i.length + 3 && t.endsWith(i)) {
537
+ t = t.slice(0, -i.length);
538
+ break;
921
539
  }
922
- for (const suf of SUFIXOS_NOMINALIZACAO) {
923
- if (p.length > suf.length + 3 && p.endsWith(suf)) {
924
- p = p.slice(0, -suf.length);
925
- break;
926
- }
540
+ for (let o of SUFIXOS_NOMINALIZACAO) if (t.length > o.length + 3 && t.endsWith(o)) {
541
+ t = t.slice(0, -o.length);
542
+ break;
927
543
  }
928
- if (p.length > 4 && p.endsWith("s") && !p.endsWith("ns")) {
929
- p = p.slice(0, -1);
930
- }
931
- return p;
544
+ return t.length > 4 && t.endsWith("s") && !t.endsWith("ns") && (t = t.slice(0, -1)), t;
932
545
  }
933
- function tokenizar(texto) {
934
- return normalizar(texto).toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((palavra) => palavra.length > 1 && !STOPWORDS.has(palavra)).map(stem);
546
+ function tokenizar(e) {
547
+ return normalizar(e).toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((e2) => e2.length > 1 && !STOPWORDS.has(e2)).map(stem);
935
548
  }
936
- function distanciaEdicao(a, b) {
937
- if (a === b) return 0;
938
- const la = a.length;
939
- const lb = b.length;
940
- if (la === 0) return lb;
941
- if (lb === 0) return la;
942
- let linhaAnterior = new Array(lb + 1);
943
- for (let j = 0; j <= lb; j++) linhaAnterior[j] = j;
944
- for (let i = 1; i <= la; i++) {
945
- const linhaAtual = [i];
946
- for (let j = 1; j <= lb; j++) {
947
- const custo = a[i - 1] === b[j - 1] ? 0 : 1;
948
- linhaAtual[j] = Math.min(
949
- linhaAtual[j - 1] + 1,
950
- linhaAnterior[j] + 1,
951
- linhaAnterior[j - 1] + custo
952
- );
953
- }
954
- linhaAnterior = linhaAtual;
955
- }
956
- return linhaAnterior[lb];
549
+ function distanciaEdicao(e, t) {
550
+ if (e === t) return 0;
551
+ let i = e.length, o = t.length;
552
+ if (0 === i) return o;
553
+ if (0 === o) return i;
554
+ let s = Array(o + 1);
555
+ for (let r = 0; r <= o; r++) s[r] = r;
556
+ for (let n = 1; n <= i; n++) {
557
+ let a = [n];
558
+ for (let l = 1; l <= o; l++) {
559
+ let c = e[n - 1] === t[l - 1] ? 0 : 1;
560
+ a[l] = Math.min(a[l - 1] + 1, s[l] + 1, s[l - 1] + c);
561
+ }
562
+ s = a;
563
+ }
564
+ return s[o];
957
565
  }
958
- function distanciaMaximaTolerada(tamanho) {
959
- if (tamanho <= 4) return 0;
960
- if (tamanho <= 7) return 1;
961
- return 2;
566
+ function distanciaMaximaTolerada(e) {
567
+ return e <= 4 ? 0 : e <= 7 ? 1 : 2;
962
568
  }
963
- var BM25_K1 = 1.5;
964
- var BM25_B = 0.75;
965
- var PESO_BM25 = 1;
966
- var PESO_FRASE = 2.5;
967
- var PESO_FUZZY = 0.4;
968
- var CONFIANCA_ALTA = 0.55;
969
- var CONFIANCA_MINIMA = 0.15;
970
- var MARGEM_MINIMA = 0.12;
971
- var SIMILARIDADE_DUPLICADA = 0.75;
972
- function extrairNomesProprios(texto) {
973
- const palavras = texto.split(/\s+/);
974
- const encontrados = [];
975
- for (let i = 1; i < palavras.length; i++) {
976
- const limpa = palavras[i].replace(/^[(["']+|[.,;:!?)\]"']+$/g, "");
977
- if (/^[A-ZÀ-Ý][a-zà-ÿ]+$/.test(limpa)) {
978
- encontrados.push(limpa);
979
- }
569
+ function extrairNomesProprios(e) {
570
+ let t = e.split(/\s+/), i = [];
571
+ for (let o = 1; o < t.length; o++) {
572
+ let s = t[o].replace(/^[(["']+|[.,;:!?)\]"']+$/g, "");
573
+ /^[A-ZÀ-Ý][a-zà-ÿ]+$/.test(s) && i.push(s);
980
574
  }
981
- return encontrados;
575
+ return i;
982
576
  }
983
577
  var KnowledgeIndex = class _KnowledgeIndex {
984
- /**
985
- * @param {string} sourceText Conteúdo do ficheiro de conhecimento.
986
- * @param {Object} [options]
987
- * @param {Record<string,string>} [options.synonyms] Mapa de termo -> termo
988
- * canónico, para ligar manualmente palavras que o stemmer não junta
989
- * sozinho (ex: { horas: 'horario' }).
990
- */
991
- constructor(sourceText, options = {}) {
992
- this.synonyms = {};
993
- for (const [de, para] of Object.entries(options.synonyms || {})) {
994
- this.synonyms[stem(normalizar(de.toLowerCase()))] = stem(normalizar(para.toLowerCase()));
995
- }
996
- this.blocks = _KnowledgeIndex.parseBlocks(sourceText);
997
- this._buildIndex();
578
+ constructor(e, t = {}) {
579
+ for (let [i, o] of (this.synonyms = {}, Object.entries(t.synonyms || {}))) this.synonyms[stem(normalizar(i.toLowerCase()))] = stem(normalizar(o.toLowerCase()));
580
+ this.blocks = _KnowledgeIndex.parseBlocks(e), this._buildIndex();
998
581
  }
999
- static parseBlocks(sourceText) {
1000
- return sourceText.split(/\n\s*\n/).map((bloco) => bloco.trim()).filter((bloco) => bloco.length > 0);
582
+ static parseBlocks(e) {
583
+ return e.split(/\n\s*\n/).map((e2) => e2.trim()).filter((e2) => e2.length > 0);
1001
584
  }
1002
- _aplicarSinonimos(tokens) {
1003
- return tokens.map((t) => this.synonyms[t] || t);
585
+ _aplicarSinonimos(e) {
586
+ return e.map((e2) => this.synonyms[e2] || e2);
1004
587
  }
1005
588
  _buildIndex() {
1006
- this.docs = this.blocks.map((bloco) => tokenizar(bloco));
1007
- this.docTextNormalizado = this.blocks.map((bloco) => normalizar(bloco).toLowerCase());
1008
- this.docFreq = /* @__PURE__ */ new Map();
1009
- this.vocabulario = /* @__PURE__ */ new Set();
1010
- for (const tokens of this.docs) {
1011
- const vistas = new Set(tokens);
1012
- for (const palavra of vistas) {
1013
- this.docFreq.set(palavra, (this.docFreq.get(palavra) || 0) + 1);
1014
- this.vocabulario.add(palavra);
1015
- }
1016
- }
1017
- this.listaVocabulario = Array.from(this.vocabulario);
1018
- this.totalDocs = this.docs.length;
1019
- this.avgDocLen = this.totalDocs === 0 ? 0 : this.docs.reduce((soma, tokens) => soma + tokens.length, 0) / this.totalDocs;
1020
- }
1021
- _idf(palavra) {
1022
- const n = this.docFreq.get(palavra) || 0;
1023
- if (n === 0) return 0;
1024
- return Math.log(1 + (this.totalDocs - n + 0.5) / (n + 0.5));
1025
- }
1026
- _termoMaisProximo(termo) {
1027
- const tolerancia = distanciaMaximaTolerada(termo.length);
1028
- if (tolerancia === 0) return null;
1029
- let melhor = null;
1030
- let melhorDist = tolerancia + 1;
1031
- for (const candidato of this.listaVocabulario) {
1032
- if (Math.abs(candidato.length - termo.length) > tolerancia) continue;
1033
- const d = distanciaEdicao(termo, candidato);
1034
- if (d < melhorDist) {
1035
- melhorDist = d;
1036
- melhor = candidato;
589
+ for (let e of (this.docs = this.blocks.map((e2) => tokenizar(e2)), this.docTextNormalizado = this.blocks.map((e2) => normalizar(e2).toLowerCase()), this.docPalavrasSignificativas = this.blocks.map((e2) => this._palavrasSignificativas(e2)), this.docFreq = /* @__PURE__ */ new Map(), this.vocabulario = /* @__PURE__ */ new Set(), this.docs)) {
590
+ let t = new Set(e);
591
+ for (let i of t) this.docFreq.set(i, (this.docFreq.get(i) || 0) + 1), this.vocabulario.add(i);
592
+ }
593
+ this.listaVocabulario = Array.from(this.vocabulario), this.totalDocs = this.docs.length, this.avgDocLen = 0 === this.totalDocs ? 0 : this.docs.reduce((e, t) => e + t.length, 0) / this.totalDocs;
594
+ }
595
+ _idf(e) {
596
+ let t = this.docFreq.get(e) || 0;
597
+ return 0 === t ? 0 : Math.log(1 + (this.totalDocs - t + 0.5) / (t + 0.5));
598
+ }
599
+ _termoMaisProximo(e) {
600
+ let t = distanciaMaximaTolerada(e.length);
601
+ if (0 === t) return null;
602
+ let i = null, o = t + 1;
603
+ for (let s of this.listaVocabulario) {
604
+ if (Math.abs(s.length - e.length) > t) continue;
605
+ let r = distanciaEdicao(e, s);
606
+ r < o && (o = r, i = s);
607
+ }
608
+ return o <= t ? i : null;
609
+ }
610
+ _scoreBM25(e, t) {
611
+ let i = this.docs[t];
612
+ if (0 === i.length) return 0;
613
+ let o = /* @__PURE__ */ new Map();
614
+ for (let s of i) o.set(s, (o.get(s) || 0) + 1);
615
+ let r = 0;
616
+ for (let n of e) {
617
+ let a = o.get(n) || 0, l = this._idf(n), c = 1;
618
+ if (0 === a) {
619
+ let d = this._termoMaisProximo(n);
620
+ if (null === d || 0 === (a = o.get(d) || 0)) continue;
621
+ l = this._idf(d), c = 0.4;
1037
622
  }
1038
- }
1039
- return melhorDist <= tolerancia ? melhor : null;
1040
- }
1041
- _scoreBM25(queryTokens, docIndex) {
1042
- const tokens = this.docs[docIndex];
1043
- if (tokens.length === 0) return 0;
1044
- const termFreq = /* @__PURE__ */ new Map();
1045
- for (const t of tokens) termFreq.set(t, (termFreq.get(t) || 0) + 1);
1046
- let score = 0;
1047
- for (const termo of queryTokens) {
1048
- let tf = termFreq.get(termo) || 0;
1049
- let idf = this._idf(termo);
1050
- let peso = 1;
1051
- if (tf === 0) {
1052
- const proximo = this._termoMaisProximo(termo);
1053
- if (proximo === null) continue;
1054
- tf = termFreq.get(proximo) || 0;
1055
- if (tf === 0) continue;
1056
- idf = this._idf(proximo);
1057
- peso = PESO_FUZZY;
1058
- }
1059
- const numerador = tf * (BM25_K1 + 1);
1060
- const denominador = tf + BM25_K1 * (1 - BM25_B + BM25_B * (tokens.length / this.avgDocLen));
1061
- score += peso * idf * (numerador / denominador);
1062
- }
1063
- return score;
1064
- }
1065
- _bonusFrase(message, docIndex) {
1066
- const msgNorm = normalizar(message).toLowerCase().replace(/[^a-z0-9\s]/g, " ");
1067
- const palavras = msgNorm.split(/\s+/).filter((p) => p.length > 1);
1068
- if (palavras.length < 2) return 0;
1069
- const textoBloco = this.docTextNormalizado[docIndex];
1070
- let bonus = 0;
1071
- for (let tamanho = Math.min(6, palavras.length); tamanho >= 2; tamanho--) {
1072
- for (let i = 0; i + tamanho <= palavras.length; i++) {
1073
- const frase = palavras.slice(i, i + tamanho).join(" ");
1074
- if (textoBloco.includes(frase)) {
1075
- bonus += tamanho * tamanho;
623
+ let h = 2.5 * a, u = a + 1.5 * (0.7 + 0.3 * (i.length / this.avgDocLen));
624
+ r += c * l * (h / u);
625
+ }
626
+ return r;
627
+ }
628
+ _palavrasSignificativas(e) {
629
+ return normalizar(e).toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((e2) => e2.length > 1 && !STOPWORDS.has(e2));
630
+ }
631
+ _bonusFrase(e, t) {
632
+ let i = this._palavrasSignificativas(e);
633
+ if (i.length < 2) return 0;
634
+ let o = this.docPalavrasSignificativas[t], s = 0;
635
+ for (let r = Math.min(6, i.length); r >= 2; r--) {
636
+ for (let n = 0; n + r <= i.length; n++) {
637
+ let a = i.slice(n, n + r);
638
+ if (this._contemSequencia(o, a)) {
639
+ s = r;
640
+ break;
1076
641
  }
1077
642
  }
643
+ if (s > 0) break;
1078
644
  }
1079
- return bonus;
1080
- }
1081
- _scoreDoc(message, queryTokens, docIndex) {
1082
- const bm25 = this._scoreBM25(queryTokens, docIndex);
1083
- const frase = this._bonusFrase(message, docIndex);
1084
- return PESO_BM25 * bm25 + PESO_FRASE * frase;
1085
- }
1086
- // Calcula o score de todos os blocos para a mensagem e devolve ordenado
1087
- // do maior para o menor. Base partilhada por search() e analyze().
1088
- _rankTodos(message) {
1089
- const queryTokens = this._aplicarSinonimos(tokenizar(message));
1090
- if (queryTokens.length === 0) return [];
1091
- const resultados = [];
1092
- for (let i = 0; i < this.totalDocs; i++) {
1093
- resultados.push({ index: i, score: this._scoreDoc(message, queryTokens, i) });
1094
- }
1095
- resultados.sort((a, b) => b.score - a.score);
1096
- return resultados;
1097
- }
1098
- /**
1099
- * Procura o bloco mais relevante para a mensagem recebida.
1100
- *
1101
- * @returns {{text: string, score: number, confidence: number, index: number} | null}
1102
- * `confidence` está sempre entre 0 e 1 — não é probabilidade
1103
- * estatística real, é uma escala interpretável para decidir um
1104
- * limiar no `.sql` (ex: "só responde se confidence > 0.3").
1105
- */
1106
- search(message) {
1107
- if (this.totalDocs === 0) return null;
1108
- const ranking = this._rankTodos(message);
1109
- if (ranking.length === 0) return null;
1110
- const melhor = ranking[0];
1111
- if (melhor.score <= 0) return null;
1112
- return {
1113
- text: this.blocks[melhor.index],
1114
- score: melhor.score,
1115
- confidence: melhor.score / (melhor.score + 3),
1116
- index: melhor.index
1117
- };
1118
- }
1119
- // Jaccard sobre os tokens (já com stem aplicado) de dois blocos já
1120
- // indexados. Usado só pra detetar "mesmo conteúdo repetido" (valor
1121
- // alto) — dois blocos sobre o mesmo assunto mas com detalhes
1122
- // diferentes normalmente NÃO têm jaccard alto (a maior parte da frase
1123
- // difere), por isso não serve pra decidir se vale a pena tentar unir.
1124
- _similaridadeBlocos(indexA, indexB) {
1125
- const a = new Set(this.docs[indexA]);
1126
- const b = new Set(this.docs[indexB]);
1127
- if (a.size === 0 || b.size === 0) return 0;
1128
- let intersecao = 0;
1129
- for (const t of a) if (b.has(t)) intersecao++;
1130
- const uniao = a.size + b.size - intersecao;
1131
- return uniao === 0 ? 0 : intersecao / uniao;
1132
- }
1133
- // Quantos tokens (com stem) os dois blocos partilham, em termos
1134
- // absolutos. Usado como gatilho pra tentar unir: basta partilharem UM
1135
- // termo de assunto ("entregamos") — quem garante que a fusão é segura
1136
- // não é isto, é a regra de "exatamente um nome próprio diferente em
1137
- // cada bloco" dentro de _tentarUnir.
1138
- _termosPartilhados(indexA, indexB) {
1139
- const a = new Set(this.docs[indexA]);
1140
- const b = this.docs[indexB];
1141
- let count = 0;
1142
- for (const t of b) if (a.has(t)) count++;
1143
- return count;
1144
- }
1145
- // Compara dois blocos concorrentes pelo nome próprio que cada um
1146
- // menciona, pra decidir se são "a mesma informação" ou "informação
1147
- // complementar que dá pra unir":
1148
- // - mesmo nome próprio nos dois (ex: os dois falam de Luanda)
1149
- // -> { tipo: 'duplicado' }: é a mesma coisa dita de formas
1150
- // diferentes, não há o que unir, usa qualquer um dos dois
1151
- // - nomes próprios diferentes (ex: Luanda vs Huambo)
1152
- // -> { tipo: 'unido', texto: '...' }: funde numa frase só
1153
- // - não dá pra identificar com segurança (nenhum nome próprio, ou
1154
- // mais de um em algum dos blocos) -> null: quem chama decide o
1155
- // que fazer a seguir (normalmente: reanalisar, ou cair no fallback)
1156
- _tentarUnir(textoA, textoB) {
1157
- const entidadesA = extrairNomesProprios(textoA);
1158
- const entidadesB = extrairNomesProprios(textoB);
1159
- if (entidadesA.length !== 1 || entidadesB.length !== 1) return null;
1160
- const entidadeA = entidadesA[0];
1161
- const entidadeB = entidadesB[0];
1162
- if (normalizar(entidadeA).toLowerCase() === normalizar(entidadeB).toLowerCase()) {
1163
- return { tipo: "duplicado" };
1164
- }
1165
- const primeiraPalavra = textoA.trim().split(/\s+/)[0].replace(/[.,;:!?]+$/, "");
1166
- return { tipo: "unido", texto: `${primeiraPalavra} em v\xE1rios lugares, como ${entidadeA} e ${entidadeB}.` };
1167
- }
1168
- // Segunda tentativa de ranking, usada só quando a primeira ficou
1169
- // ambígua: descarta metade dos termos da query (os de menor IDF, ou
1170
- // seja, os mais genéricos/comuns) e refaz o ranking só com os termos
1171
- // mais raros/decisivos. Uma query mais focada às vezes desempata o
1172
- // que uma query "cheia" deixa embolado.
1173
- _reanalisarFocado(message) {
1174
- const tokens = this._aplicarSinonimos(tokenizar(message));
1175
- if (tokens.length <= 2) return null;
1176
- const comIdf = tokens.map((t) => ({ termo: t, idf: this._idf(t) }));
1177
- comIdf.sort((a, b) => b.idf - a.idf);
1178
- const focados = comIdf.slice(0, Math.max(1, Math.ceil(tokens.length / 2))).map((x) => x.termo);
1179
- const resultados = [];
1180
- for (let i = 0; i < this.totalDocs; i++) {
1181
- resultados.push({ index: i, score: this._scoreDoc(message, focados, i) });
1182
- }
1183
- resultados.sort((a, b) => b.score - a.score);
1184
- return resultados;
1185
- }
1186
- /**
1187
- * Versão completa da busca: além do melhor bloco, avalia a evidência
1188
- * (melhor x segundo colocado) e devolve uma decisão explícita, em vez
1189
- * de deixar o `.sql` decidir tudo com um único limiar de confidence.
1190
- *
1191
- * EvidenceEvaluator: compara o melhor resultado com o segundo. Se os
1192
- * dois estão muito próximos, o motor não tem certeza de qual bloco
1193
- * responde à pergunta — mesmo que o score absoluto seja alto.
1194
- *
1195
- * ConfidenceEngine: mesma fórmula de sempre (score / (score + 3)),
1196
- * aplicada só ao melhor resultado.
1197
- *
1198
- * DecisionEngine: cruza confidence com margem para decidir entre:
1199
- * - RESPONDER confidence alta e o melhor bloco se destaca do 2o,
1200
- * OU os dois concorrentes foram reconciliados (ver
1201
- * abaixo) — nesse caso `texto` já vem pronto pra usar
1202
- * - REANALISAR os blocos concorrentes são sobre assuntos diferentes
1203
- * demais pra reconciliar, e nem a retentativa focada
1204
- * resolveu — o `.sql` decide o que fazer (normalmente
1205
- * cair no fallback do OR REPLY)
1206
- * - UNKNOWN confidence baixa demais, não há bloco que sirva
1207
- *
1208
- * Reconciliação (só entra quando o resultado não é decisivo de cara):
1209
- * 1. Se o melhor e o segundo colocado são basicamente o mesmo
1210
- * conteúdo (alta similaridade) — não há nada pra unir, usa o
1211
- * melhor tal como está.
1212
- * 2. Se são blocos diferentes mas do mesmo assunto, e cada um tem
1213
- * exatamente um nome próprio diferente (ex: "entregamos em
1214
- * Luanda" / "entregamos no Huambo") — tenta fundir numa frase só
1215
- * ("entregamos em vários lugares, como Luanda e Huambo").
1216
- * 3. Se nada disso se aplica, tenta de novo com uma versão mais
1217
- * enxuta da pergunta (só os termos mais decisivos) antes de
1218
- * desistir.
1219
- *
1220
- * @returns {{
1221
- * decision: 'RESPONDER'|'REANALISAR'|'UNKNOWN',
1222
- * confidence: number,
1223
- * margem: number,
1224
- * texto: string | null,
1225
- * unificado: boolean,
1226
- * reanalisado: boolean,
1227
- * melhor: {text: string, score: number, index: number} | null,
1228
- * segundo: {text: string, score: number, index: number} | null
1229
- * }}
1230
- */
1231
- analyze(message) {
1232
- const vazio = { decision: "UNKNOWN", confidence: 0, margem: 0, texto: null, unificado: false, reanalisado: false, melhor: null, segundo: null };
1233
- if (this.totalDocs === 0) return vazio;
1234
- const ranking = this._rankTodos(message);
1235
- if (ranking.length === 0 || ranking[0].score <= 0) return vazio;
1236
- const melhor = ranking[0];
1237
- const segundo = ranking[1] || { index: -1, score: 0 };
1238
- const margem = (melhor.score - segundo.score) / melhor.score;
1239
- const confidence = melhor.score / (melhor.score + 3);
1240
- const melhorInfo = { text: this.blocks[melhor.index], score: melhor.score, index: melhor.index };
1241
- const segundoInfo = segundo.index >= 0 ? { text: this.blocks[segundo.index], score: segundo.score, index: segundo.index } : null;
1242
- if (confidence < CONFIANCA_MINIMA) return vazio;
1243
- if (confidence >= CONFIANCA_ALTA && margem >= MARGEM_MINIMA) {
1244
- return {
1245
- decision: "RESPONDER",
1246
- confidence,
1247
- margem,
1248
- texto: melhorInfo.text,
1249
- unificado: false,
1250
- reanalisado: false,
1251
- melhor: melhorInfo,
1252
- segundo: segundoInfo
1253
- };
645
+ return 0 === s ? 0 : s / i.length;
646
+ }
647
+ _contemSequencia(e, t) {
648
+ outer: for (let i = 0; i + t.length <= e.length; i++) {
649
+ for (let o = 0; o < t.length; o++) if (e[i + o] !== t[o]) continue outer;
650
+ return true;
1254
651
  }
1255
- if (segundoInfo) {
1256
- const similaridade = this._similaridadeBlocos(melhor.index, segundo.index);
1257
- if (similaridade >= SIMILARIDADE_DUPLICADA) {
1258
- return {
1259
- decision: "RESPONDER",
1260
- confidence,
1261
- margem,
1262
- texto: melhorInfo.text,
1263
- unificado: false,
1264
- reanalisado: false,
1265
- melhor: melhorInfo,
1266
- segundo: segundoInfo
1267
- };
1268
- }
1269
- const termosPartilhados = this._termosPartilhados(melhor.index, segundo.index);
1270
- if (termosPartilhados >= 1) {
1271
- const resultado = this._tentarUnir(melhorInfo.text, segundoInfo.text);
1272
- if (resultado && resultado.tipo === "duplicado") {
1273
- return {
1274
- decision: "RESPONDER",
1275
- confidence,
1276
- margem,
1277
- texto: melhorInfo.text,
1278
- unificado: false,
1279
- reanalisado: false,
1280
- melhor: melhorInfo,
1281
- segundo: segundoInfo
1282
- };
1283
- }
1284
- if (resultado && resultado.tipo === "unido") {
1285
- return {
1286
- decision: "RESPONDER",
1287
- confidence,
1288
- margem,
1289
- texto: resultado.texto,
1290
- unificado: true,
1291
- reanalisado: false,
1292
- melhor: melhorInfo,
1293
- segundo: segundoInfo
1294
- };
1295
- }
652
+ return false;
653
+ }
654
+ _bonusTitulo(e, t) {
655
+ if (0 === e.length) return 0;
656
+ let i = new Set(this.docPalavrasSignificativas[t].slice(0, 8)), o = new Set(e), s = 0;
657
+ for (let r of o) i.has(r) && s++;
658
+ return s / o.size;
659
+ }
660
+ _scoreDoc(e, t, i) {
661
+ let o = this._scoreBM25(t, i), s = this._bonusFrase(e, i), r = this._bonusTitulo(this._palavrasSignificativas(e), i);
662
+ return 1 * o + 4 * s + 3 * r;
663
+ }
664
+ _rankTodos(e) {
665
+ let t = this._aplicarSinonimos(tokenizar(e));
666
+ if (0 === t.length) return [];
667
+ let i = [];
668
+ for (let o = 0; o < this.totalDocs; o++) i.push({ index: o, score: this._scoreDoc(e, t, o) });
669
+ return i.sort((e2, t2) => t2.score - e2.score), i;
670
+ }
671
+ search(e) {
672
+ if (0 === this.totalDocs) return null;
673
+ let t = this._rankTodos(e);
674
+ if (0 === t.length) return null;
675
+ let i = t[0];
676
+ return i.score <= 0 ? null : { text: this.blocks[i.index], score: i.score, confidence: i.score / (i.score + 3), index: i.index };
677
+ }
678
+ _similaridadeBlocos(e, t) {
679
+ let i = new Set(this.docs[e]), o = new Set(this.docs[t]);
680
+ if (0 === i.size || 0 === o.size) return 0;
681
+ let s = 0;
682
+ for (let r of i) o.has(r) && s++;
683
+ let n = i.size + o.size - s;
684
+ return 0 === n ? 0 : s / n;
685
+ }
686
+ _termosPartilhados(e, t) {
687
+ let i = new Set(this.docs[e]), o = this.docs[t], s = 0;
688
+ for (let r of o) i.has(r) && s++;
689
+ return s;
690
+ }
691
+ _tentarUnir(e, t) {
692
+ let i = extrairNomesProprios(e), o = extrairNomesProprios(t);
693
+ if (1 !== i.length || 1 !== o.length) return null;
694
+ let s = i[0], r = o[0];
695
+ if (normalizar(s).toLowerCase() === normalizar(r).toLowerCase()) return { tipo: "duplicado" };
696
+ let n = e.trim().split(/\s+/)[0].replace(/[.,;:!?]+$/, "");
697
+ return { tipo: "unido", texto: `${n} em v\xE1rios lugares, como ${s} e ${r}.` };
698
+ }
699
+ _reanalisarFocado(e) {
700
+ let t = this._aplicarSinonimos(tokenizar(e));
701
+ if (t.length <= 2) return null;
702
+ let i = t.map((e2) => ({ termo: e2, idf: this._idf(e2) }));
703
+ i.sort((e2, t2) => t2.idf - e2.idf);
704
+ let o = i.slice(0, Math.max(1, Math.ceil(t.length / 2))).map((e2) => e2.termo), s = [];
705
+ for (let r = 0; r < this.totalDocs; r++) s.push({ index: r, score: this._scoreDoc(e, o, r) });
706
+ return s.sort((e2, t2) => t2.score - e2.score), s;
707
+ }
708
+ analyze(e) {
709
+ let t = { decision: "UNKNOWN", confidence: 0, margem: 0, texto: null, unificado: false, reanalisado: false, melhor: null, segundo: null };
710
+ if (0 === this.totalDocs) return t;
711
+ let i = this._rankTodos(e);
712
+ if (0 === i.length || i[0].score <= 0) return t;
713
+ let o = i[0], s = i[1] || { index: -1, score: 0 }, r = (o.score - s.score) / o.score, n = o.score / (o.score + 3), a = { text: this.blocks[o.index], score: o.score, index: o.index }, l = s.index >= 0 ? { text: this.blocks[s.index], score: s.score, index: s.index } : null;
714
+ if (n < 0.15) return t;
715
+ if (n >= 0.55 && r >= 0.12) return { decision: "RESPONDER", confidence: n, margem: r, texto: a.text, unificado: false, reanalisado: false, melhor: a, segundo: l };
716
+ if (l) {
717
+ let c = this._similaridadeBlocos(o.index, s.index);
718
+ if (c >= 0.75) return { decision: "RESPONDER", confidence: n, margem: r, texto: a.text, unificado: false, reanalisado: false, melhor: a, segundo: l };
719
+ let d = this._termosPartilhados(o.index, s.index);
720
+ if (d >= 1) {
721
+ let h = this._tentarUnir(a.text, l.text);
722
+ if (h && "duplicado" === h.tipo) return { decision: "RESPONDER", confidence: n, margem: r, texto: a.text, unificado: false, reanalisado: false, melhor: a, segundo: l };
723
+ if (h && "unido" === h.tipo) return { decision: "RESPONDER", confidence: n, margem: r, texto: h.texto, unificado: true, reanalisado: false, melhor: a, segundo: l };
1296
724
  }
1297
725
  }
1298
- const tentativa2 = this._reanalisarFocado(message);
1299
- if (tentativa2 && tentativa2.length > 0 && tentativa2[0].score > 0) {
1300
- const melhor2 = tentativa2[0];
1301
- const segundo2 = tentativa2[1] || { index: -1, score: 0 };
1302
- const margem2 = (melhor2.score - segundo2.score) / melhor2.score;
1303
- const confidence2 = melhor2.score / (melhor2.score + 3);
1304
- if (confidence2 >= CONFIANCA_ALTA && margem2 >= MARGEM_MINIMA) {
1305
- return {
1306
- decision: "RESPONDER",
1307
- confidence: confidence2,
1308
- margem: margem2,
1309
- texto: this.blocks[melhor2.index],
1310
- unificado: false,
1311
- reanalisado: true,
1312
- melhor: { text: this.blocks[melhor2.index], score: melhor2.score, index: melhor2.index },
1313
- segundo: segundo2.index >= 0 ? { text: this.blocks[segundo2.index], score: segundo2.score, index: segundo2.index } : null
1314
- };
1315
- }
726
+ let u = this._reanalisarFocado(e);
727
+ if (u && u.length > 0 && u[0].score > 0) {
728
+ let f = u[0], $ = u[1] || { index: -1, score: 0 }, m = (f.score - $.score) / f.score, g = f.score / (f.score + 3);
729
+ if (g >= 0.55 && m >= 0.12) return { decision: "RESPONDER", confidence: g, margem: m, texto: this.blocks[f.index], unificado: false, reanalisado: true, melhor: { text: this.blocks[f.index], score: f.score, index: f.index }, segundo: $.index >= 0 ? { text: this.blocks[$.index], score: $.score, index: $.index } : null };
1316
730
  }
1317
- return {
1318
- decision: "REANALISAR",
1319
- confidence,
1320
- margem,
1321
- texto: null,
1322
- unificado: false,
1323
- reanalisado: false,
1324
- melhor: melhorInfo,
1325
- segundo: segundoInfo
1326
- };
731
+ return { decision: "REANALISAR", confidence: n, margem: r, texto: null, unificado: false, reanalisado: false, melhor: a, segundo: l };
1327
732
  }
1328
733
  };
1329
734
  var KnowledgeCache = class {
1330
- constructor(fileSystem) {
1331
- this.fileSystem = fileSystem;
1332
- this.cache = /* @__PURE__ */ new Map();
1333
- }
1334
- get(resolvedPath, options) {
1335
- if (this.cache.has(resolvedPath)) return this.cache.get(resolvedPath);
1336
- if (!this.fileSystem.exists(resolvedPath)) {
1337
- throw new Error(`BotQL/THINK: ficheiro de conhecimento n\xE3o encontrado: "${resolvedPath}"`);
1338
- }
1339
- const texto = this.fileSystem.readFile(resolvedPath);
1340
- const index = new KnowledgeIndex(texto, options);
1341
- this.cache.set(resolvedPath, index);
1342
- return index;
735
+ constructor(e) {
736
+ this.fileSystem = e, this.cache = /* @__PURE__ */ new Map();
737
+ }
738
+ get(e, t) {
739
+ if (this.cache.has(e)) return this.cache.get(e);
740
+ if (!this.fileSystem.exists(e)) throw Error(`BotQL/THINK: ficheiro de conhecimento n\xE3o encontrado: "${e}"`);
741
+ let i = this.fileSystem.readFile(e), o = new KnowledgeIndex(i, t);
742
+ return this.cache.set(e, o), o;
1343
743
  }
1344
744
  };
1345
745
  module.exports = { KnowledgeIndex, KnowledgeCache, tokenizar, normalizar, stem, distanciaEdicao };
@@ -1349,572 +749,308 @@ var BotQL = (() => {
1349
749
  // botql.js
1350
750
  var require_botql = __commonJS({
1351
751
  "botql.js"(exports, module) {
1352
- var { Parser } = require_Parser();
1353
- var { MemoryDatabase } = require_Database();
1354
- var { createDefaultFileSystem } = require_FileSystem();
1355
- var { KnowledgeCache } = require_RAG();
1356
- var WAITING_TEXTO_PADRAO = "Pensando...";
1357
- var WAITING_SEGUNDOS_PADRAO = 3;
752
+ var { Parser: e } = require_Parser();
753
+ var { MemoryDatabase: t } = require_Database();
754
+ var { createDefaultFileSystem: s } = require_FileSystem();
755
+ var { KnowledgeCache: i } = require_RAG();
1358
756
  var BotQLInterpreter = class _BotQLInterpreter {
1359
- constructor(options = {}) {
1360
- this.db = options.db || new MemoryDatabase();
1361
- this.onReply = options.onReply || null;
1362
- this.onForward = options.onForward || null;
1363
- this.onSend = options.onSend || null;
1364
- this.signalParser = options.signalParser || ((raw) => raw);
1365
- this.onThinking = options.onThinking || null;
1366
- this.botName = null;
1367
- this.platform = null;
1368
- this.connections = [];
1369
- this.handlers = { START: [], MESSAGE: [], SIGNAL: [] };
1370
- this.running = false;
1371
- this.responseConnections = /* @__PURE__ */ new Set();
1372
- this.onResponse = options.onResponse || null;
1373
- this.defaultMessageConfig = null;
1374
- this.nativeFuncs = /* @__PURE__ */ new Map();
1375
- this.registerFunction("NOW", () => (/* @__PURE__ */ new Date()).toISOString());
1376
- this.fileSystem = options.fileSystem || createDefaultFileSystem();
1377
- const defaultBasePath = typeof process !== "undefined" && process.cwd ? process.cwd() : "";
1378
- this._basePathStack = [options.basePath || defaultBasePath];
1379
- this._importedFiles = /* @__PURE__ */ new Set();
1380
- this._rootBasePath = options.basePath || defaultBasePath;
1381
- this._keywordsFileCache = /* @__PURE__ */ new Map();
1382
- this._replyFileCache = /* @__PURE__ */ new Map();
1383
- this.envValues = /* @__PURE__ */ new Map();
1384
- this.knowledgeCache = new KnowledgeCache(this.fileSystem);
1385
- }
1386
- static fromSource(source, options = {}) {
1387
- const ast = new Parser(source).parseProgram();
1388
- const interpreter = new _BotQLInterpreter(options);
1389
- interpreter.load(ast);
1390
- return interpreter;
1391
- }
1392
- // Lê um ficheiro .sql do disco (ou de outro fileSystem injetado) e usa
1393
- // a sua pasta como base para resolver IMPORTs relativos dentro dele.
1394
- static fromFile(filePath, options = {}) {
1395
- const fileSystem = options.fileSystem || createDefaultFileSystem();
1396
- const resolved = fileSystem.resolve("", filePath);
1397
- const source = fileSystem.readFile(resolved);
1398
- return _BotQLInterpreter.fromSource(source, {
1399
- ...options,
1400
- fileSystem,
1401
- basePath: fileSystem.dirname(resolved)
1402
- });
1403
- }
1404
- registerFunction(name, fn) {
1405
- this.nativeFuncs.set(name, fn);
1406
- }
1407
- // ---- Carregamento da AST: statements de topo, fora de eventos ----
1408
- //
1409
- // IMPORT é sempre resolvido antes de qualquer outro comando, independente
1410
- // de onde aparece no ficheiro (tal como o SOURCE do MySQL ou o \i do
1411
- // psql) — por isso o corpo é primeiro "achatado": todo o conteúdo vindo
1412
- // de ficheiros importados entra ANTES dos statements do próprio ficheiro,
1413
- // recursivamente, mesmo que o IMPORT apareça no meio ou no fim do texto.
1414
- load(ast, basePath = this._basePathStack[this._basePathStack.length - 1]) {
1415
- const flatBody = this._flattenImports(ast.body, basePath);
1416
- for (const node of flatBody) {
1417
- switch (node.type) {
1418
- case "CreateBot":
1419
- this.botName = node.name;
1420
- break;
1421
- case "Platform":
1422
- this.platform = node.value;
1423
- break;
1424
- case "Connect":
1425
- this.connections.push({
1426
- service: node.service,
1427
- kind: node.kind,
1428
- credential: node.credential
1429
- });
1430
- break;
1431
- case "ConnectResponse":
1432
- if (!this.envValues.has(node.alias)) {
1433
- throw new Error(`runtime error: CONNECT RESPONSE failed, alias "${node.alias}" was not imported (use IMPORT {..., N} AS ${node.alias})`);
1434
- }
1435
- this.responseConnections.add(node.alias);
1436
- break;
1437
- case "CreateTable":
1438
- this.db.createTable(node.name, node.columns, node.preventDefault);
1439
- if (node.defaultMessage) {
1440
- const senderColumn = node.name === "Context" ? "client" : (node.columns.find((c) => c.name === "client" || c.name === "sender") || {}).name;
1441
- if (!senderColumn) {
1442
- throw new Error(`runtime error: DEFAULT MESSAGE on table "${node.name}" requires a "client" or "sender" column`);
1443
- }
1444
- this.defaultMessageConfig = { table: node.name, senderColumn, ...node.defaultMessage };
1445
- }
1446
- break;
1447
- case "On":
1448
- this.handlers[node.event].push(node);
1449
- break;
1450
- case "RunBot":
1451
- this.running = true;
1452
- break;
1453
- default:
1454
- throw new Error(`runtime error: unsupported top-level statement: ${node.type}`);
1455
- }
757
+ constructor(e2 = {}) {
758
+ this.db = e2.db || new t(), this.onReply = e2.onReply || null, this.onForward = e2.onForward || null, this.onSend = e2.onSend || null, this.signalParser = e2.signalParser || ((e3) => e3), this.onThinking = e2.onThinking || null, this.botName = null, this.platform = null, this.connections = [], this.handlers = { START: [], MESSAGE: [], SIGNAL: [] }, this.running = false, this.responseConnections = /* @__PURE__ */ new Set(), this._clientesOcupados = /* @__PURE__ */ new Set(), this.onBusy = e2.onBusy || null, this.onResponse = e2.onResponse || null, this.defaultMessageConfig = null, this.nativeFuncs = /* @__PURE__ */ new Map(), this.registerFunction("NOW", () => (/* @__PURE__ */ new Date()).toISOString()), this.fileSystem = e2.fileSystem || s();
759
+ let n = "undefined" != typeof process && process.cwd ? process.cwd() : "";
760
+ this._basePathStack = [e2.basePath || n], this._importedFiles = /* @__PURE__ */ new Set(), this._rootBasePath = e2.basePath || n, this._keywordsFileCache = /* @__PURE__ */ new Map(), this._replyFileCache = /* @__PURE__ */ new Map(), this.envValues = /* @__PURE__ */ new Map(), this.knowledgeCache = new i(this.fileSystem);
761
+ }
762
+ static fromSource(t2, s2 = {}) {
763
+ let i2 = new e(t2).parseProgram(), n = new _BotQLInterpreter(s2);
764
+ return n.load(i2), n;
765
+ }
766
+ static fromFile(e2, t2 = {}) {
767
+ let i2 = t2.fileSystem || s(), n = i2.resolve("", e2), r = i2.readFile(n);
768
+ return _BotQLInterpreter.fromSource(r, { ...t2, fileSystem: i2, basePath: i2.dirname(n) });
769
+ }
770
+ registerFunction(e2, t2) {
771
+ this.nativeFuncs.set(e2, t2);
772
+ }
773
+ load(e2, t2 = this._basePathStack[this._basePathStack.length - 1]) {
774
+ let s2 = this._flattenImports(e2.body, t2);
775
+ for (let i2 of s2) switch (i2.type) {
776
+ case "CreateBot":
777
+ this.botName = i2.name;
778
+ break;
779
+ case "Platform":
780
+ this.platform = i2.value;
781
+ break;
782
+ case "Connect":
783
+ this.connections.push({ service: i2.service, kind: i2.kind, credential: i2.credential });
784
+ break;
785
+ case "ConnectResponse":
786
+ if (!this.envValues.has(i2.alias)) throw Error(`runtime error: CONNECT RESPONSE failed, alias "${i2.alias}" was not imported (use IMPORT {..., N} AS ${i2.alias})`);
787
+ this.responseConnections.add(i2.alias);
788
+ break;
789
+ case "CreateTable":
790
+ if (this.db.createTable(i2.name, i2.columns, i2.preventDefault), i2.defaultMessage) {
791
+ let n = "Context" === i2.name ? "client" : (i2.columns.find((e3) => "client" === e3.name || "sender" === e3.name) || {}).name;
792
+ if (!n) throw Error(`runtime error: DEFAULT MESSAGE on table "${i2.name}" requires a "client" or "sender" column`);
793
+ this.defaultMessageConfig = { table: i2.name, senderColumn: n, ...i2.defaultMessage };
794
+ }
795
+ break;
796
+ case "On":
797
+ this.handlers[i2.event].push(i2);
798
+ break;
799
+ case "RunBot":
800
+ this.running = true;
801
+ break;
802
+ default:
803
+ throw Error(`runtime error: unsupported top-level statement: ${i2.type}`);
1456
804
  }
1457
805
  }
1458
- // Devolve um array de statements sem nenhum nó 'Import': o conteúdo de
1459
- // cada ficheiro importado é lido, parseado e achatado recursivamente
1460
- // primeiro (imports dentro de imports também respeitam a regra), e só
1461
- // depois vêm os statements que pertencem a este próprio ficheiro.
1462
- //
1463
- // IMPORT {ficheiro.sql} (sem índice) — comportamento original: lê o
1464
- // ficheiro inteiro como código BotQL e junta ao programa.
1465
- //
1466
- // IMPORT {ficheiro.txt, N} (com índice) — não é código: lê só a
1467
- // entrada N do ficheiro (mesmo formato "N- valor" do REPLY indexado)
1468
- // e guarda em this.envValues. Com AS alias, a chave é o alias (fica
1469
- // acessível diretamente pelo nome, ex: K, CONNECT RESPONSE K); sem
1470
- // AS, mantém-se o comportamento original — chave é o nome do
1471
- // ficheiro sem extensão, só acessível via env.NOME_FICHEIRO. Um
1472
- // ficheiro sem AS não pode ser usado por CONNECT RESPONSE (exige
1473
- // alias explícito, para não colidir com outros IMPORTs do mesmo
1474
- // ficheiro em índices diferentes).
1475
- // Não produz nenhum statement — é resolvido e removido do AST aqui
1476
- // mesmo, antes de correr qualquer evento.
1477
- _flattenImports(body, basePath) {
1478
- const fromImports = [];
1479
- const ownStatements = [];
1480
- for (const node of body) {
1481
- if (node.type !== "Import") {
1482
- ownStatements.push(node);
806
+ _flattenImports(t2, s2) {
807
+ let i2 = [], n = [];
808
+ for (let r of t2) {
809
+ if ("Import" !== r.type) {
810
+ n.push(r);
1483
811
  continue;
1484
812
  }
1485
- if (node.index !== null && node.index !== void 0) {
1486
- const index = this.evalExpr(node.index, this.createContext());
1487
- const entries = this._loadIndexedFile(node.path, basePath);
1488
- const value = entries.get(Number(index));
1489
- if (value === void 0) {
1490
- throw new Error(`runtime error: IMPORT failed, entry ${index} not found in "${node.path}"`);
1491
- }
1492
- const key = node.alias || node.path.replace(/\.[^.]+$/, "");
1493
- this.envValues.set(key, value);
813
+ if (null !== r.index && void 0 !== r.index) {
814
+ let a = this.evalExpr(r.index, this.createContext()), l = this._loadIndexedFile(r.path, s2), o = l.get(Number(a));
815
+ if (void 0 === o) throw Error(`runtime error: IMPORT failed, entry ${a} not found in "${r.path}"`);
816
+ let h = r.alias || r.path.replace(/\.[^.]+$/, "");
817
+ this.envValues.set(h, o);
1494
818
  continue;
1495
819
  }
1496
- const fullPath = this.fileSystem.resolve(basePath, node.path);
1497
- if (this._importedFiles.has(fullPath)) continue;
1498
- if (!this.fileSystem.exists(fullPath)) {
1499
- throw new Error(`runtime error: IMPORT failed, file not found: "${fullPath}"`);
1500
- }
1501
- this._importedFiles.add(fullPath);
1502
- const source = this.fileSystem.readFile(fullPath);
1503
- const importedAst = new Parser(source).parseProgram();
1504
- const nestedFlat = this._flattenImports(importedAst.body, this.fileSystem.dirname(fullPath));
1505
- fromImports.push(...nestedFlat);
1506
- }
1507
- return [...fromImports, ...ownStatements];
1508
- }
1509
- // ---- Contexto de execução de um evento ----
1510
- createContext(base = {}) {
1511
- return {
1512
- vars: {
1513
- client: base.client || null,
1514
- message: base.message || null,
1515
- lastMsg: null,
1516
- SIGNAL: null
1517
- },
1518
- rawSignal: base.rawSignal || null,
1519
- lastInsertId: null
1520
- };
1521
- }
1522
- // ---- Ciclo de vida do bot ----
1523
- async start() {
1524
- for (const handler of this.handlers.START) {
1525
- const ctx = this.createContext();
1526
- await this.run(handler.body, ctx);
820
+ let u = this.fileSystem.resolve(s2, r.path);
821
+ if (this._importedFiles.has(u)) continue;
822
+ if (!this.fileSystem.exists(u)) throw Error(`runtime error: IMPORT failed, file not found: "${u}"`);
823
+ this._importedFiles.add(u);
824
+ let c = this.fileSystem.readFile(u), d = new e(c).parseProgram(), f = this._flattenImports(d.body, this.fileSystem.dirname(u));
825
+ i2.push(...f);
1527
826
  }
827
+ return [...i2, ...n];
1528
828
  }
1529
- async receiveMessage(client, message) {
1530
- const ctx = this.createContext({ client, message });
1531
- const cumprimentou = await this.greetIfNew(client);
1532
- if (cumprimentou) return ctx;
1533
- for (const handler of this.handlers.MESSAGE) {
1534
- await this.run(handler.body, ctx);
1535
- }
1536
- return ctx;
1537
- }
1538
- // Mostra a DEFAULT MESSAGE para este client, se ainda não for
1539
- // conhecido — sem precisar de nenhuma mensagem recebida. Usado por
1540
- // receiveMessage() (caso real, reativo) e também pode ser chamado
1541
- // diretamente pelo editor/preview ao abrir o chat, já que aí não há a
1542
- // restrição das plataformas reais de só poder responder depois do
1543
- // utilizador escrever primeiro. Devolve true se cumprimentou (e por
1544
- // isso nada mais deve correr nesse turno), false caso contrário —
1545
- // incluindo quando não há DEFAULT MESSAGE nenhuma configurada.
1546
- async greetIfNew(client) {
1547
- if (!this.defaultMessageConfig) return false;
1548
- const { table, senderColumn } = this.defaultMessageConfig;
1549
- const jaConhecido = this.db.getRows(table).some((row) => row[senderColumn] === client);
1550
- if (jaConhecido) return false;
1551
- const ctx = this.createContext({ client });
1552
- const text = this.resolveDefaultMessageText(this.defaultMessageConfig, ctx);
1553
- ctx.vars.lastMsg = text;
1554
- this.db.insert(table, [senderColumn], [client]);
1555
- if (this.onReply) {
1556
- await this.onReply({ target: null, text, client });
1557
- }
1558
- return true;
829
+ createContext(e2 = {}) {
830
+ return { vars: { client: e2.client || null, message: e2.message || null, lastMsg: null, SIGNAL: null }, rawSignal: e2.rawSignal || null, lastInsertId: null };
1559
831
  }
1560
- // Resolve o texto do DEFAULT MESSAGE: mesma dualidade texto-direto vs
1561
- // ficheiro indexado que o REPLY já tem (ver resolveReplyFromFile).
1562
- resolveDefaultMessageText(config, ctx) {
1563
- if (config.file) {
1564
- return this.resolveReplyFromFile(config.file, config.index, ctx);
832
+ async start() {
833
+ for (let e2 of this.handlers.START) {
834
+ let t2 = this.createContext();
835
+ await this.run(e2.body, t2);
1565
836
  }
1566
- return String(this.evalExpr(config.value, ctx));
1567
837
  }
1568
- async receiveSignal(source, rawSignal) {
1569
- const ctx = this.createContext({ rawSignal });
1570
- for (const handler of this.handlers.SIGNAL) {
1571
- if (handler.source !== source) continue;
1572
- await this.run(handler.body, ctx);
838
+ async receiveMessage(e2, t2) {
839
+ if (this._clientesOcupados.has(e2)) return this.onBusy && await this.onBusy({ client: e2, message: t2 }), null;
840
+ this._clientesOcupados.add(e2);
841
+ try {
842
+ let s2 = this.createContext({ client: e2, message: t2 }), i2 = await this.greetIfNew(e2);
843
+ if (i2) return s2;
844
+ for (let n of this.handlers.MESSAGE) await this.run(n.body, s2);
845
+ return s2;
846
+ } finally {
847
+ this._clientesOcupados.delete(e2);
1573
848
  }
1574
- return ctx;
1575
- }
1576
- // ---- Execução de uma lista de statements (corpo de um bloco) ----
1577
- async run(statements, ctx) {
1578
- let groupMatched = false;
1579
- for (let i = 0; i < statements.length; i++) {
1580
- const stmt = statements[i];
1581
- if (stmt.type === "When") {
1582
- if (i === 0 || statements[i - 1].type !== "When") groupMatched = false;
1583
- const isMatch = stmt.conditions.some((cond) => this.evalCondition(cond, ctx));
1584
- if (isMatch) {
1585
- await this.run(stmt.body, ctx);
1586
- groupMatched = true;
1587
- }
849
+ }
850
+ async greetIfNew(e2) {
851
+ if (!this.defaultMessageConfig) return false;
852
+ let { table: t2, senderColumn: s2 } = this.defaultMessageConfig, i2 = this.db.getRows(t2).some((t3) => t3[s2] === e2);
853
+ if (i2) return false;
854
+ let n = this.createContext({ client: e2 }), r = this.resolveDefaultMessageText(this.defaultMessageConfig, n);
855
+ return n.vars.lastMsg = r, this.db.insert(t2, [s2], [e2]), this.onReply && await this.onReply({ target: null, text: r, client: e2 }), true;
856
+ }
857
+ resolveDefaultMessageText(e2, t2) {
858
+ return e2.file ? this.resolveReplyFromFile(e2.file, e2.index, t2) : String(this.evalExpr(e2.value, t2));
859
+ }
860
+ async receiveSignal(e2, t2) {
861
+ let s2 = this.createContext({ rawSignal: t2 });
862
+ for (let i2 of this.handlers.SIGNAL) i2.source === e2 && await this.run(i2.body, s2);
863
+ return s2;
864
+ }
865
+ async run(e2, t2) {
866
+ let s2 = false;
867
+ for (let i2 = 0; i2 < e2.length; i2++) {
868
+ let n = e2[i2];
869
+ if ("When" === n.type) {
870
+ (0 === i2 || "When" !== e2[i2 - 1].type) && (s2 = false);
871
+ let r = n.conditions.some((e3) => this.evalCondition(e3, t2));
872
+ r && (await this.run(n.body, t2), s2 = true);
1588
873
  continue;
1589
874
  }
1590
- if (stmt.type === "Otherwise") {
1591
- if (!groupMatched) await this.run(stmt.body, ctx);
1592
- groupMatched = false;
875
+ if ("Otherwise" === n.type) {
876
+ s2 || await this.run(n.body, t2), s2 = false;
1593
877
  continue;
1594
878
  }
1595
- await this.execStatement(stmt, ctx);
1596
- }
1597
- }
1598
- evalCondition(cond, ctx) {
1599
- if (typeof ctx.vars.message !== "string") return false;
1600
- const message = ctx.vars.message.toLowerCase();
1601
- if (cond.op === "CONTAINS") {
1602
- return message.includes(cond.value.toLowerCase());
1603
- }
1604
- if (cond.op === "CONTAINS_ANY") {
1605
- return cond.values.some((v) => message.includes(v.toLowerCase()));
1606
- }
1607
- if (cond.op === "CONTAINS_KEYWORDS_FILE") {
1608
- const words = this._loadKeywordsFile(cond.path);
1609
- return words.some((v) => message.includes(v.toLowerCase()));
1610
- }
1611
- throw new Error(`runtime error: unsupported condition: ${cond.op}`);
1612
- }
1613
- // um ficheiro de keywords: uma palavra/frase por linha, nada mais.
1614
- // Sem comentários nem qualquer outra sintaxe misturada de propósito —
1615
- // um ficheiro só serve para um fim, evita ambiguidade sobre o que é
1616
- // keyword e o que não é. Resultado fica em cache — chamado a cada
1617
- // mensagem recebida, não pode reler disco/rede sempre.
1618
- _loadKeywordsFile(path) {
1619
- if (this._keywordsFileCache.has(path)) {
1620
- return this._keywordsFileCache.get(path);
1621
- }
1622
- const fullPath = this.fileSystem.resolve(this._rootBasePath, path);
1623
- if (!this.fileSystem.exists(fullPath)) {
1624
- throw new Error(`runtime error: KEYWORDS failed, file not found: "${fullPath}"`);
1625
- }
1626
- const raw = this.fileSystem.readFile(fullPath);
1627
- const words = raw.split("\n").map((linha) => linha.trim()).filter((linha) => linha.length > 0);
1628
- this._keywordsFileCache.set(path, words);
1629
- return words;
1630
- }
1631
- // Resolve o texto e o tempo mínimo do WAITING(...) de um THINK. Sem
1632
- // WAITING nenhum no .sql (stmt.waiting === null), ou com WAITING()
1633
- // vazio, usa os valores por omissão. O ficheiro do WAITING é lido tal
1634
- // e qual (texto/HTML livre) — ao contrário do REPLY/DEFAULT MESSAGE
1635
- // indexados, não é o formato "N- valor".
1636
- resolveWaitingConfig(waiting, ctx) {
1637
- if (!waiting) return { text: WAITING_TEXTO_PADRAO, seconds: WAITING_SEGUNDOS_PADRAO };
1638
- let text = WAITING_TEXTO_PADRAO;
1639
- if (waiting.file) {
1640
- const fullPath = this.fileSystem.resolve(this._rootBasePath, waiting.file);
1641
- if (!this.fileSystem.exists(fullPath)) {
1642
- throw new Error(`runtime error: WAITING failed, file not found: "${fullPath}"`);
1643
- }
1644
- text = this.fileSystem.readFile(fullPath).trim();
1645
- }
1646
- let seconds = WAITING_SEGUNDOS_PADRAO;
1647
- if (waiting.seconds) {
1648
- seconds = Number(this.evalExpr(waiting.seconds, ctx));
1649
- }
1650
- return { text, seconds };
1651
- }
1652
- // Resolve REPLY (ficheiro.txt, indice): lê o índice (número ou
1653
- // expressão que resolve a número) e devolve o texto da entrada
1654
- // correspondente do ficheiro de respostas.
1655
- resolveReplyFromFile(file, indexNode, ctx) {
1656
- const index = this.evalExpr(indexNode, ctx);
1657
- const entries = this._loadIndexedFile(file, this._rootBasePath);
1658
- const text = entries.get(Number(index));
1659
- if (text === void 0) {
1660
- throw new Error(`runtime error: REPLY failed, entry ${index} not found in "${file}"`);
1661
- }
1662
- return text;
1663
- }
1664
- // Lê um ficheiro de entradas numeradas. Duas formas, na mesma
1665
- // entrada N, nunca misturadas:
1666
- //
1667
- // N- texto — uma linha só (forma original, continua igual)
1668
- // N-{ ... } — bloco delimitado por chaves, pode ter
1669
- // varias linhas e HTML/Markdown/CSS por
1670
- // dentro. So fecha na "}" que corresponde a
1671
- // "{" que abriu — chaves internas (ex: um
1672
- // style="{color:red}" dentro do HTML) contam
1673
- // para o aninhamento e NAO fecham a entrada
1674
- // cedo. Rigoroso: uma "{" sem a "}"
1675
- // correspondente antes do fim do ficheiro e'
1676
- // erro, nunca silenciosamente ignorado.
1677
- //
1678
- // Resultado e' um Map indice -> valor, em cache — usado tanto por
1679
- // REPLY (ficheiro, N) como por IMPORT {ficheiro, N}.
1680
- _loadIndexedFile(path, basePath) {
1681
- if (this._replyFileCache.has(path)) {
1682
- return this._replyFileCache.get(path);
1683
- }
1684
- const fullPath = this.fileSystem.resolve(basePath, path);
1685
- if (!this.fileSystem.exists(fullPath)) {
1686
- throw new Error(`runtime error: file not found: "${fullPath}"`);
1687
- }
1688
- const raw = this.fileSystem.readFile(fullPath);
1689
- const entries = this._parseIndexedEntries(raw, path);
1690
- this._replyFileCache.set(path, entries);
1691
- return entries;
1692
- }
1693
- // Parser char-by-char do formato de entradas numeradas. Percorre o
1694
- // ficheiro procurando "N-" no inicio de uma linha (ignorando espacos);
1695
- // o que vem a seguir decide a forma:
1696
- // "{" logo a seguir -> bloco: conta chaves ate a correspondente
1697
- // fechar, valor e' o conteudo entre elas (sem as chaves),
1698
- // aparado nas pontas.
1699
- // qualquer outra coisa -> forma de uma linha: valor e' o resto da
1700
- // linha, aparado.
1701
- _parseIndexedEntries(raw, path) {
1702
- const entries = /* @__PURE__ */ new Map();
1703
- const len = raw.length;
1704
- let i = 0;
1705
- while (i < len) {
1706
- const lineStart = i;
1707
- let j = lineStart;
1708
- while (j < len && (raw[j] === " " || raw[j] === " ")) j++;
1709
- const numMatch = /^\d+/.exec(raw.slice(j));
1710
- if (!numMatch || raw[j + numMatch[0].length] !== "-") {
1711
- const nl2 = raw.indexOf("\n", lineStart);
1712
- i = nl2 === -1 ? len : nl2 + 1;
879
+ await this.execStatement(n, t2);
880
+ }
881
+ }
882
+ evalCondition(e2, t2) {
883
+ if ("string" != typeof t2.vars.message) return false;
884
+ let s2 = t2.vars.message.toLowerCase();
885
+ if ("CONTAINS" === e2.op) return s2.includes(e2.value.toLowerCase());
886
+ if ("CONTAINS_ANY" === e2.op) return e2.values.some((e3) => s2.includes(e3.toLowerCase()));
887
+ if ("CONTAINS_KEYWORDS_FILE" === e2.op) {
888
+ let i2 = this._loadKeywordsFile(e2.path);
889
+ return i2.some((e3) => s2.includes(e3.toLowerCase()));
890
+ }
891
+ throw Error(`runtime error: unsupported condition: ${e2.op}`);
892
+ }
893
+ _loadKeywordsFile(e2) {
894
+ if (this._keywordsFileCache.has(e2)) return this._keywordsFileCache.get(e2);
895
+ let t2 = this.fileSystem.resolve(this._rootBasePath, e2);
896
+ if (!this.fileSystem.exists(t2)) throw Error(`runtime error: KEYWORDS failed, file not found: "${t2}"`);
897
+ let s2 = this.fileSystem.readFile(t2), i2 = s2.split("\n").map((e3) => e3.trim()).filter((e3) => e3.length > 0);
898
+ return this._keywordsFileCache.set(e2, i2), i2;
899
+ }
900
+ resolveWaitingConfig(e2, t2) {
901
+ if (!e2) return { text: null, seconds: 3 };
902
+ let s2 = null;
903
+ if (null !== e2.text && void 0 !== e2.text) s2 = e2.text;
904
+ else if (e2.file) {
905
+ let i2 = this.fileSystem.resolve(this._rootBasePath, e2.file);
906
+ if (!this.fileSystem.exists(i2)) throw Error(`runtime error: WAITING failed, file not found: "${i2}"`);
907
+ s2 = this.fileSystem.readFile(i2).trim();
908
+ }
909
+ let n = 3;
910
+ return e2.seconds && (n = Number(this.evalExpr(e2.seconds, t2))), { text: s2, seconds: n };
911
+ }
912
+ resolveReplyFromFile(e2, t2, s2) {
913
+ let i2 = this.evalExpr(t2, s2), n = this._loadIndexedFile(e2, this._rootBasePath), r = n.get(Number(i2));
914
+ if (void 0 === r) throw Error(`runtime error: REPLY failed, entry ${i2} not found in "${e2}"`);
915
+ return r;
916
+ }
917
+ _loadIndexedFile(e2, t2) {
918
+ if (this._replyFileCache.has(e2)) return this._replyFileCache.get(e2);
919
+ let s2 = this.fileSystem.resolve(t2, e2);
920
+ if (!this.fileSystem.exists(s2)) throw Error(`runtime error: file not found: "${s2}"`);
921
+ let i2 = this.fileSystem.readFile(s2), n = this._parseIndexedEntries(i2, e2);
922
+ return this._replyFileCache.set(e2, n), n;
923
+ }
924
+ _parseIndexedEntries(e2, t2) {
925
+ let s2 = /* @__PURE__ */ new Map(), i2 = e2.length, n = 0;
926
+ for (; n < i2; ) {
927
+ let r = n, a = r;
928
+ for (; a < i2 && (" " === e2[a] || " " === e2[a]); ) a++;
929
+ let l = /^\d+/.exec(e2.slice(a));
930
+ if (!l || "-" !== e2[a + l[0].length]) {
931
+ let o = e2.indexOf("\n", r);
932
+ n = -1 === o ? i2 : o + 1;
1713
933
  continue;
1714
934
  }
1715
- const index = Number(numMatch[0]);
1716
- let k = j + numMatch[0].length + 1;
1717
- if (raw[k] === "{") {
1718
- let depth = 1;
1719
- const contentStart = k + 1;
1720
- let p = contentStart;
1721
- while (p < len && depth > 0) {
1722
- if (raw[p] === "{") depth++;
1723
- else if (raw[p] === "}") depth--;
1724
- p++;
1725
- }
1726
- if (depth !== 0) {
1727
- throw new Error(`runtime error: unclosed "{" for entry ${index} in "${path}"`);
1728
- }
1729
- const value2 = raw.slice(contentStart, p - 1).trim();
1730
- entries.set(index, value2);
1731
- i = p;
935
+ let h = Number(l[0]), u = a + l[0].length + 1;
936
+ if ("{" === e2[u]) {
937
+ let c = 1, d = u + 1, f = d;
938
+ for (; f < i2 && c > 0; ) "{" === e2[f] ? c++ : "}" === e2[f] && c--, f++;
939
+ if (0 !== c) throw Error(`runtime error: unclosed "{" for entry ${h} in "${t2}"`);
940
+ let p = e2.slice(d, f - 1).trim();
941
+ s2.set(h, p), n = f;
1732
942
  continue;
1733
943
  }
1734
- const nl = raw.indexOf("\n", k);
1735
- const lineEnd = nl === -1 ? len : nl;
1736
- const value = raw.slice(k, lineEnd).trim();
1737
- entries.set(index, value);
1738
- i = nl === -1 ? len : nl + 1;
944
+ let m = e2.indexOf("\n", u), w = -1 === m ? i2 : m, g = e2.slice(u, w).trim();
945
+ s2.set(h, g), n = -1 === m ? i2 : m + 1;
1739
946
  }
1740
- return entries;
947
+ return s2;
1741
948
  }
1742
- // ---- Execução de um statement de ação ----
1743
- async execStatement(stmt, ctx) {
1744
- switch (stmt.type) {
949
+ async execStatement(e2, t2) {
950
+ switch (e2.type) {
1745
951
  case "Reply": {
1746
- const text = stmt.file ? this.resolveReplyFromFile(stmt.file, stmt.index, ctx) : String(await this.evalReplyValue(stmt.value, ctx));
1747
- ctx.vars.lastMsg = text;
1748
- if (this.onReply) {
1749
- await this.onReply({ target: stmt.target, text, client: ctx.vars.client });
1750
- }
952
+ let s2 = e2.file ? this.resolveReplyFromFile(e2.file, e2.index, t2) : String(await this.evalReplyValue(e2.value, t2));
953
+ t2.vars.lastMsg = s2, this.onReply && await this.onReply({ target: e2.target, text: s2, client: t2.vars.client });
954
+ break;
955
+ }
956
+ case "Waiting": {
957
+ let i2 = this.resolveWaitingConfig(e2, t2);
958
+ this.onThinking && await this.onThinking({ client: t2.vars.client, text: i2.text }), await new Promise((e3) => setTimeout(e3, 1e3 * i2.seconds));
1751
959
  break;
1752
960
  }
1753
961
  case "Think": {
1754
- const waiting = this.resolveWaitingConfig(stmt.waiting, ctx);
1755
- const inicio = Date.now();
1756
- if (this.onThinking) {
1757
- await this.onThinking({ client: ctx.vars.client, text: waiting.text });
1758
- }
1759
- const fullPath = this.fileSystem.resolve(this._rootBasePath, stmt.file);
1760
- const index = this.knowledgeCache.get(fullPath);
1761
- const resultado = index.analyze(ctx.vars.message);
1762
- const decorrido = Date.now() - inicio;
1763
- const faltam = waiting.seconds * 1e3 - decorrido;
1764
- if (faltam > 0) {
1765
- await new Promise((resolve) => setTimeout(resolve, faltam));
1766
- }
1767
- if (resultado.decision === "RESPONDER") {
1768
- const text = resultado.texto;
1769
- ctx.vars.lastMsg = text;
1770
- if (this.onReply) {
1771
- await this.onReply({ target: null, text, client: ctx.vars.client });
1772
- }
1773
- } else if (stmt.fallback) {
1774
- await this.execStatement(stmt.fallback, ctx);
1775
- }
962
+ let n = this.resolveWaitingConfig(e2.waiting, t2), r = Date.now();
963
+ this.onThinking && await this.onThinking({ client: t2.vars.client, text: n.text });
964
+ let a = this.fileSystem.resolve(this._rootBasePath, e2.file), l = this.knowledgeCache.get(a), o = l.analyze(t2.vars.message), h = Date.now() - r, u = 1e3 * n.seconds - h;
965
+ if (u > 0 && await new Promise((e3) => setTimeout(e3, u)), "RESPONDER" === o.decision) {
966
+ let c = o.texto;
967
+ t2.vars.lastMsg = c, this.onReply && await this.onReply({ target: null, text: c, client: t2.vars.client });
968
+ } else e2.fallback && await this.execStatement(e2.fallback, t2);
1776
969
  break;
1777
970
  }
1778
971
  case "ForwardTo": {
1779
- const target = String(this.evalExpr(stmt.target, ctx));
1780
- if (this.onForward) {
1781
- await this.onForward({ target, client: ctx.vars.client });
1782
- }
972
+ let d = String(this.evalExpr(e2.target, t2));
973
+ this.onForward && await this.onForward({ target: d, client: t2.vars.client });
1783
974
  break;
1784
975
  }
1785
- case "ParseSignal": {
1786
- ctx.vars.SIGNAL = await this.signalParser(ctx.rawSignal);
976
+ case "ParseSignal":
977
+ t2.vars.SIGNAL = await this.signalParser(t2.rawSignal);
1787
978
  break;
1788
- }
1789
- case "SendTo": {
1790
- if (this.onSend) {
1791
- await this.onSend({ target: stmt.target, signal: ctx.vars.SIGNAL });
1792
- }
979
+ case "SendTo":
980
+ this.onSend && await this.onSend({ target: e2.target, signal: t2.vars.SIGNAL });
1793
981
  break;
1794
- }
1795
- case "Insert": {
1796
- this.execInsert(stmt, ctx);
982
+ case "Insert":
983
+ this.execInsert(e2, t2);
1797
984
  break;
1798
- }
1799
- case "Update": {
1800
- this.execUpdate(stmt, ctx);
985
+ case "Update":
986
+ this.execUpdate(e2, t2);
1801
987
  break;
1802
- }
1803
988
  default:
1804
- throw new Error(`runtime error: unsupported statement inside event: ${stmt.type}`);
989
+ throw Error(`runtime error: unsupported statement inside event: ${e2.type}`);
1805
990
  }
1806
991
  }
1807
- execInsert(stmt, ctx) {
1808
- let columnNames;
1809
- let values;
1810
- if (stmt.table === "Context" && (!stmt.columns || stmt.columns.length === 0) && !stmt.values) {
1811
- columnNames = ["client", "message", "created_at"];
1812
- values = [ctx.vars.client, ctx.vars.message, (/* @__PURE__ */ new Date()).toISOString()];
1813
- } else {
1814
- columnNames = (stmt.columns || []).map((c) => c.name);
1815
- values = (stmt.values || []).map((v) => this.evalExpr(v, ctx));
1816
- }
1817
- ctx.lastInsertId = this.db.insert(stmt.table, columnNames, values);
1818
- }
1819
- execUpdate(stmt, ctx) {
1820
- const value = this.evalExpr(stmt.set.value, ctx);
1821
- let whereColumn = null;
1822
- let whereValue = null;
1823
- if (stmt.where) {
1824
- whereColumn = stmt.where.left.name;
1825
- whereValue = this.evalExpr(stmt.where.right, ctx);
1826
- }
1827
- this.db.update(stmt.table, stmt.set.column, value, whereColumn, whereValue);
1828
- }
1829
- // ---- Avaliação de expressões ----
1830
- // Ponto de entrada usado só por REPLY <expr>: trata Response()/
1831
- // Response(alias) como caso especial assíncrono (chama this.onResponse,
1832
- // que fala de verdade com a IA ligada) e delega qualquer outra
1833
- // expressão ao evalExpr síncrono normal. Response() não pode ser
1834
- // composto dentro de + (ex: REPLY "x" + Response() não é suportado) —
1835
- // só faz sentido como o valor direto do REPLY, conforme a doc.
1836
- async evalReplyValue(node, ctx) {
1837
- if (node.type === "Call" && node.callee === "Response") {
1838
- return this.execResponse(node, ctx);
1839
- }
1840
- return this.evalExpr(node, ctx);
1841
- }
1842
- // Response() / Response(alias): dispara o ciclo com a IA ligada via
1843
- // CONNECT RESPONSE — envia a mensagem atual, espera, devolve o texto.
1844
- // Sem argumento só é válido com exatamente uma ligação (ambíguo com
1845
- // mais que uma); com argumento, o alias tem de corresponder a um
1846
- // CONNECT RESPONSE já feito.
1847
- async execResponse(node, ctx) {
1848
- if (!this.onResponse) {
1849
- throw new Error("runtime error: Response() failed, no AI connected (missing onResponse handler)");
1850
- }
1851
- let alias;
1852
- if (node.args.length === 0) {
1853
- if (this.responseConnections.size === 0) {
1854
- throw new Error("runtime error: Response() failed, no CONNECT RESPONSE found");
1855
- }
1856
- if (this.responseConnections.size > 1) {
1857
- throw new Error(`runtime error: Response() is ambiguous with multiple CONNECT RESPONSE (${[...this.responseConnections].join(", ")}); use Response(alias)`);
1858
- }
1859
- alias = [...this.responseConnections][0];
992
+ execInsert(e2, t2) {
993
+ let s2, i2;
994
+ "Context" !== e2.table || e2.columns && 0 !== e2.columns.length || e2.values ? (s2 = (e2.columns || []).map((e3) => e3.name), i2 = (e2.values || []).map((e3) => this.evalExpr(e3, t2))) : (s2 = ["client", "message", "created_at"], i2 = [t2.vars.client, t2.vars.message, (/* @__PURE__ */ new Date()).toISOString()]), t2.lastInsertId = this.db.insert(e2.table, s2, i2);
995
+ }
996
+ execUpdate(e2, t2) {
997
+ let s2 = this.evalExpr(e2.set.value, t2), i2 = null, n = null;
998
+ e2.where && (i2 = e2.where.left.name, n = this.evalExpr(e2.where.right, t2)), this.db.update(e2.table, e2.set.column, s2, i2, n);
999
+ }
1000
+ async evalReplyValue(e2, t2) {
1001
+ return "Call" === e2.type && "Response" === e2.callee ? this.execResponse(e2, t2) : this.evalExpr(e2, t2);
1002
+ }
1003
+ async execResponse(e2, t2) {
1004
+ if (!this.onResponse) throw Error("runtime error: Response() failed, no AI connected (missing onResponse handler)");
1005
+ let s2;
1006
+ if (0 === e2.args.length) {
1007
+ if (0 === this.responseConnections.size) throw Error("runtime error: Response() failed, no CONNECT RESPONSE found");
1008
+ if (this.responseConnections.size > 1) throw Error(`runtime error: Response() is ambiguous with multiple CONNECT RESPONSE (${[...this.responseConnections].join(", ")}); use Response(alias)`);
1009
+ s2 = [...this.responseConnections][0];
1860
1010
  } else {
1861
- const argNode = node.args[0];
1862
- if (argNode.type !== "Identifier") {
1863
- throw new Error("runtime error: Response(alias) expects an alias name, not a string or expression");
1864
- }
1865
- alias = argNode.name;
1866
- if (!this.responseConnections.has(alias)) {
1867
- throw new Error(`runtime error: Response(${alias}) failed, no CONNECT RESPONSE ${alias} found`);
1868
- }
1011
+ let i2 = e2.args[0];
1012
+ if ("Identifier" !== i2.type) throw Error("runtime error: Response(alias) expects an alias name, not a string or expression");
1013
+ if (s2 = i2.name, !this.responseConnections.has(s2)) throw Error(`runtime error: Response(${s2}) failed, no CONNECT RESPONSE ${s2} found`);
1869
1014
  }
1870
- const token = this.envValues.get(alias);
1871
- const text = await this.onResponse({ alias, token, message: ctx.vars.message, client: ctx.vars.client });
1872
- return text;
1015
+ let n = this.envValues.get(s2), r = await this.onResponse({ alias: s2, token: n, message: t2.vars.message, client: t2.vars.client });
1016
+ return r;
1873
1017
  }
1874
- evalExpr(node, ctx) {
1875
- switch (node.type) {
1018
+ evalExpr(e2, t2) {
1019
+ switch (e2.type) {
1876
1020
  case "Literal":
1877
- return node.value;
1878
- case "Identifier": {
1879
- if (node.name === "env") {
1880
- return Object.fromEntries(this.envValues);
1881
- }
1882
- if (Object.prototype.hasOwnProperty.call(ctx.vars, node.name)) {
1883
- return ctx.vars[node.name];
1884
- }
1885
- if (this.envValues.has(node.name)) {
1886
- return this.envValues.get(node.name);
1887
- }
1888
- throw new Error(`runtime error: unknown identifier: "${node.name}"`);
1889
- }
1021
+ return e2.value;
1022
+ case "Identifier":
1023
+ if ("env" === e2.name) return Object.fromEntries(this.envValues);
1024
+ if (Object.prototype.hasOwnProperty.call(t2.vars, e2.name)) return t2.vars[e2.name];
1025
+ if (this.envValues.has(e2.name)) return this.envValues.get(e2.name);
1026
+ throw Error(`runtime error: unknown identifier: "${e2.name}"`);
1890
1027
  case "Member": {
1891
- const obj = this.evalExpr(node.object, ctx);
1892
- if (obj === null || obj === void 0) return void 0;
1893
- return obj[node.property];
1028
+ let s2 = this.evalExpr(e2.object, t2);
1029
+ if (null == s2) return;
1030
+ return s2[e2.property];
1894
1031
  }
1895
1032
  case "Call": {
1896
- if (node.callee === "LAST_INSERT_ID") return ctx.lastInsertId;
1897
- const fn = this.nativeFuncs.get(node.callee);
1898
- if (!fn) throw new Error(`runtime error: unknown function: "${node.callee}"`);
1899
- const args = node.args.map((a) => this.evalExpr(a, ctx));
1900
- return fn(...args);
1033
+ if ("LAST_INSERT_ID" === e2.callee) return t2.lastInsertId;
1034
+ let i2 = this.nativeFuncs.get(e2.callee);
1035
+ if (!i2) throw Error(`runtime error: unknown function: "${e2.callee}"`);
1036
+ let n = e2.args.map((e3) => this.evalExpr(e3, t2));
1037
+ return i2(...n);
1901
1038
  }
1902
1039
  case "Binary": {
1903
- const left = this.evalExpr(node.left, ctx);
1904
- const right = this.evalExpr(node.right, ctx);
1905
- if (node.op === "+") {
1906
- if (typeof left === "number" && typeof right === "number") return left + right;
1907
- return String(left) + String(right);
1040
+ let r = this.evalExpr(e2.left, t2), a = this.evalExpr(e2.right, t2);
1041
+ if ("+" === e2.op) {
1042
+ if ("number" == typeof r && "number" == typeof a) return r + a;
1043
+ return String(r) + String(a);
1908
1044
  }
1909
- if (node.op === "=") return left === right;
1910
- throw new Error(`runtime error: unsupported operator: "${node.op}"`);
1045
+ if ("=" === e2.op) return r === a;
1046
+ throw Error(`runtime error: unsupported operator: "${e2.op}"`);
1911
1047
  }
1912
1048
  default:
1913
- throw new Error(`runtime error: unsupported expression: ${node.type}`);
1049
+ throw Error(`runtime error: unsupported expression: ${e2.type}`);
1914
1050
  }
1915
1051
  }
1916
1052
  };
1917
- module.exports = { BotQLInterpreter, MemoryDatabase };
1053
+ module.exports = { BotQLInterpreter, MemoryDatabase: t };
1918
1054
  }
1919
1055
  });
1920
1056
  return require_botql();