stageflow 0.27.0 → 0.30.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 (261) hide show
  1. package/README.md +4 -0
  2. package/builtin-skills/browser/SKILL.md +42 -0
  3. package/dist/agent/claudeAdapter.js +2 -2
  4. package/dist/agent/cursorProvider.d.ts +2 -0
  5. package/dist/agent/cursorProvider.js +30 -5
  6. package/dist/agent/fakeAgent.d.ts +1 -0
  7. package/dist/agent/fakeAgent.js +13 -0
  8. package/dist/agent/piAdapter.d.ts +3 -24
  9. package/dist/agent/piAdapter.js +49 -47
  10. package/dist/agent/piSessionFactory.d.ts +38 -0
  11. package/dist/agent/piSessionFactory.js +60 -0
  12. package/dist/agent/port.d.ts +2 -0
  13. package/dist/agent/streamLogRedact.d.ts +6 -0
  14. package/dist/agent/streamLogRedact.js +23 -1
  15. package/dist/browser/anchor.d.ts +27 -0
  16. package/dist/browser/anchor.js +127 -0
  17. package/dist/browser/auditSink.d.ts +38 -0
  18. package/dist/browser/auditSink.js +34 -0
  19. package/dist/browser/browserHost.d.ts +86 -0
  20. package/dist/browser/browserHost.js +7 -0
  21. package/dist/browser/browserSweep.d.ts +14 -0
  22. package/dist/browser/browserSweep.js +54 -0
  23. package/dist/browser/browserTeardown.d.ts +56 -0
  24. package/dist/browser/browserTeardown.js +235 -0
  25. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -0
  26. package/dist/browser/fakeRemoteBrowserHost.js +34 -0
  27. package/dist/browser/gateHandoff.d.ts +25 -0
  28. package/dist/browser/gateHandoff.js +83 -0
  29. package/dist/browser/humanLogin.d.ts +18 -0
  30. package/dist/browser/humanLogin.js +34 -0
  31. package/dist/browser/keyProvider.d.ts +10 -0
  32. package/dist/browser/keyProvider.js +39 -0
  33. package/dist/browser/localBrowserHost.d.ts +10 -0
  34. package/dist/browser/localBrowserHost.js +152 -0
  35. package/dist/browser/localProfileLock.d.ts +2 -0
  36. package/dist/browser/localProfileLock.js +218 -0
  37. package/dist/browser/localProfileStore.d.ts +5 -0
  38. package/dist/browser/localProfileStore.js +91 -0
  39. package/dist/browser/loginCheck.d.ts +53 -0
  40. package/dist/browser/loginCheck.js +147 -0
  41. package/dist/browser/memoryProfileLock.d.ts +2 -0
  42. package/dist/browser/memoryProfileLock.js +53 -0
  43. package/dist/browser/memoryProfileStore.d.ts +2 -0
  44. package/dist/browser/memoryProfileStore.js +31 -0
  45. package/dist/browser/navigationAudit.d.ts +16 -0
  46. package/dist/browser/navigationAudit.js +53 -0
  47. package/dist/browser/persistedEnv.d.ts +3 -0
  48. package/dist/browser/persistedEnv.js +15 -0
  49. package/dist/browser/profileLock.d.ts +40 -0
  50. package/dist/browser/profileLock.js +10 -0
  51. package/dist/browser/profileStore.d.ts +23 -0
  52. package/dist/browser/profileStore.js +39 -0
  53. package/dist/browser/runLiveness.d.ts +13 -0
  54. package/dist/browser/runLiveness.js +39 -0
  55. package/dist/browser/sitePolicy.d.ts +10 -0
  56. package/dist/browser/sitePolicy.js +61 -0
  57. package/dist/browser/stageBrowserEnv.d.ts +22 -0
  58. package/dist/browser/stageBrowserEnv.js +147 -0
  59. package/dist/browser/stageProfileLock.d.ts +17 -0
  60. package/dist/browser/stageProfileLock.js +39 -0
  61. package/dist/cli/browserCommand.d.ts +44 -0
  62. package/dist/cli/browserCommand.js +472 -0
  63. package/dist/cli/hostClient.d.ts +20 -0
  64. package/dist/cli/hostClient.js +36 -2
  65. package/dist/cli/initCommand.d.ts +1 -0
  66. package/dist/cli/initCommand.js +23 -7
  67. package/dist/cli/initTemplates.d.ts +5 -2
  68. package/dist/cli/initTemplates.js +25 -2
  69. package/dist/cli/runsCommand.js +6 -2
  70. package/dist/cli/triggerCommand.d.ts +12 -0
  71. package/dist/cli/triggerCommand.js +183 -0
  72. package/dist/cli/validateCommand.d.ts +1 -1
  73. package/dist/cli/validateCommand.js +23 -6
  74. package/dist/cli.js +31 -2
  75. package/dist/config/browseCatalog.d.ts +1 -0
  76. package/dist/config/browseCatalog.js +5 -4
  77. package/dist/config/builtinSkills.d.ts +4 -0
  78. package/dist/config/builtinSkills.js +23 -0
  79. package/dist/config/catalogRelativePath.d.ts +1 -0
  80. package/dist/config/catalogRelativePath.js +10 -2
  81. package/dist/config/createTrigger.d.ts +27 -0
  82. package/dist/config/createTrigger.js +232 -0
  83. package/dist/config/draftPackage.d.ts +117 -0
  84. package/dist/config/draftPackage.js +550 -0
  85. package/dist/config/ensureCatalogScanRoot.d.ts +16 -0
  86. package/dist/config/ensureCatalogScanRoot.js +73 -0
  87. package/dist/config/hostConfig.d.ts +2 -0
  88. package/dist/config/hostConfig.js +27 -0
  89. package/dist/config/listSkills.d.ts +2 -2
  90. package/dist/config/listSkills.js +17 -3
  91. package/dist/config/loadOutcome.d.ts +6 -0
  92. package/dist/config/loadPipeline.js +16 -0
  93. package/dist/config/loadStage.js +7 -0
  94. package/dist/config/loadStageflowManifest.js +25 -0
  95. package/dist/config/loadTrigger.d.ts +9 -0
  96. package/dist/config/loadTrigger.js +120 -0
  97. package/dist/config/multiProjectCatalog.d.ts +2 -0
  98. package/dist/config/multiProjectCatalog.js +26 -18
  99. package/dist/config/normalizePipelineStageEntry.js +8 -1
  100. package/dist/config/parseCompletionContract.js +6 -0
  101. package/dist/config/pipelineStageKeys.js +1 -0
  102. package/dist/config/publishDraftPackage.d.ts +7 -0
  103. package/dist/config/publishDraftPackage.js +25 -0
  104. package/dist/config/resolveCatalogContext.js +1 -1
  105. package/dist/config/resolvePipelineDag.js +1 -1
  106. package/dist/config/scanCatalogPaths.d.ts +1 -1
  107. package/dist/config/scanCatalogPaths.js +6 -2
  108. package/dist/config/stageBrowser.d.ts +12 -0
  109. package/dist/config/stageBrowser.js +118 -0
  110. package/dist/config/triggerCatalog.d.ts +10 -0
  111. package/dist/config/triggerCatalog.js +37 -0
  112. package/dist/config/validateCatalog.d.ts +13 -3
  113. package/dist/config/validateCatalog.js +83 -2
  114. package/dist/config/validateCompletionContract.js +8 -0
  115. package/dist/envelope/preEmitChecks.d.ts +1 -1
  116. package/dist/envelope/preEmitChecks.js +8 -0
  117. package/dist/logging/redact.d.ts +4 -0
  118. package/dist/logging/redact.js +17 -0
  119. package/dist/mcp/catalogTools.js +43 -8
  120. package/dist/mcp/controlTools.js +3 -1
  121. package/dist/mcp/deps.d.ts +2 -0
  122. package/dist/mcp/server.js +1 -0
  123. package/dist/mcp/tools.js +2 -0
  124. package/dist/mcp/triggerTools.d.ts +3 -0
  125. package/dist/mcp/triggerTools.js +176 -0
  126. package/dist/operatorAgent/docsRetrieval.d.ts +40 -0
  127. package/dist/operatorAgent/docsRetrieval.js +269 -0
  128. package/dist/operatorAgent/draftContext.d.ts +16 -0
  129. package/dist/operatorAgent/draftContext.js +68 -0
  130. package/dist/operatorAgent/fakeHost.d.ts +39 -0
  131. package/dist/operatorAgent/fakeHost.js +92 -0
  132. package/dist/operatorAgent/host.d.ts +40 -0
  133. package/dist/operatorAgent/host.js +179 -0
  134. package/dist/operatorAgent/index.d.ts +9 -0
  135. package/dist/operatorAgent/index.js +8 -0
  136. package/dist/operatorAgent/piModel.d.ts +80 -0
  137. package/dist/operatorAgent/piModel.js +528 -0
  138. package/dist/operatorAgent/profiles/workshopAuthor.d.ts +23 -0
  139. package/dist/operatorAgent/profiles/workshopAuthor.js +338 -0
  140. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +6 -0
  141. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +103 -0
  142. package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +57 -0
  143. package/dist/operatorAgent/profiles/workshopAuthorTools.js +623 -0
  144. package/dist/operatorAgent/proposals.d.ts +32 -0
  145. package/dist/operatorAgent/proposals.js +144 -0
  146. package/dist/operatorAgent/types.d.ts +128 -0
  147. package/dist/operatorAgent/types.js +1 -0
  148. package/dist/package-meta.d.ts +1 -1
  149. package/dist/package-meta.js +1 -1
  150. package/dist/project/findManifestRoot.d.ts +3 -0
  151. package/dist/project/findManifestRoot.js +35 -0
  152. package/dist/project/resolveStageflowContext.d.ts +7 -0
  153. package/dist/project/resolveStageflowContext.js +44 -28
  154. package/dist/runstore/assertTmpdir.js +6 -2
  155. package/dist/runstore/port.d.ts +28 -0
  156. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -1
  157. package/dist/runstore/sqlite/SqliteRunStore.js +90 -0
  158. package/dist/runstore/sqlite/migrations/008-triggers-table.d.ts +7 -0
  159. package/dist/runstore/sqlite/migrations/008-triggers-table.js +19 -0
  160. package/dist/runstore/sqlite/migrations/009-trigger-next-run.d.ts +7 -0
  161. package/dist/runstore/sqlite/migrations/009-trigger-next-run.js +19 -0
  162. package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.d.ts +7 -0
  163. package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.js +17 -0
  164. package/dist/runstore/sqlite/migrations/index.js +6 -0
  165. package/dist/runtime/answerResume.d.ts +1 -0
  166. package/dist/runtime/answerResume.js +3 -1
  167. package/dist/runtime/completionCheckRunner.d.ts +12 -1
  168. package/dist/runtime/completionCheckRunner.js +22 -0
  169. package/dist/runtime/credentialBinding.d.ts +2 -0
  170. package/dist/runtime/credentialBinding.js +3 -0
  171. package/dist/runtime/emailSource.d.ts +72 -0
  172. package/dist/runtime/emailSource.js +304 -0
  173. package/dist/runtime/githubPollSource.d.ts +86 -0
  174. package/dist/runtime/githubPollSource.js +284 -0
  175. package/dist/runtime/pipelineRunner.d.ts +4 -0
  176. package/dist/runtime/pipelineRunner.js +3 -0
  177. package/dist/runtime/pipelineScheduler.d.ts +2 -0
  178. package/dist/runtime/pipelineScheduler.js +85 -1
  179. package/dist/runtime/repositoryMaterialize.d.ts +1 -1
  180. package/dist/runtime/runManager.d.ts +11 -0
  181. package/dist/runtime/runManager.js +149 -1
  182. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  183. package/dist/runtime/runRetryCoordinator.js +1 -0
  184. package/dist/runtime/scheduleSource.d.ts +38 -0
  185. package/dist/runtime/scheduleSource.js +119 -0
  186. package/dist/runtime/settingsFile.d.ts +5 -0
  187. package/dist/runtime/settingsFile.js +61 -54
  188. package/dist/runtime/stageAttemptBootstrap.js +55 -3
  189. package/dist/runtime/stageEnvironment.js +1 -0
  190. package/dist/runtime/stageHitl.d.ts +1 -0
  191. package/dist/runtime/stageHitl.js +5 -1
  192. package/dist/runtime/stageProcessLauncher.d.ts +2 -0
  193. package/dist/runtime/stageProcessLauncher.js +44 -5
  194. package/dist/runtime/stageRunner.d.ts +5 -0
  195. package/dist/runtime/stageRunner.js +30 -6
  196. package/dist/runtime/stageStreamLog.d.ts +1 -0
  197. package/dist/runtime/stageStreamLog.js +2 -1
  198. package/dist/runtime/triggerCatalog.d.ts +6 -0
  199. package/dist/runtime/triggerCatalog.js +24 -0
  200. package/dist/runtime/triggerEventMatch.d.ts +2 -0
  201. package/dist/runtime/triggerEventMatch.js +6 -0
  202. package/dist/runtime/triggerPort.d.ts +14 -0
  203. package/dist/runtime/triggerPort.js +1 -0
  204. package/dist/runtime/triggerRunner.d.ts +17 -0
  205. package/dist/runtime/triggerRunner.js +89 -0
  206. package/dist/runtime/verifiedStageExecution.d.ts +7 -0
  207. package/dist/runtime/verifiedStageExecution.js +14 -0
  208. package/dist/runtime/webhookSignature.d.ts +1 -0
  209. package/dist/runtime/webhookSignature.js +12 -0
  210. package/dist/server/bootstrap.d.ts +6 -0
  211. package/dist/server/bootstrap.js +64 -0
  212. package/dist/server/controlToken.js +13 -0
  213. package/dist/server/createHttpHost.js +3 -0
  214. package/dist/server/healthSurfaces.js +31 -6
  215. package/dist/server/http.d.ts +15 -0
  216. package/dist/server/http.js +1251 -74
  217. package/dist/server/requestAuthContext.d.ts +1 -1
  218. package/dist/server/triggerRoutes.d.ts +15 -0
  219. package/dist/server/triggerRoutes.js +268 -0
  220. package/dist/tools/askOperator.d.ts +3 -2
  221. package/dist/types/completion.d.ts +3 -0
  222. package/dist/types/pipeline.d.ts +3 -1
  223. package/dist/types/preEmitCheck.d.ts +7 -0
  224. package/dist/types/stage.d.ts +14 -0
  225. package/dist/types/stageflowManifest.d.ts +3 -0
  226. package/dist/types/trigger.d.ts +18 -0
  227. package/dist/types/trigger.js +1 -0
  228. package/dist/ui/assets/{index-CSsDued1.css → index-Bv4p1aPK.css} +1 -1
  229. package/dist/ui/assets/index-CntB4bZR.js +133 -0
  230. package/dist/ui/index.html +2 -2
  231. package/dist/workshop/autosave.d.ts +58 -0
  232. package/dist/workshop/autosave.js +222 -0
  233. package/dist/workshop/buildStore.d.ts +79 -0
  234. package/dist/workshop/buildStore.js +326 -0
  235. package/dist/workshop/chatTurn.d.ts +232 -0
  236. package/dist/workshop/chatTurn.js +475 -0
  237. package/dist/workshop/modelSettings.d.ts +18 -0
  238. package/dist/workshop/modelSettings.js +44 -0
  239. package/dist/workshop/sessionStore.d.ts +61 -0
  240. package/dist/workshop/sessionStore.js +254 -0
  241. package/dist/workshop/workshopPin.d.ts +72 -0
  242. package/dist/workshop/workshopPin.js +193 -0
  243. package/examples/README.md +2 -1
  244. package/examples/browser-session/README.md +77 -0
  245. package/examples/browser-session/browser-session.pipeline.yaml +50 -0
  246. package/examples/browser-session/browser-session.task.yaml +6 -0
  247. package/examples/browser-session/check-login.yaml +22 -0
  248. package/examples/browser-session/fixture-server.mjs +64 -0
  249. package/examples/browser-session/human-login.yaml +31 -0
  250. package/examples/browser-session/merge.yaml +36 -0
  251. package/examples/browser-session/work-a.yaml +41 -0
  252. package/examples/browser-session/work-b.yaml +41 -0
  253. package/examples/stage-mcp/README.md +3 -2
  254. package/examples/stageflow.yaml +51 -0
  255. package/package.json +5 -1
  256. package/skills/stageflow/references/control-surface.md +3 -1
  257. package/skills/stageflow-author/references/stage-prompt-template.md +4 -4
  258. package/skills/stageflow-run/SKILL.md +2 -0
  259. package/skills/stageflow-run/references/task-and-pipeline-selection.md +3 -1
  260. package/skills/stageflow-setup/SKILL.md +11 -0
  261. package/dist/ui/assets/index-B6hlSXXn.js +0 -120
@@ -63,6 +63,8 @@ export const KNOWN_STAGEFLOW_ENV_KEYS = new Set([
63
63
  "STAGEFLOW_TOOLCHAIN_MANIFEST",
64
64
  "STAGEFLOW_SECRET_REGISTRY",
65
65
  "STAGEFLOW_BUILD_SHA",
66
+ "STAGEFLOW_TRIGGER_TICK_INTERVAL_MS",
67
+ "STAGEFLOW_GITHUB_POLL_INTERVAL_MS",
66
68
  ]);
67
69
  const KNOWN_ENV_PREFIXES = [
68
70
  "STAGEFLOW_PROVIDER_",
@@ -78,6 +80,7 @@ const FILE_KEY_TO_FIELD = {
78
80
  trust_workspace_config: "trustWorkspaceConfig",
79
81
  allow_unknown_config: "allowUnknownConfig",
80
82
  callers: "callers",
83
+ browser: "browser",
81
84
  };
82
85
  const DEFAULT_MAX_CONCURRENT = 3;
83
86
  const DEFAULT_MAX_QUEUED = 32;
@@ -299,6 +302,29 @@ export function loadHostConfig(options) {
299
302
  if (file.values.callers !== undefined) {
300
303
  callers = coerceCallers(file.values.callers, "callers");
301
304
  }
305
+ let browserBlockedSites = [];
306
+ if (file.values.browser !== undefined) {
307
+ const raw = file.values.browser;
308
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
309
+ throw new HostConfigError(`Invalid value for browser`, "config_invalid", "browser");
310
+ }
311
+ for (const sub of Object.keys(raw)) {
312
+ if (sub !== "blocked_sites") {
313
+ throw new HostConfigError(`Unknown key "${sub}" under browser`, "config_unknown_key", `browser.${sub}`);
314
+ }
315
+ }
316
+ const sites = raw.blocked_sites;
317
+ if (sites !== undefined) {
318
+ browserBlockedSites = coerceFileStringList(sites, "browser.blocked_sites")
319
+ .map((s) => s.trim().toLowerCase().replace(/^\*\./, "").replace(/^\./, ""))
320
+ .filter((s) => s.length > 0);
321
+ for (const site of browserBlockedSites) {
322
+ if (!/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/.test(site)) {
323
+ throw new HostConfigError(`Invalid domain "${site}" in browser.blocked_sites`, "config_invalid", "browser.blocked_sites");
324
+ }
325
+ }
326
+ }
327
+ }
302
328
  return {
303
329
  maxConcurrentRuns,
304
330
  maxConcurrentRunsPerProject,
@@ -309,6 +335,7 @@ export function loadHostConfig(options) {
309
335
  controlToken,
310
336
  readToken,
311
337
  callers,
338
+ browserBlockedSites,
312
339
  configFilePath: configFilePath !== undefined && existsSync(configFilePath)
313
340
  ? configFilePath
314
341
  : undefined,
@@ -1,6 +1,6 @@
1
1
  export type SkillScope = "user" | "project" | "temporary";
2
2
  /** Skill resolution origin — separate from ConfigOriginKind. */
3
- export type SkillOrigin = "run" | "checkout" | "host";
3
+ export type SkillOrigin = "run" | "checkout" | "host" | "builtin";
4
4
  export type SkillListing = {
5
5
  name: string;
6
6
  description: string;
@@ -41,7 +41,7 @@ export type SkillOriginListing = {
41
41
  origin: SkillOrigin;
42
42
  };
43
43
  /**
44
- * Resolve a skill by name. Search order: run skills → checkout `.pi/skills` → host agentDir.
44
+ * Resolve a skill by name. Search order: run skills → checkout `.pi/skills` → host agentDir → package built-ins.
45
45
  */
46
46
  export declare function resolveSkillByName(name: string, options: ListSkillsOptions): Promise<ResolvedSkill | undefined>;
47
47
  export declare function listSkills(options: ListSkillsOptions): Promise<SkillCatalog>;
@@ -1,5 +1,6 @@
1
1
  import { access, readFile, readdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { resolveBuiltinSkillFile } from "./builtinSkills.js";
3
4
  import { DefaultResourceLoader, SettingsManager, } from "@earendil-works/pi-coding-agent";
4
5
  function toListing(skill, origin) {
5
6
  return {
@@ -102,8 +103,21 @@ function originForLoaderSkill(skill, options) {
102
103
  }
103
104
  return "host";
104
105
  }
106
+ function resolveBuiltinResolved(name) {
107
+ const filePath = resolveBuiltinSkillFile(name);
108
+ if (filePath === undefined)
109
+ return undefined;
110
+ return {
111
+ name,
112
+ filePath,
113
+ baseDir: path.dirname(filePath),
114
+ scope: "user",
115
+ source: "builtin",
116
+ origin: "builtin",
117
+ };
118
+ }
105
119
  /**
106
- * Resolve a skill by name. Search order: run skills → checkout `.pi/skills` → host agentDir.
120
+ * Resolve a skill by name. Search order: run skills → checkout `.pi/skills` → host agentDir → package built-ins.
107
121
  */
108
122
  export async function resolveSkillByName(name, options) {
109
123
  if (options.runSkillsDir !== undefined) {
@@ -119,12 +133,12 @@ export async function resolveSkillByName(name, options) {
119
133
  const loader = await openSkillCatalogLoader(options);
120
134
  const match = loader.getSkills().skills.find((skill) => skill.name === name);
121
135
  if (!match)
122
- return undefined;
136
+ return resolveBuiltinResolved(name);
123
137
  try {
124
138
  await access(match.filePath);
125
139
  }
126
140
  catch {
127
- return undefined;
141
+ return resolveBuiltinResolved(name);
128
142
  }
129
143
  if (options.checkoutRoot !== undefined &&
130
144
  isUnderRoot(match.filePath, path.join(options.checkoutRoot, ".pi", "skills"))) {
@@ -1,5 +1,6 @@
1
1
  import type { ValidationFindingCode } from "./validateCatalog.js";
2
2
  export type TaskLoadCode = "task.invalid_shape" | "task.load_error" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid";
3
+ export type TriggerLoadCode = "trigger.invalid_shape" | "trigger.load_error" | "trigger.unknown_pipeline" | "trigger.unknown_task";
3
4
  export type LoadIssue = {
4
5
  code: ValidationFindingCode;
5
6
  message: string;
@@ -16,6 +17,11 @@ export type LoadIssue = {
16
17
  message: string;
17
18
  category: "task";
18
19
  taskId?: string;
20
+ } | {
21
+ code: TriggerLoadCode;
22
+ message: string;
23
+ category: "trigger";
24
+ triggerId?: string;
19
25
  } | {
20
26
  code: ValidationFindingCode | string;
21
27
  message: string;
@@ -13,6 +13,7 @@ import { validateCompletionContractForStage } from "./validateCompletionContract
13
13
  import { applyCloneChains } from "./cloneChain.js";
14
14
  import { collectRouteIfSchemaIssues, collectRouteIfIllegalCombos, collectRouteAllGatedWarnings, } from "./routeIf.js";
15
15
  import { mergeToolRequires } from "./toolRequires.js";
16
+ import { withBrowserRequires } from "./stageBrowser.js";
16
17
  function schemaCompileIssue(stageId, field, err, pipelineId) {
17
18
  const message = err instanceof Error ? err.message : String(err);
18
19
  if (err instanceof UnresolvedSchemaRefError) {
@@ -189,6 +190,15 @@ async function buildLoadedPipelineFromMerge(merge, options) {
189
190
  return loadFailure(merged.issues);
190
191
  stage = { ...stage, requires: merged.value };
191
192
  }
193
+ const inlineRequires = withBrowserRequires(stage, {
194
+ pipelineId,
195
+ stageId: entry.id,
196
+ });
197
+ if (!inlineRequires.ok)
198
+ return loadFailure(inlineRequires.issues);
199
+ if (inlineRequires.value !== undefined) {
200
+ stage = { ...stage, requires: inlineRequires.value };
201
+ }
192
202
  stages.push(stage);
193
203
  stageSources[stageId] = { kind: "inline" };
194
204
  continue;
@@ -244,8 +254,14 @@ async function buildLoadedPipelineFromMerge(merge, options) {
244
254
  return loadFailure(merged.issues);
245
255
  stageRequires = merged.value;
246
256
  }
257
+ const browser = entry.browser ?? stageOutcome.value.browser;
258
+ const fileRequires = withBrowserRequires({ browser, requires: stageRequires }, { pipelineId, stageId: entry.id });
259
+ if (!fileRequires.ok)
260
+ return loadFailure(fileRequires.issues);
261
+ stageRequires = fileRequires.value;
247
262
  const stage = {
248
263
  ...stageOutcome.value,
264
+ ...(browser !== undefined ? { browser } : {}),
249
265
  ...(entry.skill !== undefined ? { skill: entry.skill } : {}),
250
266
  ...(entry.mcp !== undefined ? { mcp: entry.mcp } : {}),
251
267
  ...(entry.secrets !== undefined ? { secrets: entry.secrets } : {}),
@@ -9,6 +9,7 @@ import { parsePreEmitChecks } from "./parsePreEmitChecks.js";
9
9
  import { readYamlObject } from "./readYamlObject.js";
10
10
  import { parseStageSecrets } from "../runtime/stageSecretDecl.js";
11
11
  import { parseToolRequires } from "./toolRequires.js";
12
+ import { parseStageBrowser } from "./stageBrowser.js";
12
13
  import { classifyYamlDocument, compileTargetContract, dialectFromKeys, mixedDialectIssue, STAGE_FILE_WIRING_KEYS, } from "./yamlDialect.js";
13
14
  const afterCompletionByStage = new WeakMap();
14
15
  /** After-phase IR from target `verify` (`when` includes after). Stamped onto DAG `completion`. */
@@ -366,6 +367,12 @@ function parseStageFields(raw, label, entryId, deferSchemaRefs, requireIo, compi
366
367
  if (requiresOutcome.value !== undefined) {
367
368
  stage.requires = requiresOutcome.value;
368
369
  }
370
+ const browserOutcome = parseStageBrowser(raw.browser, label, entryId);
371
+ if (!browserOutcome.ok)
372
+ return browserOutcome;
373
+ if (browserOutcome.value !== undefined) {
374
+ stage.browser = browserOutcome.value;
375
+ }
369
376
  const agentField = parseAgentField(raw.agent);
370
377
  if (!agentField.ok) {
371
378
  return loadFailure([
@@ -6,6 +6,7 @@ import { loadFailure, loadSuccess } from "./loadOutcome.js";
6
6
  import { parseModelField } from "./modelField.js";
7
7
  const DEFAULT_PIPELINE_PATTERN = "*.pipeline.yaml";
8
8
  const DEFAULT_TASK_PATTERN = "*.task.yaml";
9
+ const DEFAULT_TRIGGER_PATTERN = "*.trigger.yaml";
9
10
  export function manifestPathForProject(projectRoot) {
10
11
  return path.join(projectRoot, "stageflow.yaml");
11
12
  }
@@ -39,6 +40,7 @@ function parsePatterns(raw, issues) {
39
40
  const defaults = {
40
41
  pipeline: DEFAULT_PIPELINE_PATTERN,
41
42
  task: DEFAULT_TASK_PATTERN,
43
+ trigger: DEFAULT_TRIGGER_PATTERN,
42
44
  };
43
45
  if (raw === undefined || raw === null) {
44
46
  return defaults;
@@ -58,15 +60,24 @@ function parsePatterns(raw, issues) {
58
60
  : typeof record.task === "string" && record.task.trim().length > 0
59
61
  ? record.task.trim()
60
62
  : null;
63
+ const trigger = record.trigger === undefined
64
+ ? defaults.trigger
65
+ : typeof record.trigger === "string" && record.trigger.trim().length > 0
66
+ ? record.trigger.trim()
67
+ : null;
61
68
  if (pipeline === null) {
62
69
  issues.push(catalogIssue("catalog.manifest_invalid", "catalog.patterns.pipeline must be a string"));
63
70
  }
64
71
  if (task === null) {
65
72
  issues.push(catalogIssue("catalog.manifest_invalid", "catalog.patterns.task must be a string"));
66
73
  }
74
+ if (trigger === null) {
75
+ issues.push(catalogIssue("catalog.manifest_invalid", "catalog.patterns.trigger must be a string"));
76
+ }
67
77
  return {
68
78
  pipeline: pipeline ?? defaults.pipeline,
69
79
  task: task ?? defaults.task,
80
+ trigger: trigger ?? defaults.trigger,
70
81
  };
71
82
  }
72
83
  export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoot) {
@@ -109,6 +120,14 @@ export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoo
109
120
  exclude = parsed;
110
121
  }
111
122
  }
123
+ const triggersRaw = catalogRecord.triggers;
124
+ let triggers;
125
+ if (triggersRaw !== undefined) {
126
+ const parsed = parseStringArray(triggersRaw, "triggers", issues);
127
+ if (parsed !== null) {
128
+ triggers = parsed;
129
+ }
130
+ }
112
131
  const patterns = parsePatterns(catalogRecord.patterns, issues);
113
132
  let agent;
114
133
  const agentField = parseAgentField(record.agent);
@@ -136,6 +155,9 @@ export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoo
136
155
  if (exclude !== undefined) {
137
156
  catalog.exclude = exclude;
138
157
  }
158
+ if (triggers !== undefined) {
159
+ catalog.triggers = triggers;
160
+ }
139
161
  if (catalogRecord.patterns !== undefined) {
140
162
  catalog.patterns = {};
141
163
  if (typeof catalogRecord.patterns.pipeline === "string") {
@@ -144,6 +166,9 @@ export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoo
144
166
  if (typeof catalogRecord.patterns.task === "string") {
145
167
  catalog.patterns.task = patterns.task;
146
168
  }
169
+ if (typeof catalogRecord.patterns.trigger === "string") {
170
+ catalog.patterns.trigger = patterns.trigger;
171
+ }
147
172
  }
148
173
  const manifest = {
149
174
  version: 1,
@@ -0,0 +1,9 @@
1
+ import type { TriggerFile } from "../types/trigger.js";
2
+ import { type LoadOutcome } from "./loadOutcome.js";
3
+ /** Structural TriggerFile shape only. */
4
+ export declare function coerceTriggerFile(raw: unknown): TriggerFile | undefined;
5
+ export declare function parseTriggerFile(raw: unknown, source?: string): LoadOutcome<TriggerFile>;
6
+ export declare function loadTriggerFromYamlOutcome(yamlText: string, source?: string): LoadOutcome<TriggerFile>;
7
+ export declare function loadTriggerOutcome(filePath: string): Promise<LoadOutcome<TriggerFile>>;
8
+ export declare function loadTriggerFromYaml(yamlText: string, source?: string): TriggerFile;
9
+ export declare function loadTrigger(filePath: string): Promise<TriggerFile>;
@@ -0,0 +1,120 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { parse as parseYaml } from "yaml";
3
+ import { loadFailure, loadSuccess } from "./loadOutcome.js";
4
+ function isPlainObject(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function parseSchedule(raw) {
8
+ if (!isPlainObject(raw) || typeof raw.cron !== "string")
9
+ return undefined;
10
+ return {
11
+ cron: raw.cron,
12
+ ...(typeof raw.timezone === "string" ? { timezone: raw.timezone } : {}),
13
+ };
14
+ }
15
+ function parseEvent(raw) {
16
+ if (!isPlainObject(raw) || typeof raw.source !== "string")
17
+ return undefined;
18
+ return {
19
+ source: raw.source,
20
+ ...(isPlainObject(raw.match) ? { match: raw.match } : {}),
21
+ ...(isPlainObject(raw.config) ? { config: raw.config } : {}),
22
+ };
23
+ }
24
+ /** Structural TriggerFile shape only. */
25
+ export function coerceTriggerFile(raw) {
26
+ const record = raw;
27
+ if (typeof record?.id !== "string" ||
28
+ typeof record?.pipeline !== "string" ||
29
+ typeof record?.enabled !== "boolean") {
30
+ return undefined;
31
+ }
32
+ if (record.task !== undefined && typeof record.task !== "string") {
33
+ return undefined;
34
+ }
35
+ if (record.kind !== "manual" && record.kind !== "schedule" && record.kind !== "event") {
36
+ return undefined;
37
+ }
38
+ let schedule;
39
+ if (record.schedule !== undefined) {
40
+ schedule = parseSchedule(record.schedule);
41
+ if (schedule === undefined)
42
+ return undefined;
43
+ }
44
+ let event;
45
+ if (record.event !== undefined) {
46
+ event = parseEvent(record.event);
47
+ if (event === undefined)
48
+ return undefined;
49
+ }
50
+ return {
51
+ id: record.id,
52
+ pipeline: record.pipeline,
53
+ kind: record.kind,
54
+ enabled: record.enabled,
55
+ ...(typeof record.task === "string" ? { task: record.task } : {}),
56
+ ...(schedule !== undefined ? { schedule } : {}),
57
+ ...(event !== undefined ? { event } : {}),
58
+ };
59
+ }
60
+ export function parseTriggerFile(raw, source = "trigger") {
61
+ const record = raw;
62
+ const trigger = coerceTriggerFile(raw);
63
+ if (trigger === undefined) {
64
+ return loadFailure([
65
+ {
66
+ code: "trigger.invalid_shape",
67
+ message: `Invalid ${source}: id, pipeline, kind, and enabled are required`,
68
+ category: "trigger",
69
+ triggerId: typeof record?.id === "string" ? record.id : undefined,
70
+ },
71
+ ]);
72
+ }
73
+ return loadSuccess(trigger);
74
+ }
75
+ export function loadTriggerFromYamlOutcome(yamlText, source = "trigger yaml") {
76
+ let raw;
77
+ try {
78
+ raw = parseYaml(yamlText);
79
+ }
80
+ catch (err) {
81
+ const message = err instanceof Error ? err.message : String(err);
82
+ return loadFailure([
83
+ {
84
+ code: "trigger.load_error",
85
+ message,
86
+ category: "trigger",
87
+ },
88
+ ]);
89
+ }
90
+ return parseTriggerFile(raw, source);
91
+ }
92
+ export async function loadTriggerOutcome(filePath) {
93
+ let yamlText;
94
+ try {
95
+ yamlText = await readFile(filePath, "utf8");
96
+ }
97
+ catch (err) {
98
+ const message = err instanceof Error ? err.message : String(err);
99
+ return loadFailure([
100
+ {
101
+ code: "trigger.load_error",
102
+ message,
103
+ category: "trigger",
104
+ },
105
+ ]);
106
+ }
107
+ return loadTriggerFromYamlOutcome(yamlText, `trigger file ${filePath}`);
108
+ }
109
+ export function loadTriggerFromYaml(yamlText, source = "trigger yaml") {
110
+ const outcome = loadTriggerFromYamlOutcome(yamlText, source);
111
+ if (!outcome.ok)
112
+ throw new Error(outcome.issues[0].message);
113
+ return outcome.value;
114
+ }
115
+ export async function loadTrigger(filePath) {
116
+ const outcome = await loadTriggerOutcome(filePath);
117
+ if (!outcome.ok)
118
+ throw new Error(outcome.issues[0].message);
119
+ return outcome.value;
120
+ }
@@ -9,7 +9,9 @@ export type MultiProjectListResult<T> = {
9
9
  items: T[];
10
10
  root_errors: CatalogRootError[];
11
11
  roots: CatalogRoot[];
12
+ tip?: string;
12
13
  };
14
+ export declare const EMPTY_CATALOG_LIST_TIP = "No catalog entries listed. Register a project root (POST /api/projects or local sf run / ensure), ensure stageflow.yaml exists at that root, then retry list_pipelines / list_tasks. Packaged examples use project_root \"examples\" when present. Git is not required for discovery.";
13
15
  export declare function listPipelinesMultiProject(options: ResolveCatalogRootsOptions & {
14
16
  projectRootFilter?: string;
15
17
  }): Promise<MultiProjectListResult<PipelineListing & {
@@ -1,27 +1,31 @@
1
1
  import { listModelsForContext, listPipelinesForContext, listTasksForContext, } from "./browseCatalog.js";
2
2
  import { loadStageflowManifestOutcome, } from "./loadStageflowManifest.js";
3
3
  import { findCatalogRoot, isRootReadable, resolveCatalogRoots, } from "./resolveCatalogRoots.js";
4
- import { catalogContextFromStageflow } from "./resolveCatalogContext.js";
5
- import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
4
+ export const EMPTY_CATALOG_LIST_TIP = "No catalog entries listed. Register a project root (POST /api/projects or local sf run / ensure), ensure stageflow.yaml exists at that root, then retry list_pipelines / list_tasks. Packaged examples use project_root \"examples\" when present. Git is not required for discovery.";
5
+ function listTip(itemsLength, root_errors) {
6
+ if (itemsLength > 0)
7
+ return undefined;
8
+ if (root_errors.some((e) => e.code === "unknown_project_root")) {
9
+ return undefined;
10
+ }
11
+ return EMPTY_CATALOG_LIST_TIP;
12
+ }
6
13
  async function catalogContextForRoot(root) {
7
- if (root.kind === "seeded") {
8
- const outcome = await loadStageflowManifestOutcome(root.path);
9
- if (!outcome.ok) {
10
- return {
11
- projectRoot: root.path,
12
- manifest: null,
13
- manifestStatus: "invalid",
14
- issues: outcome.issues,
15
- };
16
- }
14
+ const outcome = await loadStageflowManifestOutcome(root.path);
15
+ if (!outcome.ok) {
17
16
  return {
18
17
  projectRoot: root.path,
19
- manifest: outcome.value,
20
- manifestStatus: "ok",
21
- issues: [],
18
+ manifest: null,
19
+ manifestStatus: "invalid",
20
+ issues: outcome.issues,
22
21
  };
23
22
  }
24
- return catalogContextFromStageflow(await resolveStageflowContext(root.path));
23
+ return {
24
+ projectRoot: root.path,
25
+ manifest: outcome.value,
26
+ manifestStatus: "ok",
27
+ issues: [],
28
+ };
25
29
  }
26
30
  async function selectRoots(options) {
27
31
  const roots = await resolveCatalogRoots(options);
@@ -70,6 +74,10 @@ async function selectRoots(options) {
70
74
  }
71
75
  return { roots, selected, root_errors };
72
76
  }
77
+ function withListTip(result) {
78
+ const tip = listTip(result.items.length, result.root_errors);
79
+ return tip !== undefined ? { ...result, tip } : result;
80
+ }
73
81
  export async function listPipelinesMultiProject(options) {
74
82
  const { roots, selected, root_errors } = await selectRoots(options);
75
83
  const items = [];
@@ -89,7 +97,7 @@ export async function listPipelinesMultiProject(options) {
89
97
  });
90
98
  }
91
99
  }
92
- return { items, root_errors, roots };
100
+ return withListTip({ items, root_errors, roots });
93
101
  }
94
102
  export async function listTasksMultiProject(options) {
95
103
  const { roots, selected, root_errors } = await selectRoots(options);
@@ -110,7 +118,7 @@ export async function listTasksMultiProject(options) {
110
118
  });
111
119
  }
112
120
  }
113
- return { items, root_errors, roots };
121
+ return withListTip({ items, root_errors, roots });
114
122
  }
115
123
  export async function listModelsMultiProject(options) {
116
124
  const { roots, selected, root_errors } = await selectRoots(options);
@@ -8,6 +8,7 @@ import path from "node:path";
8
8
  import { loadFailure, loadSuccess } from "./loadOutcome.js";
9
9
  import { parseStageMcp } from "./loadStage.js";
10
10
  import { parseStageSecrets } from "../runtime/stageSecretDecl.js";
11
+ import { parseStageBrowser } from "./stageBrowser.js";
11
12
  import { parseToolRequires } from "./toolRequires.js";
12
13
  import { legacyAuthoringRejected, presentLegacyKeys } from "./legacyYaml.js";
13
14
  import { compileTargetContract, dialectFromKeys, mixedDialectIssue, } from "./yamlDialect.js";
@@ -45,7 +46,8 @@ function hasBodyKey(raw) {
45
46
  key !== "skill" &&
46
47
  key !== "mcp" &&
47
48
  key !== "secrets" &&
48
- key !== "requires");
49
+ key !== "requires" &&
50
+ key !== "browser");
49
51
  }
50
52
  function readSkill(raw) {
51
53
  if (raw.skill === undefined)
@@ -129,6 +131,10 @@ export function normalizePipelineStageEntries(rawEntries, ctx) {
129
131
  if (!requiresOutcome.ok)
130
132
  return requiresOutcome;
131
133
  const requires = requiresOutcome.value;
134
+ const browserOutcome = parseStageBrowser(raw.browser, `entry at index ${index} in ${declaringPath}`, typeof raw.id === "string" ? raw.id : undefined);
135
+ if (!browserOutcome.ok)
136
+ return browserOutcome;
137
+ const browser = browserOutcome.value;
132
138
  if (uses && hasBody) {
133
139
  return loadFailure([
134
140
  {
@@ -358,6 +364,7 @@ export function normalizePipelineStageEntries(rawEntries, ctx) {
358
364
  ...(mcp !== undefined ? { mcp } : {}),
359
365
  ...(secrets !== undefined ? { secrets } : {}),
360
366
  ...(requires !== undefined ? { requires } : {}),
367
+ ...(browser !== undefined ? { browser } : {}),
361
368
  ...(cloneCap !== undefined ? { clone_cap: cloneCap } : {}),
362
369
  ...(cloneMode !== undefined ? { clone_mode: cloneMode } : {}),
363
370
  };
@@ -124,6 +124,12 @@ function parseCheck(raw, stageId, index) {
124
124
  return failure(stageId, "completion", `checks[${index}]: unknown key "${unknown}"`);
125
125
  return loadSuccess({ id, type: "payload_schema" });
126
126
  }
127
+ if (raw.type === "browser_login") {
128
+ const unknown = hasOnlyKeys(raw, ["id", "type"]);
129
+ if (unknown)
130
+ return failure(stageId, "completion", `checks[${index}]: unknown key "${unknown}"`);
131
+ return loadSuccess({ id, type: "browser_login" });
132
+ }
127
133
  if (raw.type === "gate") {
128
134
  const unknown = hasOnlyKeys(raw, ["id", "type", "kind"]);
129
135
  if (unknown)
@@ -39,6 +39,7 @@ export const BODY_KEYS = new Set([
39
39
  "mcp",
40
40
  "secrets",
41
41
  "requires",
42
+ "browser",
42
43
  "io",
43
44
  "verify",
44
45
  ]);
@@ -0,0 +1,7 @@
1
+ import { type CreateDraftPackageInput, type DraftPackageWriteResult } from "./draftPackage.js";
2
+ export type PublishDraftPackageMode = "create" | "overwrite" | "auto";
3
+ export type PublishDraftPackageResult = {
4
+ write: DraftPackageWriteResult;
5
+ catalogChanged: boolean;
6
+ };
7
+ export declare function publishDraftPackage(projectRoot: string, input: CreateDraftPackageInput, mode: PublishDraftPackageMode): Promise<PublishDraftPackageResult>;
@@ -0,0 +1,25 @@
1
+ import { createDraftPackage, overwriteDraftPackage, } from "./draftPackage.js";
2
+ import { ensureCatalogScanRoot } from "./ensureCatalogScanRoot.js";
3
+ export async function publishDraftPackage(projectRoot, input, mode) {
4
+ let write;
5
+ if (mode === "create") {
6
+ write = await createDraftPackage(projectRoot, input);
7
+ }
8
+ else if (mode === "overwrite") {
9
+ write = await overwriteDraftPackage(projectRoot, input);
10
+ }
11
+ else {
12
+ const created = await createDraftPackage(projectRoot, input);
13
+ if (created.ok || created.status !== 409) {
14
+ write = created;
15
+ }
16
+ else {
17
+ write = await overwriteDraftPackage(projectRoot, input);
18
+ }
19
+ }
20
+ if (!write.ok) {
21
+ return { write, catalogChanged: false };
22
+ }
23
+ const catalogChanged = await ensureCatalogScanRoot(projectRoot, input.directory);
24
+ return { write, catalogChanged };
25
+ }
@@ -1,7 +1,7 @@
1
1
  import { resolveStageflowContext, } from "../project/resolveStageflowContext.js";
2
2
  export function catalogContextFromStageflow(ctx) {
3
3
  return {
4
- projectRoot: ctx.isGitProject ? ctx.projectRoot : null,
4
+ projectRoot: ctx.manifestStatus === "not_git" ? null : ctx.projectRoot,
5
5
  manifest: ctx.manifest,
6
6
  manifestStatus: ctx.manifestStatus,
7
7
  issues: ctx.manifestIssues,
@@ -18,7 +18,7 @@ function wrapRawStagesForNormalize(rawStages, ctx) {
18
18
  }
19
19
  const id = typeof raw.id === "string" && raw.id ? raw.id : undefined;
20
20
  const hasUses = typeof raw.uses === "string";
21
- const hasBody = Object.keys(raw).some((key) => BODY_KEYS.has(key) && key !== "skill" && key !== "mcp");
21
+ const hasBody = Object.keys(raw).some((key) => BODY_KEYS.has(key) && key !== "skill" && key !== "mcp" && key !== "browser");
22
22
  if (hasUses || hasBody || !id) {
23
23
  return { raw, declaringPath: ctx.path };
24
24
  }
@@ -1,4 +1,4 @@
1
1
  import type { LoadedManifest } from "../types/stageflowManifest.js";
2
- export type CatalogPathKind = "pipeline" | "task";
2
+ export type CatalogPathKind = "pipeline" | "task" | "trigger";
3
3
  export declare function isExcluded(repoRelPath: string, exclude: string[] | undefined): boolean;
4
4
  export declare function scanCatalogPaths(manifest: LoadedManifest, kind: CatalogPathKind): Promise<string[]>;
@@ -60,8 +60,12 @@ async function expandCatalogEntry(projectRoot, entry, kind, pattern) {
60
60
  }
61
61
  export async function scanCatalogPaths(manifest, kind) {
62
62
  const { projectRoot, manifest: doc, patterns } = manifest;
63
- const entries = kind === "pipeline" ? doc.catalog.pipelines : doc.catalog.tasks;
64
- const pattern = kind === "pipeline" ? patterns.pipeline : patterns.task;
63
+ const entries = kind === "pipeline"
64
+ ? doc.catalog.pipelines
65
+ : kind === "task"
66
+ ? doc.catalog.tasks
67
+ : (doc.catalog.triggers ?? []);
68
+ const pattern = kind === "pipeline" ? patterns.pipeline : kind === "task" ? patterns.task : patterns.trigger;
65
69
  const exclude = doc.catalog.exclude;
66
70
  const seen = new Set();
67
71
  const resolved = [];
@@ -0,0 +1,12 @@
1
+ import type { StageBrowserConfig } from "../types/stage.js";
2
+ import { type LoadOutcome } from "./loadOutcome.js";
3
+ import { type ToolRequirement } from "./toolRequires.js";
4
+ export declare const BROWSER_TOOL_NAME = "agent-browser";
5
+ export declare function parseStageBrowser(raw: unknown, label: string, stageId?: string): LoadOutcome<StageBrowserConfig | undefined>;
6
+ export declare function withBrowserRequires(stage: {
7
+ browser?: StageBrowserConfig;
8
+ requires?: ToolRequirement[];
9
+ }, ctx: {
10
+ pipelineId: string;
11
+ stageId: string;
12
+ }): LoadOutcome<ToolRequirement[] | undefined>;