@lotics/cli 0.95.0 → 0.96.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 (2) hide show
  1. package/dist/src/cli.js +552 -57
  2. package/package.json +1 -1
package/dist/src/cli.js CHANGED
@@ -45377,8 +45377,9 @@ function buildStarterTemplate(args) {
45377
45377
  "react-native-svg": "^15.0.0",
45378
45378
  "react-native-web": "^0.21.0",
45379
45379
  // In-app routing for the scaffolded list→detail example. The app owns
45380
- // its routing; the SDK's `isEmbedded()` picks memory vs browser history.
45381
- "react-router-dom": "^7.0.0"
45380
+ // its routing; the SDK's `isEmbedded()` picks memory vs browser history. `react-router` is the
45381
+ // canonical package (react-router-dom is a shim over it).
45382
+ "react-router": "^8.3.0"
45382
45383
  },
45383
45384
  devDependencies: {
45384
45385
  "@testing-library/react": "^16.1.0",
@@ -45619,12 +45620,9 @@ export default defineConfig({
45619
45620
  // react-router hooks and the Router context never matches
45620
45621
  // ("useNavigate() may be used only in the context of a <Router>").
45621
45622
  "@lotics/app-sdk/router",
45622
- // react-router-dom (App's router) also ships compiled dist JS \u2014 pin it
45623
+ // react-router (App's router) also ships compiled dist JS \u2014 pin it
45623
45624
  // into the shared chunk so its hooks don't split the react instance
45624
45625
  // ("Invalid hook call") when a test renders the routed App tree.
45625
- "react-router-dom",
45626
- // v7: react-router-dom is a thin wrapper \u2014 the actual context
45627
- // lives in react-router; both must ride the shared chunk.
45628
45626
  "react-router",
45629
45627
  ],
45630
45628
  esbuildOptions: {
@@ -45708,7 +45706,7 @@ mount(
45708
45706
  // A single-screen app can delete the router and render one Screen directly.
45709
45707
  content: `import type { ReactNode } from "react";
45710
45708
  import { View } from "react-native";
45711
- import { useNavigate, useParams } from "react-router-dom";
45709
+ import { useNavigate, useParams } from "react-router";
45712
45710
  import { AppRouter } from "@lotics/app-sdk/router";
45713
45711
  import { useAiContext } from "@lotics/app-sdk";
45714
45712
  import { Card } from "@lotics/ui/card";
@@ -62046,6 +62044,14 @@ var runtimeKeySchema = zod_default.enum([
62046
62044
  "organization_id",
62047
62045
  "now",
62048
62046
  "change_origin",
62047
+ // PARKED — accepted in stored ASTs, rejected for new saves at parse (see
62048
+ // `PARKED_RUNTIME_KEYS` in walk_workflow_expression.ts). It is the OWNER's
62049
+ // principal (a workflow runs under owner authority), so it can't answer the
62050
+ // caller-authorization question anyone reads it for; the generated `.d.ts`
62051
+ // omits it and the docs point at `current_member_in_any_group` /
62052
+ // `runtime.triggered_by_member_id` instead. The entry stays so any stored
62053
+ // AST keeps validating and executing — drop it only once a prod probe shows
62054
+ // zero references (`workflows.steps_v2::text LIKE '%execution_principal%'`).
62049
62055
  "execution_principal",
62050
62056
  "triggered_by_member_id"
62051
62057
  ]);
@@ -64433,9 +64439,13 @@ for (const [operation, fieldTypes] of Object.entries(OPERATION_FIELD_TYPES)) {
64433
64439
  var AGGREGATABLE_FIELD_TYPES = new Set(FIELD_TYPE_OPERATIONS.keys());
64434
64440
 
64435
64441
  // ../shared/src/chat_models.ts
64436
- var CHAT_MODEL_IDS = ["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"];
64437
- var LEGACY_CHAT_MODEL_IDS = ["claude-sonnet-4-6"];
64442
+ var CHAT_MODEL_IDS = ["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-5"];
64443
+ var LEGACY_CHAT_MODEL_IDS = [];
64438
64444
  var ACCEPTED_CHAT_MODEL_IDS = [...CHAT_MODEL_IDS, ...LEGACY_CHAT_MODEL_IDS];
64445
+ var UTILITY_MODEL_ID = "claude-haiku-4-5";
64446
+ var PICKER_MODEL_IDS = CHAT_MODEL_IDS.filter(
64447
+ (id) => id !== UTILITY_MODEL_ID
64448
+ );
64439
64449
  var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
64440
64450
 
64441
64451
  // ../shared/src/schemas/apps.ts
@@ -64869,7 +64879,9 @@ var appAgentDeclarationSchema = zod_default.object({
64869
64879
  knowledge_doc_ids: zod_default.array(zod_default.string().min(1)).optional().describe(
64870
64880
  "Knowledge docs the agent may read, validated at declare time against the app owner's `use` access. Small docs are inlined into the agent's system prompt each run; a doc too large to inline requires the code tools (code_exec) in tool_names and is read by staging it into a code run. Omit for an agent that needs no knowledge."
64871
64881
  ),
64872
- model_id: zod_default.string().min(1).describe("Chat model id the agent runs on. Validated against ACCEPTED_CHAT_MODEL_IDS at run time."),
64882
+ model_id: zod_default.string().min(1).optional().describe(
64883
+ "Chat model id the agent runs on, validated against ACCEPTED_CHAT_MODEL_IDS. Omit to follow the platform default chat model, resolved at run time \u2014 the preferred choice: the agent tracks model generations with no per-app rewrite. Pin only a deliberate, tested choice."
64884
+ ),
64873
64885
  effort_level: zod_default.enum(EFFORT_LEVELS).optional().describe(
64874
64886
  "Reasoning depth for adaptive-thinking models \u2014 one of the chosen model's supported levels (validated against model_id at declare time). Omit to use the model default; ignored on models without adaptive thinking."
64875
64887
  ),
@@ -65010,7 +65022,7 @@ var agentStepSchema = stepBaseSchema.extend({
65010
65022
  instructions: zod_default.string().min(1),
65011
65023
  input: zod_default.record(zod_default.string(), toolInputValueSchema),
65012
65024
  tool_names: zod_default.array(zod_default.string().min(1)),
65013
- model_id: zod_default.string().min(1),
65025
+ model_id: zod_default.string().min(1).optional(),
65014
65026
  output: agentOutputSpecSchema
65015
65027
  });
65016
65028
  var breakStepSchema = stepBaseSchema.extend({
@@ -65516,10 +65528,10 @@ function reverse(arr) {
65516
65528
  }
65517
65529
  return [...arr].reverse();
65518
65530
  }
65519
- function slice(arr, start, end) {
65520
- if (arr == null) return [];
65521
- if (!Array.isArray(arr)) {
65522
- throw new Error("slice: expected array, got " + typeof arr);
65531
+ function slice(collection, start, end) {
65532
+ if (collection == null) return [];
65533
+ if (!Array.isArray(collection) && typeof collection !== "string") {
65534
+ throw new Error("slice: expected array or string, got " + typeof collection);
65523
65535
  }
65524
65536
  if (typeof start !== "number") {
65525
65537
  throw new Error("slice: start must be a number");
@@ -65527,7 +65539,7 @@ function slice(arr, start, end) {
65527
65539
  if (end != null && typeof end !== "number") {
65528
65540
  throw new Error("slice: end must be a number");
65529
65541
  }
65530
- return arr.slice(start, end);
65542
+ return collection.slice(start, end);
65531
65543
  }
65532
65544
  function first2(arr) {
65533
65545
  if (arr == null) return void 0;
@@ -65565,17 +65577,17 @@ function nth(arr, index) {
65565
65577
  }
65566
65578
  return arr[index];
65567
65579
  }
65568
- function at(arr, index) {
65569
- if (arr == null) return void 0;
65570
- if (!Array.isArray(arr)) {
65571
- throw new Error("at: expected array, got " + typeof arr);
65580
+ function at(collection, index) {
65581
+ if (collection == null) return void 0;
65582
+ if (!Array.isArray(collection) && typeof collection !== "string") {
65583
+ throw new Error("at: expected array or string, got " + typeof collection);
65572
65584
  }
65573
65585
  if (typeof index !== "number") {
65574
65586
  throw new Error("at: index must be a number");
65575
65587
  }
65576
- const resolved = index < 0 ? arr.length + index : index;
65577
- if (resolved < 0 || resolved >= arr.length) return void 0;
65578
- return arr[resolved];
65588
+ const resolved = index < 0 ? collection.length + index : index;
65589
+ if (resolved < 0 || resolved >= collection.length) return void 0;
65590
+ return collection[resolved];
65579
65591
  }
65580
65592
  function size(arr) {
65581
65593
  if (arr == null) return 0;
@@ -65747,10 +65759,28 @@ function list(...items) {
65747
65759
  }
65748
65760
  return items;
65749
65761
  }
65750
- function concat(...arrays) {
65751
- if (arrays.length === 0) {
65752
- throw new Error("concat: expected at least one array argument");
65762
+ function concat(...values3) {
65763
+ if (values3.length === 0) {
65764
+ throw new Error("concat: expected at least one array or string argument");
65765
+ }
65766
+ if (typeof values3.find((value) => value != null) === "string") {
65767
+ return concatStrings(values3);
65768
+ }
65769
+ return concatArrays(values3);
65770
+ }
65771
+ function concatStrings(values3) {
65772
+ let out = "";
65773
+ for (let i2 = 0; i2 < values3.length; i2++) {
65774
+ const value = values3[i2];
65775
+ if (value == null) continue;
65776
+ if (typeof value !== "string") {
65777
+ throw new Error(`concat: argument ${i2} expected string, got ${typeof value}`);
65778
+ }
65779
+ out += value;
65753
65780
  }
65781
+ return out;
65782
+ }
65783
+ function concatArrays(arrays) {
65754
65784
  let total = 0;
65755
65785
  for (let i2 = 0; i2 < arrays.length; i2++) {
65756
65786
  const arr = arrays[i2];
@@ -67133,6 +67163,18 @@ function isRuntimeKey(name) {
67133
67163
  return RUNTIME_KEYS.has(name);
67134
67164
  }
67135
67165
  var CURRENT_MEMBER_IN_ANY_GROUP = "current_member_in_any_group";
67166
+ var PARKED_RUNTIME_KEYS = /* @__PURE__ */ new Map([
67167
+ [
67168
+ "execution_principal",
67169
+ `runtime.execution_principal is not readable \u2014 a workflow runs under the OWNER's authority, so it never answers "is the person who triggered this allowed?". Authorize the caller with ${CURRENT_MEMBER_IN_ANY_GROUP}(["grp_\u2026"]) or read runtime.triggered_by_member_id.`
67170
+ ]
67171
+ ]);
67172
+ function parkedRuntimeKeyConstruct(key) {
67173
+ return `${key}_read`;
67174
+ }
67175
+ var READABLE_RUNTIME_KEYS = Array.from(RUNTIME_KEYS).filter(
67176
+ (key) => !PARKED_RUNTIME_KEYS.has(key)
67177
+ );
67136
67178
  var INLINE_HELPERS = [
67137
67179
  "formatCurrency",
67138
67180
  "randomNumber",
@@ -67164,11 +67206,22 @@ var HELPER_MENU = [
67164
67206
  `date \u2014 ${Object.keys(getDateExpressionFunctions()).sort().join(", ")}`,
67165
67207
  `other \u2014 ${[...INLINE_HELPERS].sort().join(", ")}`
67166
67208
  ].join("; ");
67209
+ var MAX_INLINE_EXPANSIONS = 1e3;
67210
+ function makeInlineState() {
67211
+ return {
67212
+ args: /* @__PURE__ */ new Map(),
67213
+ stack: [],
67214
+ budget: { remaining: MAX_INLINE_EXPANSIONS },
67215
+ used: /* @__PURE__ */ new Set()
67216
+ };
67217
+ }
67167
67218
  function makeScope(init = {}) {
67168
67219
  return {
67169
67220
  stepIds: init.stepIds ?? /* @__PURE__ */ new Set(),
67170
67221
  foreachBinds: init.foreachBinds ?? [],
67171
67222
  toolNames: init.toolNames ?? /* @__PURE__ */ new Set(),
67223
+ functions: init.functions ?? /* @__PURE__ */ new Map(),
67224
+ inline: init.inline ?? makeInlineState(),
67172
67225
  lambdaParams: init.lambdaParams ?? [],
67173
67226
  loopDepth: init.loopDepth ?? 0,
67174
67227
  lexicalBindings: init.lexicalBindings ?? [/* @__PURE__ */ new Set()],
@@ -67199,7 +67252,7 @@ var CALLBACK_HELPERS = /* @__PURE__ */ new Set([
67199
67252
  "intersectionBy",
67200
67253
  "reduce"
67201
67254
  ]);
67202
- var LAMBDA_POSITION_HINT = `Function expressions are not supported here. Lambdas are legal only INLINE as a callback argument to ${[...CALLBACK_HELPERS].sort().join(" / ")} \u2014 write the lambda directly in the call, e.g. pluck(rows, (r) => r.qty * r.price). Binding one to a name (const f = (x) => ...) or calling one directly is not supported: inline it at each call site.`;
67255
+ var LAMBDA_POSITION_HINT = `Function expressions are not supported here. A lambda \u2014 either \`(x) => <expr>\` or \`function (x) { return <expr>; }\` \u2014 is legal only INLINE as a callback argument to ${[...CALLBACK_HELPERS].sort().join(" / ")}, e.g. pluck(rows, (r) => r.qty * r.price). To NAME reusable logic, declare it at the top level instead: \`function toLine(l = {}) { return { ... }; }\` \u2014 every parameter needs a default, and the body is inlined at each call site. Binding a lambda to a name (const f = (x) => ...) or calling one directly stays unsupported.`;
67203
67256
  var BINARY_OPS = {
67204
67257
  // Both equality forms work as in JS: `==`/`!=` are loose (coercing), `===`/
67205
67258
  // `!==` are strict. The author picks the semantics; we don't second-guess.
@@ -67244,7 +67297,46 @@ var NAMESPACE_HELPERS = /* @__PURE__ */ new Map([
67244
67297
  ["Object", /* @__PURE__ */ new Map([["keys", "keys"], ["values", "values"], ["entries", "entries"]])],
67245
67298
  ["Number", /* @__PURE__ */ new Map([["parseFloat", "toNumber"]])]
67246
67299
  ]);
67300
+ var METHOD_FORM_NAMES = /* @__PURE__ */ new Set([
67301
+ // String.prototype
67302
+ "trim",
67303
+ "toUpperCase",
67304
+ "toLowerCase",
67305
+ "replace",
67306
+ "replaceAll",
67307
+ "split",
67308
+ "substring",
67309
+ "padStart",
67310
+ "padEnd",
67311
+ "startsWith",
67312
+ "endsWith",
67313
+ // String.prototype + Array.prototype
67314
+ "includes",
67315
+ "slice",
67316
+ "at",
67317
+ "concat",
67318
+ // Array.prototype
67319
+ "join",
67320
+ "reverse",
67321
+ "map",
67322
+ "filter",
67323
+ "find",
67324
+ "some",
67325
+ "every",
67326
+ "reduce",
67327
+ // Number.prototype / Object.prototype
67328
+ "toFixed",
67329
+ "toString"
67330
+ ]);
67331
+ var METHOD_FORM_MENU = [...METHOD_FORM_NAMES].sort().join(", ");
67247
67332
  var PROPERTY_HELPERS = /* @__PURE__ */ new Map([["length", "length"]]);
67333
+ var RESERVED_CALLABLE_NAMES = /* @__PURE__ */ new Set([
67334
+ ...HELPER_ALIASES.keys(),
67335
+ ...REJECTED_HELPERS.keys(),
67336
+ ...NAMESPACE_HELPERS.keys(),
67337
+ "Math",
67338
+ "Boolean"
67339
+ ]);
67248
67340
  var MAX_EXPRESSION_DEPTH = 500;
67249
67341
  function walkExpression(node, scope) {
67250
67342
  const depth = scope.depth + 1;
@@ -67300,7 +67392,7 @@ function walkExpressionNode(node, scope) {
67300
67392
  return walkObjectLiteral(node, scope);
67301
67393
  case "ArrowFunctionExpression":
67302
67394
  case "FunctionExpression":
67303
- fail(node, LAMBDA_POSITION_HINT);
67395
+ fail(node, LAMBDA_POSITION_HINT, "lambda_position");
67304
67396
  case "AssignmentExpression":
67305
67397
  fail(node, 'Assignment is only allowed for "const x = await tool(...)" tool-call bindings.');
67306
67398
  case "UpdateExpression":
@@ -67488,10 +67580,14 @@ function walkObjectLiteral(node, scope) {
67488
67580
  function walkHelperCall(node, scope) {
67489
67581
  const callee = node.callee;
67490
67582
  if (callee.type === "ArrowFunctionExpression" || callee.type === "FunctionExpression") {
67491
- fail(callee, LAMBDA_POSITION_HINT);
67583
+ fail(callee, LAMBDA_POSITION_HINT, "lambda_position");
67492
67584
  }
67493
67585
  if (node.type === "OptionalCallExpression" && node.optional === true) {
67494
- fail(node, "Optional calls (`x?.()`) are not supported. Use `?.` on member access only.");
67586
+ fail(
67587
+ node,
67588
+ "Optional calls (`x?.()`) are not supported. Use `?.` on member access only.",
67589
+ "optional_call"
67590
+ );
67495
67591
  }
67496
67592
  if ((callee.type === "MemberExpression" || callee.type === "OptionalMemberExpression") && callee.object.type === "Identifier" && callee.object.name === "Math" && !callee.computed && callee.property.type === "Identifier") {
67497
67593
  const mathFn = callee.property.name;
@@ -67600,6 +67696,17 @@ function isOptionalChainGuard(e) {
67600
67696
  return e.kind === "ternary" && e.then.kind === "lit" && e.then.value === null && e.cond.kind === "call" && e.cond.fn === "isNull";
67601
67697
  }
67602
67698
  function buildHelperCall(node, rawFn, receiver, scope) {
67699
+ const declared = scope.functions.get(rawFn);
67700
+ if (declared) {
67701
+ if (receiver !== null) {
67702
+ fail(
67703
+ node,
67704
+ `"${rawFn}" is a function declared in this workflow \u2014 call it as ${rawFn}(x), not as a method.`,
67705
+ "function_method_form"
67706
+ );
67707
+ }
67708
+ return inlineFunctionCall(node, declared, scope);
67709
+ }
67603
67710
  const rejected = REJECTED_HELPERS.get(rawFn);
67604
67711
  if (rejected) fail(node, rejected.message, rejected.construct);
67605
67712
  const fn = HELPER_ALIASES.get(rawFn) ?? rawFn;
@@ -67610,6 +67717,8 @@ function buildHelperCall(node, rawFn, receiver, scope) {
67610
67717
  fail(node, `${fn} is a reserved identifier and cannot be called.`);
67611
67718
  }
67612
67719
  if (isRuntimeKey(fn) && !isHelper(fn)) {
67720
+ const parked = PARKED_RUNTIME_KEYS.get(fn);
67721
+ if (parked) fail(node, parked, parkedRuntimeKeyConstruct(fn));
67613
67722
  fail(node, `${fn} is a runtime key, not a helper. Read it as runtime.${fn}.`);
67614
67723
  }
67615
67724
  if (!isHelper(fn)) {
@@ -67619,6 +67728,13 @@ function buildHelperCall(node, rawFn, receiver, scope) {
67619
67728
  "unknown_helper"
67620
67729
  );
67621
67730
  }
67731
+ if (receiver !== null && !METHOD_FORM_NAMES.has(rawFn)) {
67732
+ fail(
67733
+ node,
67734
+ `"${rawFn}" has no method form \u2014 call it as ${fn}(x), with the receiver as the first argument. The method form works only for helper names that are also real JS methods: ${METHOD_FORM_MENU}.`,
67735
+ "helper_method_form"
67736
+ );
67737
+ }
67622
67738
  if (fn === "filter") {
67623
67739
  const predNode = node.arguments.length > 0 ? node.arguments[node.arguments.length - 1] : void 0;
67624
67740
  if (predNode && predNode.type === "Identifier" && predNode.name === "Boolean") {
@@ -67645,11 +67761,11 @@ function walkHelperArg(arg, fn, scope) {
67645
67761
  "spread_element"
67646
67762
  );
67647
67763
  }
67648
- if (arg.type === "ArrowFunctionExpression") {
67764
+ if (arg.type === "ArrowFunctionExpression" || arg.type === "FunctionExpression") {
67649
67765
  if (!CALLBACK_HELPERS.has(fn)) {
67650
67766
  fail(
67651
67767
  arg,
67652
- `Arrow functions are not supported as arguments to "${fn}". Callbacks work on ${[...CALLBACK_HELPERS].sort().join(" / ")}.`,
67768
+ `Callback functions are not supported as arguments to "${fn}". Callbacks work on ${[...CALLBACK_HELPERS].sort().join(" / ")}.`,
67653
67769
  "lambda_not_allowed"
67654
67770
  );
67655
67771
  }
@@ -67659,7 +67775,15 @@ function walkHelperArg(arg, fn, scope) {
67659
67775
  }
67660
67776
  function walkLambda(node, scope) {
67661
67777
  if (node.async) {
67662
- fail(node, "Async arrow functions are not supported in callbacks.");
67778
+ fail(node, "Async callbacks are not supported \u2014 a callback body must be a pure, synchronous expression.");
67779
+ }
67780
+ if (node.type === "FunctionExpression") {
67781
+ if (node.generator) {
67782
+ fail(node, "Generator functions (`function*`) are not supported.", "generator_function");
67783
+ }
67784
+ if (node.id) {
67785
+ fail(node.id, `Named function expressions are not supported as callbacks \u2014 drop the name "${node.id.name}".`);
67786
+ }
67663
67787
  }
67664
67788
  const params = [];
67665
67789
  for (const p of node.params) {
@@ -67672,6 +67796,12 @@ function walkLambda(node, scope) {
67672
67796
  if (scope.toolNames.has(p.name) || isHelper(p.name)) {
67673
67797
  fail(p, `Lambda parameter "${p.name}" collides with a tool or helper.`);
67674
67798
  }
67799
+ if (scope.functions.has(p.name)) {
67800
+ fail(p, `Lambda parameter "${p.name}" collides with a function declared in this workflow.`);
67801
+ }
67802
+ if (scope.inline.args.has(p.name)) {
67803
+ fail(p, `Lambda parameter "${p.name}" collides with an enclosing function's parameter.`);
67804
+ }
67675
67805
  if (scope.stepIds.has(p.name)) {
67676
67806
  fail(p, `Lambda parameter "${p.name}" collides with a step id.`);
67677
67807
  }
@@ -67683,20 +67813,177 @@ function walkLambda(node, scope) {
67683
67813
  }
67684
67814
  params.push(p.name);
67685
67815
  }
67686
- if (node.body.type === "BlockStatement") {
67687
- fail(
67688
- node.body,
67689
- "Lambda body must be a single expression \u2014 block bodies (`(x) => { ... }`) are not supported. Fold the branches into one expression: use a ternary (`x => x.qty > 0 ? x.qty : 0`) or nested helper calls.",
67690
- "block_body_lambda"
67691
- );
67692
- }
67816
+ const bodyExpr = lambdaBodyExpression(node);
67693
67817
  const innerScope = {
67694
67818
  ...scope,
67695
67819
  lambdaParams: [...scope.lambdaParams, ...params]
67696
67820
  };
67697
- const body = walkExpression(node.body, innerScope);
67821
+ const body = walkExpression(bodyExpr, innerScope);
67698
67822
  return { kind: "lambda", params, body };
67699
67823
  }
67824
+ function lambdaBodyExpression(node) {
67825
+ if (node.body.type !== "BlockStatement") {
67826
+ return node.body;
67827
+ }
67828
+ const statements = node.body.body;
67829
+ const only = statements.length === 1 ? statements[0] : null;
67830
+ if (only && only.type === "ReturnStatement" && only.argument) {
67831
+ return only.argument;
67832
+ }
67833
+ fail(
67834
+ node.body,
67835
+ "Lambda body must reduce to a single expression \u2014 either `(x) => <expr>` or a block whose only statement is `return <expr>`. Fold the branches into one expression: use a ternary (`x => x.qty > 0 ? x.qty : 0`) or nested helper calls.",
67836
+ "block_body_lambda"
67837
+ );
67838
+ }
67839
+ function failFunctionDeclaration(node, why) {
67840
+ fail(
67841
+ node,
67842
+ `${why} A workflow function must be a single-expression helper declared at the top level: \`function name(p = <default>) { return <expr>; }\` \u2014 it is inlined at every call site.
67843
+ For logic that doesn't fit that shape, choose one:
67844
+ - bind step: \`const tag = record.Score > 80 ? "hot" : "cold";\` \u2014 compute once, reference by name in later steps.
67845
+ - inline expression: write the logic directly at each call site (often shorter than extracting).
67846
+ - separate workflow: factor the steps out and invoke from this workflow via a tool call.`,
67847
+ "function_declaration"
67848
+ );
67849
+ }
67850
+ function failUnusedFunction(node, name) {
67851
+ fail(
67852
+ node,
67853
+ `Function "${name}" is declared but never called. A declaration is inlined at its call sites and erased from the saved workflow, so an uncalled one would disappear with its body never checked \u2014 call it, or delete it.`,
67854
+ "function_unused"
67855
+ );
67856
+ }
67857
+ function collectFunctionDeclarations(body, toolNames) {
67858
+ const functions = /* @__PURE__ */ new Map();
67859
+ for (const stmt of body) {
67860
+ if (stmt.type !== "FunctionDeclaration") continue;
67861
+ if (stmt.async) {
67862
+ failFunctionDeclaration(stmt, "Async functions are not supported.");
67863
+ }
67864
+ if (stmt.generator) {
67865
+ fail(stmt, "Generator functions (`function*`) are not supported.", "generator_function");
67866
+ }
67867
+ if (!stmt.id) {
67868
+ failFunctionDeclaration(stmt, "A function declaration must be named.");
67869
+ }
67870
+ const name = stmt.id.name;
67871
+ assertFunctionName(stmt.id, name, `Function "${name}"`, toolNames);
67872
+ if (functions.has(name)) {
67873
+ fail(stmt.id, `Function "${name}" is already declared in this workflow.`);
67874
+ }
67875
+ const params = [];
67876
+ const seen = /* @__PURE__ */ new Set();
67877
+ for (const p of stmt.params) {
67878
+ if (p.type === "Identifier") {
67879
+ fail(
67880
+ p,
67881
+ `Parameter "${p.name}" of function "${name}" needs a default value \u2014 write \`${p.name} = <default>\`. The subset has no type annotations, so the default is what gives the parameter a type; without it the save-time type check reports an implicit \`any\`.`,
67882
+ "function_param_no_default"
67883
+ );
67884
+ }
67885
+ if (p.type !== "AssignmentPattern" || p.left.type !== "Identifier") {
67886
+ fail(
67887
+ p,
67888
+ `Parameters of function "${name}" must be plain names with a default (\`p = <default>\`) \u2014 destructuring and rest are not supported.`,
67889
+ "function_param_shape"
67890
+ );
67891
+ }
67892
+ const paramName = p.left.name;
67893
+ assertFunctionName(p.left, paramName, `Parameter "${paramName}"`, toolNames);
67894
+ if (seen.has(paramName)) {
67895
+ fail(p.left, `Parameter "${paramName}" of function "${name}" is declared twice.`);
67896
+ }
67897
+ seen.add(paramName);
67898
+ params.push({ name: paramName, defaultNode: p.right, node: p.left });
67899
+ }
67900
+ const statements = stmt.body.body;
67901
+ const only = statements.length === 1 ? statements[0] : null;
67902
+ if (!only || only.type !== "ReturnStatement" || !only.argument) {
67903
+ failFunctionDeclaration(
67904
+ stmt.body,
67905
+ `The body of "${name}" must be exactly one \`return <expression>;\`.`
67906
+ );
67907
+ }
67908
+ functions.set(name, { name, params, body: only.argument, node: stmt });
67909
+ }
67910
+ for (const fn of functions.values()) {
67911
+ for (const p of fn.params) {
67912
+ if (functions.has(p.name)) {
67913
+ fail(p.node, `Parameter "${p.name}" of function "${fn.name}" collides with a function declared in this workflow.`);
67914
+ }
67915
+ }
67916
+ }
67917
+ return functions;
67918
+ }
67919
+ function assertFunctionName(node, name, label, toolNames) {
67920
+ if (RESERVED_ROOTS.has(name)) {
67921
+ fail(node, `${label} uses the reserved identifier "${name}".`);
67922
+ }
67923
+ if (toolNames.has(name)) {
67924
+ fail(node, `${label} collides with the tool "${name}".`);
67925
+ }
67926
+ if (isHelper(name)) {
67927
+ fail(node, `${label} collides with the built-in helper "${name}".`);
67928
+ }
67929
+ if (RESERVED_CALLABLE_NAMES.has(name)) {
67930
+ fail(node, `${label} collides with "${name}", which the subset already gives a meaning.`);
67931
+ }
67932
+ }
67933
+ function inlineFunctionCall(node, decl, scope) {
67934
+ if (scope.inline.stack.includes(decl.name)) {
67935
+ fail(
67936
+ node,
67937
+ `Function "${decl.name}" is recursive (${[...scope.inline.stack, decl.name].join(" \u2192 ")}). Inlined functions cannot recurse \u2014 express the repetition with a loop step or a helper such as reduce.`,
67938
+ "function_recursion"
67939
+ );
67940
+ }
67941
+ if (scope.inline.budget.remaining <= 0) {
67942
+ fail(
67943
+ node,
67944
+ `This workflow expands to too many inlined function calls (max ${MAX_INLINE_EXPANSIONS}). Reduce nesting between functions, or inline the logic by hand.`,
67945
+ "function_expansion_limit"
67946
+ );
67947
+ }
67948
+ scope.inline.budget.remaining -= 1;
67949
+ scope.inline.used.add(decl.name);
67950
+ if (node.arguments.length > decl.params.length) {
67951
+ fail(
67952
+ node,
67953
+ `Function "${decl.name}" takes ${decl.params.length} argument(s), but ${node.arguments.length} were passed.`,
67954
+ "function_arity"
67955
+ );
67956
+ }
67957
+ const emptyScope = functionBodyScope(scope, decl.name, /* @__PURE__ */ new Map());
67958
+ const args = /* @__PURE__ */ new Map();
67959
+ for (let i2 = 0; i2 < decl.params.length; i2++) {
67960
+ const p = decl.params[i2];
67961
+ const argNode = node.arguments[i2];
67962
+ args.set(
67963
+ p.name,
67964
+ argNode === void 0 ? walkExpression(p.defaultNode, emptyScope) : walkHelperArg(argNode, decl.name, scope)
67965
+ );
67966
+ }
67967
+ return walkExpression(decl.body, functionBodyScope(scope, decl.name, args));
67968
+ }
67969
+ function functionBodyScope(scope, name, args) {
67970
+ return {
67971
+ stepIds: /* @__PURE__ */ new Set(),
67972
+ foreachBinds: [],
67973
+ toolNames: scope.toolNames,
67974
+ functions: scope.functions,
67975
+ inline: {
67976
+ args,
67977
+ stack: [...scope.inline.stack, name],
67978
+ budget: scope.inline.budget,
67979
+ used: scope.inline.used
67980
+ },
67981
+ lambdaParams: [],
67982
+ loopDepth: 0,
67983
+ lexicalBindings: [/* @__PURE__ */ new Set()],
67984
+ depth: scope.depth
67985
+ };
67986
+ }
67700
67987
  function flattenChain(node, scope) {
67701
67988
  let cur = node;
67702
67989
  const reverse2 = [];
@@ -67786,7 +68073,30 @@ function walkRead(node, scope) {
67786
68073
  if (chain2.segments.length === 0 && scope.toolNames.has(chain2.rootName)) {
67787
68074
  fail(node, `Tools may only be invoked, not referenced. Write "const x = await ${chain2.rootName}({...})".`);
67788
68075
  }
68076
+ if (scope.functions.has(chain2.rootName)) {
68077
+ fail(
68078
+ node,
68079
+ `Function "${chain2.rootName}" may only be called, not referenced. Write ${chain2.rootName}(...).`,
68080
+ "function_as_value"
68081
+ );
68082
+ }
67789
68083
  const root = chain2.rootName;
68084
+ const inlined = scope.inline.args.get(root);
68085
+ if (inlined !== void 0) {
68086
+ if (chain2.segments.length === 0) return inlined;
68087
+ if (inlined.kind !== "read") {
68088
+ fail(
68089
+ node,
68090
+ `Cannot read a property off parameter "${root}" \u2014 the argument passed at the call site is a computed value, not a record or binding. Bind it first (\`const x = <expr>;\`) and pass the binding.`,
68091
+ "function_arg_not_read"
68092
+ );
68093
+ }
68094
+ const base = inlined;
68095
+ return wrapOptionalGuards(chain2, (segments) => ({
68096
+ ...base,
68097
+ path: [...base.path, ...classifyRecordPath(segments)]
68098
+ }));
68099
+ }
67790
68100
  if (root === "record" || root === "prev_record") {
67791
68101
  return readFromRecord(
67792
68102
  root === "record" ? "trigger_record" : "trigger_record_prev",
@@ -67803,6 +68113,13 @@ function walkRead(node, scope) {
67803
68113
  return readFromRuntime(chain2);
67804
68114
  }
67805
68115
  if (root === "index") {
68116
+ if (scope.inline.stack.length > 0) {
68117
+ fail(
68118
+ node,
68119
+ "index is not readable inside a function \u2014 it belongs to the enclosing loop, so an inlined body would mean something different at each call site. Pass it as a parameter.",
68120
+ "function_reads_index"
68121
+ );
68122
+ }
67806
68123
  if (chain2.segments.length > 0) {
67807
68124
  fail(node, "index has no properties.");
67808
68125
  }
@@ -67831,6 +68148,7 @@ function walkRead(node, scope) {
67831
68148
  "runtime",
67832
68149
  "trigger",
67833
68150
  "index",
68151
+ ...scope.inline.args.keys(),
67834
68152
  ...lexicalNames,
67835
68153
  ...Array.from(scope.lambdaParams),
67836
68154
  ...Array.from(scope.foreachBinds),
@@ -67880,7 +68198,20 @@ function readFromChanges(chain2) {
67880
68198
  return { kind: "read", source: { from: "trigger_changes" }, path: path8 };
67881
68199
  });
67882
68200
  }
68201
+ var TRIGGER_RECORD_VIEWS = /* @__PURE__ */ new Map([
68202
+ ["data", "trigger_record"],
68203
+ ["prev_data", "trigger_record_prev"],
68204
+ ["changes", "trigger_changes"]
68205
+ ]);
67883
68206
  function readFromTrigger(chain2) {
68207
+ const head = chain2.segments[0];
68208
+ const view = head && (head.kind === "dot" || head.kind === "bracket-string") ? TRIGGER_RECORD_VIEWS.get(head.key) : void 0;
68209
+ if (head && view !== void 0) {
68210
+ const rest = chain2.segments.slice(1);
68211
+ const segments = head.optional === true && rest.length > 0 ? [{ ...rest[0], optional: true }, ...rest.slice(1)] : rest;
68212
+ const lowered = { ...chain2, segments };
68213
+ return view === "trigger_changes" ? readFromChanges(lowered) : readFromRecord(view, lowered);
68214
+ }
67884
68215
  return wrapOptionalGuards(chain2, (segments) => {
67885
68216
  const path8 = [];
67886
68217
  for (const seg of segments) {
@@ -67907,10 +68238,13 @@ function readFromRuntime(chain2) {
67907
68238
  if (first3.kind !== "dot" && first3.kind !== "bracket-string") {
67908
68239
  fail(first3.node, "runtime is keyed by name (runtime.timezone, runtime.now, ...).");
67909
68240
  }
67910
- if (!isRuntimeKey(first3.key)) {
67911
- fail(first3.node, `Unknown runtime key "${first3.key}". Allowed: ${Array.from(RUNTIME_KEYS).join(", ")}.`);
68241
+ const key = first3.key ?? "";
68242
+ if (!isRuntimeKey(key)) {
68243
+ fail(first3.node, `Unknown runtime key "${key}". Allowed: ${READABLE_RUNTIME_KEYS.join(", ")}.`);
67912
68244
  }
67913
- const source = { from: "runtime", key: first3.key };
68245
+ const parked = PARKED_RUNTIME_KEYS.get(key);
68246
+ if (parked) fail(first3.node, parked, parkedRuntimeKeyConstruct(key));
68247
+ const source = { from: "runtime", key };
67914
68248
  const path8 = [];
67915
68249
  for (const seg of segments.slice(1)) {
67916
68250
  if (seg.kind === "bracket-number") {
@@ -68101,14 +68435,25 @@ function parseWorkflowJs(source, opts) {
68101
68435
  }
68102
68436
  }
68103
68437
  function walkProgram(body, scope, stepIds, opts) {
68438
+ const functions = collectFunctionDeclarations(body, opts.toolNames ?? /* @__PURE__ */ new Set());
68439
+ const progScope = { ...scope, functions };
68104
68440
  let synthetic = 0;
68105
68441
  const steps = [];
68106
68442
  for (const stmt of body) {
68107
- steps.push(...walkStatement(stmt, scope, stepIds, opts, () => `_s${synthetic++}`, 1));
68443
+ if (stmt.type === "FunctionDeclaration") continue;
68444
+ steps.push(...walkStatement(stmt, progScope, stepIds, opts, () => `_s${synthetic++}`, 1));
68445
+ }
68446
+ for (const fn of functions.values()) {
68447
+ if (!progScope.inline.used.has(fn.name)) failUnusedFunction(fn.node, fn.name);
68108
68448
  }
68109
68449
  return steps;
68110
68450
  }
68111
68451
  var MAX_STATEMENT_DEPTH = 500;
68452
+ function assertNoFunctionCollision(node, name, scope, label) {
68453
+ if (scope.functions.has(name)) {
68454
+ fail(node, `${label} "${name}" collides with a function declared in this workflow.`);
68455
+ }
68456
+ }
68112
68457
  function walkStatement(stmt, scope, stepIds, opts, nextSyntheticId, depth) {
68113
68458
  if (depth > MAX_STATEMENT_DEPTH) {
68114
68459
  fail(stmt, `Workflow nesting is too deep (max ${MAX_STATEMENT_DEPTH} levels).`, "depth_limit");
@@ -68117,6 +68462,9 @@ function walkStatement(stmt, scope, stepIds, opts, nextSyntheticId, depth) {
68117
68462
  }
68118
68463
  function walkStatementInner(stmt, scope, stepIds, opts, nextSyntheticId, depth) {
68119
68464
  const { id: explicitId, description } = readLeadingComments(stmt);
68465
+ if (explicitId !== void 0) {
68466
+ assertNoFunctionCollision(stmt, explicitId, scope, "Step id");
68467
+ }
68120
68468
  switch (stmt.type) {
68121
68469
  case "VariableDeclaration":
68122
68470
  return walkConstBinding(stmt, scope, stepIds, opts, explicitId, description, nextSyntheticId);
@@ -68177,10 +68525,9 @@ function walkStatementInner(stmt, scope, stepIds, opts, nextSyntheticId, depth)
68177
68525
  case "ReturnStatement":
68178
68526
  return [walkReturnStatement(stmt, scope, explicitId ?? nextSyntheticId(), description)];
68179
68527
  case "FunctionDeclaration":
68180
- fail(
68528
+ failFunctionDeclaration(
68181
68529
  stmt,
68182
- 'Function declarations are not supported. For reusable logic, choose one:\n - bind step: `const tag = record.Score > 80 ? "hot" : "cold";` \u2014 compute once, reference by name in later steps.\n - inline expression: write the logic directly at each call site (often shorter than extracting).\n - separate workflow: factor the steps out and invoke from this workflow via a tool call.\nIf you were translating a JS helper habit, the bind step usually fits \u2014 workflows already let you name an expression for reuse.',
68183
- "function_declaration"
68530
+ "A function declared inside a block is not supported."
68184
68531
  );
68185
68532
  case "ClassDeclaration":
68186
68533
  fail(stmt, "Class declarations are not allowed.");
@@ -68243,6 +68590,7 @@ function walkConstDeclarator(decl, scope, stepIds, opts, explicitId, description
68243
68590
  if (opts.toolNames && opts.toolNames.has(id) || isHelper(id)) {
68244
68591
  fail(decl.id, `Cannot bind to "${id}" \u2014 name collides with a tool or helper.`);
68245
68592
  }
68593
+ assertNoFunctionCollision(decl.id, id, scope, "Binding");
68246
68594
  if (stepIds.has(id)) {
68247
68595
  fail(decl.id, `Step id "${id}" is already used.`);
68248
68596
  }
@@ -68419,6 +68767,7 @@ function walkPatternBinding(decl, scope, stepIds, opts, explicitId, description,
68419
68767
  if (opts.toolNames && opts.toolNames.has(entry.name) || isHelper(entry.name)) {
68420
68768
  fail(entry.node, `Cannot bind to "${entry.name}" \u2014 name collides with a tool or helper.`);
68421
68769
  }
68770
+ assertNoFunctionCollision(entry.node, entry.name, scope, "Binding");
68422
68771
  if (stepIds.has(entry.name)) {
68423
68772
  fail(entry.node, `Step id "${entry.name}" is already used.`);
68424
68773
  }
@@ -68462,6 +68811,7 @@ function walkLetDeclaration(stmt, scope, stepIds, opts, explicitId, description)
68462
68811
  if (opts.toolNames && opts.toolNames.has(name2) || isHelper(name2)) {
68463
68812
  fail(entry.node, `let binding "${name2}" collides with a tool or helper.`);
68464
68813
  }
68814
+ assertNoFunctionCollision(entry.node, name2, scope, "let binding");
68465
68815
  if (stepIds.has(name2)) {
68466
68816
  fail(entry.node, `let binding "${name2}" collides with a step id.`);
68467
68817
  }
@@ -68492,6 +68842,7 @@ function walkLetDeclaration(stmt, scope, stepIds, opts, explicitId, description)
68492
68842
  if (opts.toolNames && opts.toolNames.has(name) || isHelper(name)) {
68493
68843
  fail(decl.id, `let binding "${name}" collides with a tool or helper.`);
68494
68844
  }
68845
+ assertNoFunctionCollision(decl.id, name, scope, "let binding");
68495
68846
  if (stepIds.has(name)) {
68496
68847
  fail(decl.id, `let binding "${name}" collides with a step id.`);
68497
68848
  }
@@ -68822,15 +69173,13 @@ function walkForOf(stmt, scope, stepIds, opts, stepId, description, nextSyntheti
68822
69173
  if (opts.toolNames && opts.toolNames.has(bindName) || isHelper(bindName)) {
68823
69174
  fail(decl.id, `Foreach bind name "${bindName}" collides with a tool or helper.`);
68824
69175
  }
69176
+ assertNoFunctionCollision(decl.id, bindName, scope, "Foreach bind name");
68825
69177
  const items = walkForOfRight(stmt.right, scope);
68826
69178
  const innerScope = {
68827
- stepIds: scope.stepIds,
69179
+ ...scope,
68828
69180
  foreachBinds: [...scope.foreachBinds, bindName],
68829
- toolNames: scope.toolNames,
68830
- lambdaParams: scope.lambdaParams,
68831
69181
  loopDepth: scope.loopDepth + 1,
68832
- lexicalBindings: [...scope.lexicalBindings, /* @__PURE__ */ new Set()],
68833
- depth: scope.depth
69182
+ lexicalBindings: [...scope.lexicalBindings, /* @__PURE__ */ new Set()]
68834
69183
  };
68835
69184
  if (stmt.body.type !== "BlockStatement") {
68836
69185
  fail(stmt.body, "for-of body must be a { ... } block.");
@@ -68844,6 +69193,7 @@ function walkForOf(stmt, scope, stepIds, opts, stepId, description, nextSyntheti
68844
69193
  if (opts.toolNames && opts.toolNames.has(entry.name) || isHelper(entry.name)) {
68845
69194
  fail(entry.node, `Cannot bind to "${entry.name}" \u2014 name collides with a tool or helper.`);
68846
69195
  }
69196
+ assertNoFunctionCollision(entry.node, entry.name, scope, "Binding");
68847
69197
  if (stepIds.has(entry.name)) {
68848
69198
  fail(entry.node, `Step id "${entry.name}" is already used.`);
68849
69199
  }
@@ -69026,6 +69376,7 @@ function walkTry(stmt, scope, stepIds, opts, stepId, description, nextSyntheticI
69026
69376
  if (opts.toolNames && opts.toolNames.has(errorBinding) || isHelper(errorBinding)) {
69027
69377
  fail(handler.param, `Catch binding "${errorBinding}" collides with a tool or helper.`);
69028
69378
  }
69379
+ assertNoFunctionCollision(handler.param, errorBinding, scope, "Catch binding");
69029
69380
  if (stepIds.has(errorBinding)) {
69030
69381
  fail(handler.param, `Catch binding "${errorBinding}" collides with an existing step id.`);
69031
69382
  }
@@ -69285,8 +69636,7 @@ function walkAgentInvocation(call, scope, stepId, description) {
69285
69636
  if (!toolsNode) fail(arg, "agent requires `tools` (use [] for none).");
69286
69637
  const tool_names = readStaticStringArray(toolsNode, "agent `tools`");
69287
69638
  const modelNode = kw.get("model");
69288
- if (!modelNode) fail(arg, "agent requires `model`.");
69289
- const model_id = readStaticString(modelNode, "agent `model`");
69639
+ const model_id = modelNode ? readStaticString(modelNode, "agent `model`") : void 0;
69290
69640
  const outputNode = kw.get("output");
69291
69641
  if (!outputNode) fail(arg, "agent requires `output`.");
69292
69642
  const outputResult = agentOutputSpecSchema.safeParse(walkStaticJsonLiteral(outputNode));
@@ -69301,7 +69651,7 @@ function walkAgentInvocation(call, scope, stepId, description) {
69301
69651
  instructions,
69302
69652
  input,
69303
69653
  tool_names,
69304
- model_id,
69654
+ ...model_id !== void 0 ? { model_id } : {},
69305
69655
  output: outputResult.data
69306
69656
  };
69307
69657
  }
@@ -69402,6 +69752,133 @@ function readLeadingComments(stmt) {
69402
69752
  return { id, description };
69403
69753
  }
69404
69754
 
69755
+ // ../shared/src/rewrite_accumulator_appends.ts
69756
+ function rewriteAccumulatorAppends(tsApi, source) {
69757
+ const file2 = tsApi.createSourceFile(
69758
+ "/checked_source.ts",
69759
+ source,
69760
+ tsApi.ScriptTarget.ES2022,
69761
+ true
69762
+ );
69763
+ const seeded = emptyArrayLetNames(tsApi, file2);
69764
+ if (seeded.size === 0) return source;
69765
+ const edits = [];
69766
+ const visit = (node) => {
69767
+ if (tsApi.isExpressionStatement(node)) collectAppendEdits(tsApi, node, file2, seeded, edits);
69768
+ tsApi.forEachChild(node, visit);
69769
+ };
69770
+ tsApi.forEachChild(file2, visit);
69771
+ if (edits.length === 0) return source;
69772
+ return applyEdits(source, edits);
69773
+ }
69774
+ function emptyArrayLetNames(tsApi, file2) {
69775
+ const names = /* @__PURE__ */ new Set();
69776
+ const visit = (node) => {
69777
+ if (tsApi.isVariableDeclarationList(node) && (node.flags & tsApi.NodeFlags.Let) !== 0) {
69778
+ for (const decl of node.declarations) {
69779
+ if (!tsApi.isIdentifier(decl.name)) continue;
69780
+ if (decl.type) continue;
69781
+ const init = decl.initializer;
69782
+ if (init && tsApi.isArrayLiteralExpression(init) && init.elements.length === 0) {
69783
+ names.add(decl.name.text);
69784
+ }
69785
+ }
69786
+ }
69787
+ tsApi.forEachChild(node, visit);
69788
+ };
69789
+ tsApi.forEachChild(file2, visit);
69790
+ return names;
69791
+ }
69792
+ function collectAppendEdits(tsApi, stmt, file2, seeded, edits) {
69793
+ const expr = stmt.expression;
69794
+ if (!tsApi.isBinaryExpression(expr)) return;
69795
+ if (expr.operatorToken.kind !== tsApi.SyntaxKind.EqualsToken) return;
69796
+ const target = expr.left;
69797
+ if (!tsApi.isIdentifier(target) || !seeded.has(target.text)) return;
69798
+ const name = target.text;
69799
+ const start = expr.getStart(file2);
69800
+ const rhs = expr.right;
69801
+ if (tsApi.isArrayLiteralExpression(rhs)) {
69802
+ const [head, ...rest] = rhs.elements;
69803
+ if (!head || !tsApi.isSpreadElement(head)) return;
69804
+ if (!tsApi.isIdentifier(head.expression) || head.expression.text !== name) return;
69805
+ if (rest.length === 0 || rest.some((e) => tsApi.isSpreadElement(e))) return;
69806
+ pushEdits(edits, file2, {
69807
+ start,
69808
+ firstItem: rest[0],
69809
+ lastItem: rest[rest.length - 1],
69810
+ closeEnd: rhs.getEnd(),
69811
+ tokens: [name, ".push("]
69812
+ });
69813
+ return;
69814
+ }
69815
+ if (!tsApi.isCallExpression(rhs)) return;
69816
+ if (!tsApi.isIdentifier(rhs.expression) || rhs.expression.text !== "concat") return;
69817
+ if (rhs.arguments.length !== 2) return;
69818
+ const [selfArg, appended] = rhs.arguments;
69819
+ if (!tsApi.isIdentifier(selfArg) || selfArg.text !== name) return;
69820
+ if (tsApi.isSpreadElement(appended)) return;
69821
+ if (!tsApi.isArrayLiteralExpression(appended)) return;
69822
+ const items = appended.elements;
69823
+ if (items.length === 0 || items.some((e) => tsApi.isSpreadElement(e))) return;
69824
+ pushEdits(edits, file2, {
69825
+ start,
69826
+ firstItem: items[0],
69827
+ lastItem: items[items.length - 1],
69828
+ closeEnd: rhs.getEnd(),
69829
+ tokens: [name, ".push("]
69830
+ });
69831
+ }
69832
+ function pushEdits(edits, file2, plan) {
69833
+ const headStart = plan.start;
69834
+ const headEnd = plan.firstItem.getStart(file2);
69835
+ const tailStart = plan.lastItem.getEnd();
69836
+ const headText = fillRegion(plan.tokens, file2.text.slice(headStart, headEnd));
69837
+ const tailText = fillRegion([")"], file2.text.slice(tailStart, plan.closeEnd));
69838
+ if (headText === null || tailText === null) return;
69839
+ edits.push(
69840
+ { start: headStart, end: headEnd, text: headText },
69841
+ { start: tailStart, end: plan.closeEnd, text: tailText }
69842
+ );
69843
+ }
69844
+ function applyEdits(source, edits) {
69845
+ const ordered = [...edits].sort((a, b) => a.start - b.start);
69846
+ const parts = [];
69847
+ let cursor = 0;
69848
+ for (const edit of ordered) {
69849
+ parts.push(source.slice(cursor, edit.start), edit.text);
69850
+ cursor = edit.end;
69851
+ }
69852
+ parts.push(source.slice(cursor));
69853
+ return parts.join("");
69854
+ }
69855
+ function fillRegion(tokens, original) {
69856
+ const chars = original.split("").map((c) => c === "\n" || c === "\r" ? c : " ");
69857
+ let cursor = 0;
69858
+ for (const token of tokens) {
69859
+ const at2 = findPlaceableWindow(chars, cursor, token.length);
69860
+ if (at2 === null) return null;
69861
+ for (let i2 = 0; i2 < token.length; i2++) chars[at2 + i2] = token[i2];
69862
+ cursor = at2 + token.length;
69863
+ }
69864
+ return chars.join("");
69865
+ }
69866
+ function findPlaceableWindow(chars, from, width) {
69867
+ for (let i2 = from; i2 + width <= chars.length; i2++) {
69868
+ let ok = true;
69869
+ for (let j = 0; j < width; j++) {
69870
+ const c = chars[i2 + j];
69871
+ if (c === "\n" || c === "\r") {
69872
+ ok = false;
69873
+ i2 += j;
69874
+ break;
69875
+ }
69876
+ }
69877
+ if (ok) return i2;
69878
+ }
69879
+ return null;
69880
+ }
69881
+
69405
69882
  // src/workflow_envelope.ts
69406
69883
  var FALLBACK_ENVELOPE_PREFIX = "async function __workflow(): Promise<__WorkflowReturn | void> {\n";
69407
69884
  var FALLBACK_ENVELOPE_SUFFIX = "\n}";
@@ -69489,9 +69966,16 @@ function checkOneWorkflowBody(tsApi, input) {
69489
69966
  const offset = bodyLineOffset(wrapped);
69490
69967
  const subset = subsetIssues(stripWorkflowHeader(wrapped), offset);
69491
69968
  if (subset) return subset;
69969
+ const options = workflowCheckCompilerOptions(tsApi);
69492
69970
  const program = tsApi.createProgram({
69493
69971
  rootNames: [input.globalsPath, input.bodyPath],
69494
- options: workflowCheckCompilerOptions(tsApi)
69972
+ options,
69973
+ host: createCheckedSourceHost(
69974
+ tsApi,
69975
+ options,
69976
+ input.bodyPath,
69977
+ rewriteAccumulatorAppends(tsApi, wrapped)
69978
+ )
69495
69979
  });
69496
69980
  const bodyFile = program.getSourceFile(input.bodyPath);
69497
69981
  if (!bodyFile) {
@@ -69523,6 +70007,17 @@ function checkOneWorkflowBody(tsApi, input) {
69523
70007
  }
69524
70008
  return issues;
69525
70009
  }
70010
+ function createCheckedSourceHost(tsApi, options, bodyPath, bodyText) {
70011
+ const base = tsApi.createCompilerHost(options, false);
70012
+ const key = (fileName) => base.getCanonicalFileName(path4.resolve(fileName).replace(/\\/g, "/"));
70013
+ const bodyKey = key(bodyPath);
70014
+ return {
70015
+ ...base,
70016
+ getSourceFile: (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => key(fileName) === bodyKey ? tsApi.createSourceFile(fileName, bodyText, languageVersionOrOptions, true) : base.getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile),
70017
+ readFile: (fileName) => key(fileName) === bodyKey ? bodyText : base.readFile(fileName),
70018
+ fileExists: (fileName) => key(fileName) === bodyKey ? true : base.fileExists(fileName)
70019
+ };
70020
+ }
69526
70021
  function checkWorkflowBodies(tsApi, aliases) {
69527
70022
  return aliases.map(({ alias, input }) => ({
69528
70023
  alias,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.95.0",
3
+ "version": "0.96.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {