@falai/agent 4.0.0-alpha.4 → 4.0.0-alpha.5

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.
@@ -5,6 +5,11 @@
5
5
  * per-turn context) and `input` (the run's trigger payload). A path that
6
6
  * resolves to nothing keeps its placeholder, so a typo stays visible instead
7
7
  * of vanishing into an empty string.
8
+ *
9
+ * A path that resolves to an EMPTY string is a different case: the host knows
10
+ * the field and knows it is blank. Then the placeholder goes and `tidy` closes
11
+ * the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
12
+ * "Ola, tudo bem?" and never "Ola , tudo bem?".
8
13
  */
9
14
  export interface TemplateScope {
10
15
  data?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAKrE;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,GAAG,CAAC,CAS/D"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAUrE;AAiBD,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,GAAG,CAAC,CAS/D"}
@@ -6,16 +6,42 @@
6
6
  * per-turn context) and `input` (the run's trigger payload). A path that
7
7
  * resolves to nothing keeps its placeholder, so a typo stays visible instead
8
8
  * of vanishing into an empty string.
9
+ *
10
+ * A path that resolves to an EMPTY string is a different case: the host knows
11
+ * the field and knows it is blank. Then the placeholder goes and `tidy` closes
12
+ * the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
13
+ * "Ola, tudo bem?" and never "Ola , tudo bem?".
9
14
  */
10
15
  Object.defineProperty(exports, "__esModule", { value: true });
11
16
  exports.render = render;
12
17
  exports.renderDeep = renderDeep;
13
18
  const PLACEHOLDER = /\{\{\s*([^}\s]+)\s*\}\}/g;
14
19
  function render(template, scope) {
15
- return template.replace(PLACEHOLDER, (match, path) => {
20
+ let emptied = false;
21
+ const out = template.replace(PLACEHOLDER, (match, path) => {
16
22
  const value = lookup(scope, path.split("."));
17
- return value === undefined || value === null ? match : stringify(value);
23
+ if (value === undefined || value === null)
24
+ return match;
25
+ const text = stringify(value);
26
+ if (text === "")
27
+ emptied = true;
28
+ return text;
18
29
  });
30
+ return emptied ? tidy(out) : out;
31
+ }
32
+ /**
33
+ * Close the hole an empty value leaves: a doubled space, a space before
34
+ * punctuation, a space at the end of a line.
35
+ *
36
+ * Deliberately narrow. It only runs on a string where something substituted to
37
+ * "", and it only collapses a run of spaces that follows a visible character,
38
+ * so indentation in a markdown list survives.
39
+ */
40
+ function tidy(text) {
41
+ return text
42
+ .replace(/(?<=\S)[ \t]{2,}/g, " ")
43
+ .replace(/ +([,.;:!?\u2026])/g, "$1")
44
+ .replace(/[ \t]+$/gm, "");
19
45
  }
20
46
  /** `render` over every string inside a value, recursively. Non-strings pass through. */
21
47
  function renderDeep(value, scope) {
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAUH,wBAKC;AAGD,gCASC;AAnBD,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,SAAgB,MAAM,CAAC,QAAgB,EAAE,KAAoB;IAC3D,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wFAAwF;AACxF,SAAgB,UAAU,CAAI,KAAQ,EAAE,KAAoB;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAM,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAM,CAAC;IAC5F,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;IACT,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAa,EAAE,IAAc;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/utils/template.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAUH,wBAUC;AAkBD,gCASC;AAvCD,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,SAAgB,MAAM,CAAC,QAAgB,EAAE,KAAoB;IAC3D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAChE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,GAAG,IAAI,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,IAAI;SACR,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC;SACjC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC;SACpC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,wFAAwF;AACxF,SAAgB,UAAU,CAAI,KAAQ,EAAE,KAAoB;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAM,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAM,CAAC;IAC5F,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;IACT,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAa,EAAE,IAAc;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
@@ -5,6 +5,11 @@
5
5
  * per-turn context) and `input` (the run's trigger payload). A path that
6
6
  * resolves to nothing keeps its placeholder, so a typo stays visible instead
7
7
  * of vanishing into an empty string.
8
+ *
9
+ * A path that resolves to an EMPTY string is a different case: the host knows
10
+ * the field and knows it is blank. Then the placeholder goes and `tidy` closes
11
+ * the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
12
+ * "Ola, tudo bem?" and never "Ola , tudo bem?".
8
13
  */
9
14
  export interface TemplateScope {
10
15
  data?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAKrE;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,GAAG,CAAC,CAS/D"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAUrE;AAiBD,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,GAAG,CAAC,CAS/D"}
@@ -5,13 +5,39 @@
5
5
  * per-turn context) and `input` (the run's trigger payload). A path that
6
6
  * resolves to nothing keeps its placeholder, so a typo stays visible instead
7
7
  * of vanishing into an empty string.
8
+ *
9
+ * A path that resolves to an EMPTY string is a different case: the host knows
10
+ * the field and knows it is blank. Then the placeholder goes and `tidy` closes
11
+ * the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
12
+ * "Ola, tudo bem?" and never "Ola , tudo bem?".
8
13
  */
9
14
  const PLACEHOLDER = /\{\{\s*([^}\s]+)\s*\}\}/g;
10
15
  export function render(template, scope) {
11
- return template.replace(PLACEHOLDER, (match, path) => {
16
+ let emptied = false;
17
+ const out = template.replace(PLACEHOLDER, (match, path) => {
12
18
  const value = lookup(scope, path.split("."));
13
- return value === undefined || value === null ? match : stringify(value);
19
+ if (value === undefined || value === null)
20
+ return match;
21
+ const text = stringify(value);
22
+ if (text === "")
23
+ emptied = true;
24
+ return text;
14
25
  });
26
+ return emptied ? tidy(out) : out;
27
+ }
28
+ /**
29
+ * Close the hole an empty value leaves: a doubled space, a space before
30
+ * punctuation, a space at the end of a line.
31
+ *
32
+ * Deliberately narrow. It only runs on a string where something substituted to
33
+ * "", and it only collapses a run of spaces that follows a visible character,
34
+ * so indentation in a markdown list survives.
35
+ */
36
+ function tidy(text) {
37
+ return text
38
+ .replace(/(?<=\S)[ \t]{2,}/g, " ")
39
+ .replace(/ +([,.;:!?\u2026])/g, "$1")
40
+ .replace(/[ \t]+$/gm, "");
15
41
  }
16
42
  /** `render` over every string inside a value, recursively. Non-strings pass through. */
17
43
  export function renderDeep(value, scope) {
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,KAAoB;IAC3D,OAAO,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,UAAU,CAAI,KAAQ,EAAE,KAAoB;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAM,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAM,CAAC;IAC5F,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;IACT,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAa,EAAE,IAAc;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,KAAoB;IAC3D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAChE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,GAAG,IAAI,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,IAAI;SACR,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC;SACjC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC;SACpC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,UAAU,CAAI,KAAQ,EAAE,KAAoB;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAM,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAM,CAAC;IAC5F,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAC5D,CAAC;IACT,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAa,EAAE,IAAc;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtE,OAAO,GAAI,OAAmC,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAClF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
@@ -53,7 +53,7 @@ A `do` step fills the parameters with `with`. When the agent is built, `validate
53
53
  - a value has the wrong type; nothing is coerced, so `"3"` is not a number. A template such as `"{{data.cep}}"` is a string, so templates can only fill string parameters (and the items of a string array);
54
54
  - `with` names a parameter the action does not have.
55
55
 
56
- Templates in `with` are rendered right before `run` is called, against `data` (collected fields), `context` (this turn's host context) and `input` (the run's input). A path that resolves to nothing keeps its `{{...}}`, so a typo stays visible.
56
+ Templates in `with` are rendered right before `run` is called, against `data` (collected fields), `context` (this turn's host context) and `input` (the run's input). An unknown path keeps its `{{...}}`, so a typo stays visible. A path that resolves to an empty string drops out, and the space or comma it left behind goes with it.
57
57
 
58
58
  ## What `run` sees
59
59
 
@@ -410,13 +410,14 @@ Host actions, events and conditions are registered once on the agent and referen
410
410
  | `agent.dispatch`, `pendingDirective`, `Directive`, `flow.merge`, `flow.validate` | `then` / `else` on steps |
411
411
  | `Flow` class, `Step` class, `flow` namespace, `FlowOptions`, `StepOptions` | plain objects: `Flow`, `Step` |
412
412
  | `title`, `when`, `if`, `reentrant`, `requiredFields`, `optionalFields`, `onComplete`, flow `hooks` | `id` + `name`, `on[]`, `repeat`, `clearOnStart`, `onEnd`, `while` |
413
- | `requires`, `skip`, `auto`, `reply`, step `hooks`, `prepare`, `finalize` | known-field skipping, `maxAsks`, `do`, `if`, `wait`, `say` |
413
+ | `skip`, `auto`, `reply`, step `hooks`, `prepare`, `finalize` | known-field skipping, `maxAsks`, `do`, `wait`, `say` |
414
+ | `requires` | an `if` step: `{ if: { known: [...] }, then: '<step>', else: 'end' }`. Not known-field skipping — that answers "do I still need to ask?", while `requires` answers "may this step run at all?". They differ exactly when the customer never answers: `maxAsks` retires the field and the step proceeds with it unknown. |
414
415
  | `Signal`, `SignalContext`, `SignalFiring`, `signals`, `signalBatchSize`, `triggeredSignals` | `mention` flows, `repeat`, `claims` |
415
416
  | `ToolContext.updateContext / updateData / setField / dispatch`, `ToolResult.dataUpdate / contextUpdate / directive`, `ToolManager`, `ToolScope`, tool config helpers | `Tool.handler(args, ctx) → { value?, data? }` |
416
417
  | `PersistenceAdapter`, `SessionRepository`, `MessageRepository`, `PersistenceManager`, `SessionManager`, `restoreSession`, `createPersistedState`, `enterFlow`, `enterStep`, `completeCurrentFlow`, `mergeCollected` | `Store`, the seven `*Store` classes, `migrateSession` |
417
418
  | `SessionState`, `CollectedStateData`, `SessionData` | `Session` |
418
419
  | `AgentResponse.executedSteps / stoppedReason / endedFlows / appliedInstructions / isFlowComplete` | `TurnResult.outcomes / started / ended / skipped / messages / schedule / llmCalls` |
419
- | `Template` as a function, `TemplateContext`, `ConditionEvaluator`, `ConditionWhen`, `ConditionIf`, `!` exclusions | `Template = string` with `{{data.x}}` `{{context.x}}` `{{input.x}}`; `Pred` (function or JSON) |
420
+ | `Template` as a function, `TemplateContext`, `ConditionEvaluator`, `ConditionWhen`, `ConditionIf` | `Template = string` with `{{data.x}}` `{{context.x}}` `{{input.x}}`; `Pred` (function or JSON). **`!` exclusions are not gone** — a phrase opening with `!` still rules a trigger out. Copy the list across unchanged. |
420
421
  | `Term`, `terms` | put the glossary in `knowledgeBase` or an instruction |
421
422
  | `Instruction.enabled / tags / metadata` | filter before passing |
422
423
  | `promptCache`, `PromptSectionCache`, `PromptCacheConfig` | gone; every prompt is built per call. `compaction` stays and runs once per turn on the history you pass |
@@ -116,7 +116,7 @@ f.action<const P extends ParamDefs>(def: {
116
116
 
117
117
  ### Behaviour
118
118
 
119
- - `with` is rendered before `run` sees it. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced inside every string, at any depth. A path that resolves to nothing keeps its placeholder, so a typo stays visible.
119
+ - `with` is rendered before `run` sees it. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced inside every string, at any depth. An unknown path keeps its placeholder, so a typo stays visible. A path that resolves to an empty string drops out instead, and the gap it left in the sentence closes.
120
120
  - `with` is checked when the agent is built, not on the turn that reaches the step. A missing required parameter, an unknown parameter, or a value outside `enum` throws `FlowConfigurationError`. So does a wrong type: `"3"` is not a number, because values are never coerced. A string that contains `{{` skips the `enum` check, because its value is only known at run time.
121
121
  - Actions run in the Run phase, by code, with zero model calls. They run while `silenced` too.
122
122
  - A `do` step whose action name is not registered throws at build. If the registry changed under a running agent, the step reports `code: 'action-failed'` with `detail: 'unknown action "notify"'`.
@@ -174,7 +174,7 @@ const agent = f.agent({
174
174
 
175
175
  `parameters` use the same `type`, `enum` and `description` as fields (plus `optional: true`), and `run` receives them already typed: `mensagem` is a `string` above, nothing to check.
176
176
 
177
- `with` fills the parameters. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced before the action runs. A placeholder whose value is unknown stays as written, so a skipped `tamanho` reaches the seller as `{{data.tamanho}}`. Word the message without it, or fork first with an `if` step on `{ known: ["tamanho"] }`. A `do` step never talks to the model: zero calls.
177
+ `with` fills the parameters. `{{data.x}}`, `{{context.x}}` and `{{input.x}}` are replaced before the action runs. A placeholder whose value is unknown stays as written, so a skipped `tamanho` reaches the seller as `{{data.tamanho}}`. One the host filled with an empty string drops out instead. Word the message without it, or fork first with an `if` step on `{ known: ["tamanho"] }`. A `do` step never talks to the model: zero calls.
178
178
 
179
179
  The name and the parameters are checked when the agent is built. An unknown action, or a `with` missing a required parameter, throws `FlowConfigurationError` at startup with the step id and the fix.
180
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@falai/agent",
3
- "version": "4.0.0-alpha.4",
3
+ "version": "4.0.0-alpha.5",
4
4
  "description": "Conversational state engine for TypeScript where the AI understands, but the code is in control",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -59,7 +59,8 @@
59
59
  "release:alpha": "bun publish --tag alpha",
60
60
  "release": "bun publish",
61
61
  "test": "bun test tests/*.test.ts tests/scenarios/*.test.ts",
62
- "eval:live": "bun run scripts/eval/live.ts"
62
+ "eval:live": "bun run scripts/eval/live.ts",
63
+ "eval:exclusions": "bun run scripts/eval/exclusions.ts"
63
64
  },
64
65
  "keywords": [
65
66
  "ai",
@@ -5,6 +5,11 @@
5
5
  * per-turn context) and `input` (the run's trigger payload). A path that
6
6
  * resolves to nothing keeps its placeholder, so a typo stays visible instead
7
7
  * of vanishing into an empty string.
8
+ *
9
+ * A path that resolves to an EMPTY string is a different case: the host knows
10
+ * the field and knows it is blank. Then the placeholder goes and `tidy` closes
11
+ * the gap it left, so "Ola {{context.lead.name}}, tudo bem?" sends
12
+ * "Ola, tudo bem?" and never "Ola , tudo bem?".
8
13
  */
9
14
 
10
15
  export interface TemplateScope {
@@ -16,10 +21,30 @@ export interface TemplateScope {
16
21
  const PLACEHOLDER = /\{\{\s*([^}\s]+)\s*\}\}/g;
17
22
 
18
23
  export function render(template: string, scope: TemplateScope): string {
19
- return template.replace(PLACEHOLDER, (match, path: string) => {
24
+ let emptied = false;
25
+ const out = template.replace(PLACEHOLDER, (match, path: string) => {
20
26
  const value = lookup(scope, path.split("."));
21
- return value === undefined || value === null ? match : stringify(value);
27
+ if (value === undefined || value === null) return match;
28
+ const text = stringify(value);
29
+ if (text === "") emptied = true;
30
+ return text;
22
31
  });
32
+ return emptied ? tidy(out) : out;
33
+ }
34
+
35
+ /**
36
+ * Close the hole an empty value leaves: a doubled space, a space before
37
+ * punctuation, a space at the end of a line.
38
+ *
39
+ * Deliberately narrow. It only runs on a string where something substituted to
40
+ * "", and it only collapses a run of spaces that follows a visible character,
41
+ * so indentation in a markdown list survives.
42
+ */
43
+ function tidy(text: string): string {
44
+ return text
45
+ .replace(/(?<=\S)[ \t]{2,}/g, " ")
46
+ .replace(/ +([,.;:!?\u2026])/g, "$1")
47
+ .replace(/[ \t]+$/gm, "");
23
48
  }
24
49
 
25
50
  /** `render` over every string inside a value, recursively. Non-strings pass through. */