@runcofactor/cli 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/account-22LMP5PX.js +255 -0
  2. package/dist/account-22LMP5PX.js.map +1 -0
  3. package/dist/{api-catalog-7HLEN7C2.js → api-catalog-WTFQNLK6.js} +5 -5
  4. package/dist/{attachment-OVNQD6Z2.js → attachment-U3NKVPEY.js} +5 -5
  5. package/dist/augmentor-5GMNAX4M.js +198 -0
  6. package/dist/augmentor-5GMNAX4M.js.map +1 -0
  7. package/dist/{auth-VUHL74FH.js → auth-VQG2CDUD.js} +3 -3
  8. package/dist/{chunk-PZ4LCN6K.js → chunk-3X3NW7QA.js} +2 -2
  9. package/dist/{chunk-VGTKUOYS.js → chunk-46IUJXSD.js} +2 -2
  10. package/dist/{chunk-YN763NWQ.js → chunk-7VMO2WOZ.js} +10 -4
  11. package/dist/chunk-7VMO2WOZ.js.map +1 -0
  12. package/dist/{chunk-Z5LUTUDN.js → chunk-CMGI7UZD.js} +3 -3
  13. package/dist/{chunk-EEN2TZUQ.js → chunk-FNPMWDUQ.js} +3 -3
  14. package/dist/{chunk-EJ4U242G.js → chunk-HDFYUFBI.js} +2 -2
  15. package/dist/{chunk-JZMJUPZP.js → chunk-JMJVGMXI.js} +2 -2
  16. package/dist/{chunk-JZMJUPZP.js.map → chunk-JMJVGMXI.js.map} +1 -1
  17. package/dist/{chunk-QGI4UCJX.js → chunk-JYGNMKZ5.js} +62 -35
  18. package/dist/chunk-JYGNMKZ5.js.map +1 -0
  19. package/dist/{chunk-CRHPDPRS.js → chunk-KCHKFYVX.js} +30 -8
  20. package/dist/{chunk-CRHPDPRS.js.map → chunk-KCHKFYVX.js.map} +1 -1
  21. package/dist/{chunk-ZD3KHESI.js → chunk-KRWRUU6R.js} +27 -7
  22. package/dist/chunk-KRWRUU6R.js.map +1 -0
  23. package/dist/{chunk-ENDHMYEJ.js → chunk-NHTEKEYE.js} +3 -3
  24. package/dist/{chunk-CQE46HSM.js → chunk-PRBRSIZP.js} +14 -8
  25. package/dist/chunk-PRBRSIZP.js.map +1 -0
  26. package/dist/{chunk-NL7CVG56.js → chunk-UETBLPKM.js} +57 -10
  27. package/dist/chunk-UETBLPKM.js.map +1 -0
  28. package/dist/{chunk-QQVWGWPI.js → chunk-VGYN2KU2.js} +8494 -1504
  29. package/dist/chunk-VGYN2KU2.js.map +1 -0
  30. package/dist/{chunk-ZTOE3ND3.js → chunk-XJ5YYYGC.js} +4 -4
  31. package/dist/{credential-LO2SEEHU.js → credential-E6KUHRGU.js} +5 -5
  32. package/dist/{credential-template-ZN7W7734.js → credential-template-4GPOPBGD.js} +5 -5
  33. package/dist/{debug-workflow-R3RYYAXK.js → debug-workflow-XCLCY6S7.js} +5 -5
  34. package/dist/{docs-DIAFBDXQ.js → docs-COCZYMW6.js} +95 -11
  35. package/dist/docs-COCZYMW6.js.map +1 -0
  36. package/dist/{doctor-NZPUSCRE.js → doctor-ERIVTCQS.js} +3 -3
  37. package/dist/{environment-RQVU4P5M.js → environment-7Y4BV6XY.js} +5 -5
  38. package/dist/{feature-flag-XG26RURR.js → feature-flag-MHONEX6T.js} +98 -12
  39. package/dist/feature-flag-MHONEX6T.js.map +1 -0
  40. package/dist/index.js +107 -51
  41. package/dist/index.js.map +1 -1
  42. package/dist/{init-EDNLKGPT.js → init-OROOM4HF.js} +5 -5
  43. package/dist/{install-FYZ3HNZK.js → install-OJFXHY6U.js} +5 -5
  44. package/dist/{issues-NNRVTJ4L.js → issues-32GONR6C.js} +5 -5
  45. package/dist/{knobs-A2BOBH22.js → knobs-UQGGY3CP.js} +7 -6
  46. package/dist/knobs-UQGGY3CP.js.map +1 -0
  47. package/dist/{list-F33VZ4BJ.js → list-ZMIKEC5P.js} +5 -5
  48. package/dist/{local-DQDTM3XA.js → local-PUD24GGQ.js} +7 -7
  49. package/dist/{login-BUMVIB36.js → login-BSO7YX2D.js} +5 -5
  50. package/dist/{mcp-B5Y5CDMQ.js → mcp-4LS7OJUT.js} +9 -9
  51. package/dist/{mcp-B5Y5CDMQ.js.map → mcp-4LS7OJUT.js.map} +1 -1
  52. package/dist/{pdf-extract-ABWV3L5X.js → pdf-extract-YHE54XU4.js} +2 -2
  53. package/dist/product-3JKSJCBC.js +346 -0
  54. package/dist/product-3JKSJCBC.js.map +1 -0
  55. package/dist/{property-NJ4SRXQG.js → property-K5DBL4UE.js} +5 -5
  56. package/dist/{psql-VBZFCMNR.js → psql-PQFUFTSE.js} +3 -3
  57. package/dist/{record-WAO36O7G.js → record-EIIYLFJ4.js} +6 -6
  58. package/dist/{redis-DHZXHL4S.js → redis-BU36EZZI.js} +3 -3
  59. package/dist/redis-BU36EZZI.js.map +1 -0
  60. package/dist/{release-STJKUKTE.js → release-LYC2P7DZ.js} +5 -5
  61. package/dist/{sandbox-DWSGLY7B.js → sandbox-CKHXXZHU.js} +51 -7
  62. package/dist/sandbox-CKHXXZHU.js.map +1 -0
  63. package/dist/{scenarios-HCIDY725.js → scenarios-2XJ2G3KR.js} +5 -5
  64. package/dist/{session-Y5RHU54D.js → session-UTOD5ZYH.js} +8 -9
  65. package/dist/session-UTOD5ZYH.js.map +1 -0
  66. package/dist/{skill-23WAHUOB.js → skill-GMAERMQL.js} +10 -10
  67. package/dist/{skill-23WAHUOB.js.map → skill-GMAERMQL.js.map} +1 -1
  68. package/dist/skills/debug-run/SKILL.md +7 -1
  69. package/dist/skills/debug-run/history/1.3.1.md +279 -0
  70. package/dist/skills/debug-run/version.json +1 -1
  71. package/dist/skills/optimize-cache/SKILL.md +29 -2
  72. package/dist/{spec-LM5XHOQ5.js → spec-2NP36U3Y.js} +5 -5
  73. package/dist/{src-22RI5VP2.js → src-4T6GHXVO.js} +2 -2
  74. package/dist/{src-NCJPUAE6.js → src-CG52HEE7.js} +10 -8
  75. package/dist/{src-Y3K7UJEF.js → src-PK5OZXCM.js} +2 -2
  76. package/dist/{src-BGDRMOKC.js → src-UEOOC425.js} +295 -102
  77. package/dist/src-UEOOC425.js.map +1 -0
  78. package/dist/{start-SWY55CZX.js → start-3C7EEFW2.js} +5 -5
  79. package/dist/{tag-YSU2XXPI.js → tag-JSHOMLBR.js} +6 -6
  80. package/dist/{task-NLBKLD7D.js → task-YGWVYTBJ.js} +5 -5
  81. package/dist/{test-suite-QCPWZ2LS.js → test-suite-LI7ZH367.js} +10 -8
  82. package/dist/{test-suite-QCPWZ2LS.js.map → test-suite-LI7ZH367.js.map} +1 -1
  83. package/dist/trace-UMLOSIIR.js +16 -0
  84. package/dist/{triage-TT4BMDBU.js → triage-ZWJG4NBJ.js} +6 -6
  85. package/dist/{upgrade-6ZUFETKX.js → upgrade-S7CGEK6H.js} +3 -3
  86. package/dist/{workflow-2CX3ZNOQ.js → workflow-T4HSAOGW.js} +86 -15
  87. package/dist/workflow-T4HSAOGW.js.map +1 -0
  88. package/dist/{workflow-cache-GXNE6SU2.js → workflow-cache-XVEZ36KF.js} +8 -8
  89. package/dist/{workflow-debug-PRX3DWGD.js → workflow-debug-T2BULXZ7.js} +9 -9
  90. package/dist/{workflow-proposals-BW327WGL.js → workflow-proposals-XXEZICN2.js} +5 -7
  91. package/dist/workflow-proposals-XXEZICN2.js.map +1 -0
  92. package/package.json +1 -1
  93. package/dist/chunk-CQE46HSM.js.map +0 -1
  94. package/dist/chunk-NL7CVG56.js.map +0 -1
  95. package/dist/chunk-QGI4UCJX.js.map +0 -1
  96. package/dist/chunk-QQVWGWPI.js.map +0 -1
  97. package/dist/chunk-YN763NWQ.js.map +0 -1
  98. package/dist/chunk-ZD3KHESI.js.map +0 -1
  99. package/dist/docs-DIAFBDXQ.js.map +0 -1
  100. package/dist/feature-flag-XG26RURR.js.map +0 -1
  101. package/dist/knobs-A2BOBH22.js.map +0 -1
  102. package/dist/redis-DHZXHL4S.js.map +0 -1
  103. package/dist/sandbox-DWSGLY7B.js.map +0 -1
  104. package/dist/session-Y5RHU54D.js.map +0 -1
  105. package/dist/src-BGDRMOKC.js.map +0 -1
  106. package/dist/trace-A3ZY6K26.js +0 -16
  107. package/dist/workflow-2CX3ZNOQ.js.map +0 -1
  108. package/dist/workflow-proposals-BW327WGL.js.map +0 -1
  109. /package/dist/{api-catalog-7HLEN7C2.js.map → api-catalog-WTFQNLK6.js.map} +0 -0
  110. /package/dist/{attachment-OVNQD6Z2.js.map → attachment-U3NKVPEY.js.map} +0 -0
  111. /package/dist/{auth-VUHL74FH.js.map → auth-VQG2CDUD.js.map} +0 -0
  112. /package/dist/{chunk-PZ4LCN6K.js.map → chunk-3X3NW7QA.js.map} +0 -0
  113. /package/dist/{chunk-VGTKUOYS.js.map → chunk-46IUJXSD.js.map} +0 -0
  114. /package/dist/{chunk-Z5LUTUDN.js.map → chunk-CMGI7UZD.js.map} +0 -0
  115. /package/dist/{chunk-EEN2TZUQ.js.map → chunk-FNPMWDUQ.js.map} +0 -0
  116. /package/dist/{chunk-EJ4U242G.js.map → chunk-HDFYUFBI.js.map} +0 -0
  117. /package/dist/{chunk-ENDHMYEJ.js.map → chunk-NHTEKEYE.js.map} +0 -0
  118. /package/dist/{chunk-ZTOE3ND3.js.map → chunk-XJ5YYYGC.js.map} +0 -0
  119. /package/dist/{credential-LO2SEEHU.js.map → credential-E6KUHRGU.js.map} +0 -0
  120. /package/dist/{credential-template-ZN7W7734.js.map → credential-template-4GPOPBGD.js.map} +0 -0
  121. /package/dist/{debug-workflow-R3RYYAXK.js.map → debug-workflow-XCLCY6S7.js.map} +0 -0
  122. /package/dist/{doctor-NZPUSCRE.js.map → doctor-ERIVTCQS.js.map} +0 -0
  123. /package/dist/{environment-RQVU4P5M.js.map → environment-7Y4BV6XY.js.map} +0 -0
  124. /package/dist/{init-EDNLKGPT.js.map → init-OROOM4HF.js.map} +0 -0
  125. /package/dist/{install-FYZ3HNZK.js.map → install-OJFXHY6U.js.map} +0 -0
  126. /package/dist/{issues-NNRVTJ4L.js.map → issues-32GONR6C.js.map} +0 -0
  127. /package/dist/{list-F33VZ4BJ.js.map → list-ZMIKEC5P.js.map} +0 -0
  128. /package/dist/{local-DQDTM3XA.js.map → local-PUD24GGQ.js.map} +0 -0
  129. /package/dist/{login-BUMVIB36.js.map → login-BSO7YX2D.js.map} +0 -0
  130. /package/dist/{pdf-extract-ABWV3L5X.js.map → pdf-extract-YHE54XU4.js.map} +0 -0
  131. /package/dist/{property-NJ4SRXQG.js.map → property-K5DBL4UE.js.map} +0 -0
  132. /package/dist/{psql-VBZFCMNR.js.map → psql-PQFUFTSE.js.map} +0 -0
  133. /package/dist/{record-WAO36O7G.js.map → record-EIIYLFJ4.js.map} +0 -0
  134. /package/dist/{release-STJKUKTE.js.map → release-LYC2P7DZ.js.map} +0 -0
  135. /package/dist/{scenarios-HCIDY725.js.map → scenarios-2XJ2G3KR.js.map} +0 -0
  136. /package/dist/{spec-LM5XHOQ5.js.map → spec-2NP36U3Y.js.map} +0 -0
  137. /package/dist/{src-22RI5VP2.js.map → src-4T6GHXVO.js.map} +0 -0
  138. /package/dist/{src-NCJPUAE6.js.map → src-CG52HEE7.js.map} +0 -0
  139. /package/dist/{src-Y3K7UJEF.js.map → src-PK5OZXCM.js.map} +0 -0
  140. /package/dist/{start-SWY55CZX.js.map → start-3C7EEFW2.js.map} +0 -0
  141. /package/dist/{tag-YSU2XXPI.js.map → tag-JSHOMLBR.js.map} +0 -0
  142. /package/dist/{task-NLBKLD7D.js.map → task-YGWVYTBJ.js.map} +0 -0
  143. /package/dist/{trace-A3ZY6K26.js.map → trace-UMLOSIIR.js.map} +0 -0
  144. /package/dist/{triage-TT4BMDBU.js.map → triage-ZWJG4NBJ.js.map} +0 -0
  145. /package/dist/{upgrade-6ZUFETKX.js.map → upgrade-S7CGEK6H.js.map} +0 -0
  146. /package/dist/{workflow-cache-GXNE6SU2.js.map → workflow-cache-XVEZ36KF.js.map} +0 -0
  147. /package/dist/{workflow-debug-PRX3DWGD.js.map → workflow-debug-T2BULXZ7.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../types/src/workflows.ts","../../types/src/sandbox-instance-size.ts","../../types/src/node-type-labels.ts","../../types/src/index.ts","../../types/src/credentials.ts","../../types/src/issues.ts","../../types/src/local-browser.ts","../../types/src/benchmark.ts","../../types/src/api-sequence.ts","../../types/src/sandbox-tasks.ts","../../types/src/macros.ts","../../types/src/workflow-node-schemas.ts","../../types/src/browser-viewport.ts","../../types/src/identifier-stability.ts"],"sourcesContent":["export const WORKFLOW_RUN_SEGMENT_JOB_TYPE = 'workflows.run-segment' as const;\n\n// Suite Runner Orchestrator Pattern\nexport const WORKFLOW_TEST_SUITE_ORCHESTRATOR_JOB_TYPE = 'workflows.test-suite-orchestrator' as const;\nexport const WORKFLOW_TEST_SUITE_WORKFLOW_JOB_TYPE = 'workflows.test-suite-workflow' as const;\nexport const WORKFLOW_SUITE_SETUP_JOB_TYPE = 'workflows.suite-setup' as const;\nexport const WORKFLOW_SEED_JOB_TYPE = 'workflow.seed' as const;\nexport const WORKFLOW_TEARDOWN_JOB_TYPE = 'workflow.teardown' as const;\n\nexport type WorkflowSuiteRunPayload = {\n workflowId?: string; // For single-workflow authoring\n /** Filter to specific workflow IDs (e.g. CSV import auto-run) */\n workflowIds?: string[];\n /**\n * Restrict the suite to workflows whose credential references all resolve\n * to this property. When set, login nodes' `credentialTemplateId` /\n * `credentialId` are resolved to their backing property and the suite skips\n * (a) workflows with zero resolvable credentials, and (b) workflows whose\n * credentials resolve to any other property. Null/undefined = no filter.\n */\n propertyId?: string | null;\n /** Target environment for credential resolution and base-URL substitution. */\n environmentId?: string | null;\n /**\n * How the suite picks an environment per flow:\n * - `'mixed'` (default) — each flow runs against its own preferred env\n * (`workflows.defaultEnvironmentId`, else the property default). Nothing is\n * skipped for env-eligibility reasons.\n * - `'force'` — every flow runs against `environmentId`; flows ineligible for\n * it are skipped (legacy single-env behavior).\n * Omitted ⇒ `'force'` when an `environmentId` is supplied (back-compat with\n * existing single-env callers), else `'mixed'`.\n */\n environmentMode?: 'mixed' | 'force';\n triggeredByUserId?: string | null;\n triggeredReason?: string | null;\n /**\n * Filter workflows by tag(s) — any-of semantics. A workflow runs if it has\n * at least one of the supplied tag names. Empty/undefined means \"no tag filter\".\n */\n tags?: string[] | null;\n /** Filter workflows by name pattern (case-insensitive substring match) */\n namePattern?: string | null;\n /**\n * Run a persisted, user-curated suite. When set, the orchestrator loads the\n * suite's members (ignoring tag/name/id selectors) and runs them in the\n * suite's layer order — layer N+1 spawns only after every workflow in layer N\n * reaches a terminal state.\n */\n suiteId?: string;\n};\n\n/** @deprecated Use WorkflowSuiteRunPayload — kept as alias during in-flight job migration. */\nexport type WorkflowPropertyTestRunPayload = WorkflowSuiteRunPayload;\n\nexport type WorkflowRunSegmentPayload = {\n runId: string;\n};\n\nexport type WorkflowTestStatus =\n | 'queued'\n | 'running'\n | 'waiting'\n | 'success'\n | 'failed'\n | 'skipped'\n | 'flaky_success'\n | 'canceled'\n // Not run because the workflow's health is degraded (suite runs only).\n | 'degraded';\n\n/**\n * Canonical set of **test-run** statuses that are terminal (no further\n * transitions). Import this instead of re-declaring it — the orchestrator's\n * layer gating and the suite run-detail \"layer complete\" check share it.\n * NOTE: distinct from terminal *execution* statuses (completed /\n * completed_with_errors / …), which are a separate enum.\n */\nexport const TERMINAL_TEST_RUN_STATUSES: ReadonlySet<string> = new Set([\n 'success',\n 'failed',\n 'skipped',\n 'flaky_success',\n 'canceled',\n 'timeout',\n 'degraded',\n]);\n\nexport const isTerminalTestRunStatus = (status: string): boolean =>\n TERMINAL_TEST_RUN_STATUSES.has(status);\n\n/**\n * Workflow reliability dimension, orthogonal to {@link WorkflowDefinitionStatus}\n * (lifecycle). Recomputed after each suite run from the workflow's test-run history.\n *\n * - `unverified` — no terminal test runs yet\n * - `never_passed` — has run, never a single pass (authoritative via firstPassedAt)\n * - `passing` — recent window all green, streak below the reliable threshold\n * - `reliable` — enough consecutive clean passes\n * - `flaky` — latest run passed, but the recent window had failures / a flaky_success\n * - `failing` — latest run failed but NOT auto-skipped (< DEGRADE_AFTER failures, or not promoted)\n * - `degraded` — promoted + ever-passed + enough consecutive failures; skipped in suite runs\n * (\"degraded — not run\") until the workflow is edited or manually re-enabled\n */\nexport const WORKFLOW_HEALTH = [\n 'unverified',\n 'never_passed',\n 'passing',\n 'reliable',\n 'flaky',\n 'failing',\n 'degraded',\n] as const;\n\nexport type WorkflowHealth = (typeof WORKFLOW_HEALTH)[number];\n\n/**\n * Approval-axis states for a workflow definition. This axis answers \"is the test\n * testing the right thing?\" — suite inclusion is the orthogonal {@link WorkflowPromotion}\n * axis, and reliability lives in {@link WorkflowHealth}.\n *\n * User-settable via the API/CLI: `draft`, `approved`\n * (see {@link USER_SETTABLE_WORKFLOW_STATUSES}).\n *\n * Lifecycle-internal (set by the system, not by users):\n * - `generating` — the workflow is being built from a recording/import\n * - `building` — a builder validation run is in progress\n * - `diagnostic` — builder validation failed; the workflow is parked for inspection\n * - `deleted` — soft-deleted\n *\n * @deprecated `published` and `quarantined` — retained in the enum for back-compat only\n * (dropped in a follow-up). `published` was split into the approval + promotion axes;\n * `quarantined` was superseded by {@link WorkflowHealth}. Nothing sets either value.\n */\nexport const WORKFLOW_STATUSES = [\n 'draft',\n 'generating',\n 'building',\n 'published',\n 'quarantined',\n 'deleted',\n 'diagnostic',\n 'approved',\n] as const;\n\nexport type WorkflowDefinitionStatus = (typeof WORKFLOW_STATUSES)[number];\n\n/**\n * Statuses a user (or agent) can set directly via the API or CLI.\n *\n * - `draft` — not yet approved; a human has not signed off on the spec\n * - `approved` — a human verified the test checks the right thing\n *\n * Approval does NOT put a flow into suites — that's the separate {@link WorkflowPromotion}\n * axis. Reliability (trusted, flaky, degraded) is the {@link WorkflowHealth} dimension,\n * computed automatically — not user-set here.\n */\nexport const USER_SETTABLE_WORKFLOW_STATUSES = [\n 'draft',\n 'approved',\n] as const satisfies readonly WorkflowDefinitionStatus[];\n\nexport type UserSettableWorkflowStatus =\n (typeof USER_SETTABLE_WORKFLOW_STATUSES)[number];\n\n/**\n * Suite-inclusion axis, orthogonal to approval ({@link WorkflowDefinitionStatus}) and\n * reliability ({@link WorkflowHealth}). Stored on a nullable column:\n *\n * - `null` — never promoted (default; shows no indicator in the UI)\n * - `'promoted'` — runs when a test suite runs\n * - `'paused'` — explicitly excluded from suites (post-promotion state only)\n *\n * Promoting requires approval; pausing/re-promoting toggles inclusion without\n * touching approval.\n */\nexport const WORKFLOW_PROMOTIONS = ['promoted', 'paused'] as const;\n\nexport type WorkflowPromotion = (typeof WORKFLOW_PROMOTIONS)[number];\n\n/**\n * The promotion column's full value space: `null` = never promoted (the default,\n * rendered with no indicator). Single-sourced here so repos and the web app don't\n * re-declare drifting `'promoted' | 'paused' | null` literals.\n */\nexport type WorkflowPromotionState = WorkflowPromotion | null;\n\n/** Context provided by the agent when a workflow step fails */\nexport type WorkflowFailureContext = {\n /** What the agent observed happening when the step failed */\n observedBehavior: string;\n /** What was expected to happen */\n expectedBehavior: string;\n /** Steps to reproduce the failure */\n reproSteps: string;\n /** Specific questions the agent poses to help improve instructions (debug mode only) */\n questions?: string[];\n};\n\nexport type WorkspaceRedactionReason = 'secret' | 'binary' | 'large' | 'system';\n\nexport type WorkspaceManifestEntry = {\n path: string;\n type: 'file' | 'dir';\n size?: number;\n mtime?: string;\n language?: string;\n redacted?: boolean;\n redactionReason?: WorkspaceRedactionReason;\n};\n\nexport type WorkspaceManifest = {\n version: string;\n root: string;\n generatedAt: string;\n entries: WorkspaceManifestEntry[];\n};\n\nexport type WorkspaceStreamEvent =\n | {\n type: 'workspace-manifest';\n data: WorkspaceManifest;\n }\n | {\n type: 'workspace-file-created';\n data: WorkspaceManifestEntry;\n }\n | {\n type: 'workspace-file-updated';\n data: {\n path: string;\n size?: number;\n mtime?: string;\n version?: string;\n };\n }\n | {\n type: 'workspace-file-deleted';\n data: { path: string };\n }\n | {\n type: 'workspace-file-content';\n data: {\n path: string;\n content: string;\n encoding?: 'utf-8';\n version?: string;\n };\n };\n\nexport type WorkflowTestRunSummary = {\n id: string;\n workflowId: string;\n status: WorkflowTestStatus;\n snapshotVersion: number;\n errorMessage: string | null;\n linkedIssueId: string | null;\n startedAt: string | null;\n finishedAt: string | null;\n createdAt: string;\n};\n\nexport type WorkflowTestSuiteStatus = 'queued' | 'running' | 'completed';\n\nexport type WorkflowTestEvent = {\n type: 'workflow-test';\n suiteId: string;\n emittedAt: string;\n workflowId: string;\n testRunId: string;\n workflowRunId: string | null;\n status: WorkflowTestStatus;\n startedAt: string | null;\n finishedAt: string | null;\n durationMs: number | null;\n errorMessage?: string | null;\n skipReason?: string | null;\n linkedIssueId?: string | null;\n message?: string;\n /** The workflow's reliability dimension at emit time (drives the UI health badge). */\n health?: WorkflowHealth;\n // Wait/segment tracking\n resumeAt?: string | null;\n segmentIndex?: number | null;\n totalSegments?: number | null;\n /** Monotonic sequence number for ordering concurrent events */\n _seq?: number;\n};\n\nexport type WorkflowTestSuiteEvent = {\n type: 'workflow-test-suite';\n suiteId: string;\n emittedAt: string;\n status: WorkflowTestSuiteStatus;\n startedAt: string;\n finishedAt: string | null;\n durationMs: number | null;\n totalWorkflows: number;\n completedWorkflows: number;\n failedWorkflows: number;\n successfulWorkflows: number;\n skippedWorkflows: number;\n waitingWorkflows: number;\n /** @deprecated Quarantine removed; always 0. Drop with the column in a follow-up. */\n quarantinedWorkflows: number;\n /** Workflows skipped because their health is `degraded` (shown \"degraded — not run\"). */\n degradedWorkflows: number;\n seedTotal: number;\n seedSucceeded: number;\n seedFailed: number;\n teardownTotal: number;\n teardownSucceeded: number;\n teardownFailed: number;\n errorMessage?: string | null;\n message?: string;\n /** Monotonic sequence number for ordering concurrent events */\n _seq?: number;\n};\n\nexport type WorkflowTestStreamEvent = WorkflowTestEvent | WorkflowTestSuiteEvent;\n","/**\n * Sandbox Instance Sizing\n *\n * Closed preset enum of EC2 sizes a template can run on, plus a per-size\n * resource spec consumed by provisioning, compose generation, and prompts.\n *\n * Single source of truth for: launch-template instance type override,\n * docker compose mem_limit/cpus per role, prompt-template variables.\n *\n * @module types/sandbox-instance-size\n */\n\nexport const INSTANCE_SIZES = ['small', 'medium', 'large', 'xlarge', '2xlarge'] as const;\nexport type InstanceSize = (typeof INSTANCE_SIZES)[number];\n\nexport const DEFAULT_INSTANCE_SIZE: InstanceSize = 'medium';\n\nexport interface InstanceSizeSpec {\n /** EC2 instance type for `RunInstancesCommand` override. */\n ec2InstanceType: string;\n vcpu: number;\n memoryMiB: number;\n /**\n * Per-role memory caps (MiB) consumed by `resourceLimitsFor()`. Must cover\n * every key in compose-helpers.ts STATIC_LIMITS.\n *\n * Sum × per-role multiplicity should fit within `memoryMiB × 1.5` to keep\n * swap pressure bounded. See INSTANCE_SIZE_SUM_LIMIT_RATIO.\n */\n agentCapMiB: number;\n appSoftCapMiB: number;\n proxyCapMiB: number;\n emulationCapMiB: number;\n storageCapMiB: number;\n /**\n * Per-size hard CPU quota (cgroup `cpus`) for the user app tier. Other\n * roles (agent, proxy, emulation, storage) keep fixed CPU caps from\n * STATIC_LIMITS — their workloads don't scale with host vCPU. App is the\n * only role that sees a real benefit from a larger instance, so it's the\n * only one keyed here.\n *\n * Formula: max('1.0', vcpu - 1.5) — reserve 1.5 vCPU for the agent.\n * Stored as a string so docker compose receives `'1.0'` (not `'1'`),\n * matching the format of STATIC_LIMITS.cpus values.\n */\n appCpus: string;\n}\n\n/** Cap on (sum of role caps) ÷ memoryMiB — anything above this swaps too hard. */\nexport const INSTANCE_SIZE_SUM_LIMIT_RATIO = 1.5;\n\nexport const INSTANCE_SIZE_SPEC: Record<InstanceSize, InstanceSizeSpec> = {\n small: {\n ec2InstanceType: 't3.small',\n vcpu: 2,\n memoryMiB: 2048,\n // 2 GiB instance — agent capped at min(2048, 0.4 × 2048) = 819\n agentCapMiB: 819,\n appSoftCapMiB: 1024,\n proxyCapMiB: 256,\n emulationCapMiB: 128,\n storageCapMiB: 256,\n appCpus: '1.0',\n },\n medium: {\n ec2InstanceType: 't3.medium',\n vcpu: 2,\n memoryMiB: 4096,\n agentCapMiB: 1536,\n appSoftCapMiB: 1536,\n proxyCapMiB: 256,\n emulationCapMiB: 128,\n storageCapMiB: 256,\n appCpus: '1.0',\n },\n large: {\n ec2InstanceType: 't3.large',\n vcpu: 2,\n memoryMiB: 8192,\n agentCapMiB: 2048,\n appSoftCapMiB: 5120,\n proxyCapMiB: 256,\n emulationCapMiB: 128,\n storageCapMiB: 512,\n // t3.large is memory-only — same 2 vCPU as medium.\n appCpus: '1.0',\n },\n xlarge: {\n ec2InstanceType: 't3.xlarge',\n vcpu: 4,\n memoryMiB: 16384,\n agentCapMiB: 2048,\n appSoftCapMiB: 12288,\n proxyCapMiB: 256,\n emulationCapMiB: 256,\n storageCapMiB: 512,\n appCpus: '2.5',\n },\n '2xlarge': {\n ec2InstanceType: 't3.2xlarge',\n vcpu: 8,\n memoryMiB: 32768,\n agentCapMiB: 2048,\n appSoftCapMiB: 28672,\n proxyCapMiB: 512,\n emulationCapMiB: 512,\n storageCapMiB: 1024,\n appCpus: '6.5',\n },\n};\n\nconst INSTANCE_SIZE_SET = new Set<string>(INSTANCE_SIZES);\n\n/**\n * Narrowing helper for the repo boundary. Returns null on unknown input so\n * callers can surface a typed user error without `as never` / `as any`.\n */\nexport function parseInstanceSize(s: string | null | undefined): InstanceSize | null {\n return s && INSTANCE_SIZE_SET.has(s) ? (s as InstanceSize) : null;\n}\n\nconst RECOMMENDATION_MAP: Record<string, InstanceSize> = {\n // Exact preset names\n small: 'small',\n medium: 'medium',\n large: 'large',\n xlarge: 'xlarge',\n '2xlarge': '2xlarge',\n // Common AWS instance types the authoring agent may emit\n 't3.nano': 'small',\n 't3.micro': 'small',\n 't3.small': 'small',\n 't3.medium': 'medium',\n 't3.large': 'large',\n 't3.xlarge': 'xlarge',\n 't3.2xlarge': '2xlarge',\n 't2.small': 'small',\n 't2.medium': 'medium',\n 't2.large': 'large',\n 't2.xlarge': 'xlarge',\n 't2.2xlarge': '2xlarge',\n};\n\n/**\n * Maps a free-form `recommendedInstanceType` string (often produced by the\n * authoring agent before this enum existed) to the closest preset.\n *\n * Strategy: exact match against known names first, then fall back to nearest\n * memoryMiB. Ties broken toward cheaper (lower `memoryMiB`).\n */\nexport function inferInstanceSizeFromRecommendation(\n s: string | null | undefined\n): InstanceSize | null {\n if (!s) return null;\n const trimmed = s.trim().toLowerCase();\n if (!trimmed) return null;\n const exact = RECOMMENDATION_MAP[trimmed];\n if (exact) return exact;\n\n // Last-resort heuristic: parse a memory hint like \"4 GiB\" / \"8GB\" / \"2g\".\n const match = trimmed.match(/(\\d+(?:\\.\\d+)?)\\s*(g|gi|gb|gib|m|mi|mb|mib)\\b/);\n if (!match) return null;\n const value = Number.parseFloat(match[1]!);\n if (!Number.isFinite(value)) return null;\n const memMiB = match[2]!.startsWith('g') ? value * 1024 : value;\n\n let best: InstanceSize | null = null;\n let bestDelta = Number.POSITIVE_INFINITY;\n for (const size of INSTANCE_SIZES) {\n const delta = Math.abs(INSTANCE_SIZE_SPEC[size].memoryMiB - memMiB);\n if (delta < bestDelta) {\n best = size;\n bestDelta = delta;\n }\n }\n return best;\n}\n","/**\n * Display labels for workflow node types. The JSON / persisted `node.type`\n * discriminator stays as-is (e.g. `'login'`) — only the human-facing label\n * changes here. Centralizing this lets us rename copy in one place.\n */\nexport const NODE_TYPE_LABEL = {\n login: 'Acting As',\n navigate: 'Navigate',\n action: 'Action',\n assertion: 'Assertion',\n condition: 'Condition',\n wait: 'Wait',\n api_sequence: 'API Sequence',\n seed: 'Seed Data',\n setup: 'Setup Flow',\n notification: 'Notification',\n audio: 'Audio input',\n 'audio-assertion': 'Audio assertion',\n sandbox_action: 'Sandbox action',\n end: 'End',\n} as const;\n\nexport type NodeTypeKey = keyof typeof NODE_TYPE_LABEL;\nexport type NodeTypeLabel = (typeof NODE_TYPE_LABEL)[NodeTypeKey];\n\n/**\n * Normalizes legacy/alias node-type keywords to the canonical persisted type.\n * Accepts `'acting-as'` / `'acting_as'` as aliases for `'login'` so CLI\n * callers can use the new name while persisted data stays `'login'`.\n */\nexport function normalizeNodeType(input: string): string {\n if (input === 'acting-as' || input === 'acting_as') return 'login';\n return input;\n}\n","import { z } from 'zod';\nimport type { AssertionSeverity } from './workflow-node-schemas.js';\nimport type { WorkflowHealth } from './workflows.js';\n\nexport type ActionInputType =\n | 'text'\n | 'email'\n | 'password'\n | 'number'\n | 'textarea'\n | 'select'\n | 'checkbox'\n | 'multiselect'\n | 'file';\n\nexport interface ActionInputOption {\n value: string;\n label: string;\n}\n\nexport interface ActionDataRequirement {\n /**\n * Unique field identifier used to store the provided input value.\n */\n name: string;\n /**\n * Human-friendly label to display in the UI.\n */\n label: string;\n /**\n * Expected input type. Defaults to 'text' if omitted.\n */\n type?: ActionInputType;\n /**\n * Whether the field is required for the action to succeed.\n */\n required?: boolean;\n /**\n * Helpful description shown to the operator.\n */\n description?: string;\n /**\n * Placeholder text for the input.\n */\n placeholder?: string;\n /**\n * Suggested example value provided by the agent.\n */\n exampleValue?: string;\n /**\n * Optional select options for dropdown-style inputs.\n */\n options?: ActionInputOption[];\n}\n\nexport type ActionInputValues = Record<string, string>;\n\n// ── Workflow-level input definitions ──\n\n/** Value input — a simple typed field (text, number, email, select, etc.) */\nexport interface WorkflowValueInputDefinition {\n kind: 'value';\n /** Variable name (snake_case, e.g. \"user_email\") */\n name: string;\n /** Human-friendly label */\n label: string;\n /** Expected input type. Defaults to 'text' if omitted. */\n type?: ActionInputType;\n required?: boolean;\n description?: string;\n placeholder?: string;\n /** Pre-filled default value */\n defaultValue?: string;\n options?: ActionInputOption[];\n}\n\n/** Credential input — represents a login identity (future: full credential provisioning) */\nexport interface WorkflowCredentialInputDefinition {\n kind: 'credential';\n /** Namespace for credential vars (e.g. \"admin_user\" → $var.admin_user.username) */\n name: string;\n /** Human-friendly label */\n label: string;\n required?: boolean;\n description?: string;\n /** Which credential fields this input needs */\n credentialFields: {\n username?: boolean;\n password?: boolean;\n loginUrl?: boolean;\n mfaSecret?: boolean;\n };\n /** Default credential values (for scenarios) */\n defaults?: {\n username?: string;\n password?: string;\n loginUrl?: string;\n };\n /** Default logical credential template used when a scenario does not override it */\n defaultCredentialTemplateId?: string;\n}\n\nexport type WorkflowInputDefinition =\n | WorkflowValueInputDefinition\n | WorkflowCredentialInputDefinition;\n\n// ── Workflow scenarios (named input-value sets) ──\n\n/** Value-input overrides keyed by WorkflowValueInputDefinition.name */\nexport type WorkflowScenarioValueInputs = Record<string, string>;\n\n/** Credential-input overrides keyed by WorkflowCredentialInputDefinition.name */\nexport type WorkflowScenarioCredentialInputs = Record<\n string,\n {\n /** Logical credential template to resolve against the run environment */\n credentialTemplateId?: string;\n /** Legacy inline fields; retained for backward-compatible imports/storage */\n username?: string;\n password?: string;\n loginUrl?: string;\n mfaSecret?: string;\n }\n>;\n\n/**\n * A named set of workflow input values — parameterized test case for a workflow.\n * Scenarios reference existing `inputsJson` entries by name; orphan keys are\n * surfaced as warnings in the UI but are tolerated in storage.\n */\nexport interface WorkflowScenario {\n /** Stable uuid (survives renames) */\n id: string;\n /** Human-friendly name, e.g. \"Happy path\" */\n name: string;\n description?: string;\n /** At most one scenario per workflow may be default; UI enforces this. */\n isDefault?: boolean;\n /**\n * When true (or undefined — defaults to true for backwards compatibility),\n * this scenario is included when the workflow runs as part of a test suite,\n * causing the suite to fan the workflow out to one run per included scenario.\n */\n includeInSuite?: boolean;\n values?: WorkflowScenarioValueInputs;\n credentials?: WorkflowScenarioCredentialInputs;\n}\n\n// ── Workflow environment eligibility ──\n\nexport type WorkflowEnvironmentType = 'permanent' | 'ephemeral' | 'local' | 'sandbox';\n\n/**\n * Client-facing readiness of a `type='sandbox'` environment's backing\n * flow-authoring instance. Shared by the API status/ensure/heartbeat routes\n * and the web `useSandboxEnvironment` hook so the union can't drift.\n */\nexport type SandboxEnvironmentStatus =\n | 'asleep'\n | 'warming'\n | 'ready'\n | 'stopping'\n | 'failed';\n\n/** Status-route payload for a sandbox environment's backing instance. */\nexport interface SandboxEnvironmentState {\n status: SandboxEnvironmentStatus;\n templateId: string;\n instanceId: string | null;\n /** Raw `environment_instances.status` when an instance exists. */\n instanceStatus: string | null;\n url: string | null;\n expiresAt: string | null;\n /** Provisioning failure detail when `status === 'failed'`. */\n failureReason: string | null;\n}\n\n/**\n * Explicit environment eligibility constraints for a workflow.\n * Null/absent means \"any\" — workflow can run against any environment.\n * `only`: pin to specific environment IDs (e.g. prod smoke).\n * `envTypes`: allow only listed environment types (e.g. permanent only).\n */\nexport type WorkflowEnvironmentConstraints =\n | { mode: 'only'; environmentIds: string[] }\n | { mode: 'envTypes'; types: WorkflowEnvironmentType[] };\n\n/**\n * Per-environment pass history for a workflow — \"does this flow pass on this\n * env/template?\". Keyed on the STABLE target environment (`workflow_runs.environment_id`),\n * so sandbox runs attribute to the persistent sandbox env, not the throwaway\n * ephemeral instance. Computed read-time over the recent terminal-run window.\n */\nexport interface PassByEnvironment {\n environmentId: string;\n environmentName: string;\n environmentType: WorkflowEnvironmentType;\n /**\n * Whether any terminal pass (success/flaky_success) appears in the recent\n * fetched window for this env — NOT an all-time signal. This is a read-time\n * aggregate over the same bounded window the flow-health badge uses, so a flow\n * that passed long ago but only has recent failures reads as `everPassed:false`\n * here. (The authoritative all-time \"has ever passed\" lives on the workflow's\n * `firstPassedAt`, which is global, not per-env.)\n */\n everPassed: boolean;\n /** Passes within the recent window. */\n passed: number;\n /** Terminal runs within the recent window. */\n total: number;\n /** classifyHealth verdict for this env's window (e.g. passing/flaky/failing). */\n health: WorkflowHealth;\n}\n\n/**\n * Metadata about input values, including whether synthetic data was used.\n * @deprecated Synthetic tracking has been removed. Form field reporting is\n * now unified via {@link ActionFormField} in debug mode only.\n */\nexport interface ActionInputMetadata {\n /**\n * True if any input values were synthetically generated by the agent\n * rather than provided by the user.\n */\n usedSyntheticData?: boolean;\n /**\n * List of field names that were synthetically generated.\n */\n syntheticFields?: string[];\n}\n\n/**\n * Unified form field representation for debug-mode agent reporting.\n * Combines field metadata (schema) with the value used, so the agent\n * reports everything in a single array instead of separate\n * `dataRequirements` + `inputData` fields.\n */\nexport interface ActionFormField {\n /** Unique field identifier (snake_case, e.g. task_title). */\n name: string;\n /** Human-friendly label from the UI. */\n label: string;\n /** Input type. Defaults to 'text' if omitted. */\n type?: ActionInputType;\n /** Whether the field is required. */\n required?: boolean;\n /** The value entered/used for this field. */\n value: string;\n /** Select/multiselect options, if applicable. */\n options?: ActionInputOption[];\n}\n\nexport const PLAYBOOK_STEP_ACTIONS = [\n 'click',\n 'fill',\n 'fill_form',\n 'select',\n 'navigate',\n 'wait',\n 'scroll',\n 'hover',\n 'upload',\n 'verify',\n 'press_key',\n 'drag',\n 'resize',\n 'capture',\n 'extract_table',\n 'python_execute',\n 'verify_download',\n 'verify_download_content',\n 'screenshot',\n 'agent_loop',\n] as const;\n\nexport type PlaybookStepAction = (typeof PLAYBOOK_STEP_ACTIONS)[number];\nexport type LegacyPlaybookStepAction =\n | PlaybookStepAction\n | 'other'\n | 'handle_native_dialog'\n | 'toggle'\n | 'focus';\n\nfunction isPositiveIntegerString(value: string | undefined): value is string {\n if (!value || !/^\\d+$/.test(value)) return false;\n return Number(value) > 0;\n}\n\n/**\n * Zod schema for the agent-settable fields of a playbook step. This is the\n * single source of truth for what the LLM can author via the\n * `updateWorkflowStep` tool — `apps/api` imports this directly instead of\n * keeping a parallel hard-coded schema (which used to drift, e.g. duplicate\n * action enum and missing `expectedDialogs`).\n *\n * Internal fields the *runtime* attaches (actionRefsSource, screenshotS3Key)\n * are NOT in this schema — they belong on the wider {@link PlaybookStep}\n * type below. Keeping them out keeps the agent's input surface tight and\n * prevents prompt injection of internal-only fields.\n *\n * Field descriptions are LLM-tuned: they are read directly into the tool's\n * function-calling schema. Don't shorten them without considering the agent.\n */\nexport const playbookStepAgentSchema = z\n .object({\n order: z.number().describe('Step number (1-based).'),\n action: z\n .enum(PLAYBOOK_STEP_ACTIONS)\n .describe(\n 'The type of browser interaction. PREFER DETERMINISTIC VERBS even when verbose — three deterministic steps beat one agentic step, because deterministic steps replay via Playwright with no agent dispatch. ' +\n 'Each replayable playbook step must be 1:1 with an exposed browser tool you actually used and must correspond to zero or one recorded actionRef. If you cannot accomplish the intent with the browser tools you have, do not add a playbook step for it; report the limitation outside playbookSteps. Do not summarize several recorded actions into one \"other\" step; report the same 1:1 browser primitives you actually used. The only multi-field exception is one fill_form step for one browser_fill_form action. ' +\n 'Use \"verify\" for assertion checks — both inline assertions and completion assertions proving the action succeeded. ' +\n 'Use \"verify\" for inspection language such as \"inspect whether\", \"confirm\", \"observe\", or \"check\" when the result is visible in the DOM. Use \"capture\" instead only when the observed value is needed by later steps. ' +\n 'Use \"fill\" for single-field inputs. Use \"fill_form\" when browser_fill_form was called to fill multiple fields at once — one fill_form step per call. ' +\n 'Use \"click\" for checkboxes, switches, toggle buttons, and click-to-focus interactions. There is no separate \"toggle\" or \"focus\" action. ' +\n 'Use \"upload\" for file upload actions, including generated CSV/file, attach file, choose file, import file, and document/image attachment intents (browser_action action=\"upload\"). Set params.path to the staged attachment handle, generated file variable, or filename reference used by the upload tool (e.g. {path: \"$var.csv_file\"}), and include actionRefs pointing to the recorded file_upload action. Do NOT use \"other\" for file attachment/upload/import intents. ' +\n 'Use \"scroll\" only when scrolling is the user-visible action being tested; routine browser_scroll calls for visibility, inspection, or video framing are observation steps and should usually be omitted from playbookSteps. ' +\n 'Use \"press_key\" for keyboard actions (Enter, Escape, Tab, arrows). ' +\n 'Use \"press_key\" with params.key=\"Escape\" to dismiss a popover, dropdown, panel, or modal when Escape was the recorded action; use \"click\" if the recorded action clicked a close button or outside target. There is no separate \"dismiss\" action. ' +\n 'Use \"drag\" for drag-and-drop interactions. ' +\n 'Use \"resize\" when browser_resize was used to resize the viewport. Set params.width and params.height to positive integer pixel values and include the actionRef pointing to the recorded resize action. ' +\n 'Use \"capture\" to extract app-generated values (IDs, codes, URLs) for later steps via $var.name, but only from a visible DOM target or the current URL. Do not capture from performance/network timing, storage, hidden DOM, or app-global state. ' +\n 'Use \"screenshot\" only when browser_screenshot itself is the requested evidence capture. Set params.label when a label was used, optionally params.fullPage=\"true\". Do not use screenshot for routine visual checks after actions; use verify when a browser_assert proves visible state. ' +\n 'Use \"extract_table\" when browser_extract_table was used to extract tabular data from the page. ' +\n 'Use \"python_execute\" when python_execute was used with cache_label to run a data validation script. ' +\n 'Use \"verify_download\" when browser_download(action=\"list\") / browser_list_downloads proves that a file matching some pattern was downloaded. Set params.pattern to a glob (supports *, ?) or substring; optionally params.timeoutMs. This is currently agent-only at replay, so do not set actionRefs. ' +\n 'Use \"verify_download_content\" when browser_download(action=\"read\") / browser_read_download content proves CSV headers, PDF text, exported rows, or similar downloaded-file content. Set params.contains or params.pattern to the expected content predicate. This is currently agent-only at replay, so do not set actionRefs. ' +\n 'Use \"agent_loop\" ONLY when the action set is genuinely unknown until runtime — variable count of items (e.g. \"toggle row checkboxes until 3 selected\"), conditional retry that depends on UI state, or a runtime predicate over non-DOM state. Set params.goal to a one-sentence checkable predicate (e.g. params.goal = \"3 grid row checkboxes are selected\"). DO NOT use agent_loop for: known-N iteration (write N deterministic steps), single click that might double-fire (use \"click\"), retry-on-flake (the runtime handles that). agent_loop steps replay via a Playwright override authored after first run; they are non-deterministic on first run, so the bar is high. ' +\n 'Network/devtools/API inspections and unsupported browser environment mutations are diagnostic-only unless there is an exposed browser tool for the action. If visible page state proves the condition, record that as \"verify\"; otherwise omit the diagnostic observation from playbookSteps. ' +\n 'Do not use \"other\". If no concrete browser-tool-backed action above fits, omit the observation from playbookSteps and report the limitation outside the playbook.'\n ),\n target: z\n .string()\n .describe(\n 'Human-readable element description using visible labels, placeholders, or aria-labels. ' +\n 'Examples: \"\\'Submit\\' button\", \"\\'Email\\' input field\", \"\\'Status\\' dropdown\". ' +\n 'For upload actions, describe the file input and include the attachment name (e.g. \"CSV file input for \\'testing-checklist.csv\\'\"). ' +\n 'Never use coordinates, ref IDs, or internal identifiers.'\n ),\n params: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n 'Data values using $var.name references and $MACRO() expressions — NEVER hard-coded literals. ' +\n 'Common keys: value (for fill: {value: \"$EMAIL()\"}), option (for select: {option: \"Admin\"} — ' +\n 'fixed app constants like dropdown options are allowed as literals), url (for navigate: {url: \"$var.target_url\"}), ' +\n 'path (for upload: {path: \"$var.file_path\"}), key (for press_key: {key: \"Enter\"}, {key: \"Escape\"}), ' +\n 'width and height (for resize: {width: \"390\", height: \"844\"}), ' +\n 'label and fullPage (for screenshot: {label: \"seats-table\", fullPage: \"true\"}), ' +\n 'pattern or contains (for download verification: {pattern: \"report-*.csv\"} or {contains: \"Expected Header\"}), ' +\n 'expression + saveAs (for capture: prefer a visible DOM ref expression or \"() => window.location.href\"; do not use performance.getEntries() or other network/storage/global state; e.g. {saveAs: \"order_confirmation_id\"}). ' +\n 'For fill_form: keys are field names from the fill_form call, values are $var.name or $MACRO() (e.g. {company: \"$var.company\", email: \"$EMAIL()\"}). ' +\n 'Available macros: $UUID(), $EMAIL(), $FIRST_NAME(), $LAST_NAME(), $PHONE(), $RANDOM_STRING(n), ' +\n '$RANDOM_INT(min,max), $TODAY(), $DATE(days), $NOW(), $TIMESTAMP(), $SEQUENCE(\"name\").'\n ),\n note: z\n .string()\n .optional()\n .describe(\n 'Timing or quirk notes ONLY (e.g. \"Wait 1s for modal animation\", \"Scroll into view first\"). ' +\n 'Do NOT put data values, optional markers, or verification notes here.'\n ),\n actionRefs: z\n .array(z.number())\n .max(1)\n .optional()\n .describe(\n 'Zero or one recorded browser action index that corresponds to this playbook step. ' +\n 'Interaction tools, browser_resize, capture evaluates (browser_evaluate with saveAs), and browser_assert produce [Action #N] tags — collect those indices here. ' +\n 'Read-only browser_snapshot and plain browser_evaluate without saveAs do NOT produce action indices. browser_scroll and browser_screenshot may produce action indices when they are the primary requested tool call; omit actionRefs when no action tag was emitted. ' +\n 'If you took multiple cacheable UI actions, report multiple 1:1 playbook steps instead of putting multiple refs on one step or summarizing them as \"other\". ' +\n 'A fill_form call is a single action — use one fill_form playbook step with one actionRef. ' +\n 'For a click that triggers a native dialog, keep one click actionRef on the click step and set expectedDialogs rather than adding a separate dialog actionRef. ' +\n 'Actions not referenced by any playbook step are considered exploratory and will be discarded by the cache compiler.'\n ),\n expectedDialogs: z\n .enum(['accept', 'dismiss'])\n .optional()\n .describe(\n 'Set when this step is known to fire a native browser dialog (alert/confirm/prompt) — ' +\n 'e.g. clicking a \"Delete\" button that prompts \"Are you sure?\". On replay the runtime ' +\n 'pre-arms the dialog handler with this action automatically. In free-form runs, you ' +\n 'should call the dialog tool (browser_dialog for consolidated tools, browser_handle_dialog for legacy tools) with this action immediately before the step. ' +\n 'Do not add a separate dialog step — keeping the pre-arm on the triggering step keeps the ' +\n 'pre-arm and the triggering action in one place.'\n ),\n })\n .superRefine((step, ctx) => {\n if (step.action === 'agent_loop') {\n const goal = step.params?.goal;\n if (!goal || goal.trim().length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'goal'],\n message:\n 'agent_loop steps must set params.goal to a one-sentence checkable predicate (e.g. \"3 grid row checkboxes are selected\").',\n });\n }\n }\n if (step.action === 'verify_download') {\n const pattern = step.params?.pattern;\n if (!pattern || pattern.trim().length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'pattern'],\n message:\n 'verify_download steps must set params.pattern to a glob (supports *, ?) or substring matching the expected filename.',\n });\n }\n }\n if (step.action === 'verify_download_content') {\n const pattern = step.params?.pattern;\n const contains = step.params?.contains;\n if (\n (!pattern || pattern.trim().length === 0) &&\n (!contains || contains.trim().length === 0)\n ) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params'],\n message:\n 'verify_download_content steps must set params.contains or params.pattern for the expected downloaded content.',\n });\n }\n }\n if (step.action === 'resize') {\n const width = step.params?.width;\n const height = step.params?.height;\n if (!isPositiveIntegerString(width)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'width'],\n message: 'resize steps must set params.width to a positive integer pixel value.',\n });\n }\n if (!isPositiveIntegerString(height)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'height'],\n message: 'resize steps must set params.height to a positive integer pixel value.',\n });\n }\n }\n });\n\n/** Inferred type of the agent-authored fields of a playbook step. */\nexport type PlaybookStepAgentInput = z.infer<typeof playbookStepAgentSchema>;\n\n/**\n * A single step in a workflow playbook.\n *\n * Composed of {@link PlaybookStepAgentInput} (everything the agent can set\n * via `updateWorkflowStep`) plus internal fields the runtime attaches during\n * compilation/healing. Splitting these keeps the agent's input surface\n * tight while letting the runtime carry provenance + screenshot keys.\n */\nexport interface PlaybookStep extends Omit<PlaybookStepAgentInput, 'action'> {\n /** Legacy persisted rows may still contain `other`; new agent-authored steps cannot. */\n action: LegacyPlaybookStepAction;\n\n /**\n * Provenance of `actionRefs`:\n * `'recording'` (default when omitted) — emitted by the initial recording\n * compilation; transient session-specific indices that should be stripped\n * after the cache row is built.\n * `'healing'` — emitted by the heal agent during cache-failure recovery and\n * proven correct by replay. These should be preserved across compiles so\n * the next compile lands the entry as `matchStrategy='actionRef'`.\n */\n actionRefsSource?: 'recording' | 'healing';\n /** S3 key for screenshot captured after this playbook step completed. */\n screenshotS3Key?: string;\n /**\n * Where this playbook came from.\n * `'production'` (default when omitted) — emitted by a production-mode run\n * and eligible for cache replay if it carries `actionRefs`.\n * `'validation'` — emitted by a validation-mode discovery run; advisory\n * only. Never replayed deterministically (no `actionRefs`); used as a\n * seed for the next production run on this node, which can then promote\n * the playbook by attaching `actionRefs` on success.\n */\n playbookSource?: 'production' | 'validation';\n}\n\n/**\n * Execution-time snapshot of one playbook step, persisted into\n * `workflow_run_steps.metadataJson.executedPlaybook` when step-driven cache\n * replay ran for a step (full hit or partial replay before agent fallback).\n *\n * Deliberately slim: only the display-relevant agent-authored fields. Runtime\n * plumbing (`actionRefs`, `actionRefsSource`, `screenshotS3Key`,\n * `playbookSource`, `expectedDialogs`) is stripped so run rows stay small.\n * Values are sanitized back to `$var.`/`$MACRO` tokens before persistence —\n * the cache row is mutable, so this snapshot is the only exact record of what\n * replayed for a historical run.\n */\nexport interface ExecutedPlaybookStep {\n /** Step number (1-based) — joins against `StepOutcome.stepOrder`. */\n order: number;\n action: LegacyPlaybookStepAction;\n /** Human-readable element description. */\n target: string;\n /** Data values, with variable references preserved as `$var.`/`$MACRO` tokens. */\n params?: Record<string, string>;\n /** Optional human context authored alongside the step. */\n note?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Validation run findings\n// ---------------------------------------------------------------------------\n\n/**\n * One assumption an agent made during a validation run. Surfaces as a finding\n * the test author can review and either bake into the spec or reject.\n */\nexport interface ValidationAssumption {\n /** Plain-language description of what was assumed. */\n description: string;\n /** Why the agent thought this was the right interpretation. */\n basis: string;\n /** What might go wrong on a future run if the assumption is incorrect. */\n riskIfWrong: string;\n}\n\n/**\n * One open question for the test author surfaced during a validation run.\n */\nexport interface ValidationQuestion {\n /** The specific question. */\n question: string;\n /**\n * When true, this is a question that — if unanswered — will likely cause\n * future production runs to fail or behave inconsistently. UIs should\n * surface these prominently in the post-run proposal.\n */\n blockingFutureRuns: boolean;\n}\n\n/**\n * Suggested edit to a workflow node's config, produced by a validation run.\n *\n * Intentionally narrow: validation findings should propose targeted patches\n * (e.g. \"add a precondition\", \"tighten this assertion\", \"rename the data\n * requirement\"), not free-form rewrites. The shape of `value` depends on\n * `field`. This is consumed by the workflow_proposals UI for accept/reject.\n */\nexport interface NodeConfigPatch {\n /** Which area of the node config this patch touches. */\n field:\n | 'instructions'\n | 'assertions'\n | 'preconditions'\n | 'dataRequirements'\n | 'description'\n | 'other';\n /** What kind of edit is being proposed. */\n op: 'add' | 'replace' | 'remove';\n /** Human-readable summary of the proposed change. */\n summary: string;\n /**\n * Structured patch payload. Shape varies with `field` + `op`. The proposal\n * UI knows how to render each combination; the type is intentionally\n * `unknown` here so the catalog can grow without a types package change.\n */\n value?: unknown;\n}\n\n/**\n * The validation-run-specific result for a single workflow step. Persisted\n * onto `workflow_run_steps.metadataJson.validation` and aggregated into the\n * post-run `validation_findings` proposal.\n */\nexport interface ValidationFindings {\n outcome: 'valid' | 'passed_with_concerns' | 'could_not_complete';\n /** True/false branch decision for conditional nodes. */\n branch?: 'true' | 'false';\n assumptions: ValidationAssumption[];\n questions: ValidationQuestion[];\n /**\n * Concrete config edits the agent suggests for this node. Empty for\n * `valid` outcomes; required for `passed_with_concerns`; optional for\n * `could_not_complete` (the failure itself is the primary signal).\n */\n suggestedNodeConfigPatches: NodeConfigPatch[];\n}\n\n/**\n * An assertion attached directly to an action node.\n * Verified after the action completes; cacheable assertions are replayed mechanically.\n */\nexport interface InlineAssertion {\n /** Unique ID within this node */\n id: string;\n /** Freeform description of what to verify */\n condition: string;\n /** fatal = stop the test now; fail = mark the run failed but continue; warn = log and continue */\n severity: AssertionSeverity;\n /** When true, requires agent evaluation (e.g. computer vision) and cannot be cached */\n agentic?: boolean;\n}\n\nexport type BrowserMode = 'headed' | 'headless';\n\nexport * from './crawler/analysis.js';\nexport * from './api-catalog.js';\nexport * from './credentials.js';\nexport * from './environment-kinds.js';\nexport * from './trace-uploads.js';\nexport * from './qa/testing.js';\nexport * from './qa/events.js';\nexport * from './issues.js';\nexport * from './pagination.js';\nexport * from './workflows.js';\nexport * from './browser-stream.js';\nexport * from './attachments.js';\nexport * from './permissions.js';\nexport * from './workflow-cache.js';\nexport * from './cache-optimizer-events.js';\nexport * from './cache-telemetry.js';\nexport * from './instance-logs.js';\nexport * from './local-browser.js';\nexport * from './chaos.js';\nexport * from './benchmark.js';\n\nexport * from './api-sequence.js';\nexport * from './diagnostics.js';\nexport * from './conversation-stream.js';\nexport * from './sandbox-tasks.js';\nexport * from './sandbox-instance-size.js';\nexport * from './routes/index.js';\nexport * from './macros.js';\nexport * from './recording.js';\nexport * from './variable-utils.js';\nexport * from './node-outputs.js';\nexport * from './sandbox-action-node.js';\nexport * from './credential-output.js';\nexport * from './adhoc-test.js';\nexport * from './mentions.js';\nexport * from './element-hints.js';\nexport * from './ai-routing.js';\nexport * from './workflow-node-schemas.js';\nexport * from './node-type-labels.js';\nexport * from './browser-viewport.js';\nexport * from './identifier-stability.js';\nexport * from './workflow-review.js';\nexport * from './fast-video.js';\n","/**\n * Credential Types\n *\n * Strongly-typed credential data structures for each auth provider\n */\n\nimport { z } from 'zod';\n\nexport type AuthProvider =\n | 'basic'\n | 'google'\n | 'github'\n | 'microsoft'\n | 'api_key'\n | 'twitter'\n | 'custom'\n | 'api_login'\n | 'api_basic';\n\nexport type CredentialRole = 'admin' | 'user' | 'viewer' | 'custom';\n\n/**\n * Custom HTTP headers for browser automation.\n * Stored encrypted alongside credential data.\n * Useful for bypassing bot detection (e.g., User-Agent, Accept-Language).\n */\nexport type CustomHttpHeaders = Record<string, string>;\n\n/**\n * HTTP Basic Authentication credentials for browser-level auth popups.\n * Passed to Playwright via browser.newContext({ httpCredentials }).\n */\nexport type HttpCredentials = {\n username: string;\n password: string;\n};\n\n// Individual credential data types\n\nexport type BasicAuthCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type GoogleCredential = {\n email: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type GithubCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type MicrosoftCredential = {\n email: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type ApiKeyCredential = {\n key: string;\n prefix?: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type TwitterCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type ApiLoginCredential = {\n loginFields: Record<string, string>;\n cachedToken?: string;\n cachedTokenExpiresAt?: string;\n cachedTokenObtainedAt?: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type ApiBasicCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type CustomCredential = Record<string, any> & {\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\n// Union type for all credential data\nexport type CredentialData =\n | { provider: 'basic'; data: BasicAuthCredential }\n | { provider: 'google'; data: GoogleCredential }\n | { provider: 'github'; data: GithubCredential }\n | { provider: 'microsoft'; data: MicrosoftCredential }\n | { provider: 'api_key'; data: ApiKeyCredential }\n | { provider: 'twitter'; data: TwitterCredential }\n | { provider: 'api_login'; data: ApiLoginCredential }\n | { provider: 'api_basic'; data: ApiBasicCredential }\n | { provider: 'custom'; data: CustomCredential };\n\n/**\n * Login validity tracking data for session duration prediction\n */\nexport type LoginValidityInfo = {\n /** When a login was last attempted */\n lastLoginAt: Date | null;\n /** When a login last succeeded */\n lastLoginSucceededAt: Date | null;\n /** When the session was detected as expired (requiring re-login) */\n nextLoginRequiredAt: Date | null;\n /** Calculated session validity duration in minutes */\n loginValidityDurationMinutes: number | null;\n /** Estimated session expiry based on last login + known validity */\n estimatedExpiresAt: Date | null;\n};\n\n/**\n * HeaderExtractor: declarative way to derive an HTTP header value from a\n * credential's saved storage state (Playwright cookies + localStorage +\n * extracted IndexedDB). Materialized at request time by the executor and\n * merged into the same `extraHeaders` map as `customHeaders`.\n *\n * Modern SPAs (Firebase Auth, Auth0, Clerk, …) stash JWTs in localStorage or\n * IndexedDB and rely on page JS to forward them as request headers. Playwright\n * APIRequestContext only carries cookies, so those headers go missing when we\n * fetch through the browser context. Extractors close that gap.\n */\nexport const tokenRefreshPolicySchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('firebase'),\n refreshTokenPath: z.string().default('$.value.stsTokenManager.refreshToken'),\n expirationPath: z.string().default('$.value.stsTokenManager.expirationTime'),\n accessTokenPath: z.string().default('$.value.stsTokenManager.accessToken'),\n apiKeyPath: z.string().default('$.value.apiKey'),\n }),\n]);\n\nexport type TokenRefreshPolicy = z.infer<typeof tokenRefreshPolicySchema>;\n\nconst localStorageSourceSchema = z.object({\n kind: z.literal('localStorage'),\n origin: z.string().min(1),\n key: z.string().min(1),\n jsonPath: z.string().optional(),\n});\n\nconst indexedDBSourceSchema = z.object({\n kind: z.literal('indexedDB'),\n database: z.string().min(1),\n store: z.string().min(1),\n key: z.string().min(1),\n jsonPath: z.string().min(1),\n});\n\nconst cookieSourceSchema = z.object({\n kind: z.literal('cookie'),\n domain: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const headerExtractorSourceSchema = z.discriminatedUnion('kind', [\n localStorageSourceSchema,\n indexedDBSourceSchema,\n cookieSourceSchema,\n]);\n\nexport const headerExtractorSchema = z.object({\n name: z.string().min(1),\n header: z.string().min(1).optional(),\n prefix: z.string().optional(),\n source: headerExtractorSourceSchema,\n refresh: tokenRefreshPolicySchema.optional(),\n});\n\nexport type HeaderExtractorSource = z.infer<typeof headerExtractorSourceSchema>;\nexport type HeaderExtractor = z.infer<typeof headerExtractorSchema>;\n\n/**\n * Typed view of credential.metadata. Existing fields (headerName / token*)\n * predate extractors and remain supported. `headerExtractors` is the new\n * generalized lane.\n */\nexport const credentialMetadataSchema = z\n .object({\n headerName: z.string().optional(),\n tokenHeaderName: z.string().optional(),\n tokenHeaderPrefix: z.string().optional(),\n headerExtractors: z.array(headerExtractorSchema).optional(),\n })\n .passthrough();\n\nexport type CredentialMetadata = z.infer<typeof credentialMetadataSchema>;\n\n// Extract email from credential data if available\nexport function extractEmail(provider: AuthProvider, data: any): string | null {\n switch (provider) {\n case 'basic':\n return data.username || null;\n case 'google':\n case 'microsoft':\n return data.email || null;\n case 'github':\n case 'twitter': {\n // May be username or email\n const val = data.username || null;\n return val && val.includes('@') ? val : null;\n }\n case 'api_login':\n return data.loginFields?.username || data.loginFields?.email || null;\n case 'api_basic': {\n const val = data.username || null;\n return val && val.includes('@') ? val : null;\n }\n default:\n return null;\n }\n}\n","import { z } from 'zod';\nimport type { Pagination } from './pagination.js';\nexport type { Pagination } from './pagination.js';\n\nconst clamp = (value: string, max: number): string =>\n value.length > max ? value.slice(0, max) : value;\n\nconst requiredLimitedString = (label: string, max: number) =>\n z\n .string()\n .transform((value) => value.trim())\n .pipe(z.string().min(1, `${label} is required`))\n .transform((value) => clamp(value, max));\n\nconst optionalLimitedString = (max: number) =>\n z\n .string()\n .optional()\n .nullable()\n .transform((value) => {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n return clamp(trimmed, max);\n });\n\nconst ISO_DATETIME = z.string().datetime({ offset: true });\nconst UUID = z.string().uuid();\n\nexport const IssueSeveritySchema = z.enum(['low', 'medium', 'high', 'unknown']);\nexport type IssueSeverity = z.infer<typeof IssueSeveritySchema>;\n\nexport const IssueStatusSchema = z.enum(['open', 'closed', 'not_a_bug']);\nexport type IssueStatus = z.infer<typeof IssueStatusSchema>;\n\nexport interface IssueCategorySummary {\n id: string;\n key: string;\n label: string;\n description: string | null;\n instructions: string | null;\n}\n\nexport interface IssueCategorySetting extends IssueCategorySummary {\n isActive: boolean;\n isSystem: boolean;\n sortOrder: number;\n}\n\nexport const IssueSourceKindSchema = z.enum([\n 'crawler_analysis',\n 'exploratory_attempt',\n 'qa_page_test',\n 'workflow_test',\n 'chaos_test',\n 'adhoc_test',\n]);\nexport type IssueSourceKind = z.infer<typeof IssueSourceKindSchema>;\n\n/**\n * A single network request captured from a Playwright trace.\n * Bodies are truncated to 100KB.\n */\nexport interface NetworkRequestEntry {\n url: string;\n method: string;\n status: number | null;\n durationMs: number | null;\n requestBody: string | null;\n responseBody: string | null;\n requestHeaders: Record<string, string> | null;\n responseHeaders: Record<string, string> | null;\n contentType: string | null;\n}\n\n/**\n * A single console log entry captured from a Playwright trace.\n */\nexport interface ConsoleLogEntry {\n type: 'log' | 'warning' | 'error' | 'info' | 'debug';\n text: string;\n timestamp: number | null;\n location: string | null;\n}\n\n/**\n * Category of failure root cause — add new values to this array (type is derived).\n *\n * `app_changed` is distinct from both `software_bug` and `test_underspecified`: the\n * application's behavior/labels/flow changed versus the last passing run while the\n * authored test stayed the same, and the agent cannot verify from inside the sandbox\n * whether the change was intentional. The recommendation pattern is \"X appears to have\n * changed to Y; update the test to match if intentional.\" Reserve `software_bug` for\n * behavior deltas that are clearly defects (error page, 5xx, broken layout).\n */\nexport const FAILURE_DIAGNOSTIC_CATEGORIES = ['software_bug', 'dependency_issue', 'agent_confusion', 'platform_error', 'test_underspecified', 'data_collision', 'app_changed'] as const;\nexport type FailureDiagnosticCategory = typeof FAILURE_DIAGNOSTIC_CATEGORIES[number];\n\n/** Evidence item from diagnostic analysis */\nexport interface FailureDiagnosticEvidence {\n type:\n | 'network_error'\n | 'console_error'\n | 'visual'\n | 'agent_thought'\n | 'timeline_event'\n | 'history_comparison'\n | 'before_after';\n timestamp?: number;\n description: string;\n detail?: string;\n /** S3 storage key for an extracted video frame (visual evidence only) */\n frameStorageKey?: string;\n\n /** Before/after comparison fields */\n beforeFrameStorageKey?: string;\n beforeCaption?: string;\n beforeTimestamp?: number;\n beforeRunId?: string;\n afterFrameStorageKey?: string;\n afterCaption?: string;\n afterTimestamp?: number;\n afterRunId?: string;\n}\n\n/** A structured per-node improvement suggestion produced by the diagnostic agent */\nexport interface FlowSuggestion {\n id: string;\n orderIndex: number;\n nodeId: string;\n nodeType: string;\n title: string;\n rationale: string;\n configPatch: Record<string, unknown> | null;\n sourceRunId?: string;\n sourceAuditIds?: string[];\n evidenceIndexes?: number[];\n status: 'pending' | 'accepted' | 'dismissed';\n decidedAt?: string;\n decidedByUserId?: string;\n resolutionSource?: 'user' | 'system';\n resolutionReason?: 'accepted' | 'dismissed' | 'node_deleted' | 'workflow_stale';\n}\n\n/**\n * One key moment in the diagnostic's root-cause timeline — an ordered \"what to\n * watch\" event the QA tester can jump to in the run video. Surfaced both as\n * markers on the video's diagnostic-timeline track and as the timestamped\n * key-evidence list at the top of the run Summary.\n */\nexport interface RootCauseTimelineEvent {\n /**\n * Milliseconds from the start of the video recording — the same scale as the\n * run's `videoTimelineJson` entries and `FailureDiagnosticEvidence.timestamp`\n * (the `relativeTimestampMs` column in the diagnostic workspace's\n * `timeline/actions.jsonl`, NOT the absolute wall-clock `timestampMs`).\n */\n timestampMs: number;\n /** Short marker/row label (≤8 words). */\n title: string;\n /** Optional one-line elaboration shown on hover / under the row. */\n detail?: string;\n /** Visual weight of the marker. Defaults to `info` when omitted. */\n severity?: 'info' | 'warning' | 'critical';\n}\n\n/** Structured diagnostic analysis produced by the failure diagnostic agent */\nexport interface FailureDiagnostic {\n category: FailureDiagnosticCategory;\n confidence: number;\n /** One-sentence thesis — the QA tester reads only this 80% of the time. */\n title: string;\n /**\n * Legacy freeform Markdown narrative (kept for back-compat with already-persisted rows).\n * The current prompt emits `failurePoints` bullets instead; new diagnostics may omit `analysis`.\n */\n analysis?: string;\n /** 2–4 concise bullets describing the failure (current report shape). */\n failurePoints?: string[];\n evidence: FailureDiagnosticEvidence[];\n smokingGun?: {\n type: string;\n description: string;\n detail?: string;\n };\n recommendation: string;\n comparisonWithHistory?: string;\n /** Things the agent could not determine from available evidence (display-only for now). */\n openQuestions?: string[];\n /**\n * Whether the agent materialized a green-baseline comparison workspace before this\n * verdict (v2 `materialize_run` tool). `false` on an `app_changed` verdict means the\n * comparison gate was bypassed (second submission or step-cap salvage) — treat the\n * verdict as low-trust. Absent on diagnoses that predate the field.\n */\n comparisonPerformed?: boolean;\n completedAt: string;\n agentRunId?: string;\n /** Optional structured per-node improvement suggestions */\n suggestions?: FlowSuggestion[];\n /**\n * Optional ordered root-cause timeline — key moments to watch in the run\n * video, each anchored to a video-relative timestamp so the UI can mark them\n * on the player and let the tester jump to them. Absent on older diagnoses.\n */\n rootCauseTimeline?: RootCauseTimelineEvent[];\n}\n\nexport interface IssueOccurrence {\n id: string;\n issueId: string;\n orgId: string;\n propertyId: string;\n canonicalPageId: string | null;\n credentialId: string | null;\n sourceKind: IssueSourceKind;\n sourceId: string;\n detectedAt: string;\n primaryUrl: string;\n tldr: string | null;\n summary: string | null;\n errorDetails: string | null;\n screenshotStorageKey: string | null;\n screenshotCaption: string | null;\n traceStorageKey: string | null;\n videoStorageKey: string | null;\n /** Action timeline for video overlay synchronization */\n videoTimelineJson: unknown | null;\n consoleLogExcerpt: string | null;\n networkFailureUrl: string | null;\n networkFailureStatus: number | null;\n ticketReproSteps: string | null;\n ticketUserFacingSummary: string | null;\n ticketDiagnosticsAnalysis: string | null;\n dedupReasoning: string | null;\n /** Status of async failure diagnostic analysis */\n diagnosticStatus: string | null;\n /** Structured diagnostic analysis from the failure diagnostic agent */\n diagnosticJson: FailureDiagnostic | null;\n /** Resolved workflow IDs when sourceKind is 'workflow_test' */\n workflowSourceInfo?: { workflowId: string; workflowRunId: string } | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface IssueAssignee {\n id: string;\n displayName: string | null;\n primaryEmail: string | null;\n}\n\nexport interface IssueSummary {\n id: string;\n orgId: string;\n propertyId: string;\n credentialId: string | null;\n title: string;\n tldr: string | null;\n severity: IssueSeverity;\n category: IssueCategorySummary;\n confidence: number | null;\n firstSeenAt: string;\n lastSeenAt: string;\n occurrenceCount: number;\n status: IssueStatus;\n statusUpdatedAt: string;\n closedAt: string | null;\n isArchived: boolean;\n assignedToId: string | null;\n assignedTo: IssueAssignee | null;\n createdAt: string;\n updatedAt: string;\n latestOccurrence: IssueOccurrence | null;\n tags?: Array<{ id: string; name: string; color: string }>;\n}\n\nexport interface IssueDetail extends IssueSummary {\n occurrences: IssueOccurrence[];\n}\n\nexport interface PaginatedIssuesResponse {\n data: IssueSummary[];\n pagination: Pagination;\n}\n\nexport interface IssueDetailResponse {\n issue: IssueSummary;\n occurrences: IssueOccurrence[];\n occurrencesPagination: Pagination;\n commentCount: number;\n}\n\nexport interface PaginatedIssueOccurrencesResponse {\n data: IssueOccurrence[];\n pagination: Pagination;\n}\n\nexport interface IssueCommentAuthor {\n id: string;\n displayName: string | null;\n primaryEmail: string | null;\n}\n\nexport interface IssueComment {\n id: string;\n issueId: string;\n orgId: string;\n propertyId: string;\n authorId: string;\n author: IssueCommentAuthor | null;\n body: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface PaginatedIssueCommentsResponse {\n data: IssueComment[];\n pagination: Pagination;\n}\n\nconst TITLE_LIMIT = 512;\nconst TLDR_LIMIT = 512;\nconst URL_LIMIT = 2048;\nconst STORAGE_KEY_LIMIT = 1024;\nconst DIAGNOSTIC_LIMIT = 4000;\n\nexport const RecordIssueOccurrencePayloadSchema = z.object({\n orgId: UUID,\n propertyId: UUID,\n canonicalPageId: UUID.optional()\n .nullable()\n .transform((value) => value ?? null),\n credentialId: UUID.optional()\n .nullable()\n .transform((value) => value ?? null),\n issueTitle: requiredLimitedString('Issue title', TITLE_LIMIT),\n issueSeverity: IssueSeveritySchema,\n issueCategoryId: UUID.optional()\n .nullable()\n .transform((value) => value ?? null),\n issueCategoryKey: z\n .string()\n .min(1)\n .max(64)\n .optional()\n .nullable()\n .transform((value) => {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return trimmed.length ? trimmed : null;\n }),\n issueConfidence: z\n .number()\n .int()\n .min(0)\n .max(100)\n .optional()\n .nullable()\n .transform((value) => {\n if (value === null || value === undefined) return null;\n return value;\n }),\n detectedAt: ISO_DATETIME,\n sourceKind: IssueSourceKindSchema,\n sourceId: requiredLimitedString('Source identifier', STORAGE_KEY_LIMIT),\n primaryUrl: requiredLimitedString('Primary URL', URL_LIMIT),\n summary: requiredLimitedString('Summary', DIAGNOSTIC_LIMIT),\n tldr: requiredLimitedString('TLDR', TLDR_LIMIT),\n screenshotStorageKey: requiredLimitedString('Screenshot storage key', STORAGE_KEY_LIMIT),\n screenshotCaption: optionalLimitedString(DIAGNOSTIC_LIMIT),\n traceStorageKey: optionalLimitedString(STORAGE_KEY_LIMIT),\n videoStorageKey: optionalLimitedString(STORAGE_KEY_LIMIT),\n /** Action timeline for video overlay synchronization */\n videoTimelineJson: z.unknown().optional().nullable(),\n consoleLogExcerpt: optionalLimitedString(DIAGNOSTIC_LIMIT),\n networkFailureUrl: optionalLimitedString(URL_LIMIT),\n networkFailureStatus: z\n .number()\n .int()\n .optional()\n .nullable()\n .transform((value) => value ?? null),\n /** Structured network requests captured from Playwright trace */\n networkRequestsJson: z.array(z.unknown()).optional().nullable(),\n /** Structured console logs captured from Playwright trace */\n consoleLogsJson: z.array(z.unknown()).optional().nullable(),\n ticketReproSteps: optionalLimitedString(DIAGNOSTIC_LIMIT),\n ticketUserFacingSummary: requiredLimitedString('Ticket summary', DIAGNOSTIC_LIMIT),\n ticketDiagnosticsAnalysis: requiredLimitedString('Ticket diagnostics analysis', DIAGNOSTIC_LIMIT),\n});\n\nexport type RecordIssueOccurrencePayload = z.infer<typeof RecordIssueOccurrencePayloadSchema>;\n","/**\n * Local Browser Protocol Types\n *\n * WebSocket message protocol for communication between the CLI (LocalBrowserHost)\n * and the cloud API (RemoteBrowserClient). Enables cloud agents to control a\n * browser running on the user's local machine.\n */\n\n// ============================================================================\n// Session Types\n// ============================================================================\n\nexport type LocalBrowserMode = 'playwright' | 'cdp';\n\nexport type LocalBrowserSessionStatus =\n | 'pending' // Session created, waiting for CLI to connect\n | 'connecting' // CLI is connecting\n | 'connected' // CLI connected, browser ready\n | 'running' // Agent is actively using browser\n | 'disconnected' // CLI disconnected\n | 'expired' // Session timed out\n | 'error'; // Error occurred\n\nexport type LocalBrowserSession = {\n id: string;\n orgId: string;\n createdBy: string | null;\n status: LocalBrowserSessionStatus;\n browserMode: LocalBrowserMode;\n instructions: string | null;\n startUrl: string | null;\n jobId: string | null;\n connectedAt: string | null;\n disconnectedAt: string | null;\n expiresAt: string | null;\n createdAt: string;\n updatedAt: string;\n};\n\n// ============================================================================\n// WebSocket Message Types\n// ============================================================================\n\n/**\n * Base message envelope for all WebSocket messages.\n */\ninterface BaseMessage {\n /** Unique message ID for request/response correlation */\n id: string;\n /** Unix timestamp in milliseconds */\n timestamp: number;\n}\n\n/**\n * Command sent from cloud API to CLI to execute a browser action.\n */\nexport interface BrowserCommand extends BaseMessage {\n type: 'command';\n /** Method name from IBrowserClient interface */\n method: string;\n /** Serialized method arguments */\n args: unknown[];\n /** Optional context ID for multi-context support. Omitted = default context. */\n contextId?: string;\n}\n\n/**\n * Response from CLI to cloud API with execution result.\n */\nexport interface BrowserResponse extends BaseMessage {\n type: 'response';\n /** ID of the original command */\n requestId: string;\n /** Whether the command executed successfully */\n success: boolean;\n /** Return value from the method (if success=true) */\n result?: unknown;\n /** Error message (if success=false) */\n error?: string;\n /** Error stack trace for debugging */\n stack?: string;\n /** Echoed context ID from the originating command */\n contextId?: string;\n}\n\n/**\n * Binary data message for large payloads (screenshots, traces).\n * Data is base64-encoded to work over JSON WebSocket.\n */\nexport interface BinaryDataMessage extends BaseMessage {\n type: 'binary';\n /** ID of the original command this data belongs to */\n requestId: string;\n /** Type of binary data */\n dataType: 'screenshot' | 'video' | 'trace';\n /** Base64-encoded binary data */\n data: string;\n /** MIME type of the data */\n mimeType: string;\n}\n\n/**\n * Heartbeat message for connection health monitoring.\n */\nexport interface HeartbeatMessage extends BaseMessage {\n type: 'heartbeat';\n /** Direction of the heartbeat */\n direction: 'ping' | 'pong';\n}\n\n/**\n * Session lifecycle event message.\n */\nexport interface SessionMessage extends BaseMessage {\n type: 'session';\n /** Type of session event */\n event:\n | 'connected' // CLI successfully connected\n | 'disconnected' // CLI disconnected\n | 'browser_ready' // Browser launched and ready\n | 'browser_closed' // Browser was closed\n | 'error'; // Session error occurred\n /** Browser mode being used */\n browserMode?: LocalBrowserMode;\n /** Error message if event='error' */\n error?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Union of all WebSocket message types.\n */\nexport type LocalBrowserMessage =\n | BrowserCommand\n | BrowserResponse\n | BinaryDataMessage\n | HeartbeatMessage\n | SessionMessage;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nexport function isBrowserCommand(msg: unknown): msg is BrowserCommand {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as BrowserCommand).type === 'command' &&\n typeof (msg as BrowserCommand).method === 'string'\n );\n}\n\nexport function isBrowserResponse(msg: unknown): msg is BrowserResponse {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as BrowserResponse).type === 'response' &&\n typeof (msg as BrowserResponse).requestId === 'string'\n );\n}\n\nexport function isBinaryDataMessage(msg: unknown): msg is BinaryDataMessage {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as BinaryDataMessage).type === 'binary' &&\n typeof (msg as BinaryDataMessage).data === 'string'\n );\n}\n\nexport function isHeartbeatMessage(msg: unknown): msg is HeartbeatMessage {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as HeartbeatMessage).type === 'heartbeat'\n );\n}\n\nexport function isSessionMessage(msg: unknown): msg is SessionMessage {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as SessionMessage).type === 'session'\n );\n}\n\n// ============================================================================\n// API Request/Response Types\n// ============================================================================\n\nexport type CreateLocalBrowserSessionRequest = {\n /** Browser mode: 'playwright' for fresh browser, 'cdp' for existing Chrome */\n browserMode?: LocalBrowserMode;\n /** Instructions for the cloud agent */\n instructions?: string | null;\n /** Initial URL to navigate to */\n startUrl?: string | null;\n};\n\nexport type CreateLocalBrowserSessionResponse = {\n /** Session ID */\n sessionId: string;\n /** WebSocket token for CLI connection */\n wsToken: string;\n /** WebSocket URL for CLI to connect to */\n wsUrl: string;\n /** Session expiration time */\n expiresAt: string;\n};\n\nexport type LocalBrowserSessionDetail = {\n session: LocalBrowserSession;\n createdBy: { id: string; name: string | null; email: string | null } | null;\n};\n\nexport type ListLocalBrowserSessionsResponse = {\n items: LocalBrowserSessionDetail[];\n pagination: {\n page: number;\n pageSize: number;\n totalItems: number;\n totalPages: number;\n };\n};\n\n// ============================================================================\n// Job Types\n// ============================================================================\n\nexport const LOCAL_BROWSER_JOB_TYPE = 'local-browser.run' as const;\n\nexport type LocalBrowserJobPayload = {\n sessionId: string;\n instructions: string;\n startUrl?: string | null;\n};\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default timeout for browser commands in milliseconds */\nexport const BROWSER_COMMAND_TIMEOUT_MS = 60_000;\n\n/** Timeout for navigation commands in milliseconds */\nexport const BROWSER_NAVIGATION_TIMEOUT_MS = 120_000;\n\n/** Timeout for screenshot commands in milliseconds */\nexport const BROWSER_SCREENSHOT_TIMEOUT_MS = 30_000;\n\n/** Session expiration time in milliseconds (1 hour) */\nexport const SESSION_EXPIRATION_MS = 60 * 60 * 1000;\n\n/** Heartbeat interval in milliseconds */\nexport const HEARTBEAT_INTERVAL_MS = 30_000;\n","/**\n * Benchmark System Types\n *\n * Shared TypeScript types for the benchmark system. Used by:\n * - `@chatsdet/benchmark` package (expectations)\n * - `@chatsdet/db` (repository types)\n * - `apps/api/src/benchmark` (service, routes, scoring)\n * - `apps/web/src/ui/pages/superadmin` (frontend UI)\n *\n * @module @chatsdet/types/benchmark\n * @see docs/api/services/benchmark.md\n */\n\n// Job types that can be benchmarked\nexport type BenchmarkJobType = 'workflow_suite';\n\n// Run status\nexport type BenchmarkRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'timeout';\n\n// Trigger type\nexport type BenchmarkTriggerType = 'scheduled' | 'manual';\n\n// ---------------------------------------------------------------------------\n// Workflow Suite Benchmark Types\n// ---------------------------------------------------------------------------\n\nexport type BenchmarkWorkflowRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';\n\n/**\n * Configuration for workflow suite benchmarks (stored in jobConfig jsonb)\n */\nexport interface BenchmarkWorkflowSuiteConfig {\n runsPerWorkflow?: number;\n runTimeoutMinutes?: number;\n suiteTimeoutHours?: number;\n sendSlackSummary?: boolean;\n}\n\nimport { z } from 'zod';\n\nexport const benchmarkWorkflowSuiteConfigSchema = z.object({\n runsPerWorkflow: z.number().int().min(1).max(20).optional(),\n runTimeoutMinutes: z.number().int().min(1).max(720).optional(),\n suiteTimeoutHours: z.number().int().min(1).max(24).optional(),\n sendSlackSummary: z.boolean().optional(),\n});\n\n/**\n * Per-node metrics captured during a benchmark workflow run\n */\nexport interface BenchmarkNodeMetrics {\n nodeId: string;\n nodeName: string;\n nodeType: string;\n durationMs: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n status: string;\n cacheOutcome: 'hit' | 'miss' | 'fallback' | 'skipped' | null;\n cacheHitSource: 'replay' | 'recovery' | 'l2_gap' | 'wait_shortcut' | null;\n cacheOutcomeReason: string | null;\n compilationScore?: number | null;\n gapCount?: number | null;\n repairApplied?: boolean | null;\n maxInputTokens: number | null;\n contextWindowTokens: number | null;\n}\n\n/**\n * Aggregated metrics for a single benchmark workflow run\n */\nexport interface BenchmarkWorkflowRunMetrics {\n id: string;\n benchmarkRunId: string;\n sourceWorkflowId: string;\n copyWorkflowId: string;\n workflowName: string;\n runNumber: number;\n workflowRunId: string | null;\n status: BenchmarkWorkflowRunStatus;\n isCached: boolean;\n durationMs: number | null;\n totalTokens: number | null;\n inputTokens: number | null;\n outputTokens: number | null;\n totalNodes: number | null;\n successfulNodes: number | null;\n failedNodes: number | null;\n nodeMetrics: BenchmarkNodeMetrics[] | null;\n errorMessage: string | null;\n startedAt: string | null;\n completedAt: string | null;\n createdAt: string;\n}\n\n/**\n * Result of importing a workflow into the benchmark org\n */\nexport interface BenchmarkImportResult {\n workflowId: string;\n workflowName: string;\n propertyId: string;\n propertyName: string;\n credentialMappings: Array<{\n sourceCredentialId: string;\n newCredentialId: string;\n credentialName: string;\n }>;\n}\n\n// ---------------------------------------------------------------------------\n// Environment Snapshot (captured at suite run time for comparison)\n// ---------------------------------------------------------------------------\n\n/**\n * Structured snapshot of the full execution environment captured at benchmark run time.\n * Enables multi-dimensional comparison between runs to attribute result changes.\n */\nexport interface EnvironmentSnapshot {\n /** Workflow-relevant prompt versions and content hashes */\n prompts: Record<string, { version: number; contentHash: string }>;\n /** Resolved AI provider/model configuration */\n modelConfig: {\n baseProvider: string;\n defaultModel: string;\n contextWindowTokens: number;\n providerOverride: string | null;\n modelOverride: string | null;\n };\n /** Per-workflow flow graph metadata (keyed by source workflow ID) */\n workflows: Record<\n string,\n {\n name: string;\n snapshotVersion: number;\n snapshotHash: string;\n nodeCount: number;\n edgeCount: number;\n }\n >;\n /** Benchmark config metadata at run time */\n benchmarkConfig: {\n version: number;\n provider: string | null;\n model: string | null;\n promptVersion: string | null;\n jobConfig: Record<string, unknown>;\n };\n}\n\nexport interface BenchmarkWorkflowTrendPoint {\n date: string;\n benchmarkRunId: string;\n configId: string;\n workflowName: string;\n runNumber: number;\n isCached: boolean;\n status: BenchmarkWorkflowRunStatus;\n triggerType: BenchmarkTriggerType;\n durationMs: number | null;\n totalTokens: number | null;\n successfulNodes: number | null;\n failedNodes: number | null;\n totalNodes: number | null;\n cachedSteps: number | null;\n l2RecoverySteps: number | null;\n ejectedSteps: number | null;\n agentSteps: number | null;\n skippedSteps: number | null;\n}\n","/**\n * API Sequence Types\n *\n * Types for configuring and executing API sequence nodes in workflows.\n * An API sequence node executes multiple API calls in order, with support\n * for template variables that reference previous step responses.\n */\n\nimport { z } from 'zod';\n\n/**\n * Comparison operators shared by API-sequence validations and deterministic\n * workflow condition predicates. Single source of truth — both the validation\n * schemas and the condition predicate schema derive from this enum.\n */\nexport const ValidationOperatorSchema = z.enum([\n 'equals',\n 'not_equals',\n 'gt',\n 'lt',\n 'gte',\n 'lte',\n 'contains',\n 'not_contains',\n 'is_set',\n 'is_not_set',\n]);\n\nexport type ValidationOperator = z.infer<typeof ValidationOperatorSchema>;\n\n/**\n * Pure operator evaluation shared by API-sequence validations and deterministic\n * workflow conditions. `actual` / `expected` are pre-resolved string values\n * (`undefined` means \"not set\"). Returns whether the comparison passed plus a\n * human-readable message. Numeric operators coerce both operands via `Number()`;\n * non-numeric input fails with a descriptive message rather than throwing.\n */\nexport function evaluateOperator(args: {\n actual: string | undefined;\n operator: ValidationOperator;\n expected?: string;\n /** Label for the left operand in messages (e.g. a ref or variable name). */\n label?: string;\n}): { passed: boolean; message: string } {\n const { actual, operator, expected } = args;\n const label = args.label ?? 'value';\n const isSet = actual !== undefined && actual !== null;\n\n switch (operator) {\n case 'is_set':\n return {\n passed: isSet,\n message: isSet ? `${label} is set (value: ${actual})` : `${label} is not set`,\n };\n case 'is_not_set':\n return {\n passed: !isSet,\n message: !isSet ? `${label} is not set` : `${label} is set (value: ${actual})`,\n };\n case 'equals':\n return {\n passed: actual === expected,\n message:\n actual === expected\n ? `${label} equals \"${expected}\"`\n : `Expected ${label} to equal \"${expected}\", got \"${actual}\"`,\n };\n case 'not_equals':\n return {\n passed: actual !== expected,\n message:\n actual !== expected\n ? `${label} does not equal \"${expected}\"`\n : `Expected ${label} to not equal \"${expected}\"`,\n };\n case 'contains': {\n const pass = isSet && expected !== undefined && expected !== null && actual.includes(expected);\n return {\n passed: pass,\n message: pass\n ? `${label} contains \"${expected}\"`\n : `Expected ${label} to contain \"${expected}\", got \"${actual}\"`,\n };\n }\n case 'not_contains': {\n const pass =\n !isSet || expected === undefined || expected === null || !actual.includes(expected);\n return {\n passed: pass,\n message: pass\n ? `${label} does not contain \"${expected}\"`\n : `Expected ${label} to not contain \"${expected}\", got \"${actual}\"`,\n };\n }\n case 'gt':\n case 'lt':\n case 'gte':\n case 'lte': {\n const numActual = Number(actual);\n const numExpected = Number(expected);\n if (isNaN(numActual) || isNaN(numExpected)) {\n return {\n passed: false,\n message: `Cannot compare non-numeric values: \"${actual}\" ${operator} \"${expected}\"`,\n };\n }\n const ops = {\n gt: (a: number, b: number) => a > b,\n lt: (a: number, b: number) => a < b,\n gte: (a: number, b: number) => a >= b,\n lte: (a: number, b: number) => a <= b,\n } as const;\n const passed = ops[operator](numActual, numExpected);\n return {\n passed,\n message: passed\n ? `${label} (${numActual}) ${operator} ${numExpected}`\n : `Expected ${label} (${numActual}) ${operator} ${numExpected}`,\n };\n }\n default:\n return { passed: false, message: `Unknown operator: ${operator as string}` };\n }\n}\n\nexport interface ApiSequenceValidation {\n /** Reference to a step value: \"stepId.path.to.value\" (e.g., \"step3.data.accountId\") */\n left: string;\n operator: ValidationOperator;\n /** Literal value or step reference. Not needed for is_set/is_not_set. */\n right?: string;\n description?: string;\n}\n\nexport interface ApiSequenceValidationResult {\n validation: ApiSequenceValidation;\n passed: boolean;\n actualValue?: string;\n expectedValue?: string;\n message?: string;\n}\n\n/**\n * A single step in an API sequence.\n */\nexport interface ApiSequenceStep {\n /**\n * Unique identifier for this step (used for variable references).\n * If not provided, defaults to `step{index}`.\n */\n stepId?: string;\n\n /**\n * Reference to an API request in the api_requests table.\n * Provides default method, URL pattern, and example request/response data.\n */\n apiRequestId: string;\n\n /**\n * Which credential to use for this request.\n * If null, the request is made without authentication.\n */\n credentialId: string | null;\n\n /**\n * Override the HTTP method (GET, POST, PUT, PATCH, DELETE, etc.).\n * If not specified, uses the method from the referenced API request.\n */\n method?: string;\n\n /**\n * URL template with optional variable placeholders.\n * Supports {{stepN.response.path.to.value}} syntax.\n * If not specified, backfilled from the API catalog at execution time.\n *\n * @example \"https://api.example.com/users/{{step1.response.data.userId}}\"\n */\n urlTemplate?: string;\n\n /**\n * Query parameters as key-value pairs. Values support template variables.\n * These are merged into the URL at execution time (appended to any existing query string).\n */\n queryParams?: Array<{ key: string; value: string }>;\n\n /**\n * Request body template (JSON string) with optional variable placeholders.\n * Supports {{stepN.response.path.to.value}} syntax.\n * If null/undefined, uses the sample from the API request for POST/PUT/PATCH,\n * or sends no body for GET/DELETE.\n */\n bodyTemplate?: string | null;\n\n /**\n * Request header overrides with optional variable placeholders.\n * These are merged with headers from the credential and API request sample.\n */\n headersTemplate?: Record<string, string> | null;\n\n /**\n * Name to store this step's response under for use in subsequent steps.\n * Defaults to `step{index}` if not specified.\n *\n * @example \"createUserResponse\"\n */\n responseVariableName?: string;\n\n /**\n * Expected HTTP status codes for validation.\n * If specified, the step fails if the response status is not in this list.\n * If not specified, any successful response (2xx) is accepted.\n *\n * @example [200, 201]\n */\n expectedStatusCodes?: number[];\n\n /**\n * Optional description for this step (for UI display).\n */\n description?: string;\n\n /**\n * Assertions to run after this step's HTTP request completes.\n * Each validation checks a value from step results against a condition.\n */\n validations?: ApiSequenceValidation[];\n}\n\n/**\n * A variable export declaration that extracts a value from a step's response\n * and registers it in the workflow's variable registry for downstream steps.\n */\nexport interface ApiSequenceExport {\n /** Variable name using resource_field convention (e.g., \"project_id\", \"project_name\", \"user_email\") */\n variableName: string;\n /** Which step's response to extract from */\n stepId: string;\n /** Dot-notation path in the JSON body (e.g., \"data.id\") */\n responsePath: string;\n /** Whether to extract from the request body or response body. Defaults to 'response'. */\n source?: 'request' | 'response';\n /** Description for UI display */\n description?: string;\n}\n\n/**\n * Configuration for an API sequence workflow node.\n */\nexport interface ApiSequenceConfig {\n /**\n * Ordered list of API calls to execute.\n */\n steps: ApiSequenceStep[];\n\n /**\n * Human-readable description of what this sequence does.\n */\n description?: string;\n\n /**\n * If true, stop execution on the first failed step.\n * If false, continue executing remaining steps even if one fails.\n * Defaults to true.\n */\n stopOnFailure?: boolean;\n\n /**\n * Variable exports to extract from step responses and register\n * in the workflow's variable registry for downstream nodes.\n */\n exports?: ApiSequenceExport[];\n}\n\n/**\n * Result of executing a single API sequence step.\n */\nexport interface ApiSequenceStepResult {\n stepId: string;\n success: boolean;\n statusCode?: number;\n statusText?: string;\n headers?: Record<string, string>;\n body?: string | null;\n durationMs?: number;\n error?: string;\n timestamp: string;\n /** The fully resolved URL that was actually requested */\n resolvedUrl?: string;\n /** The HTTP method used */\n method?: string;\n /** The request body sent (if any) */\n requestBody?: string | null;\n /** The request headers sent */\n requestHeaders?: Record<string, string>;\n /** Results of validation assertions run after this step */\n validationResults?: ApiSequenceValidationResult[];\n}\n\n/**\n * Verification result comparing declared seed outputs against actually exported variables.\n */\nexport interface SeedOutputVerification {\n /** Whether all declared seed outputs were satisfied by exports */\n allSatisfied: boolean;\n /** dataKeys that were found in exportedVariables */\n satisfied: string[];\n /** dataKeys that were declared but missing from exportedVariables */\n missing: string[];\n}\n\n/**\n * How an api_sequence_runs row was initiated. Drives the \"trigger\" chip in the\n * flow designer's Runs panel and lets us distinguish manual designer-button\n * runs from runs spawned by a workflow execution or test suite.\n */\nexport type ApiSequenceTriggeredVia = 'designer' | 'workflow' | 'seed' | 'test-suite';\n\n/**\n * Outbound HTTP header names attached to every request issued by an\n * api_sequence step (and to Playwright browser contexts when the run\n * originates from a workflow). Together they reconstruct a deep link to the\n * failing step:\n * `https://app.trycanary.ai/flows/{flowId}?runId={runId}`\n *\n * NOTE: these headers are sent to **third-party hosts** that the step targets.\n * Values are opaque UUIDs (workflows.id / workflow_runs.id) — no credentials,\n * no PII — but be aware they cross our perimeter intentionally so customers\n * can correlate canary traffic in their own server logs.\n */\nexport const CANARY_TRACE_HEADERS = {\n flowId: 'x-canary-flow-id',\n runId: 'x-canary-run-id',\n} as const;\n\n/**\n * Build the canary trace header pair from a run's identifiers. Returns an\n * empty object when no context is available so callers can spread freely.\n */\nexport function buildCanaryTraceHeaders(\n context: { flowId: string; runId: string } | undefined\n): Record<string, string> {\n if (!context) return {};\n return {\n [CANARY_TRACE_HEADERS.flowId]: context.flowId,\n [CANARY_TRACE_HEADERS.runId]: context.runId,\n };\n}\n\n/**\n * Result of executing an entire API sequence.\n */\nexport interface ApiSequenceResult {\n success: boolean;\n stepResults: ApiSequenceStepResult[];\n totalDurationMs: number;\n failedStepId?: string;\n /** Variables extracted from step responses via the exports config */\n exportedVariables?: Record<string, string>;\n /** Verification of declared seed outputs against actual exports (seed workflows only) */\n seedOutputVerification?: SeedOutputVerification;\n}\n","/**\n * Sandbox Task & Checklist Catalog\n *\n * Code-defined task/checklist definitions shared between frontend and backend.\n * The legacy SANDBOX_TASKS is preserved for backward compat.\n * The new checklist system uses profile-driven, DAG-ordered items.\n *\n * @module types/sandbox-tasks\n */\n\n// ---------------------------------------------------------------------------\n// Artifact types\n// ---------------------------------------------------------------------------\n\n/** Artifact types with special platform behavior (template sync, file path mapping). */\nexport type WellKnownArtifactType =\n | 'docker-compose'\n | 'dockerfile'\n | 'startup-script'\n | 'setup-script'\n | 'seed-script'\n | 'environment-config';\n\n/** Any string is a valid artifact type. Well-known types get special treatment. */\nexport type SandboxArtifactType = WellKnownArtifactType | (string & {});\n\n// ---------------------------------------------------------------------------\n// Legacy task types (backward compat)\n// ---------------------------------------------------------------------------\n\nexport interface SandboxTaskDefinition {\n id: string;\n label: string;\n description: string;\n icon: string;\n producesArtifacts: SandboxArtifactType[];\n order: number;\n /** Prompt template ID in apps/api/src/ai/prompts/ */\n promptTemplate: string;\n}\n\nexport interface SandboxTaskStatus {\n status: 'pending' | 'running' | 'completed' | 'failed';\n startedAt?: string;\n completedAt?: string;\n summary?: string;\n error?: string;\n}\n\n/** A task definition merged with its runtime status and artifact info */\nexport interface SandboxTaskWithStatus extends SandboxTaskDefinition {\n taskStatus: SandboxTaskStatus;\n artifacts: Array<{\n artifactType: string;\n filename: string;\n }>;\n}\n\n/** Task SSE events are defined in conversation-stream.ts */\nimport type { TaskArtifactPublishedEvent, TaskCompletedEvent } from './conversation-stream.js';\n\nexport type TaskStreamEvent =\n | TaskArtifactPublishedEvent\n | TaskCompletedEvent;\n\nexport const SANDBOX_TASKS: SandboxTaskDefinition[] = [\n {\n id: 'setup-environment',\n label: 'Setup Environment',\n description: 'Create Docker Compose, Dockerfile, and startup script to run the app',\n icon: 'GearSix',\n producesArtifacts: ['docker-compose', 'dockerfile', 'startup-script', 'seed-script'],\n order: 1,\n promptTemplate: 'env-template-setup-environment',\n },\n];\n\n// ---------------------------------------------------------------------------\n// Checklist system — profile-driven, DAG-ordered setup items\n// ---------------------------------------------------------------------------\n\nexport type ChecklistItemId =\n | 'provisioned'\n | 'code-cloned'\n | 'environment-analyzed'\n | 'setup-script-created'\n | 'dockerfile-created'\n | 'docker-compose-created'\n | 'server-running'\n | 'server-accessible'\n | 'adhoc-test-ready'\n | 'seed-script-created'\n | 'unit-tests-runnable'\n | 'integration-tests-runnable'\n | 'template-verified';\n\nexport interface ChecklistItemDefinition {\n id: ChecklistItemId;\n label: string;\n description: string;\n dependsOn: ChecklistItemId[];\n producesArtifacts: SandboxArtifactType[];\n agentAutomatable: boolean;\n /** Whether this item must be completed/skipped before the template can flip to verified. */\n countsTowardVerification?: boolean;\n /** Prompt template ID in apps/api/src/ai/prompts/ (only for agent-automatable items) */\n promptTemplate?: string;\n}\n\nexport type ChecklistProfile = 'minimal' | 'ci-ready' | 'full-stack';\n\nexport interface ChecklistProfileDefinition {\n id: ChecklistProfile;\n label: string;\n description: string;\n items: ChecklistItemId[];\n}\n\nexport interface ChecklistItemStatus {\n status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';\n startedAt?: string;\n completedAt?: string;\n summary?: string;\n error?: string;\n}\n\n/** A checklist item definition merged with its runtime status and artifact info */\nexport interface ChecklistItemWithStatus extends ChecklistItemDefinition {\n itemStatus: ChecklistItemStatus;\n /** True when all deps are met and item is not yet completed/running/skipped */\n actionable: boolean;\n artifacts: Array<{ artifactType: string; filename: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// Checklist item definitions (DAG)\n// ---------------------------------------------------------------------------\n\nexport const CHECKLIST_ITEMS: ChecklistItemDefinition[] = [\n {\n id: 'provisioned',\n label: 'Provisioned',\n description: 'Sandbox instance has been provisioned',\n dependsOn: [],\n producesArtifacts: [],\n agentAutomatable: false,\n },\n {\n id: 'code-cloned',\n label: 'Code Cloned',\n description: 'Repository has been cloned into the sandbox',\n dependsOn: ['provisioned'],\n producesArtifacts: [],\n agentAutomatable: false,\n },\n {\n id: 'environment-analyzed',\n label: 'Environment Analyzed',\n description: 'Repository analyzed for runtime, services, and dependencies',\n dependsOn: ['code-cloned'],\n producesArtifacts: ['environment-config'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-analyze-environment',\n },\n {\n id: 'setup-script-created',\n label: 'Setup Script Created',\n description: 'Environment setup script for installing dependencies and build prerequisites',\n dependsOn: ['environment-analyzed'],\n producesArtifacts: ['setup-script'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-setup-script',\n },\n {\n id: 'unit-tests-runnable',\n label: 'Unit Tests Runnable',\n description: 'Unit test suite can be executed successfully',\n dependsOn: ['setup-script-created'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-unit-tests',\n },\n {\n id: 'dockerfile-created',\n label: 'Dockerfile Created',\n description: 'Dockerfile generated from environment analysis',\n dependsOn: ['environment-analyzed'],\n producesArtifacts: ['dockerfile'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-dockerfile',\n },\n {\n id: 'docker-compose-created',\n label: 'Docker Compose Created',\n description: 'Docker Compose file with application and service containers',\n dependsOn: ['dockerfile-created'],\n producesArtifacts: ['docker-compose'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-docker-compose',\n },\n {\n id: 'integration-tests-runnable',\n label: 'Integration Tests Runnable',\n description: 'Integration tests can connect to services and run',\n dependsOn: ['docker-compose-created'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-integration-tests',\n },\n {\n id: 'seed-script-created',\n label: 'Seed Script Created',\n description: 'Database seed script for initial data population',\n dependsOn: ['docker-compose-created'],\n producesArtifacts: ['seed-script'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-seed-script',\n },\n {\n id: 'server-running',\n label: 'Server Running',\n description: 'Application server started via startup script',\n dependsOn: ['docker-compose-created'],\n producesArtifacts: ['startup-script'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-start-server',\n },\n {\n id: 'server-accessible',\n label: 'Server Accessible',\n description: 'Application server responds to health check requests',\n dependsOn: ['server-running'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-server',\n },\n {\n id: 'adhoc-test-ready',\n label: 'Ad Hoc Test Ready',\n description:\n 'Credential-producing sandbox action validated against a running instance for sandbox-backed ad hoc tests',\n dependsOn: ['server-accessible'],\n producesArtifacts: [],\n agentAutomatable: true,\n countsTowardVerification: false,\n promptTemplate: 'env-checklist-enable-adhoc-testing',\n },\n {\n id: 'template-verified',\n label: 'Template Verified',\n description: 'A fresh instance provisioned from this template comes up healthy',\n dependsOn: ['server-accessible'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-template',\n },\n];\n\n/** O(1) lookup by checklist item ID */\nexport const CHECKLIST_ITEM_MAP: Record<ChecklistItemId, ChecklistItemDefinition> =\n Object.fromEntries(CHECKLIST_ITEMS.map((item) => [item.id, item])) as Record<\n ChecklistItemId,\n ChecklistItemDefinition\n >;\n\n// ---------------------------------------------------------------------------\n// Profile definitions\n// ---------------------------------------------------------------------------\n\nconst ALL_CHECKLIST_ITEM_IDS: ChecklistItemId[] = CHECKLIST_ITEMS.map((item) => item.id);\n\nexport const CHECKLIST_PROFILES: Record<ChecklistProfile, ChecklistProfileDefinition> = {\n minimal: {\n id: 'minimal',\n label: 'Minimal',\n description: 'Provisioned sandbox with code cloned — git access only',\n items: ['provisioned', 'code-cloned'],\n },\n 'ci-ready': {\n id: 'ci-ready',\n label: 'CI Ready',\n description: 'Code cloned with unit tests runnable — commit PRs and let CI handle the rest',\n items: ['provisioned', 'code-cloned', 'environment-analyzed', 'setup-script-created', 'unit-tests-runnable'],\n },\n 'full-stack': {\n id: 'full-stack',\n label: 'Full Stack',\n description: 'Complete containerized environment with all services, tests, and server running',\n items: ALL_CHECKLIST_ITEM_IDS,\n },\n};\n\nexport function getVerificationChecklistItemIds(profile: ChecklistProfile): ChecklistItemId[] {\n const profileItems = new Set(CHECKLIST_PROFILES[profile].items);\n return CHECKLIST_ITEMS.filter(\n (item) => profileItems.has(item.id) && item.countsTowardVerification !== false\n ).map((item) => item.id);\n}\n","/**\n * Macro system shared types and catalog.\n *\n * @module macros\n * @see apps/docs/src/content/docs/reference/macros.mdx\n *\n * Macros allow dynamic data generation in workflow test inputs.\n * Format: $MACRO_NAME(args) — resolved at execution time to fresh values.\n */\n\nexport interface MacroCatalogEntry {\n /** Macro name (uppercase, e.g. \"UUID\") */\n name: string;\n /** Human-readable description */\n description: string;\n /** Argument definitions (empty for no-arg macros) */\n args: Array<{\n name: string;\n type: 'number' | 'string';\n description: string;\n }>;\n /** Example resolved output */\n example: string;\n}\n\n/**\n * Full catalog of available macros — shared by backend resolver and frontend typeahead.\n */\nexport const MACRO_CATALOG: MacroCatalogEntry[] = [\n {\n name: 'UUID',\n description: 'Generate a random UUID v4',\n args: [],\n example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',\n },\n {\n name: 'NOW',\n description: 'Current ISO 8601 timestamp',\n args: [],\n example: '2026-02-19T14:30:00.000Z',\n },\n {\n name: 'TODAY',\n description: 'Current date (YYYY-MM-DD)',\n args: [],\n example: '2026-02-19',\n },\n {\n name: 'DATE',\n description: 'Date offset from today by N days (negative for past, positive for future)',\n args: [{ name: 'days', type: 'number', description: 'Number of days offset (e.g. -7, +30)' }],\n example: '2026-02-26',\n },\n {\n name: 'TIMESTAMP',\n description: 'Current Unix timestamp in milliseconds',\n args: [],\n example: '1771520400000',\n },\n {\n name: 'RANDOM_INT',\n description: 'Random integer in range (inclusive)',\n args: [\n { name: 'min', type: 'number', description: 'Minimum value' },\n { name: 'max', type: 'number', description: 'Maximum value' },\n ],\n example: '42',\n },\n {\n name: 'RANDOM_STRING',\n description: 'Random alphanumeric string',\n args: [{ name: 'length', type: 'number', description: 'Character count' }],\n example: 'xK9mQ',\n },\n {\n name: 'EMAIL',\n description: 'Generate a unique test email address',\n args: [],\n example: 'test-a1b2@example-test.com',\n },\n {\n name: 'FIRST_NAME',\n description: 'Random first name',\n args: [],\n example: 'Jordan',\n },\n {\n name: 'LAST_NAME',\n description: 'Random last name',\n args: [],\n example: 'Chen',\n },\n {\n name: 'PHONE',\n description: 'Random US phone number',\n args: [],\n example: '+1-555-382-7491',\n },\n {\n name: 'SEQUENCE',\n description: 'Auto-incrementing counter (resets per run)',\n args: [{ name: 'name', type: 'string', description: 'Counter name' }],\n example: '1',\n },\n {\n name: 'AI',\n description: 'AI-generated string for the given prompt (resolved once per run, cached by prompt)',\n args: [{ name: 'prompt', type: 'string', description: 'Quoted description of what to generate, e.g. \"email subject\"' }],\n example: 'Quick question about your refund',\n },\n];\n\n/** Names of all valid macros, derived from MACRO_CATALOG. */\nexport const VALID_MACRO_NAMES = new Set(MACRO_CATALOG.map((m) => m.name));\n\n/**\n * Validate all macro expressions in a string. Returns array of invalid macro names.\n * Empty array = all macros valid (or no macros present).\n */\nexport function findInvalidMacros(value: string): string[] {\n const pattern = new RegExp(MACRO_PATTERN.source, MACRO_PATTERN.flags);\n const invalid: string[] = [];\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(value)) !== null) {\n const name = match[1]!;\n if (!VALID_MACRO_NAMES.has(name)) {\n invalid.push(name);\n }\n }\n return [...new Set(invalid)];\n}\n\n/** Expected arg count per macro, derived from MACRO_CATALOG. */\nconst MACRO_ARG_COUNTS = new Map(MACRO_CATALOG.map((m) => [m.name, m.args.length]));\n\n/**\n * Normalize macro expressions in a string by stripping extra arguments\n * beyond what the catalog defines. LLMs sometimes add extra hints\n * (e.g. \"$RANDOM_STRING(10, alphanumeric)\") that crash the resolver.\n */\nexport function normalizeMacroArgs(value: string): string {\n const pattern = new RegExp(MACRO_PATTERN.source, MACRO_PATTERN.flags);\n return value.replace(pattern, (fullMatch, name: string, argsStr: string) => {\n const expectedArgCount = MACRO_ARG_COUNTS.get(name);\n if (expectedArgCount === undefined) return fullMatch;\n const args = argsStr.length > 0 ? argsStr.split(',') : [];\n if (args.length <= expectedArgCount) return fullMatch;\n const trimmed = args.slice(0, expectedArgCount).join(',');\n return `$${name}(${trimmed})`;\n });\n}\n\n/** Regex for matching macro expressions. Negative lookbehind for \\ handles escaping. */\nexport const MACRO_PATTERN = /(?<!\\\\)\\$([A-Z_]+)\\(([^)]*)\\)/g;\n\n/** Regex for matching $var.{name} variable references (simple, dotted, and step-qualified). */\nexport const VARIABLE_PATTERN = /(?<!\\\\)\\$var\\.(\\w+(?:\\.\\w+)*)/g;\n\n/**\n * Regex for matching step-qualified variable references: `$var.StepN_Label.name`.\n * Group 1 = step qualifier (e.g. \"Step2_Create_Client\"), Group 2 = variable name.\n */\nexport const QUALIFIED_VARIABLE_PATTERN = /(?<!\\\\)\\$var\\.(\\w+)\\.(\\w+)/g;\n\n// ── Typeahead entry types (used by macro/variable dropdown) ──\n\nexport type VariableSource = 'inputValue' | 'macro' | 'extracted' | 'credential' | 'seed' | 'workflowInput' | 'unknown';\n\nexport interface VariableTypeaheadEntry {\n kind: 'variable';\n /** Variable name without $var. prefix (e.g. \"project_id\") */\n name: string;\n source: VariableSource;\n description: string;\n /** Step-qualified name for disambiguation (e.g. \"Step2_Create_Client.client\") */\n qualifiedName?: string;\n /** Human-readable step label (e.g. \"Step 2: Create Client\") */\n stepLabel?: string;\n /** Design-time template or expression (e.g. \"Acme Test - $RANDOM_STRING(6)\") */\n designTimeValue?: string;\n}\n\nexport interface MacroTypeaheadEntry extends MacroCatalogEntry {\n kind: 'macro';\n}\n\nexport type TypeaheadEntry = MacroTypeaheadEntry | VariableTypeaheadEntry;\n\n/** Map macro names to the variable keys they produce at runtime. */\nexport const MACRO_TO_VAR_KEY: Record<string, string> = {\n UUID: 'uuid',\n EMAIL: 'email',\n FIRST_NAME: 'first_name',\n LAST_NAME: 'last_name',\n PHONE: 'phone',\n NOW: 'now',\n TODAY: 'today',\n DATE: 'date',\n TIMESTAMP: 'timestamp',\n RANDOM_INT: 'random_int',\n RANDOM_STRING: 'random_string',\n SEQUENCE: 'sequence',\n AI: 'ai',\n};\n\n/**\n * Scan a string for all `$var.{name}` references, returning the variable names.\n * Also handles step-qualified references like `$var.Step2_Create_Client.name`,\n * extracting just the variable name for backward-compatible tracking.\n */\nexport function scanStringForVarRefs(text: string): string[] {\n const names: string[] = [];\n\n // First extract step-qualified references ($var.Step2_Label.name)\n const qualifiedPositions = new Set<number>();\n QUALIFIED_VARIABLE_PATTERN.lastIndex = 0;\n let qMatch: RegExpExecArray | null;\n while ((qMatch = QUALIFIED_VARIABLE_PATTERN.exec(text)) !== null) {\n if (/^Step\\d+/.test(qMatch[1]!)) {\n names.push(qMatch[2]!);\n qualifiedPositions.add(qMatch.index);\n }\n }\n\n // Then extract all references, skipping positions already matched as step-qualified\n VARIABLE_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = VARIABLE_PATTERN.exec(text)) !== null) {\n if (!qualifiedPositions.has(match.index)) {\n names.push(match[1]!);\n }\n }\n return names;\n}\n\n/** Scan a string for all `$MACRO()` expressions, returning the macro names. */\nexport function scanStringForMacros(text: string): string[] {\n const names: string[] = [];\n MACRO_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = MACRO_PATTERN.exec(text)) !== null) {\n names.push(match[1]!);\n }\n return names;\n}\n","/**\n * Shared zod schemas for workflow node config primitives.\n *\n * Lives in @chatsdet/types so both `apps/api` (server-side validation +\n * tool schemas) and `packages/canary-cli` (the spec compiler) can import\n * the same source of truth without canary-cli taking a dependency on apps/api.\n *\n * @module workflow-node-schemas\n */\n\nimport { z } from 'zod';\nimport { ValidationOperatorSchema } from './api-sequence.js';\n\nexport const FormFieldSchema = z.object({\n name: z\n .string()\n .describe('Unique field identifier (snake_case, e.g., \"search_query\", \"recipient_email\")'),\n label: z.string().describe('Human-friendly label (e.g., \"Search Query\", \"Recipient Email\")'),\n type: z\n .enum([\n 'text',\n 'email',\n 'password',\n 'number',\n 'textarea',\n 'select',\n 'checkbox',\n 'multiselect',\n 'file',\n ])\n .optional()\n .describe('Input type, defaults to \"text\"'),\n required: z.boolean().optional().describe('Whether this field is required'),\n value: z.string().describe('The value to use for this field'),\n options: z\n .array(z.object({ value: z.string(), label: z.string() }))\n .optional()\n .describe('If type is select/multiselect, provide allowed options here'),\n});\n\n/**\n * Assertion severity — what happens when the assertion fails:\n * - `fatal`: the test must end now. The run halts immediately at this step.\n * - `fail`: the test may continue. The step is marked failed and the run\n * finishes marked failed, but later steps still execute.\n * - `warn`: the test should continue. The failure is recorded as a warning\n * and the run still passes.\n */\nexport const AssertionSeverityEnum = z.enum(['warn', 'fail', 'fatal']);\n\nexport type AssertionSeverity = z.infer<typeof AssertionSeverityEnum>;\n\nexport const InlineAssertionSchema = z.object({\n condition: z.string().describe('What to verify after the action'),\n severity: AssertionSeverityEnum.describe(\n 'fatal = stop the test immediately on failure, fail = mark the run failed but keep going, warn = log a warning and continue (run still passes)'\n ),\n agentic: z\n .boolean()\n .optional()\n .describe('true if requires visual/complex evaluation that cannot be cached'),\n});\n\n/**\n * Subject of a deterministic condition predicate — the left-hand operand.\n *\n * - `variable`: a workflow variable referenced by its input name (resolved from\n * the run's variable registry as `$var.<name>` at evaluation time).\n * - `acting_as`: the credential identity currently logged in. Compared against a\n * chosen credential template id; resolved from `currentActingAsTemplateId` on\n * the run's workflow state.\n */\nexport const ConditionSubjectSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('variable'),\n name: z.string().describe('Input/variable name, e.g. \"count\" for $var.count'),\n }),\n z.object({\n kind: z.literal('acting_as'),\n }),\n]);\n\nexport type ConditionSubject = z.infer<typeof ConditionSubjectSchema>;\n\n/**\n * A single typed comparison that decides a condition node's branch\n * deterministically — no LLM agent. When a condition node has a predicate it is\n * evaluated in the orchestrator; without one it falls back to agentic evaluation\n * of `conditionDescription`. v1 is a single comparison; the shape leaves room to\n * grow into compound AND/OR later.\n */\nexport const ConditionPredicateSchema = z.object({\n subject: ConditionSubjectSchema,\n operator: ValidationOperatorSchema,\n /**\n * Right-hand operand. For a `variable` subject this is a literal (or `$var`\n * reference); for `acting_as` it is a credential template id. Omitted for the\n * unary operators `is_set` / `is_not_set`.\n */\n value: z.string().optional(),\n});\n\nexport type ConditionPredicate = z.infer<typeof ConditionPredicateSchema>;\n\nexport const NodeTypeEnum = z.enum([\n 'login',\n 'navigate',\n 'action',\n 'assertion',\n 'seed',\n 'setup',\n 'wait',\n 'condition',\n 'api_sequence',\n 'end',\n 'notification',\n 'audio',\n 'audio-assertion',\n 'sandbox_action',\n]);\n\nexport type NodeType = z.infer<typeof NodeTypeEnum>;\nexport type FormField = z.infer<typeof FormFieldSchema>;\nexport type InlineAssertion = z.infer<typeof InlineAssertionSchema>;\n","/**\n * Browser viewport presets for agent browser sessions.\n *\n * Organizations pick a default preset (Settings → Agent Browser) and individual\n * runs may override it (Flow Designer → Run with config). The stored value is\n * the preset *key*; `resolveBrowserViewport` maps it to concrete dimensions\n * before reaching `PlaywrightClient.connect({ viewport })`.\n *\n * The `desktop` default (1280×800) matches `DEFAULT_VIEWPORT` in\n * `@chatsdet/browser-core` — the resolution recommended for AI computer-use\n * models.\n *\n * @module browser-viewport\n */\n\n/** Device family for a preset — drives the icon shown in the visual picker. */\nexport type BrowserViewportDevice = 'desktop' | 'laptop' | 'tablet' | 'mobile';\n\nexport interface BrowserViewportPresetSpec {\n /** Human-friendly label shown in the UI. */\n label: string;\n width: number;\n height: number;\n device: BrowserViewportDevice;\n}\n\nexport const BROWSER_VIEWPORT_PRESETS = {\n desktop: { label: 'Desktop', width: 1280, height: 800, device: 'desktop' },\n desktop_large: { label: 'Large desktop', width: 1920, height: 1080, device: 'desktop' },\n laptop: { label: 'Laptop', width: 1440, height: 900, device: 'laptop' },\n tablet: { label: 'Tablet', width: 768, height: 1024, device: 'tablet' },\n mobile: { label: 'Mobile', width: 390, height: 844, device: 'mobile' },\n} as const satisfies Record<string, BrowserViewportPresetSpec>;\n\nexport type BrowserViewportPreset = keyof typeof BROWSER_VIEWPORT_PRESETS;\n\nexport const DEFAULT_BROWSER_VIEWPORT_PRESET: BrowserViewportPreset = 'desktop';\n\nexport const BROWSER_VIEWPORT_PRESET_KEYS = Object.keys(\n BROWSER_VIEWPORT_PRESETS\n) as BrowserViewportPreset[];\n\n/** Type guard: is `value` a known preset key? */\nexport function isBrowserViewportPreset(value: unknown): value is BrowserViewportPreset {\n return typeof value === 'string' && value in BROWSER_VIEWPORT_PRESETS;\n}\n\n/**\n * Map a stored preset key (possibly null/unknown) to concrete viewport\n * dimensions, falling back to the desktop default.\n */\nexport function resolveBrowserViewport(preset: string | null | undefined): {\n width: number;\n height: number;\n} {\n const spec = isBrowserViewportPreset(preset)\n ? BROWSER_VIEWPORT_PRESETS[preset]\n : BROWSER_VIEWPORT_PRESETS[DEFAULT_BROWSER_VIEWPORT_PRESET];\n return { width: spec.width, height: spec.height };\n}\n","/**\n * Canonical classification of a DOM identifier's stability for use as a locator\n * anchor. Single source of truth — replaces the divergent, disagreeing\n * volatile/generated-id checks that used to live in (and drift between):\n * - apps/api/src/workflows/cache/build-locator.ts (`findVolatileTestIdToken`)\n * - packages/browser-core/src/snapshot-formatter-sections.ts (duplicate)\n * - packages/recording/src/scope.ts (`looksGeneratedId`)\n * - apps/api/src/workflows/cache/deterministic-locator-builder.ts (`isStableId`)\n *\n * Stability means **survives a re-seed**, NOT \"points at a real record\". The\n * canary app is re-seeded between runs, so any value derived from seeded data —\n * UUIDs, ULIDs, entity numbers (`invoice-DI-80368`), the `<timestamp>` in\n * `__new__1778798465222` — is VOLATILE even when it reliably identifies a record\n * within a single run. Only structural / framework-state tokens survive: a state\n * marker like `__new__`, a hard-coded structural `data-testid`, a landmark role.\n *\n * Output:\n * - `stable` → use the value verbatim (`[attr=\"value\"]` / `#id`).\n * - `stable-prefix`→ a structural head precedes a volatile token; anchor on\n * `[attr^=\"prefix\"]` (a non-unique prefix is then rejected\n * by the caller's global-uniqueness check).\n * - `volatile` → unusable as an anchor.\n *\n * This is locator-time classification (a context-dependent judgment), kept out\n * of the substrate per substrate-v2 principle #3 (\"synthesis is tagged, never\n * laundered\").\n */\nexport type IdentifierStability =\n | { kind: 'stable' }\n | { kind: 'stable-prefix'; prefix: string }\n | { kind: 'volatile' };\n\n// ── Whole-value generated patterns (no reusable structural prefix) ──────────\n// Framework-generated id prefixes (React useId, Radix, HeadlessUI, Mantine,\n// React Aria / Select, rc-, Vue menu-v-N).\nconst FRAMEWORK_PREFIX_RE =\n /^(?::r[0-9a-z]+:?$|radix-|headlessui-|mantine-|react-aria-|react-select-|rc-|menu-v-\\d)/i;\n// A bare hash (styled-components / content hash), e.g. `deadbeef`, `a1b2c3d4e5`.\nconst BARE_HASH_RE = /^[a-f0-9]{8,}$/i;\n// `prefix-<hex6+>` styled-component-style class/id, e.g. `sc-bdVaJa` → `foo-a1b2c3d4`.\nconst PREFIX_HASH_RE = /^[a-z]+-[0-9a-f]{6,}$/i;\n\n// ── Embedded volatile tokens (a structural head may precede them) ───────────\n// Any 8-4-4-4-12 hex value is a UUID regardless of version/variant nibble — keep\n// it version-agnostic so v7/v8 (and non-RFC) ids are caught, not just v1-5.\nconst UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;\n// ULID (Crockford base32, excludes I/L/O/U). Case-insensitive so lowercase\n// ULIDs are caught too.\nconst ULID_RE = /\\b[0-9A-HJKMNP-TV-Z]{26}\\b/i;\n// A 6+-digit run delimited by a boundary or `_` (so `__new__1778798465222`\n// is caught even though `_`→digit is not a `\\b`). Trailing/leading boundary\n// keeps `abc123456def` from matching (preserves the legacy `\\b\\d{6,}\\b`).\nconst DIGIT_RUN_RE = /(?:^|[^0-9A-Za-z])(\\d{6,})(?![0-9A-Za-z])/;\n// A trailing `-`/`_`-joined 3+-digit counter/number (`widget-123`,\n// `invoice-DI-80368`, the timestamp tail of `__new__<ts>`). 1–2 trailing digits\n// stay stable (`step-1`, `tab-42`).\nconst TRAILING_COUNTER_RE = /[-_](\\d{3,})$/;\n\nconst MIN_PREFIX_LEN = 3;\n\nfunction usablePrefix(prefix: string): boolean {\n return prefix.length >= MIN_PREFIX_LEN && /[a-z]/i.test(prefix);\n}\n\n/** Earliest index at which a volatile token begins, or null. */\nfunction earliestVolatileTokenIndex(value: string): number | null {\n let best: number | null = null;\n const consider = (idx: number | undefined) => {\n if (idx !== undefined && idx >= 0 && (best === null || idx < best)) best = idx;\n };\n consider(UUID_RE.exec(value)?.index);\n consider(ULID_RE.exec(value)?.index);\n const digit = DIGIT_RUN_RE.exec(value);\n if (digit) consider(digit.index + (digit[0].length - digit[1].length)); // start of the digits\n const counter = TRAILING_COUNTER_RE.exec(value);\n if (counter) consider(counter.index + 1); // after the `-`/`_` separator\n return best;\n}\n\nexport function classifyIdentifier(value: string): IdentifierStability {\n if (!value) return { kind: 'volatile' };\n\n // Whole-value generated → no reusable prefix.\n if (FRAMEWORK_PREFIX_RE.test(value) || BARE_HASH_RE.test(value) || PREFIX_HASH_RE.test(value)) {\n return { kind: 'volatile' };\n }\n\n const tokenIdx = earliestVolatileTokenIndex(value);\n if (tokenIdx !== null) {\n const prefix = value.slice(0, tokenIdx);\n return usablePrefix(prefix) ? { kind: 'stable-prefix', prefix } : { kind: 'volatile' };\n }\n\n return { kind: 'stable' };\n}\n\n/** True when the value is not safe to anchor on verbatim (volatile or prefix-only). */\nexport function isVolatileIdentifier(value: string): boolean {\n return classifyIdentifier(value).kind !== 'stable';\n}\n\n/** The structural prefix preceding a volatile token, or null. */\nexport function stableIdentifierPrefix(value: string): string | null {\n const c = classifyIdentifier(value);\n return c.kind === 'stable-prefix' ? c.prefix : null;\n}\n\nfunction escapeCssAttrValue(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * CSS attribute selector for `value` on `attr` (`[attr=\"value\"]` for a stable\n * value, `[attr^=\"prefix\"]` for a structural prefix), or null when too volatile.\n */\nexport function stableAttrSelector(attr: string, value: string): string | null {\n const c = classifyIdentifier(value);\n if (c.kind === 'stable') return `[${attr}=\"${escapeCssAttrValue(value)}\"]`;\n if (c.kind === 'stable-prefix') return `[${attr}^=\"${escapeCssAttrValue(c.prefix)}\"]`;\n return null;\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z"]}
@@ -2852,7 +2852,10 @@ async function reconstructSnapshots(options) {
2852
2852
  const targetActions = actionIndex !== void 0 ? [actions[actionIndex]] : actions;
2853
2853
  log("Starting trace viewer server...");
2854
2854
  const { startTraceViewerServer } = await loadTraceViewer();
2855
- const server = await startTraceViewerServer({ host: "127.0.0.1" });
2855
+ const server = await startTraceViewerServer({
2856
+ host: "127.0.0.1",
2857
+ allowedFileRoots: [path.dirname(resolvedPath)]
2858
+ });
2856
2859
  const serverUrl = server.urlPrefix("precise");
2857
2860
  const traceFileUrl = `${serverUrl}/trace/file?path=${encodeURIComponent(resolvedPath)}`;
2858
2861
  let browser;
@@ -2863,12 +2866,8 @@ async function reconstructSnapshots(options) {
2863
2866
  log("Initializing service worker...");
2864
2867
  const initPage = await context.newPage();
2865
2868
  const snapshotHtmlUrl = `${serverUrl}/trace/snapshot.html?trace=${encodeURIComponent(traceFileUrl)}&r=about:blank`;
2866
- await initPage.goto(snapshotHtmlUrl, { waitUntil: "networkidle" });
2867
- await initPage.waitForFunction(
2868
- () => !!navigator.serviceWorker.controller,
2869
- null,
2870
- { timeout: 1e4 }
2871
- );
2869
+ await initPage.goto(snapshotHtmlUrl, { waitUntil: "load", timeout: 15e3 });
2870
+ await waitForTraceServiceWorker(initPage);
2872
2871
  await initPage.close();
2873
2872
  log("Service worker ready");
2874
2873
  const snapshotPage = await context.newPage();
@@ -2921,6 +2920,21 @@ async function reconstructSnapshots(options) {
2921
2920
  });
2922
2921
  }
2923
2922
  }
2923
+ async function waitForTraceServiceWorker(page) {
2924
+ await page.waitForFunction(
2925
+ async () => {
2926
+ if (!("serviceWorker" in navigator)) return false;
2927
+ if (navigator.serviceWorker.controller) return true;
2928
+ const registration = await navigator.serviceWorker.ready.catch(() => null);
2929
+ if (!registration) return false;
2930
+ if (navigator.serviceWorker.controller) return true;
2931
+ if (!registration.active) return false;
2932
+ return true;
2933
+ },
2934
+ null,
2935
+ { timeout: 1e4 }
2936
+ );
2937
+ }
2924
2938
  async function captureSnapshot(page, serverUrl, traceFileUrl, pageId, snapshotName, includeHtml) {
2925
2939
  const snapshotUrl = `${serverUrl}/trace/snapshot/${pageId}?trace=${encodeURIComponent(traceFileUrl)}&name=${encodeURIComponent(snapshotName)}`;
2926
2940
  await page.goto(snapshotUrl, { waitUntil: "load", timeout: 15e3 });
@@ -2948,6 +2962,14 @@ function cleanupTrace(tmpPath) {
2948
2962
  async function loadTraceViewer() {
2949
2963
  const { createRequire } = await import("module");
2950
2964
  const require2 = createRequire(import.meta.url);
2965
+ try {
2966
+ const pwCorePackage = require2.resolve("playwright-core/package.json");
2967
+ const pwCoreDir = path.dirname(pwCorePackage);
2968
+ const coreBundle = require2(path.join(pwCoreDir, "lib", "coreBundle.js"));
2969
+ const factory = coreBundle?.server?.startTraceViewerServer;
2970
+ if (typeof factory === "function") return { startTraceViewerServer: factory };
2971
+ } catch {
2972
+ }
2951
2973
  try {
2952
2974
  const pwCorePackage = require2.resolve("playwright-core/package.json");
2953
2975
  const pwCoreDir = path.dirname(pwCorePackage);
@@ -2974,4 +2996,4 @@ export {
2974
2996
  downloadTrace,
2975
2997
  cleanupTrace
2976
2998
  };
2977
- //# sourceMappingURL=chunk-CRHPDPRS.js.map
2999
+ //# sourceMappingURL=chunk-KCHKFYVX.js.map