@uipath/maestro-builder-sdk 6.17.5 → 6.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/bindings-v2.js +15 -17
  2. package/dist/bpmn/bpmn-sdk.js +3 -2
  3. package/dist/bpmn/check.js +3 -2
  4. package/dist/bpmn/decompile.js +123 -137
  5. package/dist/bpmn/intsvc.js +7 -2
  6. package/dist/bpmn/serialize.js +1 -1
  7. package/dist/bpmn/typed-node.js +5 -4
  8. package/dist/case/compile-cli.js +1 -3
  9. package/dist/case/decompile.js +103 -109
  10. package/dist/case/entry-points.js +2 -1
  11. package/dist/case/merge.js +3 -5
  12. package/dist/case/serialize.js +1 -1
  13. package/dist/check.js +26 -10
  14. package/dist/core/ceql.js +5 -2
  15. package/dist/core/connector-inputs.js +4 -4
  16. package/dist/core/event-checks.js +17 -2
  17. package/dist/core/event-filters.d.ts +38 -4
  18. package/dist/core/event-filters.js +63 -7
  19. package/dist/core/expr.d.ts +1 -1
  20. package/dist/core/expr.js +3 -3
  21. package/dist/core/json-file.d.ts +2 -0
  22. package/dist/core/json-file.js +4 -0
  23. package/dist/core/library.js +2 -1
  24. package/dist/core/node-envelope.d.ts +1 -0
  25. package/dist/core/node-envelope.js +1 -1
  26. package/dist/core/own-entry.d.ts +9 -0
  27. package/dist/core/own-entry.js +11 -0
  28. package/dist/core/ts-literal.d.ts +22 -0
  29. package/dist/core/ts-literal.js +37 -0
  30. package/dist/core/typed-raw-node.js +4 -3
  31. package/dist/decompile.js +272 -300
  32. package/dist/merge.js +10 -12
  33. package/dist/registry/compat/pystr.d.ts +2 -0
  34. package/dist/registry/compat/pystr.js +35 -0
  35. package/dist/registry/compat/pyvalue.d.ts +16 -0
  36. package/dist/registry/compat/pyvalue.js +35 -0
  37. package/dist/registry/generators/markdown/pyvalue.d.ts +1 -4
  38. package/dist/registry/generators/markdown/pyvalue.js +2 -30
  39. package/dist/registry/generators/markdown/sections/inputs.js +3 -2
  40. package/dist/registry/generators/type-tree.d.ts +1 -7
  41. package/dist/registry/generators/type-tree.js +5 -22
  42. package/dist/registry/library-build/build.js +6 -6
  43. package/dist/registry/library-build/canonical.js +25 -25
  44. package/dist/registry/library-build/context.js +5 -5
  45. package/dist/registry/library-build/enrich.js +18 -18
  46. package/dist/registry/library-build/events.js +25 -24
  47. package/dist/registry/library-build/generic-expand.js +21 -20
  48. package/dist/registry/library-build/plan.js +8 -8
  49. package/dist/registry/library-build/py.d.ts +3 -10
  50. package/dist/registry/library-build/py.js +9 -52
  51. package/dist/registry/library-build/schema-action.js +6 -6
  52. package/dist/registry/prepare/event.js +2 -2
  53. package/dist/registry/prepare/source-import.js +2 -9
  54. package/dist/registry/prepare/source-labels.js +2 -9
  55. package/dist/registry/prepare/support.d.ts +2 -6
  56. package/dist/registry/prepare/support.js +3 -58
  57. package/dist/registry/pull-cli.js +6 -14
  58. package/dist/registry/search-cli.js +2 -2
  59. package/dist/schema-version.js +0 -1
  60. package/dist/serialize.js +86 -76
  61. package/package.json +2 -2
package/dist/decompile.js CHANGED
@@ -36,6 +36,7 @@ import { fileURLToPath } from 'node:url';
36
36
  import { inferScriptShape } from './core/script-return.js';
37
37
  import { DEFAULT_TRIGGER_ID } from './core/expr.js';
38
38
  import { commentText } from './core/comment-text.js';
39
+ import { tsLiteral, tsNumber, tsText } from './core/ts-literal.js';
39
40
  import { parseIxpProjectType } from './core/actions.js';
40
41
  import { readFlowGlobals } from './flow-variables.js';
41
42
  import { missingBindingLabels } from './core/binding-messages.js';
@@ -127,11 +128,11 @@ function verClause(node) {
127
128
  const shipped = [...CORE_DEFINITION_KEYS]
128
129
  .filter((k) => k.startsWith(`${node.type}@`))
129
130
  .map((k) => k.slice(node.type.length + 1));
130
- warnOnce(`node ${str(node.id)} pins ${node.type}@${tv}, but this SDK bundles ${node.type}@${shipped.join(', ') || '<none>'} only; `
131
- + `\`compile\` will refuse the emitted { version: ${str(tv)} } (versions resolve exactly, flow-builder-sdk#71). `
131
+ warnOnce(`node ${tsLiteral(node.id)} pins ${node.type}@${tv}, but this SDK bundles ${node.type}@${shipped.join(', ') || '<none>'} only; `
132
+ + `\`compile\` will refuse the emitted { version: ${tsLiteral(tv)} } (versions resolve exactly, flow-builder-sdk#71). `
132
133
  + `The source keeps the file's pair; to recompile, drop that { version } and the node takes the SDK's pinned version.`);
133
134
  }
134
- return `version: ${str(tv)}`;
135
+ return `version: ${tsLiteral(tv)}`;
135
136
  }
136
137
  function verArg(node) {
137
138
  const clause = verClause(node);
@@ -148,7 +149,7 @@ function triggerOptParts(node) {
148
149
  parts.push(ver);
149
150
  const label = node.display?.label;
150
151
  if (typeof label === 'string' && label !== definitionLabel(node))
151
- parts.push(`label: ${str(label)}`);
152
+ parts.push(`label: ${tsLiteral(label)}`);
152
153
  return parts;
153
154
  }
154
155
  /** `triggerOptParts` as the `.trigger(spec, { ... })` options argument. */
@@ -176,7 +177,7 @@ function optsArgWith(node, ctx, extra) {
176
177
  // compile. Equal-to-default is omitted so ordinary flows stay quiet.
177
178
  const label = node.display?.label;
178
179
  if (typeof label === 'string' && label !== node.id)
179
- parts.push(`label: ${str(label)}`);
180
+ parts.push(`label: ${tsLiteral(label)}`);
180
181
  const updates = ctx.flow.variables?.variableUpdates?.[node.id] ?? [];
181
182
  if (updates.length) {
182
183
  const entries = updates.map((u) => `${key(u.variableId)}: ${emitVal(u.expression, ctx.imp, ctx.inputNames)}`);
@@ -233,27 +234,13 @@ function isTrigger(type) {
233
234
  return type.startsWith('core.trigger.') || type.startsWith('uipath.connector.event.') || type.startsWith('uipath.connector.trigger.');
234
235
  }
235
236
  // ─── Injection-safe source emitters (ported verbatim from case/decompile.ts) ──
236
- /**
237
- * A JS/TS string literal (double-quoted, correctly escaped). `JSON.stringify`
238
- * escapes quotes, backslashes and control chars — so untrusted `.flow` values
239
- * (labels, expressions, code) can never break out of the literal. We also escape
240
- * U+2028/U+2029 (legal in JSON, historically illegal in JS string literals).
241
- * Non-string input still serializes to inert JSON (never executable).
242
- */
243
- function str(s) {
244
- return JSON.stringify(s).replace(/[\u2028\u2029]/g, (c) => '\\u' + c.charCodeAt(0).toString(16));
245
- }
246
237
  /** A boolean literal, coerced so an untrusted non-boolean can never reach source verbatim. */
247
238
  function boolLit(v) {
248
239
  return v ? 'true' : 'false';
249
240
  }
250
241
  /** An object key: a bare identifier when legal, otherwise a quoted string. */
251
242
  function key(k) {
252
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : str(k);
253
- }
254
- /** An arbitrary JSON-serializable value as a TS literal (used for var defaults). */
255
- function literal(v) {
256
- return JSON.stringify(v);
243
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k) ? k : tsLiteral(k);
257
244
  }
258
245
  /** Render an options object from present entries; '' when nothing is present. */
259
246
  function opts(entries) {
@@ -283,7 +270,7 @@ class Imports {
283
270
  }
284
271
  line(specifier) {
285
272
  const names = [...this.used].sort();
286
- return `import { ${names.join(', ')} } from ${str(specifier)};`;
273
+ return `import { ${names.join(', ')} } from ${tsLiteral(specifier)};`;
287
274
  }
288
275
  }
289
276
  // ─── Expression inversion ─────────────────────────────────────────────────────
@@ -344,7 +331,7 @@ function emitRef(jsBody, imp, inputNames) {
344
331
  const PATH = '[A-Za-z_]\\w*(?:\\.[A-Za-z_]\\w*|\\[\\d+\\])*';
345
332
  // !!$vars.<step>.output → ran('step')
346
333
  if ((m = body.match(/^!!\$vars\.([A-Za-z_]\w*)\.output$/)))
347
- return `${imp.use('ran')}(${str(m[1])})`;
334
+ return `${imp.use('ran')}(${tsLiteral(m[1])})`;
348
335
  // $vars.<step>.output[.<path>] → out('step'[, 'path'])
349
336
  if ((m = body.match(new RegExp(`^\\$vars\\.([A-Za-z_]\\w*)\\.output(?:\\.(${PATH}))?$`)))) {
350
337
  // A flow INPUT is published as the trigger's output, so
@@ -352,20 +339,20 @@ function emitRef(jsBody, imp, inputNames) {
352
339
  // Recover the author's spelling rather than the mechanical `out('start', …)`;
353
340
  // both re-serialize identically, but only one reads like the source.
354
341
  if (m[1] === currentTriggerId && m[2] !== undefined && inputNames.has(m[2])) {
355
- return `${imp.use('input')}(${str(m[2])})`;
342
+ return `${imp.use('input')}(${tsLiteral(m[2])})`;
356
343
  }
357
- return m[2] === undefined ? `${imp.use('out')}(${str(m[1])})` : `${imp.use('out')}(${str(m[1])}, ${str(m[2])})`;
344
+ return m[2] === undefined ? `${imp.use('out')}(${tsLiteral(m[1])})` : `${imp.use('out')}(${tsLiteral(m[1])}, ${tsLiteral(m[2])})`;
358
345
  }
359
346
  // $vars.<step>.error[.<field>] → err('step'[, 'field'])
360
347
  if ((m = body.match(new RegExp(`^\\$vars\\.([A-Za-z_]\\w*)\\.error(?:\\.(${PATH}))?$`)))) {
361
- return m[2] === undefined ? `${imp.use('err')}(${str(m[1])})` : `${imp.use('err')}(${str(m[1])}, ${str(m[2])})`;
348
+ return m[2] === undefined ? `${imp.use('err')}(${tsLiteral(m[1])})` : `${imp.use('err')}(${tsLiteral(m[1])}, ${tsLiteral(m[2])})`;
362
349
  }
363
350
  // $vars.<name> → input('name') for a declared input, else v('name').
364
351
  // The bare spelling predates flow inputs moving to `<trigger>.output.<name>`;
365
352
  // still read it so an older artifact decompiles.
366
353
  if ((m = body.match(/^\$vars\.([A-Za-z_]\w*)$/))) {
367
354
  const name = m[1];
368
- return inputNames.has(name) ? `${imp.use('input')}(${str(name)})` : `${imp.use('v')}(${str(name)})`;
355
+ return inputNames.has(name) ? `${imp.use('input')}(${tsLiteral(name)})` : `${imp.use('v')}(${tsLiteral(name)})`;
369
356
  }
370
357
  // Composite / unrecognized: a raw JS expression template.
371
358
  return `${imp.use('js')}${jsTemplate(body)}`;
@@ -380,14 +367,17 @@ function emitRef(jsBody, imp, inputNames) {
380
367
  * builder takes `Expr | string | unknown` (bare values pass through `toExpr`).
381
368
  */
382
369
  function emitVal(v, imp, inputNames) {
370
+ // a present-but-undefined value stays the TS expression `undefined` (the behaviour this branch shipped; opts() drops an entry whose value is undefined before it reaches here)
371
+ if (v === undefined)
372
+ return 'undefined';
383
373
  const raw = unwrapSource(v);
384
374
  if (raw !== undefined && !Array.isArray(v)) {
385
375
  if (raw.startsWith('=js:'))
386
376
  return emitRef(raw.slice(4), imp, inputNames);
387
- return str(raw); // a literal string (valid as Expr via toExpr, or as a raw string input)
377
+ return tsLiteral(raw); // a literal string (valid as Expr via toExpr, or as a raw string input)
388
378
  }
389
379
  // A raw JSON literal (number / boolean / null / array / object).
390
- return literal(v);
380
+ return tsLiteral(v);
391
381
  }
392
382
  /** A bare (unprefixed) JS expression, as decision/switch nodes store it. */
393
383
  function emitBareExpr(raw, imp, inputNames) {
@@ -530,7 +520,7 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
530
520
  // infers from the body first (see `scriptOutputs`), so a script whose type
531
521
  // is already plain in its code round-trips without it — emitting one there
532
522
  // would add an argument the author never wrote to every such step.
533
- return `${imp.use('script')}(${opts([['code', str(code)], ...scriptReturnsOpt(node, code)])})`;
523
+ return `${imp.use('script')}(${opts([['code', tsLiteral(code)], ...scriptReturnsOpt(node, code)])})`;
534
524
  }
535
525
  case T.delay: {
536
526
  // serialize's `delayInputs` writes `{timerType, timerPreset, timerValue?}`
@@ -538,18 +528,18 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
538
528
  // wait. Invert all three spellings.
539
529
  if (inputs.timerType === 'timeDate') {
540
530
  const until = typeof inputs.timerDate === 'string' ? inputs.timerDate : '';
541
- return `${imp.use('delay')}(${opts([['until', str(until)]])})`;
531
+ return `${imp.use('delay')}(${opts([['until', tsLiteral(until)]])})`;
542
532
  }
543
533
  const duration = inputs.timerPreset === 'custom'
544
534
  ? (typeof inputs.timerValue === 'string' ? inputs.timerValue : '')
545
535
  : (typeof inputs.timerPreset === 'string' ? inputs.timerPreset : '');
546
- return `${imp.use('delay')}(${opts([['duration', str(duration)]])})`;
536
+ return `${imp.use('delay')}(${opts([['duration', tsLiteral(duration)]])})`;
547
537
  }
548
538
  case T.documentClassify:
549
539
  case T.dynamicExtract: {
550
540
  const fr = unwrapSource(inputs.fileRef) ?? '';
551
- const fileSrc = fr.startsWith('=js:') ? emitBareExpr(fr.slice(4), imp, inputNames) : str(fr);
552
- const pageRange = typeof inputs.pageRange === 'string' && inputs.pageRange !== '' ? str(inputs.pageRange) : undefined;
541
+ const fileSrc = fr.startsWith('=js:') ? emitBareExpr(fr.slice(4), imp, inputNames) : tsLiteral(fr);
542
+ const pageRange = typeof inputs.pageRange === 'string' && inputs.pageRange !== '' ? tsLiteral(inputs.pageRange) : undefined;
553
543
  if (node.type === T.documentClassify) {
554
544
  const split = unwrapPlain(inputs.splitPages);
555
545
  return `${imp.use('documentClassify')}(${opts([
@@ -558,7 +548,7 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
558
548
  ['splitPages', split === true ? 'true' : undefined],
559
549
  ])})`;
560
550
  }
561
- const schema = inputs.schemaDocument !== undefined ? literal(inputs.schemaDocument) : undefined;
551
+ const schema = inputs.schemaDocument !== undefined ? tsLiteral(inputs.schemaDocument) : undefined;
562
552
  return `${imp.use('dynamicExtract')}(${opts([
563
553
  ['fileRef', fileSrc],
564
554
  ['schema', schema],
@@ -570,9 +560,9 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
570
560
  case T.dataFabricUpdate:
571
561
  case T.dataFabricDelete: {
572
562
  const cfg = (inputs.entityConfig ?? {});
573
- const entity = str(String(cfg.entityName ?? ''));
574
- const folder = typeof cfg._folderKey === 'string' ? str(cfg._folderKey) : undefined;
575
- const resourceKey = typeof cfg._resourceKey === 'string' ? str(cfg._resourceKey) : undefined;
563
+ const entity = tsText(cfg.entityName);
564
+ const folder = typeof cfg._folderKey === 'string' ? tsLiteral(cfg._folderKey) : undefined;
565
+ const resourceKey = typeof cfg._resourceKey === 'string' ? tsLiteral(cfg._resourceKey) : undefined;
576
566
  const val = (v) => {
577
567
  // 1.3 let `recordId` and a create's field value be a `{ type:
578
568
  // 'jsExpression', expression }` object as well as a `=js:` string. A
@@ -582,14 +572,14 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
582
572
  return emitBareExpr(String(obj.expression ?? ''), imp, inputNames);
583
573
  }
584
574
  const raw = typeof v === 'string' ? v : String(v ?? '');
585
- return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames) : str(raw);
575
+ return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames) : tsLiteral(raw);
586
576
  };
587
577
  const fieldObject = (rows) => {
588
578
  const list = Array.isArray(rows) ? rows : [];
589
579
  return `{ ${list.map((r) => `${key(String(r.field ?? ''))}: ${val(r.value)}`).join(', ')} }`;
590
580
  };
591
581
  const record = cfg.recordSource === 'fromRead'
592
- ? `{ fromRead: ${str(String(cfg.readEntityNodeId ?? ''))} }`
582
+ ? `{ fromRead: ${tsText(cfg.readEntityNodeId)} }`
593
583
  : `{ byId: ${val(cfg.recordId)} }`;
594
584
  if (node.type === T.dataFabricRead) {
595
585
  // The array is what the platform serializer reads and what compile
@@ -597,8 +587,8 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
597
587
  // here so a canvas-authored file still decompiles. Both flatten to the
598
588
  // builder's row list, where `or: true` marks each new OR alternative.
599
589
  const filterRow = (r, or = false) => opts([
600
- ['field', str(String(r.field ?? ''))],
601
- ['operator', r.operator !== undefined && r.operator !== '=' ? str(String(r.operator)) : undefined],
590
+ ['field', tsText(r.field)],
591
+ ['operator', r.operator !== undefined && r.operator !== '=' ? tsLiteral(String(r.operator)) : undefined],
602
592
  ['value', val(r.value)],
603
593
  ['or', or ? 'true' : undefined],
604
594
  ]);
@@ -621,15 +611,15 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
621
611
  // `resultMode` only exists from 1.4, and recovering it re-selects 1.4 on
622
612
  // the way back out — so a 1.0 node stays a 1.0 node through the round trip.
623
613
  const mode = cfg.resultMode === 'single' || cfg.resultMode === 'multiple'
624
- ? str(cfg.resultMode) : undefined;
614
+ ? tsLiteral(cfg.resultMode) : undefined;
625
615
  const sort = cfg._sort && typeof cfg._sort === 'object'
626
616
  ? opts([
627
- ['field', str(String(cfg._sort.field ?? ''))],
628
- ['direction', cfg._sort.direction === 'desc' ? str('desc') : undefined],
617
+ ['field', tsText(cfg._sort.field)],
618
+ ['direction', cfg._sort.direction === 'desc' ? tsLiteral('desc') : undefined],
629
619
  ])
630
620
  : undefined;
631
621
  const fields = Array.isArray(cfg._selectedFieldNames) && cfg._selectedFieldNames.length
632
- ? `[${cfg._selectedFieldNames.map((f) => str(String(f))).join(', ')}]`
622
+ ? `[${cfg._selectedFieldNames.map((f) => tsLiteral(String(f))).join(', ')}]`
633
623
  : undefined;
634
624
  return `${imp.use('dataFabricRead')}(${opts([
635
625
  ['entity', entity], ['filters', filters], ['resultMode', mode],
@@ -661,7 +651,7 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
661
651
  const raw = typeof x === 'string' ? x : String(x?.expression ?? x ?? '');
662
652
  return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames)
663
653
  : raw.startsWith('$vars.') ? emitBareExpr(raw, imp, inputNames)
664
- : str(raw);
654
+ : tsLiteral(raw);
665
655
  };
666
656
  if (node.type === T.createOutgoingCall) {
667
657
  return `${imp.use('createOutgoingCall')}(${opts([['from', v(i.from)], ['to', v(i.to)]])})`;
@@ -690,16 +680,16 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
690
680
  // value, so it must not come back as one.
691
681
  if (raw === undefined || raw === '')
692
682
  return undefined;
693
- return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames) : str(raw);
683
+ return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames) : tsLiteral(raw);
694
684
  };
695
685
  const priority = unwrapPlain(inputs.priority);
696
686
  return `${imp.use('queueItem')}(${opts([
697
- ['queue', str(String(q.name ?? ''))],
698
- ['folderPath', str(String(q.folderPath ?? ''))],
699
- ['key', str(String(q.key ?? ''))],
687
+ ['queue', tsText(q.name)],
688
+ ['folderPath', tsText(q.folderPath)],
689
+ ['key', tsText(q.key)],
700
690
  ['item', rows.length ? objLit(rows) : '{}'],
701
691
  // `Normal` is the definition's default — only a departure was authored.
702
- ['priority', typeof priority === 'string' && priority !== 'Normal' ? str(priority) : undefined],
692
+ ['priority', typeof priority === 'string' && priority !== 'Normal' ? tsLiteral(priority) : undefined],
703
693
  ['reference', optStr('reference')],
704
694
  ['deferDate', optStr('deferDate')],
705
695
  ['dueDate', optStr('dueDate')],
@@ -741,7 +731,7 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
741
731
  const raw = typeof x === 'string' ? x : String(x?.expression ?? x ?? '');
742
732
  return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames)
743
733
  : raw.startsWith('$vars.') ? emitBareExpr(raw, imp, inputNames)
744
- : str(raw);
734
+ : tsLiteral(raw);
745
735
  };
746
736
  const num = (x) => {
747
737
  if (x === undefined || x === null)
@@ -813,16 +803,16 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
813
803
  const model = (i.model ?? {});
814
804
  const nested = (k) => typeof model[k] === 'string' && model[k] !== '' ? model[k] : undefined;
815
805
  return `${imp.use('ixpExtract')}(${opts([
816
- ['project', str(node.type)],
806
+ ['project', tsLiteral(node.type)],
817
807
  // `modelName`/`projectName` are one value in two fields; either recovers it.
818
- ['modelName', str(plain('modelName') ?? plain('projectName') ?? nested('modelName') ?? '')],
819
- ['name', str(nested('modelDisplayName') ?? plain('modelName') ?? '')],
820
- ['folderName', str(plain('folderName') ?? nested('folderName') ?? '')],
821
- ['fileRef', fr.startsWith('=js:') ? emitBareExpr(fr.slice(4), imp, inputNames) : str(fr)],
822
- ['pageRange', plain('pageRange') !== undefined ? str(plain('pageRange')) : undefined],
823
- ['versionTag', plain('versionTag') !== undefined ? str(plain('versionTag')) : undefined],
808
+ ['modelName', tsLiteral(plain('modelName') ?? plain('projectName') ?? nested('modelName') ?? '')],
809
+ ['name', tsLiteral(nested('modelDisplayName') ?? plain('modelName') ?? '')],
810
+ ['folderName', tsLiteral(plain('folderName') ?? nested('folderName') ?? '')],
811
+ ['fileRef', fr.startsWith('=js:') ? emitBareExpr(fr.slice(4), imp, inputNames) : tsLiteral(fr)],
812
+ ['pageRange', plain('pageRange') !== undefined ? tsLiteral(plain('pageRange')) : undefined],
813
+ ['versionTag', plain('versionTag') !== undefined ? tsLiteral(plain('versionTag')) : undefined],
824
814
  ['description', plain('description') ?? nested('description') !== undefined
825
- ? str((plain('description') ?? nested('description'))) : undefined],
815
+ ? tsLiteral((plain('description') ?? nested('description'))) : undefined],
826
816
  ])})`;
827
817
  }
828
818
  // Every family this SDK AUTHORS is recovered above — a guard test asserts
@@ -866,23 +856,16 @@ function scriptReturnsOpt(node, code) {
866
856
  // A field map is the richer statement, so it wins when there is one.
867
857
  const entries = Object.entries(props)
868
858
  .filter(([, v]) => typeof v?.type === 'string')
869
- .map(([k, v]) => [k, str(v.type)]);
859
+ .map(([k, v]) => [k, tsLiteral(v.type)]);
870
860
  if (entries.length > 0)
871
861
  return [['returns', opts(entries)]];
872
862
  }
873
- return [['returns', str(declared)]];
863
+ return [['returns', tsLiteral(declared)]];
874
864
  }
875
865
  /** Key-sorted copy, so a property map compares by content and not by order. */
876
866
  function sortKeys(o) {
877
867
  return Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
878
868
  }
879
- /** A validated numeric literal — never emit a raw JSON value that could carry code. */
880
- function numLit(v) {
881
- const n = Number(v);
882
- if (!Number.isFinite(n))
883
- throw new Error(`decompile: expected a number, got ${JSON.stringify(v)}.`);
884
- return String(n);
885
- }
886
869
  /**
887
870
  * Invert an HTTP node. The plain node (`core.action.http`) keeps its request at
888
871
  * the top level (`url` / `method` / `headers` / `queryParams` / `body` /
@@ -907,28 +890,28 @@ function emitHttp(node, managed, imp, inputNames) {
907
890
  const headersSrc = emitValMap(headers, imp, inputNames) || undefined;
908
891
  const querySrc = emitValMap(query, imp, inputNames) || undefined;
909
892
  const contentType = !managed && typeof inputs.contentType === 'string' && inputs.contentType !== 'application/json'
910
- ? str(inputs.contentType) : undefined;
911
- const timeout = typeof inputs.timeout === 'string' && inputs.timeout !== 'PT15M' ? str(inputs.timeout) : undefined;
912
- const retry = inputs.retryCount !== undefined && Number(inputs.retryCount) !== 0 ? numLit(inputs.retryCount) : undefined;
893
+ ? tsLiteral(inputs.contentType) : undefined;
894
+ const timeout = typeof inputs.timeout === 'string' && inputs.timeout !== 'PT15M' ? tsLiteral(inputs.timeout) : undefined;
895
+ const retry = inputs.retryCount !== undefined && Number(inputs.retryCount) !== 0 ? tsNumber(inputs.retryCount) : undefined;
913
896
  const branchRows = Array.isArray(inputs.branches) ? inputs.branches : [];
914
897
  const branchesSrc = branchRows.length
915
898
  ? `[${branchRows.map((b) => {
916
899
  const cond = unwrapSource(b.conditionExpression) ?? '';
917
900
  const expr = emitBareExpr(cond.startsWith('=js:') ? cond.slice(4) : cond, imp, inputNames);
918
- return `{ name: ${str(String(b.name ?? b.id ?? ''))}, condition: ${expr} }`;
901
+ return `{ name: ${tsText(b.name ?? b.id)}, condition: ${expr} }`;
919
902
  }).join(', ')}]`
920
903
  : undefined;
921
904
  const connectionValue = detail.connectionId ?? detail.connectionResourceId ?? req.connection;
922
905
  const connection = managed && typeof connectionValue === 'string' && connectionValue !== 'ImplicitConnection'
923
- ? str(connectionValue) : undefined;
906
+ ? tsLiteral(connectionValue) : undefined;
924
907
  const folder = managed && typeof detail.connectionFolderKey === 'string' && detail.connectionFolderKey.length > 0
925
- ? str(detail.connectionFolderKey) : undefined;
908
+ ? tsLiteral(detail.connectionFolderKey) : undefined;
926
909
  const targetConnector = managed && typeof req.targetConnector === 'string'
927
910
  && req.targetConnector !== 'uipath-uipath-http'
928
- ? str(req.targetConnector) : undefined;
911
+ ? tsLiteral(req.targetConnector) : undefined;
929
912
  const spec = opts([
930
913
  ['url', emitVal(req.url ?? '', imp, inputNames)],
931
- ['method', method !== 'GET' ? str(method) : undefined],
914
+ ['method', method !== 'GET' ? tsLiteral(method) : undefined],
932
915
  ['headers', headersSrc],
933
916
  ['query', querySrc],
934
917
  ['body', bodySrc],
@@ -964,43 +947,43 @@ function emitTransform(node, imp, inputNames) {
964
947
  const cfg = op?.config ?? {};
965
948
  if (op?.type === 'filter') {
966
949
  const filters = (cfg.filters ?? []).map((f) => objLit([
967
- ['field', str(f.field)],
968
- ['condition', str(f.condition)],
969
- ...(f.value !== undefined ? [['value', literal(f.value)]] : []),
950
+ ['field', tsLiteral(f.field)],
951
+ ['condition', tsLiteral(f.condition)],
952
+ ...(f.value !== undefined ? [['value', tsLiteral(f.value)]] : []),
970
953
  ]));
971
954
  return objLit([
972
- ['type', str('filter')],
973
- ...(cfg.operation !== undefined ? [['operation', str(cfg.operation)]] : []),
955
+ ['type', tsLiteral('filter')],
956
+ ...(cfg.operation !== undefined ? [['operation', tsLiteral(cfg.operation)]] : []),
974
957
  ['filters', `[${filters.join(', ')}]`],
975
958
  ]);
976
959
  }
977
960
  if (op?.type === 'map') {
978
961
  const mappings = (cfg.mappings ?? []).map((m) => objLit([
979
- ['field', str(m.field)],
980
- ...(m.transformation && m.transformation !== 'copy' ? [['transformation', str(m.transformation)]] : []),
981
- ...(m.renameTo ? [['renameTo', str(m.renameTo)]] : []),
962
+ ['field', tsLiteral(m.field)],
963
+ ...(m.transformation && m.transformation !== 'copy' ? [['transformation', tsLiteral(m.transformation)]] : []),
964
+ ...(m.renameTo ? [['renameTo', tsLiteral(m.renameTo)]] : []),
982
965
  ]));
983
966
  return objLit([
984
- ['type', str('map')],
967
+ ['type', tsLiteral('map')],
985
968
  ...(cfg.keepOriginalFields ? [['keepOriginalFields', boolLit(cfg.keepOriginalFields)]] : []),
986
969
  ['mappings', `[${mappings.join(', ')}]`],
987
970
  ]);
988
971
  }
989
972
  // groupBy
990
973
  const aggs = (cfg.aggregations ?? []).map((a) => objLit([
991
- ['operation', str(a.operation)],
992
- ['alias', str(a.alias)],
993
- ...(a.field ? [['field', str(a.field)]] : []),
974
+ ['operation', tsLiteral(a.operation)],
975
+ ['alias', tsLiteral(a.alias)],
976
+ ...(a.field ? [['field', tsLiteral(a.field)]] : []),
994
977
  ]));
995
978
  return objLit([
996
- ['type', str('groupBy')],
997
- ['groupByField', str(cfg.groupByField)],
979
+ ['type', tsLiteral('groupBy')],
980
+ ['groupByField', tsLiteral(cfg.groupByField)],
998
981
  ['aggregations', `[${aggs.join(', ')}]`],
999
982
  ]);
1000
983
  });
1001
984
  const spec = opts([
1002
985
  ['collection', collection],
1003
- ['variant', variant ? str(variant) : undefined],
986
+ ['variant', variant ? tsLiteral(variant) : undefined],
1004
987
  ['operations', `[${opSrcs.join(', ')}]`],
1005
988
  ]);
1006
989
  return `${imp.use('transform')}(${spec})`;
@@ -1180,7 +1163,7 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1180
1163
  };
1181
1164
  const promptSrc = (v) => {
1182
1165
  const raw = plain(v);
1183
- return raw === undefined ? undefined : str(unrender(raw));
1166
+ return raw === undefined ? undefined : tsLiteral(unrender(raw));
1184
1167
  };
1185
1168
  const inputRows = refs
1186
1169
  // Emit in the file's own order, not the length-sorted one.
@@ -1192,7 +1175,7 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1192
1175
  });
1193
1176
  const outVars = (Array.isArray(i.agentOutputVariables) ? i.agentOutputVariables : []);
1194
1177
  const returns = outVars.length
1195
- ? objLit(outVars.map((v) => [String(v.id ?? ''), str(String(v.type ?? 'string'))]))
1178
+ ? objLit(outVars.map((v) => [String(v.id ?? ''), tsLiteral(String(v.type ?? 'string'))]))
1196
1179
  : undefined;
1197
1180
  // The resources, from the agent's own artifact edges.
1198
1181
  const resource = (port) => (graph?.outEdges(node.id) ?? [])
@@ -1212,12 +1195,12 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1212
1195
  const props = v?.properties;
1213
1196
  if (!props || Object.keys(props).length === 0)
1214
1197
  return undefined;
1215
- return objLit(Object.entries(props).map(([k, x]) => [k, str(String(x?.type ?? 'string'))]));
1198
+ return objLit(Object.entries(props).map(([k, x]) => [k, tsLiteral(String(x?.type ?? 'string'))]));
1216
1199
  };
1217
1200
  return opts([
1218
- ['kind', str('clientside')],
1219
- ['name', str(rPlain(t, 'name') ?? '')],
1220
- ['description', rPlain(t, 'description') !== undefined ? str(rPlain(t, 'description')) : undefined],
1201
+ ['kind', tsLiteral('clientside')],
1202
+ ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1203
+ ['description', rPlain(t, 'description') !== undefined ? tsLiteral(rPlain(t, 'description')) : undefined],
1221
1204
  ['inputs', schemaTypes(ti.inputSchema)],
1222
1205
  ['returns', schemaTypes(ti.outputSchema)],
1223
1206
  ]);
@@ -1229,10 +1212,10 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1229
1212
  if (!row || row.mode !== 'text-builder')
1230
1213
  return undefined;
1231
1214
  const v = row.textValue;
1232
- return typeof v === 'number' ? String(v) : typeof v === 'string' ? str(v) : JSON.stringify(v);
1215
+ return typeof v === 'number' ? String(v) : typeof v === 'string' ? tsLiteral(v) : JSON.stringify(v);
1233
1216
  };
1234
1217
  return opts([
1235
- ['kind', str('httpRequest')],
1218
+ ['kind', tsLiteral('httpRequest')],
1236
1219
  ['url', fixed('url')], ['method', fixed('method')],
1237
1220
  ['headers', fixed('headers')], ['params', fixed('params')],
1238
1221
  ['body', fixed('body')], ['timeout', fixed('timeout')],
@@ -1240,37 +1223,37 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1240
1223
  }
1241
1224
  if (type.startsWith('uipath.agent.resource.tool.mcp.')) {
1242
1225
  return opts([
1243
- ['kind', str('mcp')],
1244
- ['name', str(rPlain(t, 'name') ?? '')],
1245
- ['key', str(rPlain(t, 'referenceKey') ?? '')],
1226
+ ['kind', tsLiteral('mcp')],
1227
+ ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1228
+ ['key', tsLiteral(rPlain(t, 'referenceKey') ?? '')],
1246
1229
  // The mapper's field name for the canonical server slug.
1247
- ['slug', rPlain(t, 'serverUrl') !== undefined ? str(rPlain(t, 'serverUrl')) : undefined],
1248
- ['folderPath', rPlain(t, 'folderPath') !== undefined ? str(rPlain(t, 'folderPath')) : undefined],
1249
- ['mcpType', rPlain(t, 'mcpType') !== undefined ? str(rPlain(t, 'mcpType')) : undefined],
1230
+ ['slug', rPlain(t, 'serverUrl') !== undefined ? tsLiteral(rPlain(t, 'serverUrl')) : undefined],
1231
+ ['folderPath', rPlain(t, 'folderPath') !== undefined ? tsLiteral(rPlain(t, 'folderPath')) : undefined],
1232
+ ['mcpType', rPlain(t, 'mcpType') !== undefined ? tsLiteral(rPlain(t, 'mcpType')) : undefined],
1250
1233
  ]);
1251
1234
  }
1252
1235
  if (type.startsWith('uipath.agent.resource.tool.a2a.')) {
1253
1236
  return opts([
1254
- ['kind', str('a2a')],
1255
- ['name', str(rPlain(t, 'name') ?? '')],
1256
- ['key', str(rPlain(t, 'referenceKey') ?? '')],
1257
- ['slug', str(rPlain(t, 'slug') ?? '')],
1258
- ['folderPath', rPlain(t, 'folderPath') !== undefined ? str(rPlain(t, 'folderPath')) : undefined],
1237
+ ['kind', tsLiteral('a2a')],
1238
+ ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1239
+ ['key', tsLiteral(rPlain(t, 'referenceKey') ?? '')],
1240
+ ['slug', tsLiteral(rPlain(t, 'slug') ?? '')],
1241
+ ['folderPath', rPlain(t, 'folderPath') !== undefined ? tsLiteral(rPlain(t, 'folderPath')) : undefined],
1259
1242
  ]);
1260
1243
  }
1261
1244
  if (type.startsWith('uipath.agent.resource.tool.builtin.')) {
1262
1245
  return opts([
1263
- ['kind', str('builtin')],
1264
- ['tool', str(type.slice('uipath.agent.resource.tool.builtin.'.length))],
1265
- ['description', rPlain(t, 'description') !== undefined ? str(rPlain(t, 'description')) : undefined],
1246
+ ['kind', tsLiteral('builtin')],
1247
+ ['tool', tsLiteral(type.slice('uipath.agent.resource.tool.builtin.'.length))],
1248
+ ['description', rPlain(t, 'description') !== undefined ? tsLiteral(rPlain(t, 'description')) : undefined],
1266
1249
  ]);
1267
1250
  }
1268
1251
  if (type.startsWith('uipath.agent.resource.tool.ixp.')) {
1269
1252
  const tail = type.slice('uipath.agent.resource.tool.ixp.'.length);
1270
1253
  return opts([
1271
- ['kind', str('ixp')],
1272
- ['projectId', str(tail.slice(tail.indexOf('.') + 1))],
1273
- ['name', str(rPlain(t, 'name') ?? '')],
1254
+ ['kind', tsLiteral('ixp')],
1255
+ ['projectId', tsLiteral(tail.slice(tail.indexOf('.') + 1))],
1256
+ ['name', tsLiteral(rPlain(t, 'name') ?? '')],
1274
1257
  ]);
1275
1258
  }
1276
1259
  // The per-instance resource kinds (process / api / flow / maestro / agent /
@@ -1292,13 +1275,13 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1292
1275
  return typeof row?.default === 'string' ? row.default : undefined;
1293
1276
  };
1294
1277
  return opts([
1295
- ['kind', str(ns[1])],
1296
- ['key', str(type.slice(ns[0].length))],
1297
- ['name', str(bound('name') ?? rPlain(t, 'name') ?? '')],
1298
- ['folderPath', str(bound('folderPath') ?? '')],
1278
+ ['kind', tsLiteral(ns[1])],
1279
+ ['key', tsLiteral(type.slice(ns[0].length))],
1280
+ ['name', tsLiteral(bound('name') ?? rPlain(t, 'name') ?? '')],
1281
+ ['folderPath', tsLiteral(bound('folderPath') ?? '')],
1299
1282
  ]);
1300
1283
  }
1301
- return opts([['kind', str('builtin')], ['tool', str('summarize')]]); // unreachable in practice
1284
+ return opts([['kind', tsLiteral('builtin')], ['tool', tsLiteral('summarize')]]); // unreachable in practice
1302
1285
  });
1303
1286
  const contextSrc = resource('context').map((c) => {
1304
1287
  // `query` is a MODE TRIPLE, not a string: the author's value lands in
@@ -1310,17 +1293,17 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1310
1293
  : typeof q === 'string' && q !== '' ? q
1311
1294
  : undefined;
1312
1295
  return opts([
1313
- ['name', str(rPlain(c, 'indexName') ?? String(c.display?.label ?? ''))],
1314
- ['id', str(rPlain(c, 'indexId') ?? '')],
1315
- ['folderPath', rPlain(c, 'folderPath') !== undefined ? str(rPlain(c, 'folderPath')) : undefined],
1316
- ['folderKey', rPlain(c, 'folderKey') !== undefined ? str(rPlain(c, 'folderKey')) : undefined],
1317
- ['query', queryText !== undefined ? str(queryText) : undefined],
1296
+ ['name', tsLiteral(rPlain(c, 'indexName') ?? String(c.display?.label ?? ''))],
1297
+ ['id', tsLiteral(rPlain(c, 'indexId') ?? '')],
1298
+ ['folderPath', rPlain(c, 'folderPath') !== undefined ? tsLiteral(rPlain(c, 'folderPath')) : undefined],
1299
+ ['folderKey', rPlain(c, 'folderKey') !== undefined ? tsLiteral(rPlain(c, 'folderKey')) : undefined],
1300
+ ['query', queryText !== undefined ? tsLiteral(queryText) : undefined],
1318
1301
  ]);
1319
1302
  });
1320
1303
  const escalationSrc = resource('escalation').map((e) => {
1321
1304
  const ei = rInputs(e);
1322
1305
  const recipients = Array.isArray(ei.recipients)
1323
- ? ei.recipients.map((r) => str(String(r.value ?? '')))
1306
+ ? ei.recipients.map((r) => tsText(r.value))
1324
1307
  : [];
1325
1308
  const outcomeNames = Object.keys((ei.outcomeMapping ?? {}));
1326
1309
  const isQuickForm = e.type === 'uipath.agent.resource.escalation.quick-form';
@@ -1330,24 +1313,24 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1330
1313
  const app = ei.app;
1331
1314
  const extra = (ei._additionalProps ?? {});
1332
1315
  return opts([
1333
- ['variant', isQuickForm ? str('quick-form') : undefined],
1334
- ['name', str(rPlain(e, 'name') ?? '')],
1335
- ['description', rPlain(e, 'description') !== undefined ? str(rPlain(e, 'description')) : undefined],
1316
+ ['variant', isQuickForm ? tsLiteral('quick-form') : undefined],
1317
+ ['name', tsLiteral(rPlain(e, 'name') ?? '')],
1318
+ ['description', rPlain(e, 'description') !== undefined ? tsLiteral(rPlain(e, 'description')) : undefined],
1336
1319
  ['fields', rows.length ? `[${rows.map((f) => opts([
1337
- ['id', str(String(f.id ?? ''))],
1338
- ['type', str(String(f.type ?? 'text'))],
1339
- ['direction', str(String(f.direction ?? 'output'))],
1320
+ ['id', tsText(f.id)],
1321
+ ['type', tsLiteral(String(f.type ?? 'text'))],
1322
+ ['direction', tsLiteral(String(f.direction ?? 'output'))],
1340
1323
  ])).join(', ')}]` : undefined],
1341
1324
  ['app', app === undefined ? undefined : opts([
1342
- ['key', str(String(app.key ?? ''))],
1343
- ['name', str(String(app.name ?? ''))],
1344
- ['folderPath', typeof app.folderPath === 'string' && app.folderPath !== '' ? str(app.folderPath) : undefined],
1325
+ ['key', tsText(app.key)],
1326
+ ['name', tsText(app.name)],
1327
+ ['folderPath', typeof app.folderPath === 'string' && app.folderPath !== '' ? tsLiteral(app.folderPath) : undefined],
1345
1328
  ])],
1346
1329
  ['recipients', recipients.length ? `[${recipients.join(', ')}]` : undefined],
1347
- ['outcomes', outcomeNames.length ? `[${outcomeNames.map((o) => str(o)).join(', ')}]` : undefined],
1348
- ['taskTitle', typeof extra.taskTitle === 'string' && extra.taskTitle !== '' ? str(extra.taskTitle) : undefined],
1330
+ ['outcomes', outcomeNames.length ? `[${outcomeNames.map((o) => tsLiteral(o)).join(', ')}]` : undefined],
1331
+ ['taskTitle', typeof extra.taskTitle === 'string' && extra.taskTitle !== '' ? tsLiteral(extra.taskTitle) : undefined],
1349
1332
  // 'medium' is the platform default.
1350
- ['priority', typeof extra.priority === 'string' && extra.priority !== 'medium' ? str(extra.priority) : undefined],
1333
+ ['priority', typeof extra.priority === 'string' && extra.priority !== 'medium' ? tsLiteral(extra.priority) : undefined],
1351
1334
  ]);
1352
1335
  });
1353
1336
  const mem = resource('memory')[0];
@@ -1357,15 +1340,15 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1357
1340
  const id = tail.slice(tail.indexOf('.') + 1);
1358
1341
  const mi = rInputs(mem);
1359
1342
  return opts([
1360
- ['name', str(rPlain(mem, 'name') ?? '')],
1361
- ['id', str(id)],
1362
- ['description', rPlain(mem, 'description') !== undefined ? str(rPlain(mem, 'description')) : undefined],
1343
+ ['name', tsLiteral(rPlain(mem, 'name') ?? '')],
1344
+ ['id', tsLiteral(id)],
1345
+ ['description', rPlain(mem, 'description') !== undefined ? tsLiteral(rPlain(mem, 'description')) : undefined],
1363
1346
  // The platform defaults: few-shot on, similarity 0, k 3, hybrid search.
1364
1347
  ['dynamicFewShotLearning', unwrapPlain(mi.dynamicFewShotLearning) === false ? 'false' : undefined],
1365
1348
  ['semanticSimilarity', num(mi.semanticSimilarity) !== undefined && num(mi.semanticSimilarity) !== '0' ? num(mi.semanticSimilarity) : undefined],
1366
1349
  ['kValue', num(mi.kValue) !== undefined && num(mi.kValue) !== '3' ? num(mi.kValue) : undefined],
1367
1350
  ['searchMode', rPlain(mem, 'searchMode') !== undefined && rPlain(mem, 'searchMode') !== 'hybrid'
1368
- ? str(rPlain(mem, 'searchMode')) : undefined],
1351
+ ? tsLiteral(rPlain(mem, 'searchMode')) : undefined],
1369
1352
  ]);
1370
1353
  })();
1371
1354
  const guardrails = Array.isArray(i.guardrails) && i.guardrails.length > 0
@@ -1375,22 +1358,22 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1375
1358
  if (node.type === T.voiceAgent) {
1376
1359
  const v = (i.voice ?? {});
1377
1360
  const voiceSrc = Object.keys(v).length === 0 ? undefined : opts([
1378
- ['model', typeof v.model === 'string' ? str(v.model) : undefined],
1379
- ['persona', typeof v.persona === 'string' ? str(v.persona) : undefined],
1361
+ ['model', typeof v.model === 'string' ? tsLiteral(v.model) : undefined],
1362
+ ['persona', typeof v.persona === 'string' ? tsLiteral(v.persona) : undefined],
1380
1363
  ['temperature', num(v.temperature)],
1381
1364
  ['maxTokens', num(v.maxTokens)],
1382
1365
  ]);
1383
1366
  return `${imp.use('voiceAgent')}(${opts([
1384
- ['systemPrompt', promptSrc(i.systemPrompt) ?? str('')],
1367
+ ['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
1385
1368
  ['inputs', inputRows.length ? objLit(inputRows) : undefined],
1386
1369
  ['callContext', (() => {
1387
1370
  const raw = plain(i.callContext) ?? '';
1388
1371
  const js = raw.startsWith('=js:') ? raw.slice(4) : raw;
1389
- return js.startsWith('$vars.') ? emitBareExpr(js, imp, inputNames) : str(js);
1372
+ return js.startsWith('$vars.') ? emitBareExpr(js, imp, inputNames) : tsLiteral(js);
1390
1373
  })()],
1391
1374
  ['voice', voiceSrc],
1392
1375
  ['maxIterations', num(i.maxIterations)],
1393
- ['source', str(plain(i.source) ?? '')],
1376
+ ['source', tsLiteral(plain(i.source) ?? '')],
1394
1377
  ])})`;
1395
1378
  }
1396
1379
  if (node.type === T.conversationalAgent) {
@@ -1407,7 +1390,7 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1407
1390
  // `out(...)` and anything composite to a `js` template. Any other string
1408
1391
  // is literal text, as the product reads it (flow-schema
1409
1392
  // `coerceToExpressionValue`), even when it looks like `$vars.…`.
1410
- return raw.startsWith('=js:') ? emitRef(raw.slice(4), imp, inputNames) : str(raw);
1393
+ return raw.startsWith('=js:') ? emitRef(raw.slice(4), imp, inputNames) : tsLiteral(raw);
1411
1394
  };
1412
1395
  // `'simple'` mode DERIVES the four runtime fields from `context`, so only
1413
1396
  // the context binding is the author's; recovering the derived ones would
@@ -1415,7 +1398,7 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1415
1398
  const mode = typeof st.mode === 'string' ? st.mode : 'simple';
1416
1399
  const settingsSrc = mode === 'custom'
1417
1400
  ? opts([
1418
- ['mode', str('custom')],
1401
+ ['mode', tsLiteral('custom')],
1419
1402
  ['conversationId', settingExpr('conversationId')],
1420
1403
  ['exchangeId', settingExpr('exchangeId')],
1421
1404
  ['messages', settingExpr('messages')],
@@ -1423,8 +1406,8 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1423
1406
  ])
1424
1407
  : opts([['context', settingExpr('context')]]);
1425
1408
  return `${imp.use('conversationalAgent')}(${opts([
1426
- ['model', str(plain(i.model) ?? '')],
1427
- ['systemPrompt', promptSrc(i.systemPrompt) ?? str('')],
1409
+ ['model', tsLiteral(plain(i.model) ?? '')],
1410
+ ['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
1428
1411
  // `settings` is required on ConversationalAgentInputs, so an empty one
1429
1412
  // stays as `{}`, which check then reports as CONV_AGENT_NO_TURN.
1430
1413
  ['settings', settingsSrc || '{}'],
@@ -1432,19 +1415,19 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1432
1415
  ['maxTokenPerResponse', num(i.maxTokenPerResponse)],
1433
1416
  ['maxIterations', num(i.maxIterations)],
1434
1417
  ['guardrails', guardrails],
1435
- ['source', str(plain(i.source) ?? '')],
1418
+ ['source', tsLiteral(plain(i.source) ?? '')],
1436
1419
  ])})`;
1437
1420
  }
1438
1421
  return `${imp.use('inlineAgent')}(${opts([
1439
- ['model', str(plain(i.model) ?? '')],
1440
- ['systemPrompt', promptSrc(i.systemPrompt) ?? str('')],
1441
- ['userPrompt', promptSrc(i.userPrompt) ?? str('')],
1422
+ ['model', tsLiteral(plain(i.model) ?? '')],
1423
+ ['systemPrompt', promptSrc(i.systemPrompt) ?? tsLiteral('')],
1424
+ ['userPrompt', promptSrc(i.userPrompt) ?? tsLiteral('')],
1442
1425
  ['inputs', inputRows.length ? objLit(inputRows) : undefined],
1443
1426
  ['returns', returns],
1444
1427
  ['temperature', num(i.temperature)],
1445
1428
  ['maxTokenPerResponse', num(i.maxTokenPerResponse)],
1446
1429
  ['maxIterations', num(i.maxIterations)],
1447
- ['mode', plain(i.mode) !== undefined ? str(plain(i.mode)) : undefined],
1430
+ ['mode', plain(i.mode) !== undefined ? tsLiteral(plain(i.mode)) : undefined],
1448
1431
  ['guardrails', guardrails],
1449
1432
  ['context', list(contextSrc)],
1450
1433
  ['tools', list(toolSrc)],
@@ -1453,7 +1436,7 @@ function emitAgentCluster(node, imp, inputNames, graph) {
1453
1436
  // The agent's directory uuid is derived from the flow and step ids, but a
1454
1437
  // decompiled flow must keep the ORIGINAL — the sidecar lives at
1455
1438
  // `<source>/agent.json`, so a re-derived one would point at nothing.
1456
- ['source', str(plain(i.source) ?? '')],
1439
+ ['source', tsLiteral(plain(i.source) ?? '')],
1457
1440
  ])})`;
1458
1441
  }
1459
1442
  /**
@@ -1496,11 +1479,11 @@ function emitHitl(node, imp, inputNames, graph) {
1496
1479
  const id = String(f.id ?? '');
1497
1480
  const direction = String(f.direction ?? 'output');
1498
1481
  return opts([
1499
- ['id', str(id)],
1482
+ ['id', tsLiteral(id)],
1500
1483
  // serialize defaults a row's label to its id.
1501
- ['label', typeof f.label === 'string' && f.label !== id ? str(f.label) : undefined],
1502
- ['type', str(String(f.type ?? 'text'))],
1503
- ['direction', str(direction)],
1484
+ ['label', typeof f.label === 'string' && f.label !== id ? tsLiteral(f.label) : undefined],
1485
+ ['type', tsLiteral(String(f.type ?? 'text'))],
1486
+ ['direction', tsLiteral(direction)],
1504
1487
  ['value', direction === 'input' || direction === 'inOut' ? bindingExpr(f.binding) : undefined],
1505
1488
  ['required', f.required === true ? 'true' : undefined],
1506
1489
  ]);
@@ -1509,7 +1492,7 @@ function emitHitl(node, imp, inputNames, graph) {
1509
1492
  const outcomes = outcomeRows.map((o) => {
1510
1493
  const name = String(o.name ?? '');
1511
1494
  // A bare string is the shorthand; only `action: 'End'` needs the object.
1512
- return o.action === 'End' ? opts([['name', str(name)], ['action', str('End')]]) : str(name);
1495
+ return o.action === 'End' ? opts([['name', tsLiteral(name)], ['action', tsLiteral('End')]]) : tsLiteral(name);
1513
1496
  });
1514
1497
  // Delivery: only an override comes back. The definition's own default is what
1515
1498
  // serialize emits when the author wrote nothing.
@@ -1521,11 +1504,11 @@ function emitHitl(node, imp, inputNames, graph) {
1521
1504
  const assignee = (rec.assignee ?? {});
1522
1505
  const channels = Array.isArray(rec.channels) ? rec.channels : undefined;
1523
1506
  recipientSrc = opts([
1524
- ['channels', channels ? `[${channels.map((c) => str(c)).join(', ')}]` : undefined],
1507
+ ['channels', channels ? `[${channels.map((c) => tsLiteral(c)).join(', ')}]` : undefined],
1525
1508
  ['assignee', opts([
1526
- ['type', str(String(assignee.type ?? 'user'))],
1527
- ['value', typeof assignee.value === 'string' ? str(assignee.value) : undefined],
1528
- ['displayName', typeof assignee.displayName === 'string' ? str(assignee.displayName) : undefined],
1509
+ ['type', tsLiteral(String(assignee.type ?? 'user'))],
1510
+ ['value', typeof assignee.value === 'string' ? tsLiteral(assignee.value) : undefined],
1511
+ ['displayName', typeof assignee.displayName === 'string' ? tsLiteral(assignee.displayName) : undefined],
1529
1512
  ])],
1530
1513
  ]);
1531
1514
  }
@@ -1540,15 +1523,15 @@ function emitHitl(node, imp, inputNames, graph) {
1540
1523
  const bindings = (i.appInputBindings ?? {});
1541
1524
  const appInputs = Object.keys(bindings).length === 0 ? undefined : objLit(Object.entries(bindings).map(([k, v]) => {
1542
1525
  const raw = String(v ?? '');
1543
- return [k, raw.startsWith('=') ? emitBareExpr(`$${raw.slice(1)}`, imp, inputNames) : str(raw)];
1526
+ return [k, raw.startsWith('=') ? emitBareExpr(`$${raw.slice(1)}`, imp, inputNames) : tsLiteral(raw)];
1544
1527
  }));
1545
1528
  const appSrc = app === undefined ? undefined : opts([
1546
1529
  // `displayName` is serialize's echo of `name`; only `name` is authored.
1547
- ['name', str(String(app.name ?? ''))],
1548
- ['key', str(String(app.key ?? ''))],
1549
- ['folderPath', typeof app.folderPath === 'string' && app.folderPath !== '' ? str(app.folderPath) : undefined],
1530
+ ['name', tsText(app.name)],
1531
+ ['key', tsText(app.key)],
1532
+ ['folderPath', typeof app.folderPath === 'string' && app.folderPath !== '' ? tsLiteral(app.folderPath) : undefined],
1550
1533
  ['appSystemName', app.appSystemName === null ? 'null'
1551
- : typeof app.appSystemName === 'string' ? str(app.appSystemName) : undefined],
1534
+ : typeof app.appSystemName === 'string' ? tsLiteral(app.appSystemName) : undefined],
1552
1535
  ['inputs', appInputs],
1553
1536
  ]);
1554
1537
  // The validation station's own inputs ride as PLAIN `$vars.…` paths.
@@ -1557,15 +1540,15 @@ function emitHitl(node, imp, inputNames, graph) {
1557
1540
  if (raw === undefined)
1558
1541
  return undefined;
1559
1542
  const js = raw.startsWith('=js:') ? raw.slice(4) : raw;
1560
- return js.startsWith('$vars.') ? emitBareExpr(js, imp, inputNames) : str(js);
1543
+ return js.startsWith('$vars.') ? emitBareExpr(js, imp, inputNames) : tsLiteral(js);
1561
1544
  };
1562
1545
  const render = plain(i.validationRenderType);
1563
1546
  const documentSrc = node.type !== T.hitlDocValidation ? undefined : opts([
1564
- ['extractionResult', stationExpr(i.extractionResult) ?? str('')],
1565
- ['storageBucket', plain(i.storageBucket) !== undefined ? str(plain(i.storageBucket)) : undefined],
1566
- ['documentId', plain(i.documentId) !== undefined ? str(plain(i.documentId)) : undefined],
1547
+ ['extractionResult', stationExpr(i.extractionResult) ?? tsLiteral('')],
1548
+ ['storageBucket', plain(i.storageBucket) !== undefined ? tsLiteral(plain(i.storageBucket)) : undefined],
1549
+ ['documentId', plain(i.documentId) !== undefined ? tsLiteral(plain(i.documentId)) : undefined],
1567
1550
  // 'standard' is the definition's default.
1568
- ['render', render !== undefined && render !== 'standard' ? str(render) : undefined],
1551
+ ['render', render !== undefined && render !== 'standard' ? tsLiteral(render) : undefined],
1569
1552
  ['taxonomy', stationExpr(i.taxonomy)],
1570
1553
  ]);
1571
1554
  // On the base node the VERSION says it: 1.1 routes per outcome, 1.2 adds the
@@ -1596,7 +1579,7 @@ function emitHitl(node, imp, inputNames, graph) {
1596
1579
  const fanOut = !outcomePorts && derivesHandles && !versionRoutesHitl(node) && outcomeIdsOfNode(node).length > 1
1597
1580
  && (legacyCompleted || graph?.convergedFanOut(node.id) === true);
1598
1581
  if (legacyCompleted) {
1599
- warnOnce(`human task ${str(node.id)} leaves on the legacy 'completed' handle, which Studio Web no longer draws `
1582
+ warnOnce(`human task ${tsLiteral(node.id)} leaves on the legacy 'completed' handle, which Studio Web no longer draws `
1600
1583
  + `once a task has outcomes (it reads the edge as the primary outcome only). Recompiling emits one `
1601
1584
  + `outcome-<id> edge per outcome to the same step, so every outcome continues — what the edge meant `
1602
1585
  + `before flow-workbench #3512.`);
@@ -1604,11 +1587,11 @@ function emitHitl(node, imp, inputNames, graph) {
1604
1587
  const title = plain(i.title);
1605
1588
  const priority = plain(i.priority);
1606
1589
  return `${imp.use('hitl')}(${opts([
1607
- ['variant', variant !== undefined ? str(variant) : undefined],
1590
+ ['variant', variant !== undefined ? tsLiteral(variant) : undefined],
1608
1591
  // serialize defaults the title to the step's own name.
1609
- ['title', title !== undefined && title !== node.id ? str(title) : undefined],
1610
- ['priority', priority !== undefined && priority !== 'Low' ? str(priority) : undefined],
1611
- ['labels', plain(i.labels) !== undefined ? str(plain(i.labels)) : undefined],
1592
+ ['title', title !== undefined && title !== node.id ? tsLiteral(title) : undefined],
1593
+ ['priority', priority !== undefined && priority !== 'Low' ? tsLiteral(priority) : undefined],
1594
+ ['labels', plain(i.labels) !== undefined ? tsLiteral(plain(i.labels)) : undefined],
1612
1595
  ['recipient', recipientSrc],
1613
1596
  ['app', appSrc],
1614
1597
  ['document', documentSrc],
@@ -1645,7 +1628,7 @@ function unboundMarker(node, labels, key, action) {
1645
1628
  if (missing.length === 0)
1646
1629
  return '';
1647
1630
  const names = missing.join(' and ');
1648
- warnOnce(`node ${str(node.id)} (${node.type}) has no ${missing.join(' or ')} in the .flow (an unbound draft). `
1631
+ warnOnce(`node ${tsLiteral(node.id)} (${node.type}) has no ${missing.join(' or ')} in the .flow (an unbound draft). `
1649
1632
  + `The source leaves ${missing.length > 1 ? 'them' : 'it'} out, and compile refuses the node (BINDING_MISSING) `
1650
1633
  + `until it names both labels: run \`${prepareCommand(key, action ?? '<action>')}\`, then pass `
1651
1634
  + `\`connection\` and \`folder\`.`);
@@ -1685,8 +1668,8 @@ function emitEventSubscription(node, imp, inputNames) {
1685
1668
  .map((f) => eventFilterFromTreeLeaf(f))
1686
1669
  .filter((f) => f !== undefined)
1687
1670
  .map((f) => opts([
1688
- ['field', str(f.field)],
1689
- [f.operator, typeof f.value === 'string' ? str(f.value) : JSON.stringify(f.value)],
1671
+ ['field', tsLiteral(f.field)],
1672
+ [f.operator, typeof f.value === 'string' ? tsLiteral(f.value) : JSON.stringify(f.value)],
1690
1673
  ]));
1691
1674
  }
1692
1675
  catch {
@@ -1701,13 +1684,13 @@ function emitEventSubscription(node, imp, inputNames) {
1701
1684
  const conn = typeof detail?.connectionId === 'string' ? detail.connectionId : undefined;
1702
1685
  const folder = typeof detail?.connectionFolderKey === 'string' ? detail.connectionFolderKey : undefined;
1703
1686
  return opts([
1704
- ['connector', str(connector)],
1705
- ['event', str(event)],
1687
+ ['connector', tsLiteral(connector)],
1688
+ ['event', tsLiteral(event)],
1706
1689
  ['where', whereRows.length ? objLit(whereRows) : undefined],
1707
- ['object', object !== undefined ? str(object) : undefined],
1690
+ ['object', object !== undefined ? tsLiteral(object) : undefined],
1708
1691
  ['filters', filterRows.length ? `[${filterRows.join(', ')}]` : undefined],
1709
- ['connection', conn !== undefined ? str(conn) : undefined],
1710
- ['folder', folder !== undefined ? str(folder) : undefined],
1692
+ ['connection', conn !== undefined ? tsLiteral(conn) : undefined],
1693
+ ['folder', folder !== undefined ? tsLiteral(folder) : undefined],
1711
1694
  ]) + unboundMarker(node, { connection: conn, folder }, connector, event);
1712
1695
  }
1713
1696
  /**
@@ -1780,18 +1763,18 @@ function emitPublishedResource(node, imp, inputNames) {
1780
1763
  .map(([k, v]) => [k, emitVal(v, imp, inputNames)]);
1781
1764
  const schema = def?.outputDefinition?.output?.schema?.properties;
1782
1765
  const returns = schema && Object.keys(schema).length > 0
1783
- ? objLit(Object.entries(schema).map(([k, v]) => [k, str(String(v?.type ?? 'string'))]))
1766
+ ? objLit(Object.entries(schema).map(([k, v]) => [k, tsLiteral(String(v?.type ?? 'string'))]))
1784
1767
  : undefined;
1785
1768
  return `${imp.use(family.factory)}(${opts([
1786
- ['key', str(key)],
1787
- ['name', name !== undefined ? str(name) : undefined],
1788
- ['location', inSolution ? str('in-solution') : undefined],
1789
- ['folderPath', !inSolution && folderPath !== undefined ? str(folderPath) : undefined],
1790
- ['projectId', inSolution && projectId !== undefined ? str(projectId) : undefined],
1769
+ ['key', tsLiteral(key)],
1770
+ ['name', name !== undefined ? tsLiteral(name) : undefined],
1771
+ ['location', inSolution ? tsLiteral('in-solution') : undefined],
1772
+ ['folderPath', !inSolution && folderPath !== undefined ? tsLiteral(folderPath) : undefined],
1773
+ ['projectId', inSolution && projectId !== undefined ? tsLiteral(projectId) : undefined],
1791
1774
  ['inputs', inputRows.length ? objLit(inputRows) : undefined],
1792
1775
  ['returns', returns],
1793
- ['form', family.form !== undefined ? str(family.form) : undefined],
1794
- ['completion', fireAndForget ? str('fire-and-forget') : undefined],
1776
+ ['form', family.form !== undefined ? tsLiteral(family.form) : undefined],
1777
+ ['completion', fireAndForget ? tsLiteral('fire-and-forget') : undefined],
1795
1778
  ])})`;
1796
1779
  }
1797
1780
  function emitConnector(node, imp, inputNames, o) {
@@ -1820,12 +1803,12 @@ function emitConnector(node, imp, inputNames, o) {
1820
1803
  const folder = typeof detail.connectionFolderKey === 'string' ? detail.connectionFolderKey : undefined;
1821
1804
  const object = connectorObjectName(detail.configuration);
1822
1805
  const optsSrc = opts([
1823
- ['connection', connection !== undefined ? str(connection) : undefined],
1824
- ['folder', folder !== undefined ? str(folder) : undefined],
1825
- ['object', object !== undefined ? str(object) : undefined],
1806
+ ['connection', connection !== undefined ? tsLiteral(connection) : undefined],
1807
+ ['folder', folder !== undefined ? tsLiteral(folder) : undefined],
1808
+ ['object', object !== undefined ? tsLiteral(object) : undefined],
1826
1809
  ]);
1827
1810
  const inputsSrc = objLit(flat) || '{}';
1828
- const args = [str(split.key), str(split.action), inputsSrc, ...(optsSrc ? [optsSrc] : [])];
1811
+ const args = [tsLiteral(split.key), tsLiteral(split.action), inputsSrc, ...(optsSrc ? [optsSrc] : [])];
1829
1812
  return `${imp.use('connector')}(${args.join(', ')})`
1830
1813
  + unboundMarker(node, { connection, folder }, split.key, split.action);
1831
1814
  }
@@ -1847,8 +1830,8 @@ function unsupportedSpec(node, imp, o) {
1847
1830
  const inputs = node.inputs ?? {};
1848
1831
  const inputsSrc = Object.keys(inputs).length > 0 ? JSON.stringify(inputs, null, 2) : undefined;
1849
1832
  return `${imp.use('rawNode')}(${opts([
1850
- ['nodeType', str(node.type)],
1851
- ['version', str(String(node.typeVersion ?? ''))],
1833
+ ['nodeType', tsLiteral(node.type)],
1834
+ ['version', tsText(node.typeVersion)],
1852
1835
  ['manifest', constName],
1853
1836
  ['inputs', inputsSrc],
1854
1837
  ])})`;
@@ -1879,7 +1862,7 @@ const TYPE_NAMES = new Set([
1879
1862
  function typeExpr(type, imp) {
1880
1863
  if (TYPE_NAMES.has(type))
1881
1864
  return `${imp.use('types')}.${type}`;
1882
- return str(type);
1865
+ return tsLiteral(type);
1883
1866
  }
1884
1867
  /**
1885
1868
  * A global carrying metadata decompiles to the rich VarSpec spelling
@@ -1889,15 +1872,15 @@ function typeExpr(type, imp) {
1889
1872
  function declExpr(g, withDefault, imp, shared) {
1890
1873
  const meta = [];
1891
1874
  if (withDefault && g.defaultValue !== undefined)
1892
- meta.push(`default: ${literal(g.defaultValue)}`);
1875
+ meta.push(`default: ${tsLiteral(g.defaultValue)}`);
1893
1876
  if (g.description !== undefined)
1894
- meta.push(`description: ${str(g.description)}`);
1877
+ meta.push(`description: ${tsLiteral(g.description)}`);
1895
1878
  if (g.subType !== undefined)
1896
- meta.push(`subType: ${str(g.subType)}`);
1879
+ meta.push(`subType: ${tsLiteral(g.subType)}`);
1897
1880
  if (g.schema !== undefined)
1898
- meta.push(`schema: ${literal(g.schema)}`);
1881
+ meta.push(`schema: ${tsLiteral(g.schema)}`);
1899
1882
  if (shared !== undefined)
1900
- meta.push(`shared: ${str(shared)}`);
1883
+ meta.push(`shared: ${tsLiteral(shared)}`);
1901
1884
  if (meta.length === 0)
1902
1885
  return typeExpr(g.type, imp);
1903
1886
  return `{ type: ${typeExpr(g.type, imp)}, ${meta.join(', ')} }`;
@@ -1954,50 +1937,49 @@ function emitVariables(e, depth, flow, imp, epTriggers = new Set(), shared) {
1954
1937
  e.line(depth, `.output(${shape(outputs, true)})`);
1955
1938
  for (const g of inouts) {
1956
1939
  const rich = declExpr(g, false, imp);
1957
- const def = g.defaultValue !== undefined ? `, ${literal(g.defaultValue)}` : '';
1958
- e.line(depth, `.var(${str(g.name ?? g.id)}, ${rich}${def})`);
1940
+ const def = g.defaultValue !== undefined ? `, ${tsLiteral(g.defaultValue)}` : '';
1941
+ e.line(depth, `.var(${tsLiteral(g.name ?? g.id)}, ${rich}${def})`);
1959
1942
  }
1960
1943
  }
1961
1944
  // ─── Trigger ──────────────────────────────────────────────────────────────────
1945
+ /**
1946
+ * The trigger factory call for a root trigger node, or `undefined` for a type
1947
+ * this file cannot read back. Both the default root (`.trigger(...)`) and every
1948
+ * extra root (`.entryPoint(id, ...)`) read it.
1949
+ */
1950
+ function triggerFactory(t, imp, inputNames) {
1951
+ // Decompiled source spells the default `manual()` out on purpose: it is more
1952
+ // explicit than hand-authored source so the node's exact definition version
1953
+ // survives recompilation (design §5.3).
1954
+ if (t.type === T.triggerManual)
1955
+ return `${imp.use('manual')}()`;
1956
+ if (t.type === T.triggerScheduled)
1957
+ return `${imp.use('scheduled')}(${opts([['every', tsLiteral(scheduleEvery(t))]])})`;
1958
+ // The form schema is DERIVED from the root's inputs at compile time, so
1959
+ // nothing of it needs recovering — the factory takes no arguments.
1960
+ if (t.type === T.triggerForm)
1961
+ return `${imp.use('formTrigger')}()`;
1962
+ if (t.type === T.triggerConversation)
1963
+ return `${imp.use('conversationTrigger')}()`;
1964
+ if (t.type === T.triggerVoice)
1965
+ return `${imp.use('voiceTrigger')}()`;
1966
+ // A connector EVENT trigger — the same subscription body as a mid-flow wait.
1967
+ const sub = emitEventSubscription(t, imp, inputNames);
1968
+ if (sub !== undefined)
1969
+ return `${imp.use('onEvent')}(${sub})`;
1970
+ return undefined;
1971
+ }
1962
1972
  function emitTrigger(e, depth, trigger, imp, o, inputNames = new Set()) {
1963
1973
  // The trigger's id is addressable — a flow's inputs are published as its
1964
1974
  // output — so a renamed one has to survive the round trip. `start` is the
1965
1975
  // default and stays implicit.
1966
1976
  if (trigger.id && trigger.id !== DEFAULT_TRIGGER_ID)
1967
- e.line(depth, `.triggerId(${str(trigger.id)})`);
1968
- if (trigger.type === T.triggerManual) {
1969
- // The default trigger, spelled out: decompiled source is deliberately more
1970
- // explicit than hand-authored source so the node's exact definition version
1971
- // survives recompilation (design §5.3).
1972
- e.line(depth, `.trigger(${imp.use('manual')}()${triggerOptsArg(trigger)})`);
1973
- return;
1974
- }
1975
- if (trigger.type === T.triggerScheduled) {
1976
- e.line(depth, `.trigger(${imp.use('scheduled')}(${opts([['every', str(scheduleEvery(trigger))]])})${triggerOptsArg(trigger)})`);
1977
+ e.line(depth, `.triggerId(${tsLiteral(trigger.id)})`);
1978
+ const factory = triggerFactory(trigger, imp, inputNames);
1979
+ if (factory !== undefined) {
1980
+ e.line(depth, `.trigger(${factory}${triggerOptsArg(trigger)})`);
1977
1981
  return;
1978
1982
  }
1979
- if (trigger.type === T.triggerForm) {
1980
- // The schema is DERIVED from the flow's inputs at compile time, so nothing
1981
- // of it needs recovering — the factory takes no arguments.
1982
- e.line(depth, `.trigger(${imp.use('formTrigger')}()${triggerOptsArg(trigger)})`);
1983
- return;
1984
- }
1985
- if (trigger.type === T.triggerConversation) {
1986
- e.line(depth, `.trigger(${imp.use('conversationTrigger')}()${triggerOptsArg(trigger)})`);
1987
- return;
1988
- }
1989
- if (trigger.type === T.triggerVoice) {
1990
- e.line(depth, `.trigger(${imp.use('voiceTrigger')}()${triggerOptsArg(trigger)})`);
1991
- return;
1992
- }
1993
- // A connector EVENT trigger — the same subscription body as a mid-flow wait.
1994
- {
1995
- const sub = emitEventSubscription(trigger, imp, inputNames);
1996
- if (sub !== undefined) {
1997
- e.line(depth, `.trigger(${imp.use('onEvent')}(${sub})${triggerOptsArg(trigger)})`);
1998
- return;
1999
- }
2000
- }
2001
1983
  if (o.bestEffort === false) {
2002
1984
  throw new Error(`decompile: unsupported trigger type "${trigger.type}".`);
2003
1985
  }
@@ -2183,14 +2165,14 @@ function isIdentityEndSource(raw, name) {
2183
2165
  function returnOptsArg(node) {
2184
2166
  const parts = [];
2185
2167
  if (node.id !== 'end')
2186
- parts.push(`name: ${str(node.id)}`);
2168
+ parts.push(`name: ${tsLiteral(node.id)}`);
2187
2169
  const ver = verClause(node);
2188
2170
  if (ver)
2189
2171
  parts.push(ver);
2190
2172
  // The serializer labels an unlabelled End `End`, so that needs no carrying.
2191
2173
  const label = node.display?.label;
2192
2174
  if (typeof label === 'string' && label !== 'End')
2193
- parts.push(`label: ${str(label)}`);
2175
+ parts.push(`label: ${tsLiteral(label)}`);
2194
2176
  return parts.length ? `, { ${parts.join(', ')} }` : '';
2195
2177
  }
2196
2178
  /**
@@ -2229,7 +2211,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2229
2211
  while (cursor !== undefined && cursor !== stop && cursor !== EXIT) {
2230
2212
  // A ref forward: this path re-enters a step the main path reaches later.
2231
2213
  if (refInto?.has(cursor)) {
2232
- segs.push(`.stepToRef(${str(cursor)})`);
2214
+ segs.push(`.stepToRef(${tsLiteral(cursor)})`);
2233
2215
  return segs;
2234
2216
  }
2235
2217
  const node = ctx.graph.byId.get(cursor);
@@ -2241,7 +2223,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2241
2223
  // terminates are excluded: they are not named steps, so there is nothing a ref
2242
2224
  // could resolve against, and each path emitting its own is already correct.
2243
2225
  if (ctx.emitted.has(cursor) && node.type !== T.end && node.type !== T.terminate) {
2244
- segs.push(`.stepToRef(${str(cursor)})`);
2226
+ segs.push(`.stepToRef(${tsLiteral(cursor)})`);
2245
2227
  return segs;
2246
2228
  }
2247
2229
  ctx.emitted.add(cursor);
@@ -2250,7 +2232,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2250
2232
  return segs;
2251
2233
  }
2252
2234
  if (node.type === T.terminate) {
2253
- segs.push(`.terminate(${str(node.id)}, undefined${optsArg(node, ctx)})`);
2235
+ segs.push(`.terminate(${tsLiteral(node.id)}, undefined${optsArg(node, ctx)})`);
2254
2236
  return segs;
2255
2237
  }
2256
2238
  if (node.type === T.merge) {
@@ -2266,10 +2248,10 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2266
2248
  const elseSegs = region(portTarget(ctx.graph, node.id, 'false'), r, scope, ipdom, ctx, refInto);
2267
2249
  const labels = armLabels(node);
2268
2250
  if (labels.trueLabel)
2269
- thenSegs.unshift(`.label(${str(labels.trueLabel)})`);
2251
+ thenSegs.unshift(`.label(${tsLiteral(labels.trueLabel)})`);
2270
2252
  if (labels.falseLabel)
2271
- elseSegs.unshift(`.label(${str(labels.falseLabel)})`);
2272
- segs.push(`.branch(${str(node.id)}, ${cond},\n${reindent(armCb('t', thenSegs), ' ')},\n${reindent(armCb('e', elseSegs), ' ')}${verArg(node)})`);
2253
+ elseSegs.unshift(`.label(${tsLiteral(labels.falseLabel)})`);
2254
+ segs.push(`.branch(${tsLiteral(node.id)}, ${cond},\n${reindent(armCb('t', thenSegs), ' ')},\n${reindent(armCb('e', elseSegs), ' ')}${verArg(node)})`);
2273
2255
  cursor = r === EXIT ? undefined : r;
2274
2256
  continue;
2275
2257
  }
@@ -2313,14 +2295,14 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2313
2295
  const cond = typeof condRaw === 'string' && condRaw.length
2314
2296
  ? emitBareExpr(condRaw.startsWith('=js:') ? condRaw.slice(4) : condRaw, ctx.imp, ctx.inputNames)
2315
2297
  : `${ctx.imp.use('lit')}(true)`;
2316
- segs.push(`.doWhile(${str(node.id)}, ${cond}, ${armCb('b', bodySegs)}${opts})`);
2298
+ segs.push(`.doWhile(${tsLiteral(node.id)}, ${cond}, ${armCb('b', bodySegs)}${opts})`);
2317
2299
  }
2318
2300
  else {
2319
2301
  const collRaw = unwrapSource(inputs.collection);
2320
2302
  const collection = collRaw !== undefined
2321
2303
  ? emitBareExpr(collRaw.startsWith('=js:') ? collRaw.slice(4) : collRaw, ctx.imp, ctx.inputNames)
2322
2304
  : `${ctx.imp.use('lit')}([])`;
2323
- segs.push(`.loop(${str(node.id)}, ${collection}, ${armCb('b', bodySegs)}${opts})`);
2305
+ segs.push(`.loop(${tsLiteral(node.id)}, ${collection}, ${armCb('b', bodySegs)}${opts})`);
2324
2306
  }
2325
2307
  // A container can carry an error handler too — `core.logic.loop` declares
2326
2308
  // the handle, and `.onError()` after `.loop(...)` authors it. Without this
@@ -2334,7 +2316,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2334
2316
  continue;
2335
2317
  }
2336
2318
  if (node.type === T.subflow) {
2337
- segs.push(`.step(${str(node.id)}, ${emitSubflow(node, ctx)}${optsArg(node, ctx)})`);
2319
+ segs.push(`.step(${tsLiteral(node.id)}, ${emitSubflow(node, ctx)}${optsArg(node, ctx)})`);
2338
2320
  cursor = scopeSucc(ctx.graph, node.id, scope)[0];
2339
2321
  continue;
2340
2322
  }
@@ -2352,14 +2334,14 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2352
2334
  const r = ipdom.get(node.id) ?? stop;
2353
2335
  const armSrcs = exitPorts.map(({ value, port }) => {
2354
2336
  const armSegs = region(portTarget(ctx.graph, node.id, port), r, scope, ipdom, ctx, refInto);
2355
- return `{ value: ${str(value)}, body: ${armCb('b', armSegs)} }`;
2337
+ return `{ value: ${tsLiteral(value)}, body: ${armCb('b', armSegs)} }`;
2356
2338
  });
2357
2339
  const armList = `[\n${armSrcs.map((a) => reindent(a, ' ')).join(',\n')},\n]`;
2358
- segs.push(`.stepSwitch(${str(node.id)}, ${spec}, ${armList}${optsArg(node, ctx)})`);
2340
+ segs.push(`.stepSwitch(${tsLiteral(node.id)}, ${spec}, ${armList}${optsArg(node, ctx)})`);
2359
2341
  cursor = r === EXIT ? undefined : r;
2360
2342
  continue;
2361
2343
  }
2362
- segs.push(`.step(${str(node.id)}, ${spec}${optsArg(node, ctx)})`);
2344
+ segs.push(`.step(${tsLiteral(node.id)}, ${spec}${optsArg(node, ctx)})`);
2363
2345
  // An error handler on this step (source port `error` + `errorHandlingEnabled`).
2364
2346
  const succ = scopeSucc(ctx.graph, node.id, scope);
2365
2347
  const handler = errorHandlerSeg(node.id, succ, scope, ipdom, ctx);
@@ -2373,7 +2355,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2373
2355
  const branchSegs = region(be.targetNodeId, EXIT, scope, ipdom, ctx, mainReach);
2374
2356
  if (branchSegs.length === 0)
2375
2357
  branchSegs.push('.return({})');
2376
- segs.push(`.stepToList(${str(be.sourcePort)}, ${armCb('p', branchSegs)})`);
2358
+ segs.push(`.stepToList(${tsLiteral(be.sourcePort)}, ${armCb('p', branchSegs)})`);
2377
2359
  }
2378
2360
  // A per-outcome human task routes each NON-primary outcome from its own
2379
2361
  // `outcome-<slug>` exit; the primary one continues the main path, so it is
@@ -2388,7 +2370,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2388
2370
  const outSegs = region(oe.targetNodeId, EXIT, scope, ipdom, ctx, mainReach);
2389
2371
  if (outSegs.length === 0)
2390
2372
  outSegs.push('.return({})');
2391
- segs.push(`.stepToList(${str(oe.sourcePort)}, ${armCb('p', outSegs)})`);
2373
+ segs.push(`.stepToList(${tsLiteral(oe.sourcePort)}, ${armCb('p', outSegs)})`);
2392
2374
  }
2393
2375
  }
2394
2376
  // A body edge into the enclosing container's break handle: the path exits
@@ -2410,7 +2392,7 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2410
2392
  const join = ipdom.get(node.id);
2411
2393
  if (join && join !== EXIT && ctx.graph.byId.get(join)?.type === T.merge) {
2412
2394
  const arms = succ.map((t, i) => armCb(`a${i}`, region(t, join, scope, ipdom, ctx, refInto)));
2413
- segs.push(`.parallel(${str(join)}, [\n${arms.map((a) => reindent(a, ' ')).join(',\n')},\n]${verArg(ctx.graph.byId.get(join))})`);
2395
+ segs.push(`.parallel(${tsLiteral(join)}, [\n${arms.map((a) => reindent(a, ' ')).join(',\n')},\n]${verArg(ctx.graph.byId.get(join))})`);
2414
2396
  cursor = portTarget(ctx.graph, join, 'output');
2415
2397
  continue;
2416
2398
  }
@@ -2534,12 +2516,12 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
2534
2516
  if (m) {
2535
2517
  valueLiteral = m[2].trim();
2536
2518
  try {
2537
- value = literal(JSON.parse(valueLiteral));
2519
+ value = tsLiteral(JSON.parse(valueLiteral));
2538
2520
  }
2539
2521
  catch {
2540
2522
  // A non-JSON literal (e.g. single-quoted) — strip matching quotes if any.
2541
2523
  const q = valueLiteral.match(/^'(.*)'$/);
2542
- value = q ? str(q[1]) : str(valueLiteral);
2524
+ value = q ? tsLiteral(q[1]) : tsLiteral(valueLiteral);
2543
2525
  }
2544
2526
  }
2545
2527
  else {
@@ -2547,7 +2529,7 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
2547
2529
  }
2548
2530
  const armSegs = region(portTarget(ctx.graph, node.id, `case-${c.id}`), r, scope, ipdom, ctx, refInto);
2549
2531
  const defaultLabel = `Case ${valueLiteral}`;
2550
- const labelPart = c.label && c.label !== defaultLabel ? `, label: ${str(c.label)}` : '';
2532
+ const labelPart = c.label && c.label !== defaultLabel ? `, label: ${tsLiteral(c.label)}` : '';
2551
2533
  caseSrcs.push(`{ value: ${value}${labelPart}, body: ${armCb('b', armSegs)} }`);
2552
2534
  }
2553
2535
  const on = emitBareExpr(disc ?? '', ctx.imp, ctx.inputNames);
@@ -2564,7 +2546,7 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
2564
2546
  ? `\n/* TODO: this switch's case expressions are not a uniform \`<discriminant> === <value>\`; the recovered on/value are best-effort and need review. */`
2565
2547
  : '';
2566
2548
  const ver = verArg(node);
2567
- return `.switch(${str(node.id)}, ${on}, ${caseList}${defArm}${ver && !defArm ? ', undefined' : ''}${ver})${warn}`;
2549
+ return `.switch(${tsLiteral(node.id)}, ${on}, ${caseList}${defArm}${ver && !defArm ? ', undefined' : ''}${ver})${warn}`;
2568
2550
  }
2569
2551
  /** The manifest-less annotation node type — the only annotation `.flow` has. */
2570
2552
  const STICKY_NOTE_TYPE = 'stickyNote';
@@ -2588,14 +2570,14 @@ function stickyNotesSeg(nodes, flow) {
2588
2570
  const ui = { ...(n.ui ?? {}), ...(hoisted[n.id] ?? {}) };
2589
2571
  const pos = ui.position ?? { x: 0, y: 0 };
2590
2572
  const fields = [
2591
- `id: ${str(n.id)}`,
2592
- `content: ${str(typeof i.content === 'string' ? i.content : '')}`,
2573
+ `id: ${tsLiteral(n.id)}`,
2574
+ `content: ${tsLiteral(typeof i.content === 'string' ? i.content : '')}`,
2593
2575
  ];
2594
2576
  if (typeof i.color === 'string' && i.color !== 'yellow')
2595
- fields.push(`color: ${str(i.color)}`);
2596
- fields.push(`x: ${numLit(pos.x)}`, `y: ${numLit(pos.y)}`);
2577
+ fields.push(`color: ${tsLiteral(i.color)}`);
2578
+ fields.push(`x: ${tsNumber(pos.x)}`, `y: ${tsNumber(pos.y)}`);
2597
2579
  if (ui.size && typeof ui.size.width === 'number' && typeof ui.size.height === 'number') {
2598
- fields.push(`width: ${numLit(ui.size.width)}`, `height: ${numLit(ui.size.height)}`);
2580
+ fields.push(`width: ${tsNumber(ui.size.width)}`, `height: ${tsNumber(ui.size.height)}`);
2599
2581
  }
2600
2582
  return ` { ${fields.join(', ')} },`;
2601
2583
  });
@@ -2645,9 +2627,9 @@ function layoutSeg(flow, nodes, edges) {
2645
2627
  const pos = ui.position;
2646
2628
  if (!pos || typeof pos.x !== 'number' || typeof pos.y !== 'number')
2647
2629
  continue;
2648
- const fields = [`position: { x: ${numLit(pos.x)}, y: ${numLit(pos.y)} }`];
2630
+ const fields = [`position: { x: ${tsNumber(pos.x)}, y: ${tsNumber(pos.y)} }`];
2649
2631
  if (ui.size && typeof ui.size.width === 'number' && typeof ui.size.height === 'number') {
2650
- fields.push(`size: { width: ${numLit(ui.size.width)}, height: ${numLit(ui.size.height)} }`);
2632
+ fields.push(`size: { width: ${tsNumber(ui.size.width)}, height: ${tsNumber(ui.size.height)} }`);
2651
2633
  }
2652
2634
  if (typeof ui.collapsed === 'boolean')
2653
2635
  fields.push(`collapsed: ${boolLit(ui.collapsed)}`);
@@ -2671,7 +2653,7 @@ function layoutSeg(flow, nodes, edges) {
2671
2653
  continue;
2672
2654
  const rendered = pts
2673
2655
  .filter((p) => p && typeof p.x === 'number' && typeof p.y === 'number')
2674
- .map((p) => `{ x: ${numLit(p.x)}, y: ${numLit(p.y)}${typeof p.id === 'string' ? `, id: ${str(p.id)}` : ''} }`);
2656
+ .map((p) => `{ x: ${tsNumber(p.x)}, y: ${tsNumber(p.y)}${typeof p.id === 'string' ? `, id: ${tsLiteral(p.id)}` : ''} }`);
2675
2657
  if (rendered.length)
2676
2658
  parts.push(`${which}: [${rendered.join(', ')}]`);
2677
2659
  }
@@ -2680,7 +2662,7 @@ function layoutSeg(flow, nodes, edges) {
2680
2662
  const pair = `${e.sourceNodeId}->${e.targetNodeId}`;
2681
2663
  if ((pairCount.get(pair) ?? 0) !== 1)
2682
2664
  continue; // ambiguous — see the note above
2683
- edgeEntries.push(`${str(pair)}: { ${parts.join(', ')} }`);
2665
+ edgeEntries.push(`${tsLiteral(pair)}: { ${parts.join(', ')} }`);
2684
2666
  }
2685
2667
  if (!nodeEntries.length && !edgeEntries.length)
2686
2668
  return undefined;
@@ -2725,7 +2707,7 @@ function emitChildFlow(name, child, ctx) {
2725
2707
  emitted: new Set(),
2726
2708
  o: ctx.o,
2727
2709
  };
2728
- const parts = [`${ctx.imp.use('flow')}(${str(name)})`];
2710
+ const parts = [`${ctx.imp.use('flow')}(${tsLiteral(name)})`];
2729
2711
  // Variables.
2730
2712
  const shape = (dir) => `{ ${globals.filter((g) => g.direction === dir).map((g) => `${key(g.name ?? g.id)}: ${declExpr(g, dir !== 'inout', ctx.imp)}`).join(', ')} }`;
2731
2713
  if (globals.some((g) => g.direction === 'in'))
@@ -2733,8 +2715,8 @@ function emitChildFlow(name, child, ctx) {
2733
2715
  if (globals.some((g) => g.direction === 'out'))
2734
2716
  parts.push(`.output(${shape('out')})`);
2735
2717
  for (const g of globals.filter((g) => g.direction === 'inout')) {
2736
- const def = g.defaultValue !== undefined ? `, ${literal(g.defaultValue)}` : '';
2737
- parts.push(`.var(${str(g.name ?? g.id)}, ${declExpr(g, false, ctx.imp)}${def})`);
2718
+ const def = g.defaultValue !== undefined ? `, ${tsLiteral(g.defaultValue)}` : '';
2719
+ parts.push(`.var(${tsLiteral(g.name ?? g.id)}, ${declExpr(g, false, ctx.imp)}${def})`);
2738
2720
  }
2739
2721
  // Body: the child's trigger is its own manual Start; walk from its successor.
2740
2722
  const trigger = (child.nodes ?? []).find((n) => isTrigger(n.type) && !(child.edges ?? []).some((e) => e.targetNodeId === n.id));
@@ -2788,7 +2770,7 @@ export function decompile(flow, options = {}) {
2788
2770
  (flow.nodes ?? []).find((n) => String(n.type ?? '').startsWith('core.trigger.'))?.id ??
2789
2771
  DEFAULT_TRIGGER_ID;
2790
2772
  const e = new Emit();
2791
- e.line(0, `export default ${imp.use('flow')}(${str(slugId(flow))})`);
2773
+ e.line(0, `export default ${imp.use('flow')}(${tsLiteral(slugId(flow))})`);
2792
2774
  const d = 1;
2793
2775
  // The file-format version FIRST, mirroring the order the file itself carries
2794
2776
  // it (`id`, `version`, `name`) — and emitted even when it equals this SDK's
@@ -2806,9 +2788,9 @@ export function decompile(flow, options = {}) {
2806
2788
  e.line(d, `// TODO: ${commentText(schema.problem)}`);
2807
2789
  }
2808
2790
  if (flow.name !== undefined && flow.name !== flow.id)
2809
- e.line(d, `.name(${str(flow.name)})`);
2791
+ e.line(d, `.name(${tsLiteral(flow.name)})`);
2810
2792
  if (typeof flow.description === 'string') {
2811
- e.line(d, `.description(${str(flow.description)})`);
2793
+ e.line(d, `.description(${tsLiteral(flow.description)})`);
2812
2794
  }
2813
2795
  // Trigger nodes. One root is the default: the isDefaultEntryPoint carrier
2814
2796
  // when the file marks one (a multi-root flow always does), else `start`.
@@ -2831,7 +2813,7 @@ export function decompile(flow, options = {}) {
2831
2813
  const detached = detachedTopLevelNodes(flow.nodes ?? [], graph, triggerNodes, trigger);
2832
2814
  const detachedIds = new Set(detached.map((n) => n.id));
2833
2815
  if (detached.length > 0) {
2834
- const listing = detached.map((n) => `${str(n.id)} (${n.type})`).join(', ');
2816
+ const listing = detached.map((n) => `${tsLiteral(n.id)} (${n.type})`).join(', ');
2835
2817
  const message = `${detached.length} node(s) are not reachable from any trigger — no edge leads to them: ${listing}. `
2836
2818
  + 'The builder has no detached-node construct, so this source omits them and recompiling drops them. '
2837
2819
  + 'Connect each one with an edge in the .flow first, or edit that node\'s JSON directly.';
@@ -2839,14 +2821,14 @@ export function decompile(flow, options = {}) {
2839
2821
  throw new Error(`decompile: ${message}`);
2840
2822
  warnOnce(message);
2841
2823
  for (const n of detached) {
2842
- e.line(d, `// TODO: node ${commentText(str(n.id))} (${commentText(n.type)}) is not reachable from any trigger and is NOT in this source; recompiling drops it. Connect it in the .flow or edit its JSON directly.`);
2824
+ e.line(d, `// TODO: node ${commentText(tsLiteral(n.id))} (${commentText(n.type)}) is not reachable from any trigger and is NOT in this source; recompiling drops it. Connect it in the .flow or edit its JSON directly.`);
2843
2825
  }
2844
2826
  }
2845
2827
  // `{ shared }` input copies on the roots (#924). Any other trigger-level
2846
2828
  // update has no builder spelling: say so instead of dropping it.
2847
2829
  const shared = sharedCopies(flow, triggerNodes);
2848
2830
  if (shared.unrecognized.length) {
2849
- const listing = shared.unrecognized.map((u) => `${str(u.variableId)} on ${str(u.trigger)}`).join(', ');
2831
+ const listing = shared.unrecognized.map((u) => `${tsLiteral(u.variableId)} on ${tsLiteral(u.trigger)}`).join(', ');
2850
2832
  const message = `${shared.unrecognized.length} variable update(s) on a trigger node are not a { shared } input copy: ${listing}. `
2851
2833
  + 'The builder writes a trigger-level update only for { type, shared: \'<var>\' } (the row `<var> ← $vars.<trigger>.output.<input>`), '
2852
2834
  + 'so this source omits them and recompiling drops them.';
@@ -2930,21 +2912,11 @@ function scheduleEvery(t) {
2930
2912
  }
2931
2913
  /** One `.entryPoint(id, trigger, { inputs, version }, prefix)` segment for a non-default root. */
2932
2914
  function entryPointSeg(t, scope, ipdom, ctx) {
2933
- const factory = t.type === T.triggerScheduled
2934
- ? `${ctx.imp.use('scheduled')}(${opts([['every', str(scheduleEvery(t))]])})`
2935
- : t.type === T.triggerForm
2936
- ? `${ctx.imp.use('formTrigger')}()`
2937
- : t.type === T.triggerConversation
2938
- ? `${ctx.imp.use('conversationTrigger')}()`
2939
- : t.type === T.triggerVoice
2940
- ? `${ctx.imp.use('voiceTrigger')}()`
2941
- : t.type === T.triggerManual
2942
- ? `${ctx.imp.use('manual')}()`
2943
- : (() => {
2944
- if (ctx.o.bestEffort === false)
2945
- throw new Error(`decompile: unsupported entry-point trigger type "${t.type}".`);
2946
- return `${ctx.imp.use('manual')}() /* TODO: unsupported trigger type ${commentText(t.type)} */`;
2947
- })();
2915
+ const known = triggerFactory(t, ctx.imp, ctx.inputNames);
2916
+ if (known === undefined && ctx.o.bestEffort === false) {
2917
+ throw new Error(`decompile: unsupported entry-point trigger type "${t.type}".`);
2918
+ }
2919
+ const factory = known ?? `${ctx.imp.use('manual')}() /* TODO: unsupported trigger type ${commentText(t.type)} */`;
2948
2920
  const scoped = readFlowGlobals(ctx.flow.variables, runStrict)
2949
2921
  .filter((g) => g.direction === 'in' && g.triggerNodeId === t.id);
2950
2922
  const optParts = [];
@@ -2957,5 +2929,5 @@ function entryPointSeg(t, scope, ipdom, ctx) {
2957
2929
  const prefixSegs = first !== undefined ? region(first, EXIT, scope, ipdom, ctx) : [];
2958
2930
  const optArgText = optParts.length ? `, { ${optParts.join(', ')} }` : (prefixSegs.length ? ', undefined' : '');
2959
2931
  const prefix = prefixSegs.length ? `, ${armCb('b', prefixSegs)}` : '';
2960
- return `.entryPoint(${str(t.id)}, ${factory}${optArgText}${prefix})`;
2932
+ return `.entryPoint(${tsLiteral(t.id)}, ${factory}${optArgText}${prefix})`;
2961
2933
  }