@keystrokehq/cli 0.1.27 → 0.1.29

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __require, t as __commonJSMin } from "./chunk-DiodbrVj.mjs";
3
- import { Bn as credentialInputSchema, Cr as toJSONSchema, Gn as normalizeCredentialList, Gt as PromptResponseSchema, Kt as PublicModelsResponseSchema, W as DEFAULT_CLOUD_PLATFORM_ORIGIN, Yt as ROUTE_MANIFEST_REL_PATH, cr as custom, dr as literal, gr as preprocess, lr as discriminatedUnion, mr as object, nr as _enum, or as array, pr as number, rr as _function, sr as boolean$1, tr as ZodType, vr as string, yr as union } from "./dist-BOhrc_Nv.mjs";
4
- import "./dist-Re6HHSqz.mjs";
3
+ import { Dr as union, Er as string, Gt as PromptResponseSchema, Kt as PublicModelsResponseSchema, Qn as normalizeCredentialList, Sr as object, W as DEFAULT_CLOUD_PLATFORM_ORIGIN, Yt as ROUTE_MANIFEST_REL_PATH, _r as discriminatedUnion, dr as _function, gr as custom, hr as boolean$1, jr as toJSONSchema, lr as ZodType, mr as array, nr as parseStoredRouteManifest, qn as credentialInputSchema, rr as requiredDisplayTextSchema, ur as _enum, wr as preprocess, xr as number, yr as literal } from "./dist-COhdZicI.mjs";
4
+ import "./dist-9VWONIo1.mjs";
5
5
  import "./chunk-BZUGFHVS-CPWRFwK8.mjs";
6
6
  import "./chunk-DLL7UR66-BUYgzxnR.mjs";
7
7
  import "./chunk-TN7HHBQW-CSB_R-XD.mjs";
@@ -24,6 +24,7 @@ import { readdir, stat } from "node:fs/promises";
24
24
  import { pathToFileURL } from "node:url";
25
25
  import { createHash } from "node:crypto";
26
26
  import { AsyncLocalStorage } from "node:async_hooks";
27
+ import ts from "typescript";
27
28
  import { boolean, doublePrecision, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
28
29
  import { index as index$1, integer as integer$1, primaryKey as primaryKey$1, real, sqliteTable, text as text$1, uniqueIndex as uniqueIndex$1 } from "drizzle-orm/sqlite-core";
29
30
  import { sql } from "drizzle-orm";
@@ -337,6 +338,1527 @@ function hasAppLayout(dir) {
337
338
  const dist = join(dir, "dist");
338
339
  return existsSync(join(src, "agents")) || existsSync(join(src, "skills")) || existsSync(join(src, "files")) || existsSync(join(dist, "agents")) || existsSync(join(dist, ".keystroke", "assets.mjs"));
339
340
  }
341
+ //#endregion
342
+ //#region ../../packages/workflow-canvas/dist/index.mjs
343
+ /** Root segment for statements in the workflow `run` body. */
344
+ const RUN_BODY_SEGMENT = "run-body";
345
+ /** Join path segments for hashing / debugging. */
346
+ function formatCallSitePath(path) {
347
+ return path.join(">");
348
+ }
349
+ /**
350
+ * Deterministic id for a durable call site from its structural path. The Phase 5
351
+ * build transform must compute the same path + id for each invocation.
352
+ */
353
+ function callSiteId(path) {
354
+ return createHash("sha256").update(formatCallSitePath(path)).digest("hex").slice(0, 16);
355
+ }
356
+ /** Id for an opaque code-block node at a structural location. */
357
+ function codeBlockSiteId(path) {
358
+ return callSiteId([...path, "code-block"]);
359
+ }
360
+ /** Unwrap `await`/parenthesized wrappers down to the inner expression. */
361
+ function unwrap(expr) {
362
+ let current = expr;
363
+ while (ts.isAwaitExpression(current) || ts.isParenthesizedExpression(current)) current = current.expression;
364
+ return current;
365
+ }
366
+ /** Leftmost identifier text of a property-access / call chain (`a.b().c` → "a"). */
367
+ function baseIdentifierText(expr) {
368
+ let current = expr;
369
+ while (true) {
370
+ if (ts.isPropertyAccessExpression(current)) {
371
+ current = current.expression;
372
+ continue;
373
+ }
374
+ if (ts.isCallExpression(current)) {
375
+ current = current.expression;
376
+ continue;
377
+ }
378
+ if (ts.isNonNullExpression(current) || ts.isParenthesizedExpression(current)) {
379
+ current = current.expression;
380
+ continue;
381
+ }
382
+ break;
383
+ }
384
+ return ts.isIdentifier(current) ? current.text : void 0;
385
+ }
386
+ /**
387
+ * Classify an expression as a durable step invocation, or return null. Handles
388
+ * the closed grammar: `x.run(...)`, `x.scope(...).run(...)`,
389
+ * `x.run(...).scope(...)`, `x.prompt(...)`, `promptLlm(...)`, and
390
+ * `ctx.sleep(...)` / `ctx.hook(...)`. Step identity is the structural
391
+ * {@link callSiteId}; there is no user-authored step id.
392
+ */
393
+ function classifyCall(expression, ctxParamName) {
394
+ const expr = unwrap(expression);
395
+ if (!ts.isCallExpression(expr)) return null;
396
+ const callee = expr.expression;
397
+ if (ts.isIdentifier(callee)) {
398
+ if (callee.text === "promptLlm") return {
399
+ key: "promptLlm",
400
+ callKind: "llm"
401
+ };
402
+ if (callee.text === "sleep") return {
403
+ key: "sleep",
404
+ callKind: "wait"
405
+ };
406
+ if (callee.text === "hook") return {
407
+ key: "hook",
408
+ callKind: "hook"
409
+ };
410
+ return null;
411
+ }
412
+ if (!ts.isPropertyAccessExpression(callee)) return null;
413
+ const method = callee.name.text;
414
+ const base = baseIdentifierText(callee.expression);
415
+ switch (method) {
416
+ case "run":
417
+ if (!base) return null;
418
+ return {
419
+ key: base,
420
+ callKind: "workflow-step",
421
+ importName: base
422
+ };
423
+ case "prompt":
424
+ if (!base) return null;
425
+ return {
426
+ key: base,
427
+ callKind: "agent",
428
+ importName: base
429
+ };
430
+ case "sleep":
431
+ if (base !== ctxParamName) return null;
432
+ return {
433
+ key: "sleep",
434
+ callKind: "wait"
435
+ };
436
+ case "hook":
437
+ if (base !== ctxParamName) return null;
438
+ return {
439
+ key: "hook",
440
+ callKind: "hook"
441
+ };
442
+ case "scope": return classifyCall(callee.expression, ctxParamName);
443
+ case "__site": return classifyCall(callee.expression, ctxParamName);
444
+ default: return null;
445
+ }
446
+ }
447
+ /** Detect `Promise.all([...])` and return its element expressions. */
448
+ function asPromiseAll(expression) {
449
+ const expr = unwrap(expression);
450
+ if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return null;
451
+ const callee = expr.expression;
452
+ if (!ts.isIdentifier(callee.expression) || callee.expression.text !== "Promise" || callee.name.text !== "all") return null;
453
+ const [arg] = expr.arguments;
454
+ if (!arg || !ts.isArrayLiteralExpression(arg)) return null;
455
+ return [...arg.elements];
456
+ }
457
+ /**
458
+ * `const approval = ctx.hook(...)` / `ctx.sleep(...)` without an immediate `await` on
459
+ * the initializer — the durable step should appear at the later `await approval` site.
460
+ */
461
+ function deferredHookSleepCall(initializer, ctxParamName) {
462
+ if (ts.isAwaitExpression(initializer)) return null;
463
+ const inner = unwrap(initializer);
464
+ if (!ts.isCallExpression(inner)) return null;
465
+ const step = classifyCall(initializer, ctxParamName);
466
+ if (!step || step.callKind !== "hook" && step.callKind !== "wait") return null;
467
+ return inner;
468
+ }
469
+ function isAwaitIdentifier(expression, varName) {
470
+ const inner = unwrap(expression);
471
+ return ts.isIdentifier(inner) && inner.text === varName;
472
+ }
473
+ /** Whether a statement is `await <varName>` (expr stmt or var decl initializer). */
474
+ function statementAwaitingIdentifier(statement, varName) {
475
+ if (ts.isExpressionStatement(statement)) return isAwaitIdentifier(statement.expression, varName);
476
+ if (ts.isVariableStatement(statement)) {
477
+ for (const decl of statement.declarationList.declarations) if (decl.initializer && isAwaitIdentifier(decl.initializer, varName)) return true;
478
+ }
479
+ if (ts.isIfStatement(statement)) return statementAwaitingIdentifier(statement.thenStatement, varName) || (statement.elseStatement ? statementAwaitingIdentifier(statement.elseStatement, varName) : false);
480
+ if (ts.isBlock(statement)) return statement.statements.some((inner) => statementAwaitingIdentifier(inner, varName));
481
+ if (ts.isForStatement(statement) || ts.isForOfStatement(statement) || ts.isForInStatement(statement) || ts.isWhileStatement(statement) || ts.isDoStatement(statement)) return statementAwaitingIdentifier(statement.statement, varName);
482
+ if (ts.isTryStatement(statement)) return statementAwaitingIdentifier(statement.tryBlock, varName) || (statement.catchClause ? statementAwaitingIdentifier(statement.catchClause.block, varName) : false) || (statement.finallyBlock ? statementAwaitingIdentifier(statement.finallyBlock, varName) : false);
483
+ return false;
484
+ }
485
+ /**
486
+ * Path of the first later statement in the same block that `await`s `varName`, or null
487
+ * when the handle is never awaited (falls back to inline placement at the decl site).
488
+ */
489
+ function findDeferredAwaitPath(statements, fromIndex, blockPath, varName) {
490
+ for (let index = fromIndex + 1; index < statements.length; index++) {
491
+ const statement = statements[index];
492
+ if (statementAwaitingIdentifier(statement, varName)) return [...blockPath, `stmt:${index}`];
493
+ }
494
+ return null;
495
+ }
496
+ /**
497
+ * The single structural traversal shared by every consumer of {@link callSiteId}:
498
+ * the graph producer's golden map ({@link computeCallSiteIds}), the contained-id
499
+ * collector ({@link collectContainedCallSiteIds}), and — mirrored, not shared —
500
+ * the graph builder itself. Assigning call paths in one place keeps the
501
+ * cross-package `callSiteId` invariant from drifting: any construct handled here
502
+ * (ternary prongs, deferred hook/sleep, loops, switch, try, `Promise.all`,
503
+ * same-file helper expansion) is handled identically for the map and the set.
504
+ */
505
+ function walkCallSites(root, basePath, options, visit) {
506
+ const state = {
507
+ ctxParamName: options.ctxParamName,
508
+ localFunctions: options.localFunctions,
509
+ callCounters: /* @__PURE__ */ new Map(),
510
+ walked: /* @__PURE__ */ new Set(),
511
+ visit
512
+ };
513
+ if (ts.isBlock(root)) walkStatements([...root.statements], basePath, state);
514
+ else if (ts.isStatement(root)) walkStatement(root, basePath, state);
515
+ else if (ts.isExpression(root)) walkExpression(root, basePath, state);
516
+ else ts.forEachChild(root, (child) => {
517
+ if (ts.isStatement(child)) walkStatement(child, basePath, state);
518
+ else if (ts.isExpression(child)) walkExpression(child, basePath, state);
519
+ });
520
+ }
521
+ function nextCallPath(state, basePath) {
522
+ const key = basePath.join(">");
523
+ const index = state.callCounters.get(key) ?? 0;
524
+ state.callCounters.set(key, index + 1);
525
+ return [...basePath, `call:${index}`];
526
+ }
527
+ function peekCallPath(state, basePath) {
528
+ const key = basePath.join(">");
529
+ const index = state.callCounters.get(key) ?? 0;
530
+ return [...basePath, `call:${index}`];
531
+ }
532
+ function reserveCallPath(state, callPath) {
533
+ const callSegment = callPath.at(-1);
534
+ if (!callSegment?.startsWith("call:")) return;
535
+ const index = Number.parseInt(callSegment.slice(5), 10);
536
+ if (Number.isNaN(index)) return;
537
+ const key = callPath.slice(0, -1).join(">");
538
+ const current = state.callCounters.get(key) ?? 0;
539
+ state.callCounters.set(key, Math.max(current, index + 1));
540
+ }
541
+ function walkStatements(statements, path, state) {
542
+ statements.forEach((statement, index) => {
543
+ walkStatement(statement, [...path, `stmt:${index}`], state, statements, index);
544
+ });
545
+ }
546
+ function walkStatement(statement, path, state, statements = [], statementIndex = 0) {
547
+ if (ts.isReturnStatement(statement)) {
548
+ if (statement.expression) walkExpression(statement.expression, path, state);
549
+ return;
550
+ }
551
+ if (ts.isExpressionStatement(statement)) {
552
+ walkExpression(statement.expression, path, state);
553
+ return;
554
+ }
555
+ if (ts.isVariableStatement(statement)) {
556
+ for (const decl of statement.declarationList.declarations) {
557
+ if (!decl.initializer) continue;
558
+ const deferredCall = deferredHookSleepCall(decl.initializer, state.ctxParamName);
559
+ if (deferredCall && ts.isIdentifier(decl.name) && statements.length > 0) {
560
+ const awaitPath = findDeferredAwaitPath(statements, statementIndex, path.slice(0, -1), decl.name.text);
561
+ if (awaitPath) {
562
+ const callPath = peekCallPath(state, awaitPath);
563
+ state.visit(deferredCall, callPath);
564
+ reserveCallPath(state, callPath);
565
+ continue;
566
+ }
567
+ }
568
+ walkExpression(decl.initializer, path, state);
569
+ }
570
+ return;
571
+ }
572
+ if (ts.isIfStatement(statement)) {
573
+ const conditions = [];
574
+ let elseBranch;
575
+ let current = statement;
576
+ while (current) {
577
+ conditions.push({
578
+ handle: `cond-${conditions.length}`,
579
+ branch: current.thenStatement
580
+ });
581
+ const next = current.elseStatement;
582
+ if (next && ts.isIfStatement(next)) {
583
+ current = next;
584
+ continue;
585
+ }
586
+ elseBranch = next;
587
+ current = void 0;
588
+ }
589
+ for (const { handle, branch } of conditions) walkStatement(branch, [...path, `if:${handle}`], state);
590
+ if (elseBranch) walkStatement(elseBranch, [...path, "if:else"], state);
591
+ return;
592
+ }
593
+ if (ts.isForStatement(statement) || ts.isForOfStatement(statement) || ts.isForInStatement(statement) || ts.isWhileStatement(statement) || ts.isDoStatement(statement)) {
594
+ walkStatement(statement.statement, [...path, "loop"], state);
595
+ return;
596
+ }
597
+ if (ts.isSwitchStatement(statement)) {
598
+ for (const [index, clause] of statement.caseBlock.clauses.entries()) {
599
+ const handle = ts.isCaseClause(clause) ? `case-${index}` : "default";
600
+ walkStatements([...clause.statements], [...path, `switch:${handle}`], state);
601
+ }
602
+ return;
603
+ }
604
+ if (ts.isBlock(statement)) {
605
+ walkStatements([...statement.statements], path, state);
606
+ return;
607
+ }
608
+ if (ts.isTryStatement(statement)) {
609
+ walkStatements([...statement.tryBlock.statements], [...path, "try"], state);
610
+ if (statement.catchClause?.block) walkStatements([...statement.catchClause.block.statements], [...path, "catch"], state);
611
+ if (statement.finallyBlock) walkStatements([...statement.finallyBlock.statements], [...path, "finally"], state);
612
+ }
613
+ }
614
+ function walkExpression(expression, path, state) {
615
+ const parallel = asPromiseAll(expression);
616
+ if (parallel) {
617
+ parallel.forEach((element, index) => {
618
+ walkExpression(element, [...path, `parallel:${index}`], state);
619
+ });
620
+ return;
621
+ }
622
+ if (classifyCall(expression, state.ctxParamName)) {
623
+ const node = unwrap(expression);
624
+ if (ts.isCallExpression(node)) state.visit(node, nextCallPath(state, path));
625
+ return;
626
+ }
627
+ if (ts.isConditionalExpression(expression)) {
628
+ walkExpression(expression.whenTrue, [...path, "cond:then"], state);
629
+ walkExpression(expression.whenFalse, [...path, "cond:else"], state);
630
+ return;
631
+ }
632
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
633
+ const helperName = expression.expression.text;
634
+ const helperBody = state.localFunctions.get(helperName);
635
+ if (helperBody) {
636
+ if (state.walked.has(helperName)) return;
637
+ state.walked.add(helperName);
638
+ const helperPath = [`helper:${helperName}`];
639
+ if (ts.isBlock(helperBody)) walkStatements([...helperBody.statements], helperPath, state);
640
+ else if (ts.isExpression(helperBody)) walkExpression(helperBody, helperPath, state);
641
+ return;
642
+ }
643
+ }
644
+ ts.forEachChild(expression, (child) => {
645
+ if (ts.isExpression(child)) walkExpression(child, path, state);
646
+ });
647
+ }
648
+ /** Convenience: collect all callSiteIds reachable from `root`. */
649
+ function collectCallSiteIds(root, basePath, options) {
650
+ const out = /* @__PURE__ */ new Set();
651
+ walkCallSites(root, basePath, options, (_call, callPath) => {
652
+ out.add(callSiteId(callPath));
653
+ });
654
+ return [...out];
655
+ }
656
+ /**
657
+ * Collect durable call-site ids hidden inside an off-grammar subtree (a
658
+ * `code-block` node's `containedCallSiteIds`). Uses the same {@link walkCallSites}
659
+ * traversal as {@link computeCallSiteIds}, so the ids attributed to a code-block
660
+ * always match the ids the build transform injects at those call sites.
661
+ */
662
+ function collectContainedCallSiteIds(node, ctxParamName, localFunctions, basePath = [RUN_BODY_SEGMENT]) {
663
+ return collectCallSiteIds(node, basePath, {
664
+ ctxParamName,
665
+ localFunctions
666
+ });
667
+ }
668
+ /**
669
+ * Index same-file helper functions by name (top-level `function foo()` and
670
+ * `const foo = () => {}` / function-expression consts) so step detection can
671
+ * descend into a local helper a workflow calls. Cross-file helpers are
672
+ * intentionally absent (single-file boundary).
673
+ */
674
+ function collectLocalFunctions(sourceFile) {
675
+ const byName = /* @__PURE__ */ new Map();
676
+ for (const statement of sourceFile.statements) {
677
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body) {
678
+ byName.set(statement.name.text, statement.body);
679
+ continue;
680
+ }
681
+ if (ts.isVariableStatement(statement)) {
682
+ for (const decl of statement.declarationList.declarations) if (ts.isIdentifier(decl.name) && decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) byName.set(decl.name.text, decl.initializer.body);
683
+ }
684
+ }
685
+ return byName;
686
+ }
687
+ /**
688
+ * Count textual invocations of each same-file helper across the workflow `run`
689
+ * body and every other same-file helper body. A helper invoked from more than
690
+ * one call site cannot be inlined as distinct real step nodes: the build injects
691
+ * one `callSiteId` per physical call site in the helper body, so every invocation
692
+ * shares those ids and folds occurrences at runtime (like a loop). Rendering it as
693
+ * one shared subgraph produces misleading cross-branch / self-loop edges, so a
694
+ * multiply-invoked helper collapses to a per-call-site `code-block` instead.
695
+ */
696
+ function sharedHelperNames(located, localFunctions) {
697
+ const counts = /* @__PURE__ */ new Map();
698
+ const tally = (root) => {
699
+ const visit = (node) => {
700
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
701
+ const name = node.expression.text;
702
+ if (localFunctions.has(name)) counts.set(name, (counts.get(name) ?? 0) + 1);
703
+ }
704
+ ts.forEachChild(node, visit);
705
+ };
706
+ visit(root);
707
+ };
708
+ tally(located.body);
709
+ for (const body of localFunctions.values()) tally(body);
710
+ const shared = /* @__PURE__ */ new Set();
711
+ for (const [name, count] of counts) if (count >= 2) shared.add(name);
712
+ return shared;
713
+ }
714
+ /** Split camelCase identifiers into sentence case (`hasBreaking` → `Has breaking`). */
715
+ function prettifyCamelCaseIdentifier(name) {
716
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/\s+/).map((word, index) => {
717
+ const lower = word.toLowerCase();
718
+ return index === 0 ? lower.charAt(0).toUpperCase() + lower.slice(1) : lower;
719
+ }).join(" ");
720
+ }
721
+ function unwrapParenthesized(expression) {
722
+ let current = expression;
723
+ while (ts.isParenthesizedExpression(current)) current = current.expression;
724
+ return current;
725
+ }
726
+ function identifierNameFromExpression(expression) {
727
+ const unwrapped = unwrapParenthesized(expression);
728
+ if (ts.isPropertyAccessExpression(unwrapped) || ts.isPropertyAccessChain(unwrapped)) return unwrapped.name.text;
729
+ if (ts.isIdentifier(unwrapped)) return unwrapped.text;
730
+ }
731
+ /**
732
+ * Human-readable label for an `if`/`switch` condition. Member access and bare
733
+ * identifiers prettify (`changes.hasBreaking` → `Has breaking`); everything
734
+ * else falls back to normalized source text.
735
+ */
736
+ function formatConditionLabel(expression, sourceFile) {
737
+ const identifierName = identifierNameFromExpression(expression);
738
+ if (identifierName) return prettifyCamelCaseIdentifier(identifierName);
739
+ return expression.getText(sourceFile).replace(/\s+/g, " ").trim();
740
+ }
741
+ /**
742
+ * Find the input-object argument of a durable call. Descends through the
743
+ * build-injected `.__site(...)` and user `.scope(...)` wrappers to the
744
+ * `.run({...})` / `.prompt({...})` / `promptLlm({...})` call and returns its
745
+ * first argument (the input object literal), or undefined.
746
+ */
747
+ function extractStepInputArg(expression) {
748
+ return unwrapToCall(expression)?.arguments[0];
749
+ }
750
+ function combineField(base, path) {
751
+ if (base && path) return `${base}.${path}`;
752
+ return base ?? path;
753
+ }
754
+ /** Leftmost identifier + the dotted property path of a `a.b.c` chain (`{ base: "a", path: "b.c" }`). */
755
+ function propertyAccessParts(expr) {
756
+ const parts = [];
757
+ let current = expr;
758
+ while (ts.isPropertyAccessExpression(current)) {
759
+ parts.unshift(current.name.text);
760
+ current = current.expression;
761
+ }
762
+ if (!ts.isIdentifier(current)) return null;
763
+ return {
764
+ base: current.text,
765
+ path: parts.join(".")
766
+ };
767
+ }
768
+ function referenceToken(source) {
769
+ return {
770
+ kind: "reference",
771
+ sourceNodeId: source.nodeId,
772
+ ...source.field ? { field: source.field } : {}
773
+ };
774
+ }
775
+ function resolveIdentifier(name, scope) {
776
+ if (name === scope.inputParamName) return {
777
+ kind: "reference",
778
+ sourceNodeId: scope.entryNodeId
779
+ };
780
+ const source = scope.bindings.get(name);
781
+ if (source) return referenceToken(source);
782
+ return {
783
+ kind: "variable",
784
+ text: name
785
+ };
786
+ }
787
+ /** The callee's source name for a call expression (`buildPrompt`, `jobs.map`); never its args. */
788
+ function calleeName(call, sourceFile) {
789
+ const callee = call.expression;
790
+ if (ts.isIdentifier(callee)) return callee.text;
791
+ if (ts.isPropertyAccessExpression(callee)) {
792
+ const parts = propertyAccessParts(callee);
793
+ if (parts) return `${parts.base}.${parts.path}`;
794
+ }
795
+ return callee.getText(sourceFile);
796
+ }
797
+ function resolvePropertyAccess(expr, scope, sourceFile) {
798
+ const parts = propertyAccessParts(expr);
799
+ if (!parts) return {
800
+ kind: "variable",
801
+ text: expr.getText(sourceFile)
802
+ };
803
+ if (parts.base === scope.inputParamName) return {
804
+ kind: "reference",
805
+ sourceNodeId: scope.entryNodeId,
806
+ field: parts.path
807
+ };
808
+ const source = scope.bindings.get(parts.base);
809
+ if (source) {
810
+ const field = combineField(source.field, parts.path);
811
+ return {
812
+ kind: "reference",
813
+ sourceNodeId: source.nodeId,
814
+ ...field ? { field } : {}
815
+ };
816
+ }
817
+ return {
818
+ kind: "variable",
819
+ text: expr.getText(sourceFile)
820
+ };
821
+ }
822
+ /** Tokenize one value expression into a binding (literal text and/or upstream references). */
823
+ function tokensFromExpression(expr, scope, sourceFile) {
824
+ const value = unwrap(expr);
825
+ if (ts.isStringLiteralLike(value)) return { tokens: [{
826
+ kind: "text",
827
+ text: value.text
828
+ }] };
829
+ if (ts.isNumericLiteral(value)) return { tokens: [{
830
+ kind: "text",
831
+ text: value.text
832
+ }] };
833
+ if (value.kind === ts.SyntaxKind.TrueKeyword) return { tokens: [{
834
+ kind: "text",
835
+ text: "true"
836
+ }] };
837
+ if (value.kind === ts.SyntaxKind.FalseKeyword) return { tokens: [{
838
+ kind: "text",
839
+ text: "false"
840
+ }] };
841
+ if (value.kind === ts.SyntaxKind.NullKeyword) return { tokens: [{
842
+ kind: "text",
843
+ text: "null"
844
+ }] };
845
+ if (ts.isIdentifier(value)) return { tokens: [resolveIdentifier(value.text, scope)] };
846
+ if (ts.isPropertyAccessExpression(value)) return { tokens: [resolvePropertyAccess(value, scope, sourceFile)] };
847
+ if (ts.isTemplateExpression(value)) {
848
+ const tokens = [];
849
+ if (value.head.text) tokens.push({
850
+ kind: "text",
851
+ text: value.head.text
852
+ });
853
+ for (const span of value.templateSpans) {
854
+ tokens.push(...tokensFromExpression(span.expression, scope, sourceFile).tokens);
855
+ if (span.literal.text) tokens.push({
856
+ kind: "text",
857
+ text: span.literal.text
858
+ });
859
+ }
860
+ return {
861
+ tokens,
862
+ ...value.getText(sourceFile).includes("\n") ? { multiline: true } : {}
863
+ };
864
+ }
865
+ if (ts.isObjectLiteralExpression(value) || ts.isArrayLiteralExpression(value)) return {
866
+ tokens: [{
867
+ kind: "text",
868
+ text: value.getText(sourceFile)
869
+ }],
870
+ multiline: true
871
+ };
872
+ if (ts.isCallExpression(value)) return { tokens: [{
873
+ kind: "call",
874
+ name: calleeName(value, sourceFile)
875
+ }] };
876
+ return { tokens: [{
877
+ kind: "variable",
878
+ text: value.getText(sourceFile)
879
+ }] };
880
+ }
881
+ /**
882
+ * Capture per-field call-site bindings from a step's input-object argument. Each
883
+ * property becomes a {@link WorkflowCanvasRawBinding} (literal text and/or
884
+ * references to upstream node outputs). Returns undefined when the argument isn't
885
+ * an object literal (e.g. `a.run(payload)`), or has no bindable properties.
886
+ */
887
+ function inputBindingsFromCall(expression, scope, sourceFile) {
888
+ const arg = extractStepInputArg(expression);
889
+ if (!arg || !ts.isObjectLiteralExpression(arg)) return;
890
+ const bindings = {};
891
+ for (const prop of arg.properties) {
892
+ if (ts.isShorthandPropertyAssignment(prop)) {
893
+ bindings[prop.name.text] = { tokens: [resolveIdentifier(prop.name.text, scope)] };
894
+ continue;
895
+ }
896
+ if (ts.isPropertyAssignment(prop)) {
897
+ const key = ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
898
+ if (!key) continue;
899
+ bindings[key] = tokensFromExpression(prop.initializer, scope, sourceFile);
900
+ }
901
+ }
902
+ return Object.keys(bindings).length > 0 ? bindings : void 0;
903
+ }
904
+ /**
905
+ * Capture static `promptLlm(prompt, options)` config from an llm call site. Reads
906
+ * literal `model`/`thinkingLevel` (strings) and `maxTokens`/`temperature` (numbers),
907
+ * plus the PRESENCE of `system`/`outputSchema` (usually expressions, not literals).
908
+ * Returns undefined when no options object literal or no recognizable fields.
909
+ */
910
+ function llmOptionsFromCall(expression) {
911
+ const optionsArg = unwrapToCall(expression)?.arguments[1];
912
+ if (!optionsArg || !ts.isObjectLiteralExpression(optionsArg)) return;
913
+ const options = {};
914
+ for (const prop of optionsArg.properties) {
915
+ if (!ts.isPropertyAssignment(prop)) continue;
916
+ const key = ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
917
+ if (!key) continue;
918
+ const value = unwrap(prop.initializer);
919
+ switch (key) {
920
+ case "model":
921
+ if (ts.isStringLiteralLike(value)) options.model = value.text;
922
+ break;
923
+ case "thinkingLevel":
924
+ if (ts.isStringLiteralLike(value)) options.thinkingLevel = value.text;
925
+ break;
926
+ case "maxTokens":
927
+ if (ts.isNumericLiteral(value)) options.maxTokens = Number(value.text);
928
+ break;
929
+ case "temperature":
930
+ if (ts.isNumericLiteral(value)) options.temperature = Number(value.text);
931
+ break;
932
+ case "system":
933
+ options.hasSystemPrompt = true;
934
+ break;
935
+ case "outputSchema":
936
+ options.hasOutputSchema = true;
937
+ break;
938
+ }
939
+ }
940
+ return Object.keys(options).length > 0 ? options : void 0;
941
+ }
942
+ /** Descend wrapper calls (`.scope`/`.__site`) to the underlying durable call. */
943
+ function unwrapToCall(expression) {
944
+ let expr = unwrap(expression);
945
+ while (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
946
+ const method = expr.expression.name.text;
947
+ if (method === "scope" || method === "__site") {
948
+ expr = unwrap(expr.expression.expression);
949
+ continue;
950
+ }
951
+ break;
952
+ }
953
+ return ts.isCallExpression(expr) ? expr : void 0;
954
+ }
955
+ /**
956
+ * Tokenize a `promptLlm(prompt, options)` call's options object into per-field
957
+ * bindings (model/system/outputSchema/maxTokens/…), the same token model used for
958
+ * action inputs — so a `model: CLASSIFY_MODEL` renders as a variable chip and a
959
+ * `system: dailySystem()` as a call chip, instead of being dropped. Returns
960
+ * undefined when there's no options object literal.
961
+ */
962
+ function llmOptionBindingsFromCall(expression, scope, sourceFile) {
963
+ const optionsArg = unwrapToCall(expression)?.arguments[1];
964
+ if (!optionsArg || !ts.isObjectLiteralExpression(optionsArg)) return;
965
+ const bindings = {};
966
+ for (const prop of optionsArg.properties) {
967
+ if (ts.isShorthandPropertyAssignment(prop)) {
968
+ bindings[prop.name.text] = { tokens: [resolveIdentifier(prop.name.text, scope)] };
969
+ continue;
970
+ }
971
+ if (ts.isPropertyAssignment(prop)) {
972
+ const key = ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name) ? prop.name.text : void 0;
973
+ if (!key) continue;
974
+ bindings[key] = tokensFromExpression(prop.initializer, scope, sourceFile);
975
+ }
976
+ }
977
+ return Object.keys(bindings).length > 0 ? bindings : void 0;
978
+ }
979
+ /** Statically-evaluate a literal initializer to a scalar, or undefined if not literal. */
980
+ function literalConstantValue(initializer) {
981
+ const value = unwrap(initializer);
982
+ if (ts.isStringLiteralLike(value)) return value.text;
983
+ if (ts.isNumericLiteral(value)) return Number(value.text);
984
+ if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(value.operand)) return -Number(value.operand.text);
985
+ if (value.kind === ts.SyntaxKind.TrueKeyword) return true;
986
+ if (value.kind === ts.SyntaxKind.FalseKeyword) return false;
987
+ }
988
+ /**
989
+ * Collect top-level `const NAME = <literal scalar>` declarations from the workflow
990
+ * source so the platform can resolve `variable` value tokens (e.g. a model pinned to
991
+ * a named const) to their concrete value. Scalars only — computed or non-literal
992
+ * consts (and anything function-local) are intentionally skipped.
993
+ */
994
+ function collectTopLevelConstants(sourceFile) {
995
+ const constants = {};
996
+ for (const statement of sourceFile.statements) {
997
+ if (!ts.isVariableStatement(statement) || !(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
998
+ for (const decl of statement.declarationList.declarations) {
999
+ if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
1000
+ const value = literalConstantValue(decl.initializer);
1001
+ if (value !== void 0) constants[decl.name.text] = value;
1002
+ }
1003
+ }
1004
+ return constants;
1005
+ }
1006
+ /**
1007
+ * Record the producer outputs a variable declaration binds, so later call sites
1008
+ * can reference them. Handles `const x = await a.run()` (→ `x` = the node) and
1009
+ * `const { text } = await a.prompt()` (→ `text` = the node's `text` field).
1010
+ */
1011
+ function recordDeclarationBinding(name, nodeId, scope) {
1012
+ if (ts.isIdentifier(name)) {
1013
+ scope.bindings.set(name.text, { nodeId });
1014
+ return;
1015
+ }
1016
+ if (ts.isObjectBindingPattern(name)) for (const element of name.elements) {
1017
+ if (!ts.isIdentifier(element.name)) continue;
1018
+ const field = element.propertyName && ts.isIdentifier(element.propertyName) ? element.propertyName.text : element.name.text;
1019
+ scope.bindings.set(element.name.text, {
1020
+ nodeId,
1021
+ field
1022
+ });
1023
+ }
1024
+ }
1025
+ const ENTRY_NODE_ID = "entry";
1026
+ const MAX_LABEL_LENGTH = 60;
1027
+ function truncate(text) {
1028
+ const collapsed = text.replace(/\s+/g, " ").trim();
1029
+ return collapsed.length > MAX_LABEL_LENGTH ? `${collapsed.slice(0, MAX_LABEL_LENGTH - 1)}…` : collapsed;
1030
+ }
1031
+ /** First-party + integration packages live under this scope; `<app>` is the slug. */
1032
+ const APP_PACKAGE_SCOPE = "@keystrokehq/";
1033
+ function appSlugFromSpecifier(specifier) {
1034
+ if (!specifier.startsWith(APP_PACKAGE_SCOPE)) return;
1035
+ return specifier.slice(13).split("/")[0] || void 0;
1036
+ }
1037
+ function collectAppImportsBySymbol(sourceFile) {
1038
+ const bySymbol = /* @__PURE__ */ new Map();
1039
+ for (const statement of sourceFile.statements) {
1040
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
1041
+ const appSlug = appSlugFromSpecifier(statement.moduleSpecifier.text);
1042
+ const clause = statement.importClause;
1043
+ if (!appSlug || !clause) continue;
1044
+ if (clause.name) bySymbol.set(clause.name.text, appSlug);
1045
+ if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) for (const element of clause.namedBindings.elements) bySymbol.set(element.name.text, appSlug);
1046
+ }
1047
+ return bySymbol;
1048
+ }
1049
+ function stepLabel(info) {
1050
+ switch (info.callKind) {
1051
+ case "wait": return "Sleep";
1052
+ case "hook": return "Wait for signal";
1053
+ case "llm": return info.key === "promptLlm" ? "LLM prompt" : info.key;
1054
+ case "agent":
1055
+ case "workflow-step": return info.key;
1056
+ }
1057
+ }
1058
+ var GraphBuilder = class {
1059
+ sourceFile;
1060
+ resolveSlug;
1061
+ nodes = [];
1062
+ edges = [];
1063
+ nodeIds = /* @__PURE__ */ new Set();
1064
+ callCounters = /* @__PURE__ */ new Map();
1065
+ structuralCount = 0;
1066
+ edgeCount = 0;
1067
+ exitId = null;
1068
+ /** >0 while walking inside a branch (if/switch/loop/try), so a `return` there
1069
+ * is modeled as its own local "Output" terminal instead of a long edge to the
1070
+ * single shared exit (which reads as crossing the whole graph). */
1071
+ inBranchDepth = 0;
1072
+ /** True while inlining a helper called in VALUE position (`const x = await f()`),
1073
+ * so a `return` inside it is a value handoff to the caller (its expression's
1074
+ * frontier), NOT the workflow's terminal output. False for the run body and for
1075
+ * helpers dispatched in tail position (`return f()`), whose returns ARE outputs. */
1076
+ returnAsValue = false;
1077
+ appImportsBySymbol;
1078
+ localFunctions;
1079
+ /** Same-file helpers invoked from >1 call site — collapsed to a code-block per site. */
1080
+ sharedHelpers = /* @__PURE__ */ new Set();
1081
+ /** First open endpoints per expanded helper (for deduped re-entry). */
1082
+ helperEntryByName = /* @__PURE__ */ new Map();
1083
+ helperExitByName = /* @__PURE__ */ new Map();
1084
+ /** Lexical symbol table for resolving call-site value references to upstream nodes. */
1085
+ bindingScope = {
1086
+ bindings: /* @__PURE__ */ new Map(),
1087
+ entryNodeId: ENTRY_NODE_ID
1088
+ };
1089
+ /** Hook/sleep handles created in a var decl but awaited later in the run body. */
1090
+ pendingDeferredSteps = /* @__PURE__ */ new Map();
1091
+ constructor(sourceFile, resolveSlug) {
1092
+ this.sourceFile = sourceFile;
1093
+ this.resolveSlug = resolveSlug;
1094
+ this.appImportsBySymbol = collectAppImportsBySymbol(sourceFile);
1095
+ this.localFunctions = collectLocalFunctions(sourceFile);
1096
+ }
1097
+ subtreeHasStep(node, ctxParamName, visiting = /* @__PURE__ */ new Set()) {
1098
+ let found = false;
1099
+ const visit = (current) => {
1100
+ if (found) return;
1101
+ if ((ts.isCallExpression(current) || ts.isAwaitExpression(current)) && classifyCall(current, ctxParamName)) {
1102
+ found = true;
1103
+ return;
1104
+ }
1105
+ if (ts.isCallExpression(current) && ts.isIdentifier(current.expression)) {
1106
+ const helperBody = this.localFunctions.get(current.expression.text);
1107
+ if (helperBody && !visiting.has(current.expression.text)) {
1108
+ visiting.add(current.expression.text);
1109
+ if (this.subtreeHasStep(helperBody, ctxParamName, visiting)) {
1110
+ found = true;
1111
+ return;
1112
+ }
1113
+ }
1114
+ }
1115
+ ts.forEachChild(current, visit);
1116
+ };
1117
+ visit(node);
1118
+ return found;
1119
+ }
1120
+ /**
1121
+ * Whether a helper called in VALUE position is safe to inline as real step nodes.
1122
+ * Only straight-line helpers qualify — a block of variable/expression statements
1123
+ * with at most a single trailing `return` (no `if`/`switch`/loop/`try`/`throw`),
1124
+ * or a concise non-ternary expression body. Helpers with internal control flow or
1125
+ * multiple returns are genuinely ambiguous as values (which `return` is "the"
1126
+ * result?), so they collapse to a single `code-block` instead (run attribution is
1127
+ * preserved via `containedCallSiteIds`). Helpers dispatched in tail position
1128
+ * (`return f()`) are inlined regardless — there their returns ARE the output.
1129
+ */
1130
+ isLinearValueHelper(helperBody) {
1131
+ if (!ts.isBlock(helperBody)) return ts.isExpression(helperBody) && !ts.isConditionalExpression(helperBody);
1132
+ let returnCount = 0;
1133
+ const statements = helperBody.statements;
1134
+ for (const [index, statement] of statements.entries()) {
1135
+ if (ts.isVariableStatement(statement) || ts.isExpressionStatement(statement)) continue;
1136
+ if (ts.isReturnStatement(statement)) {
1137
+ returnCount += 1;
1138
+ if (returnCount > 1 || index !== statements.length - 1) return false;
1139
+ continue;
1140
+ }
1141
+ return false;
1142
+ }
1143
+ return true;
1144
+ }
1145
+ build(located) {
1146
+ this.sharedHelpers = sharedHelperNames(located, this.localFunctions);
1147
+ this.bindingScope = {
1148
+ bindings: /* @__PURE__ */ new Map(),
1149
+ entryNodeId: ENTRY_NODE_ID,
1150
+ ...located.inputParamName ? { inputParamName: located.inputParamName } : {}
1151
+ };
1152
+ const incoming = [{ nodeId: this.addNode("entry", { label: "Start" }, "entry") }];
1153
+ const runPath = [RUN_BODY_SEGMENT];
1154
+ const { body, ctxParamName } = located;
1155
+ const open = ts.isBlock(body) ? this.walkStatements([...body.statements], incoming, ctxParamName, void 0, runPath) : this.walkConcise(body, incoming, ctxParamName, runPath);
1156
+ if (open.length > 0) this.connect(open, this.getExit());
1157
+ const constants = collectTopLevelConstants(this.sourceFile);
1158
+ return {
1159
+ nodes: this.nodes,
1160
+ edges: this.edges,
1161
+ ...Object.keys(constants).length > 0 ? { constants } : {}
1162
+ };
1163
+ }
1164
+ push(node) {
1165
+ this.nodes.push(node);
1166
+ this.nodeIds.add(node.id);
1167
+ return node.id;
1168
+ }
1169
+ addNode(nodeType, data, id, parentId) {
1170
+ const node = {
1171
+ id,
1172
+ nodeType,
1173
+ data
1174
+ };
1175
+ if (parentId) node.parentId = parentId;
1176
+ return this.push(node);
1177
+ }
1178
+ nextCallPath(basePath) {
1179
+ const key = formatCallSitePath(basePath);
1180
+ const index = this.callCounters.get(key) ?? 0;
1181
+ this.callCounters.set(key, index + 1);
1182
+ return [...basePath, `call:${index}`];
1183
+ }
1184
+ peekCallPath(basePath) {
1185
+ const key = formatCallSitePath(basePath);
1186
+ const index = this.callCounters.get(key) ?? 0;
1187
+ return [...basePath, `call:${index}`];
1188
+ }
1189
+ /** Reserve a precomputed call index so a later materialization does not shift ids. */
1190
+ reserveCallPath(callPath) {
1191
+ const callSegment = callPath.at(-1);
1192
+ if (!callSegment?.startsWith("call:")) return;
1193
+ const index = Number.parseInt(callSegment.slice(5), 10);
1194
+ if (Number.isNaN(index)) return;
1195
+ const key = formatCallSitePath(callPath.slice(0, -1));
1196
+ const current = this.callCounters.get(key) ?? 0;
1197
+ this.callCounters.set(key, Math.max(current, index + 1));
1198
+ }
1199
+ addStructural(label, parentId) {
1200
+ const id = `merge-${this.structuralCount++}`;
1201
+ return this.addNode("merge", { label }, id, parentId);
1202
+ }
1203
+ addErrorNode(label, parentId) {
1204
+ const id = `error-${this.structuralCount++}`;
1205
+ return this.addNode("error", { label }, id, parentId);
1206
+ }
1207
+ /** A local terminal output node for an early `return` inside a branch. */
1208
+ addOutputNode(parentId) {
1209
+ const id = `output-${this.structuralCount++}`;
1210
+ return this.addNode("exit", { label: "Output" }, id, parentId);
1211
+ }
1212
+ addDecision(label, outputs, parentId) {
1213
+ const id = `decision-${this.structuralCount++}`;
1214
+ return this.addNode("decision", {
1215
+ label,
1216
+ outputs
1217
+ }, id, parentId);
1218
+ }
1219
+ addCodeBlock(label, path, parentId, containedCallSiteIds) {
1220
+ const id = codeBlockSiteId(path);
1221
+ if (this.nodeIds.has(id)) return id;
1222
+ const data = {
1223
+ label,
1224
+ ...containedCallSiteIds?.length ? { containedCallSiteIds } : {}
1225
+ };
1226
+ return this.addNode("code-block", data, id, parentId);
1227
+ }
1228
+ addStep(info, expression, callPath, parentId) {
1229
+ const id = callSiteId(callPath);
1230
+ if (this.nodeIds.has(id)) return id;
1231
+ const data = {
1232
+ label: stepLabel(info),
1233
+ callKind: info.callKind
1234
+ };
1235
+ const appSlug = info.importName ? this.appImportsBySymbol.get(info.importName) : void 0;
1236
+ if (appSlug) data.appSlug = appSlug;
1237
+ if (info.importName && this.resolveSlug) {
1238
+ const resolved = this.resolveSlug(info.importName, this.sourceFile);
1239
+ if (resolved) {
1240
+ data.slug = resolved.slug;
1241
+ if (resolved.description) data.description = resolved.description;
1242
+ if (resolved.name) data.label = resolved.name;
1243
+ }
1244
+ }
1245
+ if (info.callKind === "llm") {
1246
+ const llmOptions = llmOptionsFromCall(expression);
1247
+ if (llmOptions) data.llmOptions = llmOptions;
1248
+ const optionBindings = llmOptionBindingsFromCall(expression, this.bindingScope, this.sourceFile);
1249
+ if (optionBindings) data.inputBindings = optionBindings;
1250
+ } else if (info.callKind !== "wait" && info.callKind !== "hook") {
1251
+ const inputBindings = inputBindingsFromCall(expression, this.bindingScope, this.sourceFile);
1252
+ if (inputBindings) data.inputBindings = inputBindings;
1253
+ }
1254
+ return this.addNode("step", data, id, parentId);
1255
+ }
1256
+ addGroup(groupKind, label, parentId) {
1257
+ const nodeType = groupKind === "loop" ? "loop" : "parallel";
1258
+ const groupId = `${nodeType}-${this.structuralCount++}`;
1259
+ const groupNode = {
1260
+ id: groupId,
1261
+ nodeType,
1262
+ data: {
1263
+ label,
1264
+ groupKind
1265
+ }
1266
+ };
1267
+ if (parentId) groupNode.parentId = parentId;
1268
+ this.push(groupNode);
1269
+ const groupStartId = `${groupId}-start`;
1270
+ this.push({
1271
+ id: groupStartId,
1272
+ nodeType: "loop-start",
1273
+ data: { label: "Start" },
1274
+ parentId: groupId
1275
+ });
1276
+ return {
1277
+ groupId,
1278
+ groupStartId
1279
+ };
1280
+ }
1281
+ getExit() {
1282
+ if (this.exitId === null) this.exitId = this.addNode("exit", { label: "Output" }, "exit");
1283
+ return this.exitId;
1284
+ }
1285
+ addEdge(source, target, endpoint) {
1286
+ const edge = {
1287
+ id: `edge-${this.edgeCount++}`,
1288
+ source,
1289
+ target
1290
+ };
1291
+ if (endpoint?.label) edge.label = endpoint.label;
1292
+ if (endpoint?.sourceHandle) edge.sourceHandle = endpoint.sourceHandle;
1293
+ if (endpoint?.branchKind) edge.data = { branchKind: endpoint.branchKind };
1294
+ this.edges.push(edge);
1295
+ }
1296
+ connect(incoming, target) {
1297
+ for (const endpoint of incoming) this.addEdge(endpoint.nodeId, target, endpoint);
1298
+ }
1299
+ walkHelperCall(helperName, helperBody, incoming, ctxParamName, parentId, visiting, tailPosition) {
1300
+ const cachedExit = this.helperExitByName.get(helperName);
1301
+ const cachedEntry = this.helperEntryByName.get(helperName);
1302
+ if (cachedEntry && cachedExit) {
1303
+ this.connect(incoming, cachedEntry);
1304
+ return cachedExit.map((nodeId) => ({ nodeId }));
1305
+ }
1306
+ if (visiting.has(helperName)) return incoming;
1307
+ visiting.add(helperName);
1308
+ const helperPath = [`helper:${helperName}`];
1309
+ const nodesBefore = this.nodes.length;
1310
+ const previousReturnAsValue = this.returnAsValue;
1311
+ this.returnAsValue = !tailPosition;
1312
+ const result = ts.isBlock(helperBody) ? this.walkStatements([...helperBody.statements], incoming, ctxParamName, parentId, helperPath, visiting) : this.walkValue(helperBody, incoming, ctxParamName, parentId, helperPath, visiting);
1313
+ this.returnAsValue = previousReturnAsValue;
1314
+ const firstNewStep = this.nodes.slice(nodesBefore).find((n) => n.nodeType === "step");
1315
+ if (firstNewStep) {
1316
+ this.helperEntryByName.set(helperName, firstNewStep.id);
1317
+ this.helperExitByName.set(helperName, result.map((endpoint) => endpoint.nodeId));
1318
+ }
1319
+ visiting.delete(helperName);
1320
+ return result;
1321
+ }
1322
+ tryMaterializeDeferredAwait(expression, incoming, parentId) {
1323
+ let current = expression;
1324
+ while (ts.isAwaitExpression(current) || ts.isParenthesizedExpression(current)) current = current.expression;
1325
+ if (!ts.isIdentifier(current)) return null;
1326
+ const pending = this.pendingDeferredSteps.get(current.text);
1327
+ if (!pending) return null;
1328
+ this.pendingDeferredSteps.delete(current.text);
1329
+ const id = this.addStep(pending.step, pending.expression, pending.callPath, parentId);
1330
+ this.reserveCallPath(pending.callPath);
1331
+ this.connect(incoming, id);
1332
+ return [{ nodeId: id }];
1333
+ }
1334
+ tryWalkLocalHelper(expression, incoming, ctxParamName, parentId, path, visiting, tailPosition) {
1335
+ let current = expression;
1336
+ while (ts.isAwaitExpression(current) || ts.isParenthesizedExpression(current)) current = current.expression;
1337
+ if (!ts.isCallExpression(current) || !ts.isIdentifier(current.expression)) return null;
1338
+ const helperName = current.expression.text;
1339
+ const helperBody = this.localFunctions.get(helperName);
1340
+ if (!helperBody || !this.subtreeHasStep(helperBody, ctxParamName)) return null;
1341
+ if (this.sharedHelpers.has(helperName)) return null;
1342
+ if (!tailPosition && !this.isLinearValueHelper(helperBody)) return null;
1343
+ return this.walkHelperCall(helperName, helperBody, incoming, ctxParamName, parentId, visiting, tailPosition);
1344
+ }
1345
+ walkStatements(statements, incoming, ctxParamName, parentId, path, visiting = /* @__PURE__ */ new Set()) {
1346
+ let frontier = incoming;
1347
+ for (const [index, statement] of statements.entries()) frontier = this.walkStatement(statement, frontier, ctxParamName, parentId, [...path, `stmt:${index}`], visiting, statements, index);
1348
+ return frontier;
1349
+ }
1350
+ walkStatement(statement, incoming, ctxParamName, parentId, path, visiting = /* @__PURE__ */ new Set(), statements = [], statementIndex = 0) {
1351
+ if (ts.isReturnStatement(statement)) return this.walkReturn(statement, incoming, ctxParamName, parentId, path, visiting);
1352
+ if (ts.isThrowStatement(statement)) {
1353
+ const id = this.addErrorNode("Error", parentId);
1354
+ this.connect(incoming, id);
1355
+ return [];
1356
+ }
1357
+ if (ts.isExpressionStatement(statement)) return this.walkValue(statement.expression, incoming, ctxParamName, parentId, path, visiting);
1358
+ if (ts.isVariableStatement(statement)) {
1359
+ let frontier = incoming;
1360
+ for (const decl of statement.declarationList.declarations) {
1361
+ if (!decl.initializer) continue;
1362
+ const deferredCall = deferredHookSleepCall(decl.initializer, ctxParamName);
1363
+ if (deferredCall && ts.isIdentifier(decl.name) && statements.length > 0) {
1364
+ const awaitPath = findDeferredAwaitPath(statements, statementIndex, path.slice(0, -1), decl.name.text);
1365
+ if (awaitPath) {
1366
+ const step = classifyCall(decl.initializer, ctxParamName);
1367
+ if (step) {
1368
+ const callPath = this.peekCallPath(awaitPath);
1369
+ this.pendingDeferredSteps.set(decl.name.text, {
1370
+ step,
1371
+ expression: deferredCall,
1372
+ callPath
1373
+ });
1374
+ recordDeclarationBinding(decl.name, callSiteId(callPath), this.bindingScope);
1375
+ }
1376
+ continue;
1377
+ }
1378
+ }
1379
+ const isStep = classifyCall(decl.initializer, ctxParamName) !== null;
1380
+ frontier = this.walkValue(decl.initializer, frontier, ctxParamName, parentId, path, visiting);
1381
+ if (isStep && frontier.length === 1) recordDeclarationBinding(decl.name, frontier[0].nodeId, this.bindingScope);
1382
+ }
1383
+ return frontier;
1384
+ }
1385
+ if (ts.isIfStatement(statement)) return this.walkIf(statement, incoming, ctxParamName, parentId, path, visiting);
1386
+ if (ts.isForStatement(statement) || ts.isForOfStatement(statement) || ts.isForInStatement(statement) || ts.isWhileStatement(statement) || ts.isDoStatement(statement)) return this.walkLoop(statement, incoming, ctxParamName, parentId, path, visiting);
1387
+ if (ts.isSwitchStatement(statement)) return this.walkSwitch(statement, incoming, ctxParamName, parentId, path, visiting);
1388
+ if (ts.isBlock(statement)) return this.walkStatements([...statement.statements], incoming, ctxParamName, parentId, path, visiting);
1389
+ if (ts.isTryStatement(statement)) return this.walkTry(statement, incoming, ctxParamName, parentId, path, visiting);
1390
+ if (this.subtreeHasStep(statement, ctxParamName)) {
1391
+ const contained = collectContainedCallSiteIds(statement, ctxParamName, this.localFunctions, path);
1392
+ const id = this.addCodeBlock(truncate(statement.getText(this.sourceFile)), path, parentId, contained);
1393
+ this.connect(incoming, id);
1394
+ return [{ nodeId: id }];
1395
+ }
1396
+ return incoming;
1397
+ }
1398
+ walkValue(expression, incoming, ctxParamName, parentId, path, visiting = /* @__PURE__ */ new Set(), tailPosition = false) {
1399
+ const helperWalk = this.tryWalkLocalHelper(expression, incoming, ctxParamName, parentId, path, visiting, tailPosition);
1400
+ if (helperWalk) return helperWalk;
1401
+ if (ts.isConditionalExpression(expression) && this.subtreeHasStep(expression, ctxParamName)) return this.walkConditional(expression, incoming, ctxParamName, parentId, path, visiting, tailPosition);
1402
+ const parallel = asPromiseAll(expression);
1403
+ if (parallel) return this.walkParallel(parallel, incoming, ctxParamName, parentId, path, visiting);
1404
+ const deferredAwait = this.tryMaterializeDeferredAwait(expression, incoming, parentId);
1405
+ if (deferredAwait) return deferredAwait;
1406
+ const step = classifyCall(expression, ctxParamName);
1407
+ if (step) {
1408
+ const id = this.addStep(step, expression, this.nextCallPath(path), parentId);
1409
+ this.connect(incoming, id);
1410
+ return [{ nodeId: id }];
1411
+ }
1412
+ if (this.subtreeHasStep(expression, ctxParamName)) {
1413
+ const contained = collectContainedCallSiteIds(expression, ctxParamName, this.localFunctions, path);
1414
+ const id = this.addCodeBlock(truncate(expression.getText(this.sourceFile)), path, parentId, contained);
1415
+ this.connect(incoming, id);
1416
+ return [{ nodeId: id }];
1417
+ }
1418
+ return incoming;
1419
+ }
1420
+ walkConcise(expression, incoming, ctxParamName, path) {
1421
+ const frontier = this.walkValue(expression, incoming, ctxParamName, void 0, path, void 0, true);
1422
+ this.connect(frontier, this.getExit());
1423
+ return [];
1424
+ }
1425
+ walkReturn(statement, incoming, ctxParamName, parentId, path, visiting) {
1426
+ if (this.returnAsValue) return statement.expression ? this.walkValue(statement.expression, incoming, ctxParamName, parentId, path, visiting) : incoming;
1427
+ let frontier = incoming;
1428
+ if (statement.expression) frontier = this.walkValue(statement.expression, frontier, ctxParamName, parentId, path, visiting, true);
1429
+ const target = this.inBranchDepth > 0 ? this.addOutputNode(parentId) : this.getExit();
1430
+ this.connect(frontier, target);
1431
+ return [];
1432
+ }
1433
+ walkParallel(elements, incoming, ctxParamName, parentId, path, visiting) {
1434
+ const { groupId, groupStartId } = this.addGroup("parallel", "Parallel", parentId);
1435
+ this.connect(incoming, groupId);
1436
+ for (const [index, element] of elements.entries()) {
1437
+ const branchPath = [...path, `parallel:${index}`];
1438
+ if (!classifyCall(element, ctxParamName) && !this.subtreeHasStep(element, ctxParamName)) continue;
1439
+ this.walkValue(element, [{
1440
+ nodeId: groupStartId,
1441
+ branchKind: "parallel-branch"
1442
+ }], ctxParamName, groupId, branchPath, visiting);
1443
+ }
1444
+ return [{ nodeId: groupId }];
1445
+ }
1446
+ walkIf(statement, incoming, ctxParamName, parentId, path, visiting) {
1447
+ this.inBranchDepth++;
1448
+ const conditions = [];
1449
+ let elseBranch;
1450
+ let current = statement;
1451
+ while (current) {
1452
+ conditions.push({
1453
+ handle: `cond-${conditions.length}`,
1454
+ label: truncate(formatConditionLabel(current.expression, this.sourceFile)),
1455
+ branch: current.thenStatement
1456
+ });
1457
+ const next = current.elseStatement;
1458
+ if (next && ts.isIfStatement(next)) {
1459
+ current = next;
1460
+ continue;
1461
+ }
1462
+ elseBranch = next;
1463
+ current = void 0;
1464
+ }
1465
+ const outputs = [...conditions.map(({ handle, label }) => ({
1466
+ id: handle,
1467
+ label
1468
+ })), {
1469
+ id: "else",
1470
+ label: "Else"
1471
+ }];
1472
+ const decisionId = this.addDecision(truncate(formatConditionLabel(statement.expression, this.sourceFile)), outputs, parentId);
1473
+ this.connect(incoming, decisionId);
1474
+ const branchOpens = conditions.map(({ handle, branch }) => this.walkStatement(branch, [{
1475
+ nodeId: decisionId,
1476
+ branchKind: "then",
1477
+ sourceHandle: handle
1478
+ }], ctxParamName, parentId, [...path, `if:${handle}`], visiting));
1479
+ const elseOpen = elseBranch ? this.walkStatement(elseBranch, [{
1480
+ nodeId: decisionId,
1481
+ branchKind: "else",
1482
+ sourceHandle: "else"
1483
+ }], ctxParamName, parentId, [...path, "if:else"], visiting) : [{
1484
+ nodeId: decisionId,
1485
+ branchKind: "else",
1486
+ sourceHandle: "else"
1487
+ }];
1488
+ this.inBranchDepth--;
1489
+ return this.mergeBranches([...branchOpens, elseOpen], parentId);
1490
+ }
1491
+ walkConditional(expression, incoming, ctxParamName, parentId, path, visiting, tailPosition) {
1492
+ const label = truncate(formatConditionLabel(expression.condition, this.sourceFile));
1493
+ const outputs = [{
1494
+ id: "cond-0",
1495
+ label
1496
+ }, {
1497
+ id: "else",
1498
+ label: "Else"
1499
+ }];
1500
+ const decisionId = this.addDecision(label, outputs, parentId);
1501
+ this.connect(incoming, decisionId);
1502
+ const thenOpen = this.walkValue(expression.whenTrue, [{
1503
+ nodeId: decisionId,
1504
+ branchKind: "then",
1505
+ sourceHandle: "cond-0"
1506
+ }], ctxParamName, parentId, [...path, "cond:then"], visiting, tailPosition);
1507
+ const elseOpen = this.walkValue(expression.whenFalse, [{
1508
+ nodeId: decisionId,
1509
+ branchKind: "else",
1510
+ sourceHandle: "else"
1511
+ }], ctxParamName, parentId, [...path, "cond:else"], visiting, tailPosition);
1512
+ return this.mergeBranches([thenOpen, elseOpen], parentId);
1513
+ }
1514
+ walkLoop(statement, incoming, ctxParamName, parentId, path, visiting) {
1515
+ this.inBranchDepth++;
1516
+ const label = ts.isForOfStatement(statement) ? "For each item" : "Loop";
1517
+ const { groupId, groupStartId } = this.addGroup("loop", label, parentId);
1518
+ this.connect(incoming, groupId);
1519
+ this.walkStatement(statement.statement, [{ nodeId: groupStartId }], ctxParamName, groupId, [...path, "loop"], visiting);
1520
+ this.inBranchDepth--;
1521
+ return [{ nodeId: groupId }];
1522
+ }
1523
+ walkSwitch(statement, incoming, ctxParamName, parentId, path, visiting) {
1524
+ this.inBranchDepth++;
1525
+ const clauses = statement.caseBlock.clauses;
1526
+ const outputs = clauses.map((clause, index) => ts.isCaseClause(clause) ? {
1527
+ id: `case-${index}`,
1528
+ label: truncate(clause.expression.getText(this.sourceFile))
1529
+ } : {
1530
+ id: "default",
1531
+ label: "default"
1532
+ });
1533
+ const decisionId = this.addDecision(truncate(formatConditionLabel(statement.expression, this.sourceFile)), outputs, parentId);
1534
+ this.connect(incoming, decisionId);
1535
+ const branches = clauses.map((clause, index) => {
1536
+ const handle = ts.isCaseClause(clause) ? `case-${index}` : "default";
1537
+ return this.walkStatements([...clause.statements], [{
1538
+ nodeId: decisionId,
1539
+ sourceHandle: outputs[index].id,
1540
+ branchKind: ts.isCaseClause(clause) ? "then" : "else"
1541
+ }], ctxParamName, parentId, [...path, `switch:${handle}`], visiting);
1542
+ });
1543
+ this.inBranchDepth--;
1544
+ return this.mergeBranches(branches, parentId);
1545
+ }
1546
+ walkTry(statement, incoming, ctxParamName, parentId, path, visiting) {
1547
+ this.inBranchDepth++;
1548
+ const beforeTry = this.nodes.length;
1549
+ const tryOpen = this.walkStatements([...statement.tryBlock.statements], incoming, ctxParamName, parentId, [...path, "try"], visiting);
1550
+ const protectedEntry = this.nodes[beforeTry];
1551
+ const protectedEntryId = protectedEntry?.id;
1552
+ const protectedEntryIsStep = protectedEntry?.nodeType === "step";
1553
+ let frontier = tryOpen;
1554
+ const catchBlock = statement.catchClause?.block;
1555
+ if (catchBlock && this.subtreeHasStep(catchBlock, ctxParamName)) if (protectedEntryId && protectedEntryIsStep) {
1556
+ const errorOpen = this.walkStatements([...catchBlock.statements], [{
1557
+ nodeId: protectedEntryId,
1558
+ sourceHandle: "error",
1559
+ branchKind: "error",
1560
+ label: "on error"
1561
+ }], ctxParamName, parentId, [...path, "catch"], visiting);
1562
+ frontier = this.mergeBranches([tryOpen, errorOpen], parentId);
1563
+ } else frontier = this.walkStatements([...catchBlock.statements], tryOpen, ctxParamName, parentId, [...path, "catch"], visiting);
1564
+ if (statement.finallyBlock) frontier = this.walkStatements([...statement.finallyBlock.statements], frontier, ctxParamName, parentId, [...path, "finally"], visiting);
1565
+ this.inBranchDepth--;
1566
+ return frontier;
1567
+ }
1568
+ mergeBranches(branches, parentId) {
1569
+ const open = branches.flat();
1570
+ if (open.length === 0) return [];
1571
+ const mergeId = this.addStructural("Merge", parentId);
1572
+ this.connect(open, mergeId);
1573
+ return [{ nodeId: mergeId }];
1574
+ }
1575
+ };
1576
+ /** Build a workflow-canvas graph (entry → control flow → exit) from a located workflow. */
1577
+ function buildGraph(sourceFile, located, options) {
1578
+ return new GraphBuilder(sourceFile, options?.resolveSlug).build(located);
1579
+ }
1580
+ function isDefineWorkflowCallee(expr) {
1581
+ if (ts.isIdentifier(expr)) return expr.text === "defineWorkflow";
1582
+ if (ts.isPropertyAccessExpression(expr)) return expr.name.text === "defineWorkflow";
1583
+ return false;
1584
+ }
1585
+ function readRunParamCtxName(params) {
1586
+ const ctxParam = params[1];
1587
+ if (ctxParam && ts.isIdentifier(ctxParam.name)) return ctxParam.name.text;
1588
+ }
1589
+ function readRunParamInputName(params) {
1590
+ const inputParam = params[0];
1591
+ if (inputParam && ts.isIdentifier(inputParam.name)) return inputParam.name.text;
1592
+ }
1593
+ function readWorkflowObject(obj) {
1594
+ let slug;
1595
+ let body;
1596
+ let ctxParamName;
1597
+ let inputParamName;
1598
+ for (const prop of obj.properties) {
1599
+ const name = prop.name && (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) ? prop.name.text : void 0;
1600
+ if (name === "slug" && ts.isPropertyAssignment(prop) && ts.isStringLiteral(prop.initializer)) {
1601
+ slug = prop.initializer.text;
1602
+ continue;
1603
+ }
1604
+ if (name !== "run") continue;
1605
+ if (ts.isMethodDeclaration(prop) && prop.body) {
1606
+ body = prop.body;
1607
+ inputParamName = readRunParamInputName(prop.parameters);
1608
+ ctxParamName = readRunParamCtxName(prop.parameters);
1609
+ continue;
1610
+ }
1611
+ if (ts.isPropertyAssignment(prop)) {
1612
+ const init = prop.initializer;
1613
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
1614
+ body = init.body;
1615
+ inputParamName = readRunParamInputName(init.parameters);
1616
+ ctxParamName = readRunParamCtxName(init.parameters);
1617
+ }
1618
+ }
1619
+ }
1620
+ if (!body) return null;
1621
+ return {
1622
+ slug,
1623
+ body,
1624
+ inputParamName,
1625
+ ctxParamName
1626
+ };
1627
+ }
1628
+ /**
1629
+ * Find the `defineWorkflow({ ... })` call in a source file and extract its `run`
1630
+ * body. Returns null when the file has no recognizable workflow definition.
1631
+ */
1632
+ function locateWorkflow(sourceFile) {
1633
+ let located = null;
1634
+ const visit = (node) => {
1635
+ if (located) return;
1636
+ if (ts.isCallExpression(node) && isDefineWorkflowCallee(node.expression)) {
1637
+ const [arg] = node.arguments;
1638
+ if (arg && ts.isObjectLiteralExpression(arg)) {
1639
+ located = readWorkflowObject(arg);
1640
+ if (located) return;
1641
+ }
1642
+ }
1643
+ ts.forEachChild(node, visit);
1644
+ };
1645
+ visit(sourceFile);
1646
+ return located;
1647
+ }
1648
+ /**
1649
+ * Maps each durable call site in a workflow `run` body to its deterministic
1650
+ * {@link callSiteId} — the SAME id the graph producer mints for the corresponding
1651
+ * step node. The Phase 5 build transform consumes this to inject the id into the
1652
+ * compiled call so the runtime emits `step:<callSiteId>#<occurrence>`, lining run
1653
+ * events and credential consumers up 1:1 with canvas nodes.
1654
+ *
1655
+ * Keyed by the outermost durable {@link ts.CallExpression} (after unwrapping
1656
+ * `await`/parens) — i.e. the node the transform rewrites: `x.run(i)`,
1657
+ * `x.scope(s).run(i)`, `x.run(i).scope(s)`, `promptLlm(i, opts)`,
1658
+ * `ctx.sleep(d)`, `ctx.hook(opts)`.
1659
+ *
1660
+ * Shares the {@link walkCallSites} traversal with the contained-id collector so
1661
+ * the structural `callSiteId` invariant cannot drift between the two.
1662
+ */
1663
+ function computeCallSiteIds(sourceFile) {
1664
+ const located = locateWorkflow(sourceFile);
1665
+ const map = /* @__PURE__ */ new Map();
1666
+ if (!located) return map;
1667
+ walkCallSites(located.body, [RUN_BODY_SEGMENT], {
1668
+ ctxParamName: located.ctxParamName,
1669
+ localFunctions: collectLocalFunctions(sourceFile)
1670
+ }, (call, callPath) => {
1671
+ map.set(call, callSiteId(callPath));
1672
+ });
1673
+ return map;
1674
+ }
1675
+ /**
1676
+ * Static reliability checks over a single workflow (or would-be helper) source
1677
+ * file. ERRORS block a deploy (they produce an invisible/misleading canvas):
1678
+ * durable steps outside a workflow file, and steps nested as call arguments.
1679
+ * WARNINGS explain each degraded (opaque code-block) construct so the author can
1680
+ * opt into the supported grammar.
1681
+ */
1682
+ function diagnoseWorkflowSource(source, fileName) {
1683
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1684
+ const located = locateWorkflow(sourceFile);
1685
+ if (!located) return crossFileStepDiagnostics(sourceFile, fileName);
1686
+ const shared = sharedHelperNames(located, collectLocalFunctions(sourceFile));
1687
+ return [
1688
+ ...nestedStepArgDiagnostics(sourceFile, fileName, located.ctxParamName),
1689
+ ...stepInputSpreadDiagnostics(sourceFile, fileName, located.ctxParamName),
1690
+ ...opaqueCodeBlockDiagnostics(sourceFile, located, fileName, shared)
1691
+ ];
1692
+ }
1693
+ function lineColumn(sourceFile, node) {
1694
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
1695
+ return {
1696
+ line: line + 1,
1697
+ column: character + 1
1698
+ };
1699
+ }
1700
+ function snippet(sourceFile, node) {
1701
+ const text = node.getText(sourceFile).replace(/\s+/g, " ").trim();
1702
+ return text.length > 80 ? `${text.slice(0, 79)}…` : text;
1703
+ }
1704
+ /** Imported identifier name → its module specifier (`@keystrokehq/gmail/actions`, `../actions/x`). */
1705
+ function collectImportsBySymbol(sourceFile) {
1706
+ const bySymbol = /* @__PURE__ */ new Map();
1707
+ for (const statement of sourceFile.statements) {
1708
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
1709
+ const specifier = statement.moduleSpecifier.text;
1710
+ const clause = statement.importClause;
1711
+ if (!clause) continue;
1712
+ if (clause.name) bySymbol.set(clause.name.text, specifier);
1713
+ if (clause.namedBindings) {
1714
+ if (ts.isNamedImports(clause.namedBindings)) for (const element of clause.namedBindings.elements) bySymbol.set(element.name.text, specifier);
1715
+ else if (ts.isNamespaceImport(clause.namedBindings)) bySymbol.set(clause.namedBindings.name.text, specifier);
1716
+ }
1717
+ }
1718
+ return bySymbol;
1719
+ }
1720
+ /**
1721
+ * Durable steps come from either an integration package (`@keystrokehq/<app>/...`)
1722
+ * or the project's own actions/agents (imported by relative path). Third-party
1723
+ * packages (drizzle `db.run()`, etc.) use bare, non-`@keystrokehq` specifiers, so
1724
+ * excluding those avoids false positives while still catching real project steps.
1725
+ */
1726
+ function isStepBearingSpecifier(specifier) {
1727
+ if (!specifier) return false;
1728
+ return specifier.startsWith("@keystrokehq/") || specifier.startsWith(".");
1729
+ }
1730
+ /** Whether `call` is the object of a `.scope(...)`/`.__site(...)` chain (an inner link). */
1731
+ function isChainedInnerCall(call) {
1732
+ const parent = call.parent;
1733
+ return ts.isPropertyAccessExpression(parent) && (parent.name.text === "scope" || parent.name.text === "__site") && parent.expression === call;
1734
+ }
1735
+ function forEachDurableRoot(sourceFile, ctxParamName, visit) {
1736
+ const walk = (node) => {
1737
+ if (ts.isCallExpression(node) && classifyCall(node, ctxParamName) && !isChainedInnerCall(node)) visit(node);
1738
+ ts.forEachChild(node, walk);
1739
+ };
1740
+ walk(sourceFile);
1741
+ }
1742
+ function isFunctionLike(node) {
1743
+ return ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node);
1744
+ }
1745
+ /** Durable step calls in a file that is not a workflow — invisible + un-injected. */
1746
+ function crossFileStepDiagnostics(sourceFile, fileName) {
1747
+ const importsBySymbol = collectImportsBySymbol(sourceFile);
1748
+ const out = [];
1749
+ const seen = /* @__PURE__ */ new Set();
1750
+ forEachDurableRoot(sourceFile, void 0, (call) => {
1751
+ const info = classifyCall(call, void 0);
1752
+ if (!info) return;
1753
+ if (!(info.callKind === "llm" || (info.callKind === "workflow-step" || info.callKind === "agent") && info.importName !== void 0 && isStepBearingSpecifier(importsBySymbol.get(info.importName)))) return;
1754
+ const { line, column } = lineColumn(sourceFile, call);
1755
+ const dedupe = `${line}:${column}`;
1756
+ if (seen.has(dedupe)) return;
1757
+ seen.add(dedupe);
1758
+ out.push({
1759
+ severity: "error",
1760
+ code: "cross-file-step",
1761
+ fileName,
1762
+ line,
1763
+ column,
1764
+ message: `Durable step call \`${snippet(sourceFile, call)}\` is outside a workflow file. Steps only run and render on the canvas when they live in a \`defineWorkflow\` file (its \`run\` body or a same-file helper). This step is invisible to the canvas and cannot be attributed to runs. Move it into the workflow, or keep this module pure (return data; let the workflow call the step).`
1765
+ });
1766
+ });
1767
+ return out;
1768
+ }
1769
+ /** Durable step calls nested as an argument to another call — the inner step is dropped. */
1770
+ function nestedStepArgDiagnostics(sourceFile, fileName, ctxParamName) {
1771
+ const out = [];
1772
+ const seen = /* @__PURE__ */ new Set();
1773
+ forEachDurableRoot(sourceFile, ctxParamName, (call) => {
1774
+ let node = call;
1775
+ let parent = node.parent;
1776
+ while (parent && !ts.isSourceFile(parent)) {
1777
+ if (isFunctionLike(parent)) return;
1778
+ if (ts.isCallExpression(parent) && parent.arguments.includes(node)) {
1779
+ if (asPromiseAll(parent)) return;
1780
+ const { line, column } = lineColumn(sourceFile, call);
1781
+ const dedupe = `${line}:${column}`;
1782
+ if (!seen.has(dedupe)) {
1783
+ seen.add(dedupe);
1784
+ out.push({
1785
+ severity: "error",
1786
+ code: "nested-step-arg",
1787
+ fileName,
1788
+ line,
1789
+ column,
1790
+ message: `Durable step call \`${snippet(sourceFile, call)}\` is nested as an argument to \`${snippet(sourceFile, parent.expression)}(…)\`. The nested step is dropped from the canvas (and un-attributable in runs). Assign it to a variable first, then pass the variable: \`const r = await step.run(…); other.run({ x: r });\`.`
1791
+ });
1792
+ }
1793
+ return;
1794
+ }
1795
+ if (ts.isStatement(parent)) return;
1796
+ node = parent;
1797
+ parent = parent.parent;
1798
+ }
1799
+ });
1800
+ return out;
1801
+ }
1802
+ /** Spread / computed keys in a step input object — silently missing from the inspector. */
1803
+ function stepInputSpreadDiagnostics(sourceFile, fileName, ctxParamName) {
1804
+ const out = [];
1805
+ forEachDurableRoot(sourceFile, ctxParamName, (call) => {
1806
+ const arg = extractStepInputArg(call);
1807
+ if (!arg || !ts.isObjectLiteralExpression(arg)) return;
1808
+ for (const prop of arg.properties) {
1809
+ const isSpread = ts.isSpreadAssignment(prop);
1810
+ const isComputed = ts.isPropertyAssignment(prop) && ts.isComputedPropertyName(prop.name);
1811
+ if (!isSpread && !isComputed) continue;
1812
+ const { line, column } = lineColumn(sourceFile, prop);
1813
+ out.push({
1814
+ severity: "warning",
1815
+ code: "step-input-spread",
1816
+ fileName,
1817
+ line,
1818
+ column,
1819
+ message: `${isSpread ? "Spread" : "Computed-key"} property \`${snippet(sourceFile, prop)}\` in a step input isn't shown in the step inspector. List the fields explicitly so each input renders as a value chip.`
1820
+ });
1821
+ }
1822
+ });
1823
+ return out;
1824
+ }
1825
+ function opaqueCodeBlockReason(label, shared) {
1826
+ if (/\bPromise\.(race|allSettled|any)\b/.test(label)) return "Promise.race/allSettled/any isn't modeled as branches; it renders opaque.";
1827
+ if (/\bPromise\.all\b/.test(label)) return "Promise.all over a computed array (e.g. `.map(...)`) can't be shown as parallel branches. Pass an array literal of explicit step calls, or use a for-of loop.";
1828
+ if (/\.(map|forEach|filter|reduce|flatMap)\s*\(/.test(label)) return "Array-method iteration (`.map`/`.filter`/`.forEach`) containing steps renders as one opaque block. Use a for-of loop to render an explicit loop.";
1829
+ const helperMatch = label.match(/(?:await\s+)?([A-Za-z0-9_$]+)\s*\(/);
1830
+ if (helperMatch && shared.has(helperMatch[1])) return `Helper \`${helperMatch[1]}\` is called from more than one site, so each call renders as an opaque block (its steps share one id and can't be shown individually). Inline it, or call the steps directly in \`run()\`.`;
1831
+ return "This construct is outside the supported grammar and renders as an opaque block; the steps inside it aren't shown individually.";
1832
+ }
1833
+ /** One warning per opaque code-block the producer emits, with an explicit reason. */
1834
+ function opaqueCodeBlockDiagnostics(sourceFile, located, fileName, shared) {
1835
+ let graph;
1836
+ try {
1837
+ graph = buildGraph(sourceFile, located);
1838
+ } catch {
1839
+ return [];
1840
+ }
1841
+ return graph.nodes.filter((node) => node.nodeType === "code-block").map((node) => {
1842
+ const label = node.data.label ?? "";
1843
+ return {
1844
+ severity: "warning",
1845
+ code: "opaque-code-block",
1846
+ fileName,
1847
+ message: `Renders as an opaque code block: \`${label}\`. ${opaqueCodeBlockReason(label, shared)}`
1848
+ };
1849
+ });
1850
+ }
1851
+ /**
1852
+ * Deterministic deploy-time producer: parse a workflow's TypeScript source and
1853
+ * emit its control-flow skeleton (entry → steps/branches/loops → exit) as a
1854
+ * {@link WorkflowCanvasGraph}.
1855
+ */
1856
+ function buildWorkflowCanvasGraph(source, options = {}) {
1857
+ const sourceFile = ts.createSourceFile(options.fileName ?? "workflow.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1858
+ const located = locateWorkflow(sourceFile);
1859
+ if (!located) return null;
1860
+ return buildGraph(sourceFile, located, { resolveSlug: options.resolveSlug });
1861
+ }
340
1862
  const replaceEditSchema = object({
341
1863
  oldText: string().describe("Exact text to replace. Must be unique in the file and must not overlap other edits in this call."),
342
1864
  newText: string().describe("Replacement text")
@@ -1669,25 +3191,49 @@ const workflows = pgTable("workflows", {
1669
3191
  id: text("id").primaryKey(),
1670
3192
  projectId: text("project_id").notNull(),
1671
3193
  slug: text("slug").notNull(),
1672
- name: text("name"),
1673
- description: text("description"),
3194
+ name: text("name").notNull(),
3195
+ description: text("description").notNull(),
1674
3196
  moduleFile: text("module_file").notNull(),
1675
3197
  subscribable: integer("subscribable").notNull(),
1676
3198
  registeredAt: timestamp("registered_at", { withTimezone: true }).notNull(),
1677
3199
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(),
1678
- deletedAt: timestamp("deleted_at", { withTimezone: true })
3200
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
3201
+ runInputValues: jsonb("run_input_values").$type(),
3202
+ canvasAnnotationsJson: jsonb("canvas_annotations_json").$type(),
3203
+ canvasAnnotationsHash: text("canvas_annotations_hash"),
3204
+ canvasAnnotationsModel: text("canvas_annotations_model"),
3205
+ canvasAnnotationsStatus: text("canvas_annotations_status").$type().notNull().default("idle"),
3206
+ canvasAnnotationsGeneratedAt: timestamp("canvas_annotations_generated_at", { withTimezone: true }),
3207
+ overviewSummary: text("overview_summary"),
3208
+ overviewPhasesJson: jsonb("overview_phases_json").$type(),
3209
+ overviewHash: text("overview_hash"),
3210
+ overviewModel: text("overview_model"),
3211
+ overviewStatus: text("overview_status").$type().notNull().default("idle"),
3212
+ overviewGeneratedAt: timestamp("overview_generated_at", { withTimezone: true })
1679
3213
  }, (table) => [uniqueIndex("workflows_project_id_slug_idx").on(table.projectId, table.slug)]);
1680
3214
  const workflowsSqlite = sqliteTable("workflows", {
1681
3215
  id: text$1("id").primaryKey(),
1682
3216
  projectId: text$1("project_id").notNull(),
1683
3217
  slug: text$1("slug").notNull(),
1684
- name: text$1("name"),
1685
- description: text$1("description"),
3218
+ name: text$1("name").notNull(),
3219
+ description: text$1("description").notNull(),
1686
3220
  moduleFile: text$1("module_file").notNull(),
1687
3221
  subscribable: integer$1("subscribable", { mode: "boolean" }).notNull(),
1688
3222
  registeredAt: integer$1("registered_at", { mode: "timestamp_ms" }).notNull(),
1689
3223
  updatedAt: integer$1("updated_at", { mode: "timestamp_ms" }).notNull(),
1690
- deletedAt: integer$1("deleted_at", { mode: "timestamp_ms" })
3224
+ deletedAt: integer$1("deleted_at", { mode: "timestamp_ms" }),
3225
+ runInputValues: text$1("run_input_values", { mode: "json" }).$type(),
3226
+ canvasAnnotationsJson: text$1("canvas_annotations_json", { mode: "json" }).$type(),
3227
+ canvasAnnotationsHash: text$1("canvas_annotations_hash"),
3228
+ canvasAnnotationsModel: text$1("canvas_annotations_model"),
3229
+ canvasAnnotationsStatus: text$1("canvas_annotations_status").$type().notNull().default("idle"),
3230
+ canvasAnnotationsGeneratedAt: integer$1("canvas_annotations_generated_at", { mode: "timestamp_ms" }),
3231
+ overviewSummary: text$1("overview_summary"),
3232
+ overviewPhasesJson: text$1("overview_phases_json", { mode: "json" }).$type(),
3233
+ overviewHash: text$1("overview_hash"),
3234
+ overviewModel: text$1("overview_model"),
3235
+ overviewStatus: text$1("overview_status").$type().notNull().default("idle"),
3236
+ overviewGeneratedAt: integer$1("overview_generated_at", { mode: "timestamp_ms" })
1691
3237
  }, (table) => [uniqueIndex$1("workflows_project_id_slug_idx").on(table.projectId, table.slug)]);
1692
3238
  const resourceActivity = pgTable("resource_activity", {
1693
3239
  id: text("id").primaryKey(),
@@ -1831,15 +3377,29 @@ object({
1831
3377
  //#endregion
1832
3378
  //#region ../../packages/action/dist/index.mjs
1833
3379
  const zodSchema$3 = custom((v) => v instanceof ZodType, "must be a Zod schema");
1834
- object({
3380
+ /** Runtime validation for an unbranded action definition. */
3381
+ const actionCoreSchema = object({
1835
3382
  slug: string().trim().min(1),
1836
- name: string().optional(),
1837
- description: string().optional(),
3383
+ name: requiredDisplayTextSchema,
3384
+ description: requiredDisplayTextSchema,
1838
3385
  input: zodSchema$3,
1839
3386
  output: zodSchema$3,
1840
3387
  credentials: array(credentialInputSchema).optional(),
1841
3388
  run: _function()
1842
3389
  });
3390
+ const ACTION$1 = Symbol.for("keystroke.action");
3391
+ /**
3392
+ * Validates brand + shape via `actionCoreSchema` so discovery and guards reject
3393
+ * malformed definitions.
3394
+ */
3395
+ function isAction(value) {
3396
+ if (typeof value !== "object" || value === null) return false;
3397
+ if (!(ACTION$1 in value) || value[ACTION$1] !== true) return false;
3398
+ return actionCoreSchema.safeParse(value).success;
3399
+ }
3400
+ function getActionCredentialRequirements(action) {
3401
+ return action.credentials;
3402
+ }
1843
3403
  new AsyncLocalStorage();
1844
3404
  new AsyncLocalStorage();
1845
3405
  new AbortController().signal;
@@ -18982,8 +20542,8 @@ const zodSchema$2 = custom((v) => v instanceof ZodType, "must be a Zod schema");
18982
20542
  /** Runtime validation for an unbranded workflow definition. */
18983
20543
  const workflowCoreSchema = object({
18984
20544
  slug: string().trim().min(1),
18985
- name: string().optional(),
18986
- description: string().optional(),
20545
+ name: requiredDisplayTextSchema,
20546
+ description: requiredDisplayTextSchema,
18987
20547
  subscription: object({ mode: _enum(["system", "subscribable"]).optional() }).optional(),
18988
20548
  input: zodSchema$2,
18989
20549
  output: zodSchema$2,
@@ -18999,6 +20559,7 @@ function isWorkflow(value) {
18999
20559
  if (!(WORKFLOW$1 in value) || value[WORKFLOW$1] !== true) return false;
19000
20560
  return workflowCoreSchema.safeParse(value).success;
19001
20561
  }
20562
+ new AsyncLocalStorage();
19002
20563
  const storage = new AsyncLocalStorage();
19003
20564
  registerWorkflowRunGetter(() => {
19004
20565
  const store = storage.getStore();
@@ -19808,11 +21369,12 @@ function normalizeLegacySlugFields$1(value) {
19808
21369
  return next;
19809
21370
  }
19810
21371
  const slugField$1 = string().trim().min(1);
19811
- const optionalTextField$1 = string().optional();
21372
+ const requiredTextField$1 = string().trim().min(1);
21373
+ const optionalTextField$1 = string().trim().min(1).optional();
19812
21374
  const baseSourceShape$1 = {
19813
21375
  slug: slugField$1,
19814
- name: optionalTextField$1,
19815
- description: optionalTextField$1,
21376
+ name: requiredTextField$1,
21377
+ description: requiredTextField$1,
19816
21378
  attach: _function()
19817
21379
  };
19818
21380
  /** Runtime validation for a trigger source (webhook | cron | poll). */
@@ -19887,11 +21449,9 @@ function attachmentSlugFromRecord(attachment) {
19887
21449
  return slug;
19888
21450
  }
19889
21451
  function triggerMetaFromAttachment(attachment) {
19890
- const name = attachment.name ?? attachment.source.name;
19891
- const description = attachment.description ?? attachment.source.description;
19892
21452
  return {
19893
- ...name !== void 0 ? { name } : {},
19894
- ...description !== void 0 ? { description } : {}
21453
+ name: attachment.name ?? attachment.source.name,
21454
+ description: attachment.description ?? attachment.source.description
19895
21455
  };
19896
21456
  }
19897
21457
  /**
@@ -19920,6 +21480,8 @@ function isManifestAgent(value) {
19920
21480
  }
19921
21481
  function validateManifestAgent(value, filePath) {
19922
21482
  if (!isManifestAgent(value)) throw new Error(`${filePath} must default-export defineAgent(...)`);
21483
+ if (!value.name?.trim()) throw new Error(`${filePath} agent must include a non-empty name`);
21484
+ if (!value.description?.trim()) throw new Error(`${filePath} agent must include a non-empty description`);
19923
21485
  return value;
19924
21486
  }
19925
21487
  function normalizeWebhookEndpoint(endpoint) {
@@ -19978,6 +21540,30 @@ function actionSlug(tool) {
19978
21540
  const record = tool;
19979
21541
  return typeof record.slug === "string" ? record.slug : void 0;
19980
21542
  }
21543
+ function toolCredentialRequirements(tool) {
21544
+ const record = tool;
21545
+ if (isManifestAction(tool)) return getManifestActionCredentialRequirements(tool);
21546
+ return "credentials" in record && Array.isArray(record.credentials) ? record.credentials : void 0;
21547
+ }
21548
+ /**
21549
+ * Credential requirements per tool slug — the agent's credential consumer key.
21550
+ * Powers the workflow-canvas credential overlay for agent nodes (which resolve
21551
+ * per tool, not per call site).
21552
+ */
21553
+ function collectAgentToolRequirements(agent) {
21554
+ const byTool = {};
21555
+ for (const tool of agent.tools ?? []) {
21556
+ const slug = actionSlug(tool);
21557
+ const requirements = toolCredentialRequirements(tool);
21558
+ if (!slug || !requirements?.length) continue;
21559
+ byTool[slug] = normalizeCredentialList(requirements).map((requirement) => ({
21560
+ key: requirement.key,
21561
+ kind: requirement.kind,
21562
+ ...requirement.scope ? { scope: requirement.scope } : {}
21563
+ }));
21564
+ }
21565
+ return byTool;
21566
+ }
19981
21567
  /** App-kind slugs required by the agent's tools (same as `credential_instances.app_slug`). */
19982
21568
  function collectAgentAppSlugs(agent) {
19983
21569
  const slugs = /* @__PURE__ */ new Set();
@@ -20002,6 +21588,7 @@ function countAgentCredentials(agent) {
20002
21588
  }
20003
21589
  /** Single source of truth for the `kind: "agent"` route-manifest entry shape. */
20004
21590
  function agentManifestEntry(agent, options) {
21591
+ const toolRequirements = collectAgentToolRequirements(agent);
20005
21592
  return {
20006
21593
  kind: "agent",
20007
21594
  slug: agent.slug,
@@ -20013,7 +21600,69 @@ function agentManifestEntry(agent, options) {
20013
21600
  toolCount: agent.tools?.length ?? 0,
20014
21601
  credentialCount: countAgentCredentials(agent),
20015
21602
  appSlugs: collectAgentAppSlugs(agent),
20016
- toolSlugs: collectAgentToolSlugs(agent)
21603
+ toolSlugs: collectAgentToolSlugs(agent),
21604
+ ...Object.keys(toolRequirements).length ? { toolRequirements } : {}
21605
+ };
21606
+ }
21607
+ function resolveActionFromModule(mod, filePath) {
21608
+ if (isAction(mod.default)) return mod.default;
21609
+ const named = Object.values(mod).filter(isAction);
21610
+ if (named.length === 1) return named[0];
21611
+ if (named.length > 1) throw new Error(`${filePath} exports multiple actions; use one action per file`);
21612
+ throw new Error(`${filePath} must export defineAction(...) (default or single named export)`);
21613
+ }
21614
+ async function importActionDefinition(filePath, options) {
21615
+ const appRoot = resolveAppRoot(filePath);
21616
+ const href = pathToFileURL(filePath).href;
21617
+ return runWithAppRoot(appRoot, async () => {
21618
+ return resolveActionFromModule(await (options?.reload ? import(`${href}?keystroke=${Date.now()}`) : import(href)), filePath);
21619
+ });
21620
+ }
21621
+ async function discoverActions(actionsDir, options) {
21622
+ const files = await discoverModuleFileEntries(actionsDir, {
21623
+ nestedEntry: "action",
21624
+ duplicateLabel: "action module file"
21625
+ });
21626
+ const actions = [];
21627
+ for (const { filePath, moduleFile } of files) {
21628
+ const definition = await importActionDefinition(filePath, options);
21629
+ const key = definition.slug.trim();
21630
+ if (!key) throw new Error(`${filePath} action must define a non-empty slug`);
21631
+ actions.push({
21632
+ key,
21633
+ filePath,
21634
+ moduleFile,
21635
+ definition
21636
+ });
21637
+ }
21638
+ return actions;
21639
+ }
21640
+ /**
21641
+ * Reduce a definition's `.credentials` list to the deploy-time summaries the
21642
+ * workflow-canvas credential overlay needs (`key` / `kind` / pinned `scope`).
21643
+ * The Zod schema is intentionally dropped — only the resolution metadata ships.
21644
+ */
21645
+ function credentialRequirementSummaries(list) {
21646
+ if (!list?.length) return [];
21647
+ return normalizeCredentialList(list).map((requirement) => ({
21648
+ key: requirement.key,
21649
+ kind: requirement.kind,
21650
+ ...requirement.scope ? { scope: requirement.scope } : {}
21651
+ }));
21652
+ }
21653
+ /** Single source of truth for the `kind: "action"` route-manifest entry shape. */
21654
+ function actionManifestEntry(action, options) {
21655
+ const requirements = credentialRequirementSummaries(getActionCredentialRequirements(action));
21656
+ return {
21657
+ kind: "action",
21658
+ slug: action.slug,
21659
+ moduleFile: options.moduleFile,
21660
+ name: action.name,
21661
+ description: action.description,
21662
+ ...options.appSlug ? { appSlug: options.appSlug } : {},
21663
+ inputSchema: options.inputSchema,
21664
+ outputSchema: options.outputSchema,
21665
+ ...requirements.length ? { requirements } : {}
20017
21666
  };
20018
21667
  }
20019
21668
  function pollGroupId(discovered) {
@@ -20153,7 +21802,7 @@ function validateProjectModules(input) {
20153
21802
  ]);
20154
21803
  }
20155
21804
  const SKIP_DIRS = new Set([".git", "node_modules"]);
20156
- function toPosix$1(path) {
21805
+ function toPosix$2(path) {
20157
21806
  return path.split(sep).join("/");
20158
21807
  }
20159
21808
  function parseSkillFrontmatter(raw) {
@@ -20174,23 +21823,26 @@ function parseSkillFrontmatter(raw) {
20174
21823
  return out;
20175
21824
  }
20176
21825
  function walkSkillFiles(root, dir, out) {
20177
- for (const name of readdirSync(dir).sort()) {
20178
- const absolute = join(dir, name);
21826
+ for (const entry of readdirSync(dir).sort()) {
21827
+ const absolute = join(dir, entry);
20179
21828
  const stats = statSync(absolute);
20180
21829
  if (stats.isDirectory()) {
20181
- if (SKIP_DIRS.has(name)) continue;
21830
+ if (SKIP_DIRS.has(entry)) continue;
20182
21831
  walkSkillFiles(root, absolute, out);
20183
21832
  continue;
20184
21833
  }
20185
- if (!stats.isFile() || name !== "SKILL.md") continue;
20186
- const moduleFile = toPosix$1(relative(root, absolute));
20187
- const slug = toPosix$1(relative(join(root, "src", "skills"), absolute)).replace(/\/?SKILL\.md$/, "");
21834
+ if (!stats.isFile() || entry !== "SKILL.md") continue;
21835
+ const moduleFile = toPosix$2(relative(root, absolute));
21836
+ const slug = toPosix$2(relative(join(root, "src", "skills"), absolute)).replace(/\/?SKILL\.md$/, "");
20188
21837
  if (!slug) continue;
20189
21838
  const frontmatter = parseSkillFrontmatter(readFileSync(absolute, "utf8"));
21839
+ const name = frontmatter.name?.trim();
21840
+ const description = frontmatter.description?.trim();
21841
+ if (!name || !description) throw new Error(`${moduleFile} skill frontmatter must include non-empty name and description`);
20190
21842
  out.push({
20191
21843
  slug,
20192
- name: frontmatter.name,
20193
- description: frontmatter.description,
21844
+ name,
21845
+ description,
20194
21846
  moduleFile
20195
21847
  });
20196
21848
  }
@@ -20227,7 +21879,21 @@ function serializeRouteManifest(manifest) {
20227
21879
  toolCount: entry.toolCount,
20228
21880
  credentialCount: entry.credentialCount,
20229
21881
  appSlugs: entry.appSlugs,
20230
- toolSlugs: entry.toolSlugs
21882
+ toolSlugs: entry.toolSlugs,
21883
+ ...entry.toolRequirements ? { toolRequirements: entry.toolRequirements } : {}
21884
+ });
21885
+ break;
21886
+ case "action":
21887
+ entries.push({
21888
+ kind: entry.kind,
21889
+ slug: entry.slug,
21890
+ moduleFile: entry.moduleFile,
21891
+ ...entry.appSlug !== void 0 ? { appSlug: entry.appSlug } : {},
21892
+ name: entry.name,
21893
+ description: entry.description,
21894
+ inputSchema: entry.inputSchema,
21895
+ outputSchema: entry.outputSchema,
21896
+ ...entry.requirements ? { requirements: entry.requirements } : {}
20231
21897
  });
20232
21898
  break;
20233
21899
  case "workflow":
@@ -20238,7 +21904,12 @@ function serializeRouteManifest(manifest) {
20238
21904
  description: entry.description,
20239
21905
  subscribable: entry.subscribable,
20240
21906
  moduleFile: entry.moduleFile,
20241
- requestSchema: schemaToJson(entry.request)
21907
+ requestSchema: schemaToJson(entry.request),
21908
+ responseSchema: schemaToJson(entry.response),
21909
+ ...entry.flowGraph ? {
21910
+ flowGraph: entry.flowGraph,
21911
+ flowGraphSourceHash: entry.flowGraphSourceHash
21912
+ } : {}
20242
21913
  });
20243
21914
  break;
20244
21915
  case "trigger-webhook":
@@ -20247,6 +21918,7 @@ function serializeRouteManifest(manifest) {
20247
21918
  endpoint: entry.endpoint,
20248
21919
  attachmentIds: entry.attachmentIds,
20249
21920
  moduleFile: entry.moduleFile,
21921
+ ...entry.sourceHash ? { sourceHash: entry.sourceHash } : {},
20250
21922
  attachmentSchemas: Object.fromEntries(Object.entries(entry.attachmentSchemas).map(([attachmentSlug, schemas]) => [attachmentSlug, {
20251
21923
  requestSchema: schemaToJson(schemas.request),
20252
21924
  ...schemas.filter ? { filterSchema: schemaToJson(schemas.filter) } : {}
@@ -20260,9 +21932,10 @@ function serializeRouteManifest(manifest) {
20260
21932
  attachmentId: entry.attachmentId,
20261
21933
  attachmentIds: entry.attachmentIds,
20262
21934
  moduleFile: entry.moduleFile,
21935
+ ...entry.sourceHash ? { sourceHash: entry.sourceHash } : {},
20263
21936
  schedule: entry.schedule,
20264
- ...entry.name !== void 0 ? { name: entry.name } : {},
20265
- ...entry.description !== void 0 ? { description: entry.description } : {}
21937
+ name: entry.name,
21938
+ description: entry.description
20266
21939
  });
20267
21940
  break;
20268
21941
  case "trigger-poll-group":
@@ -20313,6 +21986,8 @@ function isManifestWorkflow(value) {
20313
21986
  }
20314
21987
  function validateManifestWorkflow(value, filePath) {
20315
21988
  if (!isManifestWorkflow(value)) throw new Error(`${filePath} must default-export defineWorkflow(...)`);
21989
+ if (!value.name?.trim()) throw new Error(`${filePath} workflow must include a non-empty name`);
21990
+ if (!value.description?.trim()) throw new Error(`${filePath} workflow must include a non-empty description`);
20316
21991
  return value;
20317
21992
  }
20318
21993
  const TRIGGER_ATTACHMENT = Symbol.for("keystroke.triggerAttachment");
@@ -20331,11 +22006,12 @@ function normalizeLegacySlugFields(value) {
20331
22006
  return next;
20332
22007
  }
20333
22008
  const slugField = string().trim().min(1);
20334
- const optionalTextField = string().optional();
22009
+ const requiredTextField = string().trim().min(1);
22010
+ const optionalTextField = string().trim().min(1).optional();
20335
22011
  const baseSourceShape = {
20336
22012
  slug: slugField,
20337
- name: optionalTextField,
20338
- description: optionalTextField,
22013
+ name: requiredTextField,
22014
+ description: requiredTextField,
20339
22015
  attach: _function()
20340
22016
  };
20341
22017
  const triggerSourceSchema = preprocess(normalizeLegacySlugFields, discriminatedUnion("kind", [
@@ -20501,11 +22177,181 @@ async function discoverWorkflows(workflowsDir, options) {
20501
22177
  }
20502
22178
  return workflows;
20503
22179
  }
22180
+ function toPosix$1(path) {
22181
+ return path.split("\\").join("/");
22182
+ }
22183
+ function buildSymbolImportMap(source, fileName) {
22184
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
22185
+ const map = /* @__PURE__ */ new Map();
22186
+ for (const statement of sourceFile.statements) {
22187
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
22188
+ const specifier = statement.moduleSpecifier.text;
22189
+ const clause = statement.importClause;
22190
+ if (!clause) continue;
22191
+ if (clause.name) map.set(clause.name.text, {
22192
+ specifier,
22193
+ exportName: "default"
22194
+ });
22195
+ if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) for (const element of clause.namedBindings.elements) {
22196
+ const exportName = (element.propertyName ?? element.name).text;
22197
+ map.set(element.name.text, {
22198
+ specifier,
22199
+ exportName
22200
+ });
22201
+ }
22202
+ }
22203
+ return map;
22204
+ }
22205
+ function resolveRelativeModuleFile(projectRoot, workflowModuleFile, specifier) {
22206
+ if (!specifier.startsWith(".")) return;
22207
+ const workflowDir = dirname(join(projectRoot, workflowModuleFile));
22208
+ const candidates = [
22209
+ join(workflowDir, specifier),
22210
+ `${join(workflowDir, specifier)}.ts`,
22211
+ join(workflowDir, `${specifier}.ts`),
22212
+ join(workflowDir, specifier, "index.ts")
22213
+ ];
22214
+ for (const candidate of candidates) if (existsSync(candidate)) return toPosix$1(relative(projectRoot, candidate));
22215
+ }
22216
+ function toResolvedStepSlug(entry) {
22217
+ return {
22218
+ slug: entry.slug,
22219
+ name: entry.name,
22220
+ description: entry.description
22221
+ };
22222
+ }
22223
+ /** Build a deploy-time slug resolver from a workflow file's imports + definition registry. */
22224
+ function createResolveSlugFn(projectRoot, workflowModuleFile, workflowSource, registryByModuleFile, integrationRegistry) {
22225
+ const importMap = buildSymbolImportMap(workflowSource, workflowModuleFile);
22226
+ return (importName) => {
22227
+ const imported = importMap.get(importName);
22228
+ if (!imported) return;
22229
+ const { specifier, exportName } = imported;
22230
+ if (specifier.startsWith(".")) {
22231
+ const moduleFile = resolveRelativeModuleFile(projectRoot, workflowModuleFile, specifier);
22232
+ if (!moduleFile) return;
22233
+ const entry = registryByModuleFile.get(moduleFile);
22234
+ return entry ? toResolvedStepSlug(entry) : void 0;
22235
+ }
22236
+ const entry = integrationRegistry?.get(`${specifier}#${exportName}`);
22237
+ return entry ? toResolvedStepSlug(entry) : void 0;
22238
+ };
22239
+ }
22240
+ function buildSlugRegistry(entries) {
22241
+ return new Map(entries.map((entry) => [entry.moduleFile, entry]));
22242
+ }
22243
+ /** The framework package — never treated as an integration action source. */
22244
+ const FRAMEWORK_PACKAGE = "@keystrokehq/keystroke";
22245
+ /**
22246
+ * Map an integration import specifier to its app slug.
22247
+ *
22248
+ * `@keystrokehq/slackbot/actions` → `slackbot`, `@keystrokehq/github` → `github`.
22249
+ * Returns `undefined` for the framework package and any non-`@keystrokehq` scope.
22250
+ */
22251
+ function integrationAppSlugFromSpecifier(specifier) {
22252
+ if (specifier === FRAMEWORK_PACKAGE || specifier.startsWith(`${FRAMEWORK_PACKAGE}/`)) return;
22253
+ return /^@keystrokehq\/([^/]+)(?:\/.*)?$/.exec(specifier)?.[1];
22254
+ }
22255
+ /**
22256
+ * The integration action subpath convention (`@keystrokehq/<app>/actions`). We only
22257
+ * import these — the real authoring convention — to avoid loading whole packages.
22258
+ */
22259
+ function isIntegrationActionsSpecifier(specifier) {
22260
+ return /^@keystrokehq\/[^/]+\/actions$/.test(specifier) && specifier !== FRAMEWORK_PACKAGE;
22261
+ }
22262
+ /** Map each integration `/actions` specifier imported by a file to the exports it uses. */
22263
+ function collectIntegrationImports(source, fileName) {
22264
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
22265
+ const bySpecifier = /* @__PURE__ */ new Map();
22266
+ for (const statement of sourceFile.statements) {
22267
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;
22268
+ const specifier = statement.moduleSpecifier.text;
22269
+ if (!isIntegrationActionsSpecifier(specifier)) continue;
22270
+ const usage = bySpecifier.get(specifier) ?? {
22271
+ names: /* @__PURE__ */ new Set(),
22272
+ importsAll: false
22273
+ };
22274
+ const clause = statement.importClause;
22275
+ if (clause?.name) usage.names.add("default");
22276
+ const bindings = clause?.namedBindings;
22277
+ if (bindings) if (ts.isNamespaceImport(bindings)) usage.importsAll = true;
22278
+ else for (const element of bindings.elements) usage.names.add(element.propertyName?.text ?? element.name.text);
22279
+ bySpecifier.set(specifier, usage);
22280
+ }
22281
+ return bySpecifier;
22282
+ }
22283
+ /**
22284
+ * Discover actions imported from integration packages (`@keystrokehq/<app>/actions`)
22285
+ * by the project's workflow/agent source files.
22286
+ *
22287
+ * Each `/actions` subpath is resolved from the project's `node_modules`, dynamically
22288
+ * imported, and its action exports enumerated. Best-effort: a missing or broken
22289
+ * integration package never fails the manifest build — it is logged and skipped.
22290
+ */
22291
+ async function discoverIntegrationActions(projectRoot, sourceFiles, options) {
22292
+ const usageBySpecifier = /* @__PURE__ */ new Map();
22293
+ for (const sourceFile of sourceFiles) {
22294
+ let source;
22295
+ try {
22296
+ source = readFileSync(join(projectRoot, sourceFile), "utf8");
22297
+ } catch {
22298
+ continue;
22299
+ }
22300
+ for (const [specifier, usage] of collectIntegrationImports(source, sourceFile)) {
22301
+ const existing = usageBySpecifier.get(specifier) ?? {
22302
+ names: /* @__PURE__ */ new Set(),
22303
+ importsAll: false
22304
+ };
22305
+ for (const name of usage.names) existing.names.add(name);
22306
+ existing.importsAll = existing.importsAll || usage.importsAll;
22307
+ usageBySpecifier.set(specifier, existing);
22308
+ }
22309
+ }
22310
+ const entries = [];
22311
+ const registry = /* @__PURE__ */ new Map();
22312
+ const seenSlugs = new Set(options?.existingSlugs);
22313
+ const require = createRequire(join(projectRoot, "package.json"));
22314
+ for (const [specifier, usage] of usageBySpecifier) {
22315
+ const appSlug = integrationAppSlugFromSpecifier(specifier);
22316
+ if (!appSlug) continue;
22317
+ try {
22318
+ const resolvedPath = require.resolve(specifier);
22319
+ const href = pathToFileURL(resolvedPath).href;
22320
+ const mod = await runWithAppRoot(resolveAppRoot(resolvedPath), async () => await import(href));
22321
+ for (const [exportName, value] of Object.entries(mod)) {
22322
+ if (!isAction(value)) continue;
22323
+ if (!usage.importsAll && !usage.names.has(exportName)) continue;
22324
+ const definition = value;
22325
+ registry.set(`${specifier}#${exportName}`, {
22326
+ slug: definition.slug,
22327
+ name: definition.name,
22328
+ description: definition.description
22329
+ });
22330
+ if (seenSlugs.has(definition.slug)) continue;
22331
+ seenSlugs.add(definition.slug);
22332
+ entries.push(actionManifestEntry(definition, {
22333
+ moduleFile: specifier,
22334
+ appSlug,
22335
+ inputSchema: schemaToJson(definition.input),
22336
+ outputSchema: schemaToJson(definition.output)
22337
+ }));
22338
+ }
22339
+ } catch (error) {
22340
+ const message = error instanceof Error ? error.message : String(error);
22341
+ console.warn(`[manifest] skipped integration actions for ${specifier}: ${message}`);
22342
+ }
22343
+ }
22344
+ return {
22345
+ entries,
22346
+ registry
22347
+ };
22348
+ }
20504
22349
  function resolveDistModuleDirs(projectRoot) {
20505
22350
  const distBase = join(projectRoot, "dist");
20506
22351
  if (!existsSync(distBase)) throw new Error(`Build output missing at ${distBase}. Run keystroke build before emitting the route manifest.`);
20507
22352
  return {
20508
22353
  agentsDir: join(distBase, "agents"),
22354
+ actionsDir: join(distBase, "actions"),
20509
22355
  workflowsDir: join(distBase, "workflows"),
20510
22356
  triggersDir: join(distBase, "triggers")
20511
22357
  };
@@ -20521,6 +22367,33 @@ function hashFileContents(absPath) {
20521
22367
  }
20522
22368
  }
20523
22369
  /**
22370
+ * Run the deploy-time AST producer over a workflow's source file to attach its
22371
+ * control-flow skeleton + a content hash (for staleness). Best-effort: skips
22372
+ * when the resolved moduleFile is not a readable `.ts` source (e.g. dist-only
22373
+ * builds) or the source has no recognizable workflow, and never fails the build.
22374
+ */
22375
+ function produceWorkflowFlowGraph(projectRoot, moduleFile, options) {
22376
+ if (!moduleFile.endsWith(".ts")) return;
22377
+ const absolute = join(projectRoot, moduleFile);
22378
+ if (!existsSync(absolute)) return;
22379
+ try {
22380
+ const source = readFileSync(absolute, "utf8");
22381
+ const flowGraph = buildWorkflowCanvasGraph(source, {
22382
+ fileName: moduleFile,
22383
+ resolveSlug: options?.slugRegistry ? createResolveSlugFn(projectRoot, moduleFile, source, options.slugRegistry, options.integrationRegistry) : void 0
22384
+ });
22385
+ if (!flowGraph) return;
22386
+ return {
22387
+ flowGraph,
22388
+ flowGraphSourceHash: createHash("sha256").update(source).digest("hex")
22389
+ };
22390
+ } catch (error) {
22391
+ const message = error instanceof Error ? error.message : String(error);
22392
+ console.warn(`[manifest] skipped workflow flow graph for ${moduleFile}: ${message}`);
22393
+ return;
22394
+ }
22395
+ }
22396
+ /**
20524
22397
  * Resolve manifest moduleFile values to project-root-relative source paths.
20525
22398
  *
20526
22399
  * Discovery runs over compiled `dist/` modules, so the raw moduleFile is a
@@ -20581,6 +22454,7 @@ async function buildStoredRouteManifestForProject(projectRoot, options) {
20581
22454
  const dirs = resolveDistModuleDirs(projectRoot);
20582
22455
  const sourcePaths = new SourceModuleFileResolver(projectRoot);
20583
22456
  const manifest = [{ kind: "health" }];
22457
+ const slugRegistryEntries = [];
20584
22458
  const agentEntries = await discoverAgentEntries(dirs.agentsDir, reload);
20585
22459
  const workflows = await discoverWorkflows(dirs.workflowsDir, reload);
20586
22460
  const attachments = await discoverTriggerAttachments(dirs.triggersDir, reload);
@@ -20594,22 +22468,72 @@ async function buildStoredRouteManifestForProject(projectRoot, options) {
20594
22468
  agent: await importAgentDefinition(entry.filePath, reload)
20595
22469
  })));
20596
22470
  await validateAgentModelIds(loadedAgents.map(({ agent }) => agent.model));
22471
+ const resolvedAgents = [];
20597
22472
  for (const { entry, agent } of loadedAgents) {
20598
22473
  const moduleFile = await sourcePaths.resolve("agents", "agent", dirs.agentsDir, entry.filePath);
20599
- manifest.push(agentManifestEntry(agent, { moduleFile }));
22474
+ resolvedAgents.push({
22475
+ agent,
22476
+ moduleFile
22477
+ });
22478
+ slugRegistryEntries.push({
22479
+ slug: agent.slug,
22480
+ name: agent.name,
22481
+ description: agent.description,
22482
+ moduleFile
22483
+ });
22484
+ }
22485
+ const actions = await discoverActions(dirs.actionsDir, reload);
22486
+ const resolvedActions = [];
22487
+ for (const action of actions) {
22488
+ const moduleFile = await sourcePaths.resolve("actions", "action", dirs.actionsDir, action.filePath);
22489
+ resolvedActions.push({
22490
+ action,
22491
+ moduleFile
22492
+ });
22493
+ slugRegistryEntries.push({
22494
+ slug: action.definition.slug,
22495
+ name: action.definition.name,
22496
+ description: action.definition.description,
22497
+ moduleFile
22498
+ });
20600
22499
  }
22500
+ const resolvedWorkflows = [];
20601
22501
  for (const workflow of workflows) {
20602
22502
  const moduleFile = await sourcePaths.resolve("workflows", "workflow", dirs.workflowsDir, workflow.filePath);
20603
- manifest.push({
20604
- kind: "workflow",
22503
+ resolvedWorkflows.push({
22504
+ workflow,
22505
+ moduleFile
22506
+ });
22507
+ slugRegistryEntries.push({
20605
22508
  slug: workflow.definition.slug,
20606
22509
  name: workflow.definition.name,
20607
22510
  description: workflow.definition.description,
20608
- subscribable: workflow.definition.subscription?.mode === "subscribable",
20609
- moduleFile,
20610
- request: workflow.definition.input
22511
+ moduleFile
20611
22512
  });
20612
22513
  }
22514
+ const slugRegistry = buildSlugRegistry(slugRegistryEntries);
22515
+ const integration = await discoverIntegrationActions(projectRoot, [...resolvedWorkflows.map(({ moduleFile }) => moduleFile), ...resolvedAgents.map(({ moduleFile }) => moduleFile)], { existingSlugs: new Set(slugRegistryEntries.map((entry) => entry.slug)) });
22516
+ for (const { agent, moduleFile } of resolvedAgents) manifest.push(agentManifestEntry(agent, { moduleFile }));
22517
+ for (const { action, moduleFile } of resolvedActions) manifest.push(actionManifestEntry(action.definition, {
22518
+ moduleFile,
22519
+ inputSchema: schemaToJson(action.definition.input),
22520
+ outputSchema: schemaToJson(action.definition.output)
22521
+ }));
22522
+ for (const entry of integration.entries) manifest.push(entry);
22523
+ for (const { workflow, moduleFile } of resolvedWorkflows) manifest.push({
22524
+ kind: "workflow",
22525
+ slug: workflow.definition.slug,
22526
+ name: workflow.definition.name,
22527
+ description: workflow.definition.description,
22528
+ subscribable: workflow.definition.subscription?.mode === "subscribable",
22529
+ moduleFile,
22530
+ request: workflow.definition.input,
22531
+ response: workflow.definition.output,
22532
+ ...produceWorkflowFlowGraph(projectRoot, moduleFile, {
22533
+ slugRegistry,
22534
+ integrationRegistry: integration.registry
22535
+ })
22536
+ });
20613
22537
  const discoveredBySlug = new Map(attachments.map((attachment) => [attachment.slug, attachment]));
20614
22538
  const cronByTriggerSlug = /* @__PURE__ */ new Map();
20615
22539
  const pollByGroupId = /* @__PURE__ */ new Map();
@@ -20674,9 +22598,9 @@ async function buildStoredRouteManifestForProject(projectRoot, options) {
20674
22598
  discovered,
20675
22599
  options: { attachmentSlug: discovered.slug }
20676
22600
  }];
20677
- const attachmentMeta = Object.fromEntries(bindings.flatMap(({ discovered: row }) => {
22601
+ const attachmentMeta = Object.fromEntries(bindings.map(({ discovered: row }) => {
20678
22602
  const meta = triggerMetaFromAttachment(row.attachment);
20679
- return Object.keys(meta).length > 0 ? [[row.slug, meta]] : [];
22603
+ return [row.slug, meta];
20680
22604
  }));
20681
22605
  manifest.push({
20682
22606
  kind: "trigger-webhook",
@@ -20686,16 +22610,16 @@ async function buildStoredRouteManifestForProject(projectRoot, options) {
20686
22610
  ...sourceHash ? { sourceHash } : {},
20687
22611
  request: webhookMatchSchemaForBindings(bindings),
20688
22612
  attachmentSchemas: webhookManifestAttachmentSchemasFromBindings(bindings),
20689
- ...Object.keys(attachmentMeta).length > 0 ? { attachmentMeta } : {},
22613
+ attachmentMeta,
20690
22614
  response: PromptResponseSchema
20691
22615
  });
20692
22616
  }
20693
- return buildStoredRouteManifestFromContext({
22617
+ return parseStoredRouteManifest(buildStoredRouteManifestFromContext({
20694
22618
  manifest,
20695
22619
  options: {},
20696
22620
  projectRoot,
20697
22621
  skills: discoverSkillManifestEntries(projectRoot)
20698
- });
22622
+ }));
20699
22623
  } finally {
20700
22624
  if (previousRoot === void 0) delete process.env.KEYSTROKE_ROOT;
20701
22625
  else process.env.KEYSTROKE_ROOT = previousRoot;
@@ -20706,6 +22630,6 @@ async function emitStoredRouteManifestForProject(projectRoot) {
20706
22630
  persistStoredRouteManifest(projectRoot, await buildStoredRouteManifestForProject(projectRoot));
20707
22631
  }
20708
22632
  //#endregion
20709
- export { validatePollGroups as A, attachmentSlugFromRecord as B, pollRouteFromSourceSlug as C, validateAttachmentTargets as D, toStoredRouteManifest as E, webhookManifestAttachmentSchemasFromBindings as F, readKeystrokeIgnoreDirective as G, discoverEntries as H, webhookMatchSchemaForBindings as I, walkTypeScriptFiles as J, shouldSkipKeystrokeModuleFile as K, webhookRouteFromEndpoint as L, validateTriggerAttachments as M, validateUniqueAttachmentSlugs as N, validateImportedTriggerAttachment as O, validateUniqueTriggerSourceSlugs as P, workflowKeyForAttachment as R, pollGroupRouteFromId as S, serializeRouteManifest as T, discoverModuleFileEntries as U, packAssetDirs as V, entryIdFromFile as W, importAgentDefinition as _, buildStoredRouteManifestForProject as a, persistStoredRouteManifest as b, collectAgentAppSlugs as c, discoverAgentEntries as d, discoverSkillManifestEntries as f, emitStoredRouteManifestForProject as g, discoverWorkflows as h, buildPollGroups as i, validateProjectModules as j, validateImportedWorkflowDefinition as k, collectAgentToolSlugs as l, discoverWorkflowEntries as m, agentManifestEntry as n, buildStoredRouteManifestFromContext as o, discoverTriggerAttachments as p, validateUniqueModuleKeys as q, agentRouteFromKey as r, buildWebhookBindingsByRoute as s, agentKeyForAttachment as t, countAgentCredentials as u, importTriggerAttachments as v, schemaToJson as w, pollGroupId as x, importWorkflowDefinition as y, workflowRouteFromKey as z };
22633
+ export { validatePollGroups as A, attachmentSlugFromRecord as B, pollRouteFromSourceSlug as C, validateAttachmentTargets as D, toStoredRouteManifest as E, webhookManifestAttachmentSchemasFromBindings as F, packAssetDirs as G, computeCallSiteIds as H, webhookMatchSchemaForBindings as I, entryIdFromFile as J, discoverEntries as K, webhookRouteFromEndpoint as L, validateTriggerAttachments as M, validateUniqueAttachmentSlugs as N, validateImportedTriggerAttachment as O, validateUniqueTriggerSourceSlugs as P, walkTypeScriptFiles as Q, workflowKeyForAttachment as R, pollGroupRouteFromId as S, serializeRouteManifest as T, diagnoseWorkflowSource as U, classifyCall as V, locateWorkflow as W, shouldSkipKeystrokeModuleFile as X, readKeystrokeIgnoreDirective as Y, validateUniqueModuleKeys as Z, importAgentDefinition as _, buildStoredRouteManifestForProject as a, persistStoredRouteManifest as b, collectAgentAppSlugs as c, discoverAgentEntries as d, discoverSkillManifestEntries as f, emitStoredRouteManifestForProject as g, discoverWorkflows as h, buildPollGroups as i, validateProjectModules as j, validateImportedWorkflowDefinition as k, collectAgentToolSlugs as l, discoverWorkflowEntries as m, agentManifestEntry as n, buildStoredRouteManifestFromContext as o, discoverTriggerAttachments as p, discoverModuleFileEntries as q, agentRouteFromKey as r, buildWebhookBindingsByRoute as s, agentKeyForAttachment as t, countAgentCredentials as u, importTriggerAttachments as v, schemaToJson as w, pollGroupId as x, importWorkflowDefinition as y, workflowRouteFromKey as z };
20710
22634
 
20711
- //# sourceMappingURL=dist-C1QOfWMM.mjs.map
22635
+ //# sourceMappingURL=dist-C0_71CpG.mjs.map