stageflow 0.27.0 → 0.31.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 (270) 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 +10 -0
  5. package/dist/agent/cursorProvider.js +83 -8
  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/providerAuth.d.ts +7 -3
  14. package/dist/agent/providerAuth.js +5 -3
  15. package/dist/agent/streamLogRedact.d.ts +6 -0
  16. package/dist/agent/streamLogRedact.js +23 -1
  17. package/dist/browser/anchor.d.ts +27 -0
  18. package/dist/browser/anchor.js +127 -0
  19. package/dist/browser/auditSink.d.ts +38 -0
  20. package/dist/browser/auditSink.js +34 -0
  21. package/dist/browser/browserHost.d.ts +86 -0
  22. package/dist/browser/browserHost.js +7 -0
  23. package/dist/browser/browserSweep.d.ts +14 -0
  24. package/dist/browser/browserSweep.js +54 -0
  25. package/dist/browser/browserTeardown.d.ts +56 -0
  26. package/dist/browser/browserTeardown.js +235 -0
  27. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -0
  28. package/dist/browser/fakeRemoteBrowserHost.js +34 -0
  29. package/dist/browser/gateHandoff.d.ts +25 -0
  30. package/dist/browser/gateHandoff.js +83 -0
  31. package/dist/browser/humanLogin.d.ts +18 -0
  32. package/dist/browser/humanLogin.js +34 -0
  33. package/dist/browser/keyProvider.d.ts +10 -0
  34. package/dist/browser/keyProvider.js +39 -0
  35. package/dist/browser/localBrowserHost.d.ts +10 -0
  36. package/dist/browser/localBrowserHost.js +152 -0
  37. package/dist/browser/localProfileLock.d.ts +2 -0
  38. package/dist/browser/localProfileLock.js +218 -0
  39. package/dist/browser/localProfileStore.d.ts +5 -0
  40. package/dist/browser/localProfileStore.js +91 -0
  41. package/dist/browser/loginCheck.d.ts +53 -0
  42. package/dist/browser/loginCheck.js +147 -0
  43. package/dist/browser/memoryProfileLock.d.ts +2 -0
  44. package/dist/browser/memoryProfileLock.js +53 -0
  45. package/dist/browser/memoryProfileStore.d.ts +2 -0
  46. package/dist/browser/memoryProfileStore.js +31 -0
  47. package/dist/browser/navigationAudit.d.ts +16 -0
  48. package/dist/browser/navigationAudit.js +53 -0
  49. package/dist/browser/persistedEnv.d.ts +3 -0
  50. package/dist/browser/persistedEnv.js +15 -0
  51. package/dist/browser/profileLock.d.ts +40 -0
  52. package/dist/browser/profileLock.js +10 -0
  53. package/dist/browser/profileStore.d.ts +23 -0
  54. package/dist/browser/profileStore.js +39 -0
  55. package/dist/browser/runLiveness.d.ts +13 -0
  56. package/dist/browser/runLiveness.js +39 -0
  57. package/dist/browser/sitePolicy.d.ts +10 -0
  58. package/dist/browser/sitePolicy.js +61 -0
  59. package/dist/browser/stageBrowserEnv.d.ts +22 -0
  60. package/dist/browser/stageBrowserEnv.js +147 -0
  61. package/dist/browser/stageProfileLock.d.ts +17 -0
  62. package/dist/browser/stageProfileLock.js +39 -0
  63. package/dist/cli/browserCommand.d.ts +44 -0
  64. package/dist/cli/browserCommand.js +472 -0
  65. package/dist/cli/doctorCommand.js +7 -0
  66. package/dist/cli/hostClient.d.ts +20 -0
  67. package/dist/cli/hostClient.js +36 -2
  68. package/dist/cli/initCommand.d.ts +1 -0
  69. package/dist/cli/initCommand.js +23 -7
  70. package/dist/cli/initTemplates.d.ts +5 -2
  71. package/dist/cli/initTemplates.js +25 -2
  72. package/dist/cli/providersCommand.d.ts +1 -1
  73. package/dist/cli/providersCommand.js +4 -4
  74. package/dist/cli/runsCommand.js +6 -2
  75. package/dist/cli/triggerCommand.d.ts +12 -0
  76. package/dist/cli/triggerCommand.js +183 -0
  77. package/dist/cli/validateCommand.d.ts +1 -1
  78. package/dist/cli/validateCommand.js +23 -6
  79. package/dist/cli.js +32 -3
  80. package/dist/config/browseCatalog.d.ts +1 -0
  81. package/dist/config/browseCatalog.js +5 -4
  82. package/dist/config/builtinSkills.d.ts +4 -0
  83. package/dist/config/builtinSkills.js +23 -0
  84. package/dist/config/catalogRelativePath.d.ts +1 -0
  85. package/dist/config/catalogRelativePath.js +10 -2
  86. package/dist/config/createTrigger.d.ts +27 -0
  87. package/dist/config/createTrigger.js +232 -0
  88. package/dist/config/draftPackage.d.ts +117 -0
  89. package/dist/config/draftPackage.js +550 -0
  90. package/dist/config/ensureCatalogScanRoot.d.ts +16 -0
  91. package/dist/config/ensureCatalogScanRoot.js +73 -0
  92. package/dist/config/hostConfig.d.ts +2 -0
  93. package/dist/config/hostConfig.js +27 -0
  94. package/dist/config/listSkills.d.ts +2 -2
  95. package/dist/config/listSkills.js +17 -3
  96. package/dist/config/loadOutcome.d.ts +6 -0
  97. package/dist/config/loadPipeline.js +16 -0
  98. package/dist/config/loadStage.js +7 -0
  99. package/dist/config/loadStageflowManifest.js +25 -0
  100. package/dist/config/loadTrigger.d.ts +9 -0
  101. package/dist/config/loadTrigger.js +120 -0
  102. package/dist/config/multiProjectCatalog.d.ts +2 -0
  103. package/dist/config/multiProjectCatalog.js +26 -18
  104. package/dist/config/normalizePipelineStageEntry.js +8 -1
  105. package/dist/config/parseCompletionContract.js +6 -0
  106. package/dist/config/pipelineStageKeys.js +1 -0
  107. package/dist/config/publishDraftPackage.d.ts +7 -0
  108. package/dist/config/publishDraftPackage.js +25 -0
  109. package/dist/config/resolveCatalogContext.js +1 -1
  110. package/dist/config/resolvePipelineDag.js +1 -1
  111. package/dist/config/scanCatalogPaths.d.ts +1 -1
  112. package/dist/config/scanCatalogPaths.js +6 -2
  113. package/dist/config/stageBrowser.d.ts +12 -0
  114. package/dist/config/stageBrowser.js +118 -0
  115. package/dist/config/triggerCatalog.d.ts +10 -0
  116. package/dist/config/triggerCatalog.js +37 -0
  117. package/dist/config/validateCatalog.d.ts +13 -3
  118. package/dist/config/validateCatalog.js +83 -2
  119. package/dist/config/validateCompletionContract.js +8 -0
  120. package/dist/envelope/preEmitChecks.d.ts +1 -1
  121. package/dist/envelope/preEmitChecks.js +8 -0
  122. package/dist/logging/redact.d.ts +4 -0
  123. package/dist/logging/redact.js +17 -0
  124. package/dist/mcp/catalogTools.js +43 -8
  125. package/dist/mcp/controlTools.js +3 -1
  126. package/dist/mcp/deps.d.ts +2 -0
  127. package/dist/mcp/server.js +1 -0
  128. package/dist/mcp/tools.js +2 -0
  129. package/dist/mcp/triggerTools.d.ts +3 -0
  130. package/dist/mcp/triggerTools.js +176 -0
  131. package/dist/operatorAgent/docsRetrieval.d.ts +40 -0
  132. package/dist/operatorAgent/docsRetrieval.js +269 -0
  133. package/dist/operatorAgent/draftContext.d.ts +16 -0
  134. package/dist/operatorAgent/draftContext.js +68 -0
  135. package/dist/operatorAgent/fakeHost.d.ts +39 -0
  136. package/dist/operatorAgent/fakeHost.js +92 -0
  137. package/dist/operatorAgent/host.d.ts +40 -0
  138. package/dist/operatorAgent/host.js +179 -0
  139. package/dist/operatorAgent/index.d.ts +9 -0
  140. package/dist/operatorAgent/index.js +8 -0
  141. package/dist/operatorAgent/piModel.d.ts +80 -0
  142. package/dist/operatorAgent/piModel.js +528 -0
  143. package/dist/operatorAgent/profiles/workshopAuthor.d.ts +23 -0
  144. package/dist/operatorAgent/profiles/workshopAuthor.js +338 -0
  145. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +6 -0
  146. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +103 -0
  147. package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +57 -0
  148. package/dist/operatorAgent/profiles/workshopAuthorTools.js +623 -0
  149. package/dist/operatorAgent/proposals.d.ts +32 -0
  150. package/dist/operatorAgent/proposals.js +144 -0
  151. package/dist/operatorAgent/types.d.ts +128 -0
  152. package/dist/operatorAgent/types.js +1 -0
  153. package/dist/package-meta.d.ts +1 -1
  154. package/dist/package-meta.js +1 -1
  155. package/dist/project/findManifestRoot.d.ts +3 -0
  156. package/dist/project/findManifestRoot.js +35 -0
  157. package/dist/project/resolveStageflowContext.d.ts +7 -0
  158. package/dist/project/resolveStageflowContext.js +44 -28
  159. package/dist/runstore/assertTmpdir.js +6 -2
  160. package/dist/runstore/diskUsage.d.ts +1 -1
  161. package/dist/runstore/diskUsage.js +2 -7
  162. package/dist/runstore/port.d.ts +28 -0
  163. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -1
  164. package/dist/runstore/sqlite/SqliteRunStore.js +90 -0
  165. package/dist/runstore/sqlite/migrations/008-triggers-table.d.ts +7 -0
  166. package/dist/runstore/sqlite/migrations/008-triggers-table.js +19 -0
  167. package/dist/runstore/sqlite/migrations/009-trigger-next-run.d.ts +7 -0
  168. package/dist/runstore/sqlite/migrations/009-trigger-next-run.js +19 -0
  169. package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.d.ts +7 -0
  170. package/dist/runstore/sqlite/migrations/010-trigger-adapter-state.js +17 -0
  171. package/dist/runstore/sqlite/migrations/index.js +6 -0
  172. package/dist/runtime/answerResume.d.ts +1 -0
  173. package/dist/runtime/answerResume.js +3 -1
  174. package/dist/runtime/completionCheckRunner.d.ts +12 -1
  175. package/dist/runtime/completionCheckRunner.js +22 -0
  176. package/dist/runtime/credentialBinding.d.ts +7 -2
  177. package/dist/runtime/credentialBinding.js +20 -28
  178. package/dist/runtime/emailSource.d.ts +72 -0
  179. package/dist/runtime/emailSource.js +304 -0
  180. package/dist/runtime/githubPollSource.d.ts +86 -0
  181. package/dist/runtime/githubPollSource.js +284 -0
  182. package/dist/runtime/pipelineRunner.d.ts +4 -0
  183. package/dist/runtime/pipelineRunner.js +3 -0
  184. package/dist/runtime/pipelineScheduler.d.ts +2 -0
  185. package/dist/runtime/pipelineScheduler.js +86 -1
  186. package/dist/runtime/repositoryMaterialize.d.ts +1 -1
  187. package/dist/runtime/runManager.d.ts +11 -0
  188. package/dist/runtime/runManager.js +161 -3
  189. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  190. package/dist/runtime/runRetryCoordinator.js +1 -0
  191. package/dist/runtime/scheduleSource.d.ts +38 -0
  192. package/dist/runtime/scheduleSource.js +119 -0
  193. package/dist/runtime/settingsFile.d.ts +7 -2
  194. package/dist/runtime/settingsFile.js +66 -56
  195. package/dist/runtime/stageAttemptBootstrap.js +55 -3
  196. package/dist/runtime/stageEnvironment.js +1 -0
  197. package/dist/runtime/stageHitl.d.ts +1 -0
  198. package/dist/runtime/stageHitl.js +5 -1
  199. package/dist/runtime/stageProcessLauncher.d.ts +3 -0
  200. package/dist/runtime/stageProcessLauncher.js +52 -5
  201. package/dist/runtime/stageRunner.d.ts +5 -0
  202. package/dist/runtime/stageRunner.js +30 -6
  203. package/dist/runtime/stageStreamLog.d.ts +1 -0
  204. package/dist/runtime/stageStreamLog.js +2 -1
  205. package/dist/runtime/triggerCatalog.d.ts +6 -0
  206. package/dist/runtime/triggerCatalog.js +24 -0
  207. package/dist/runtime/triggerEventMatch.d.ts +2 -0
  208. package/dist/runtime/triggerEventMatch.js +6 -0
  209. package/dist/runtime/triggerPort.d.ts +14 -0
  210. package/dist/runtime/triggerPort.js +1 -0
  211. package/dist/runtime/triggerRunner.d.ts +17 -0
  212. package/dist/runtime/triggerRunner.js +89 -0
  213. package/dist/runtime/verifiedStageExecution.d.ts +7 -0
  214. package/dist/runtime/verifiedStageExecution.js +14 -0
  215. package/dist/runtime/webhookSignature.d.ts +1 -0
  216. package/dist/runtime/webhookSignature.js +12 -0
  217. package/dist/server/bootstrap.d.ts +6 -0
  218. package/dist/server/bootstrap.js +70 -0
  219. package/dist/server/controlToken.js +13 -0
  220. package/dist/server/createHttpHost.js +3 -0
  221. package/dist/server/healthSurfaces.js +31 -6
  222. package/dist/server/http.d.ts +15 -0
  223. package/dist/server/http.js +1251 -74
  224. package/dist/server/requestAuthContext.d.ts +1 -1
  225. package/dist/server/triggerRoutes.d.ts +15 -0
  226. package/dist/server/triggerRoutes.js +268 -0
  227. package/dist/tools/askOperator.d.ts +3 -2
  228. package/dist/types/completion.d.ts +3 -0
  229. package/dist/types/pipeline.d.ts +3 -1
  230. package/dist/types/preEmitCheck.d.ts +7 -0
  231. package/dist/types/stage.d.ts +14 -0
  232. package/dist/types/stageflowManifest.d.ts +3 -0
  233. package/dist/types/trigger.d.ts +18 -0
  234. package/dist/types/trigger.js +1 -0
  235. package/dist/ui/assets/{index-CSsDued1.css → index-Bv4p1aPK.css} +1 -1
  236. package/dist/ui/assets/index-p8Iou3zk.js +133 -0
  237. package/dist/ui/index.html +2 -2
  238. package/dist/workshop/autosave.d.ts +58 -0
  239. package/dist/workshop/autosave.js +222 -0
  240. package/dist/workshop/buildStore.d.ts +79 -0
  241. package/dist/workshop/buildStore.js +326 -0
  242. package/dist/workshop/chatTurn.d.ts +232 -0
  243. package/dist/workshop/chatTurn.js +475 -0
  244. package/dist/workshop/modelSettings.d.ts +18 -0
  245. package/dist/workshop/modelSettings.js +44 -0
  246. package/dist/workshop/sessionStore.d.ts +61 -0
  247. package/dist/workshop/sessionStore.js +254 -0
  248. package/dist/workshop/workshopPin.d.ts +72 -0
  249. package/dist/workshop/workshopPin.js +193 -0
  250. package/examples/README.md +2 -1
  251. package/examples/browser-session/README.md +77 -0
  252. package/examples/browser-session/browser-session.pipeline.yaml +50 -0
  253. package/examples/browser-session/browser-session.task.yaml +6 -0
  254. package/examples/browser-session/check-login.yaml +22 -0
  255. package/examples/browser-session/fixture-server.mjs +64 -0
  256. package/examples/browser-session/human-login.yaml +31 -0
  257. package/examples/browser-session/merge.yaml +36 -0
  258. package/examples/browser-session/work-a.yaml +41 -0
  259. package/examples/browser-session/work-b.yaml +41 -0
  260. package/examples/conditional-fork/decide.yaml +1 -1
  261. package/examples/github-release/README.md +17 -3
  262. package/examples/stage-mcp/README.md +3 -2
  263. package/examples/stageflow.yaml +51 -0
  264. package/package.json +9 -4
  265. package/skills/stageflow/references/control-surface.md +3 -1
  266. package/skills/stageflow-author/references/stage-prompt-template.md +4 -4
  267. package/skills/stageflow-run/SKILL.md +2 -0
  268. package/skills/stageflow-run/references/task-and-pipeline-selection.md +3 -1
  269. package/skills/stageflow-setup/SKILL.md +11 -0
  270. package/dist/ui/assets/index-B6hlSXXn.js +0 -120
@@ -0,0 +1,17 @@
1
+ import type { RunStore } from "../runstore/port.js";
2
+ import type { RunManager, StartRunResult } from "./runManager.js";
3
+ import type { TaskFile } from "../types/task.js";
4
+ /**
5
+ * Resolves a trigger id to its `*.trigger.yaml` catalog definition, resolves its
6
+ * `pipeline`/`task` refs to catalog files, and starts a real run through
7
+ * `RunManager.startRun` — the same internal entry point the CLI/MCP/console use.
8
+ * On a successful start, records the fire on the trigger's store row.
9
+ *
10
+ * `task` presence on the trigger definition is the either/or mode switch: a
11
+ * catalog-mode trigger (`task` set) rejects a supplied `options.task`; a
12
+ * dynamic-mode trigger (`task` absent) requires one.
13
+ */
14
+ export declare function fireTrigger(triggerId: string, store: RunStore, runManager: RunManager, options?: {
15
+ cwd?: string;
16
+ task?: TaskFile;
17
+ }): Promise<StartRunResult>;
@@ -0,0 +1,89 @@
1
+ import path from "node:path";
2
+ import { catalogContextFromStageflow } from "../config/resolveCatalogContext.js";
3
+ import { getCatalogScanPaths } from "../config/browseCatalog.js";
4
+ import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
5
+ import { findCatalogPathById, findTriggerDefinition } from "./triggerCatalog.js";
6
+ /**
7
+ * Resolves a trigger id to its `*.trigger.yaml` catalog definition, resolves its
8
+ * `pipeline`/`task` refs to catalog files, and starts a real run through
9
+ * `RunManager.startRun` — the same internal entry point the CLI/MCP/console use.
10
+ * On a successful start, records the fire on the trigger's store row.
11
+ *
12
+ * `task` presence on the trigger definition is the either/or mode switch: a
13
+ * catalog-mode trigger (`task` set) rejects a supplied `options.task`; a
14
+ * dynamic-mode trigger (`task` absent) requires one.
15
+ */
16
+ export async function fireTrigger(triggerId, store, runManager, options = {}) {
17
+ const cwd = options.cwd ?? process.cwd();
18
+ const ctx = catalogContextFromStageflow(await resolveStageflowContext(cwd));
19
+ const scanPaths = await getCatalogScanPaths(ctx);
20
+ if (!scanPaths) {
21
+ return { ok: false, reason: "No Stageflow catalog found", status: 404 };
22
+ }
23
+ const found = await findTriggerDefinition(scanPaths.triggerPaths, triggerId);
24
+ if (!found) {
25
+ return { ok: false, reason: `Trigger not found: ${triggerId}`, status: 404 };
26
+ }
27
+ const { path: triggerPath, definition: trigger } = found;
28
+ if (!trigger.enabled) {
29
+ return { ok: false, reason: `Trigger is disabled: ${triggerId}`, status: 409 };
30
+ }
31
+ const suppliedTask = options.task;
32
+ if (trigger.task !== undefined && suppliedTask !== undefined) {
33
+ return {
34
+ ok: false,
35
+ reason: `Trigger "${triggerId}" already has a catalog task "${trigger.task}"; task override is not allowed`,
36
+ status: 422,
37
+ code: "trigger.task_override_not_allowed",
38
+ };
39
+ }
40
+ if (trigger.task === undefined && suppliedTask === undefined) {
41
+ return {
42
+ ok: false,
43
+ reason: `Trigger "${triggerId}" has no catalog task; a task must be supplied when firing`,
44
+ status: 422,
45
+ code: "trigger.task_required",
46
+ };
47
+ }
48
+ const pipelinePath = await findCatalogPathById(scanPaths.pipelinePaths, trigger.pipeline);
49
+ if (!pipelinePath) {
50
+ return {
51
+ ok: false,
52
+ reason: `Trigger "${triggerId}" references unknown pipeline "${trigger.pipeline}"`,
53
+ status: 400,
54
+ };
55
+ }
56
+ let taskInput;
57
+ if (trigger.task !== undefined) {
58
+ const taskPath = await findCatalogPathById(scanPaths.taskPaths, trigger.task);
59
+ if (!taskPath) {
60
+ return {
61
+ ok: false,
62
+ reason: `Trigger "${triggerId}" references unknown task "${trigger.task}"`,
63
+ status: 400,
64
+ };
65
+ }
66
+ taskInput = taskPath;
67
+ }
68
+ else {
69
+ taskInput = suppliedTask;
70
+ }
71
+ const projectRoot = ctx.projectRoot ?? undefined;
72
+ const definitionRef = projectRoot !== undefined
73
+ ? path.relative(projectRoot, triggerPath).replace(/\\/g, "/")
74
+ : triggerPath;
75
+ await store.upsertTrigger({
76
+ id: triggerId,
77
+ definitionRef,
78
+ enabled: trigger.enabled,
79
+ });
80
+ const started = await runManager.startRun({
81
+ pipeline: pipelinePath,
82
+ task: taskInput,
83
+ ...(projectRoot !== undefined ? { projectRoot } : {}),
84
+ });
85
+ if (started.ok) {
86
+ await store.recordTriggerFired(triggerId, started.runId);
87
+ }
88
+ return started;
89
+ }
@@ -2,6 +2,8 @@ import type { StageRunResult } from "../agent/port.js";
2
2
  import type { RunStore } from "../runstore/port.js";
3
3
  import type { ResolvedPipelineDag } from "../types/pipeline.js";
4
4
  import type { StageConfig } from "../types/stage.js";
5
+ import type { BrowserRunner } from "../browser/browserHost.js";
6
+ import { type LoginCheckConfig, type LoginCheckOptions } from "../browser/loginCheck.js";
5
7
  import type { StageRoots } from "./stageRoots.js";
6
8
  /** Sidecar written before completion command checks so they can inspect fork_choice. */
7
9
  export declare const COMPLETION_CANDIDATE_ENVELOPE_FILENAME = "completion-candidate-envelope.json";
@@ -29,4 +31,9 @@ export declare function createVerifiedStageExecution(options: {
29
31
  dag?: ResolvedPipelineDag;
30
32
  roots: StageRoots;
31
33
  commandEnv?: NodeJS.ProcessEnv;
34
+ browserLogin?: {
35
+ check: LoginCheckConfig;
36
+ runner?: BrowserRunner;
37
+ options?: LoginCheckOptions;
38
+ };
32
39
  }): VerifiedStageExecution;
@@ -9,6 +9,9 @@ import { COMPLETION_VERIFICATION_FAILURE_PREFIX, } from "../types/completion.js"
9
9
  import { runCompletionContract, } from "./completionCheckRunner.js";
10
10
  import { readCompletionCheckoutBaseline, writeCompletionCheckoutBaseline, } from "./completionCheckoutBaseline.js";
11
11
  import { createGitCheckoutCapability } from "./gitCheckoutCapability.js";
12
+ import { readStagePersistedBrowserEnv } from "../browser/stageBrowserEnv.js";
13
+ import { defaultBrowserRunner } from "../browser/browserTeardown.js";
14
+ import { runLoginCheck, } from "../browser/loginCheck.js";
12
15
  /** Sidecar written before completion command checks so they can inspect fork_choice. */
13
16
  export const COMPLETION_CANDIDATE_ENVELOPE_FILENAME = "completion-candidate-envelope.json";
14
17
  export function completionVerificationFailureReason(failedCheckIds) {
@@ -88,6 +91,17 @@ export function createVerifiedStageExecution(options) {
88
91
  checkoutBefore,
89
92
  checkoutError,
90
93
  checklistAttestations: candidate.envelope.checklist_attestations,
94
+ ...(options.browserLogin !== undefined
95
+ ? {
96
+ browserLogin: async () => {
97
+ const env = await readStagePersistedBrowserEnv(roots.runWorkspaceDir, stageId);
98
+ if (env === undefined) {
99
+ throw new Error("browser session env is missing for this stage");
100
+ }
101
+ return runLoginCheck(env, options.browserLogin.check, options.browserLogin.runner ?? defaultBrowserRunner, options.browserLogin.options);
102
+ },
103
+ }
104
+ : {}),
91
105
  onCheckStart: async (check) => {
92
106
  await store.upsertVerificationCheckResult(runId, stageId, {
93
107
  check_id: check.id,
@@ -0,0 +1 @@
1
+ export declare function verifyHmacSignature(rawBody: Buffer, headerValue: string, secret: string, scheme: "hex" | "base64"): boolean;
@@ -0,0 +1,12 @@
1
+ import crypto from "node:crypto";
2
+ export function verifyHmacSignature(rawBody, headerValue, secret, scheme) {
3
+ const eqIndex = headerValue.indexOf("=");
4
+ const hasPrefix = eqIndex !== -1 && eqIndex < headerValue.length - 1;
5
+ const provided = hasPrefix ? headerValue.slice(eqIndex + 1) : headerValue;
6
+ const computed = crypto.createHmac("sha256", secret).update(rawBody).digest(scheme);
7
+ const providedBuffer = Buffer.from(provided, scheme);
8
+ const computedBuffer = Buffer.from(computed, scheme);
9
+ if (providedBuffer.length !== computedBuffer.length)
10
+ return false;
11
+ return crypto.timingSafeEqual(providedBuffer, computedBuffer);
12
+ }
@@ -45,6 +45,12 @@ export type StageflowHostBootstrap = {
45
45
  /** Periodic retention GC handle when enabled; already `.unref()`'d. */
46
46
  gcInterval?: NodeJS.Timeout;
47
47
  stopGcInterval: () => void;
48
+ /** Stops the schedule-trigger tick loop started at boot. */
49
+ stopScheduleSource: () => void;
50
+ /** Stops the GitHub poll-trigger loop started at boot. */
51
+ stopGithubPollSource: () => void;
52
+ /** Stops the email-trigger IMAP/IDLE listeners started at boot. */
53
+ stopEmailSource: () => void;
48
54
  /** Filesystem classification for `$STAGEFLOW_HOME` (Slot 8). */
49
55
  storeFilesystem?: StoreFilesystemClassification;
50
56
  /**
@@ -12,10 +12,15 @@ import { assertStoreFilesystemSupported } from "../runstore/storeFilesystem.js";
12
12
  import { assertStoreQuickCheck, isSqliteCorruptError, maybeCheckpointResidualWal, rethrowAsStoreIntegrityFailed, } from "../runstore/sqlite/applyStorePragmas.js";
13
13
  import { RunManager } from "../runtime/runManager.js";
14
14
  import { PI_CODING_AGENT_DIR_ENV } from "../runtime/stageRoots.js";
15
+ import { ScheduleSource, triggerTickIntervalMsFromEnv, } from "../runtime/scheduleSource.js";
16
+ import { GithubPollSource, githubPollIntervalMsFromEnv, } from "../runtime/githubPollSource.js";
17
+ import { EmailSource } from "../runtime/emailSource.js";
18
+ import { fireTrigger } from "../runtime/triggerRunner.js";
15
19
  import { createRunChangeBus, getRunChangeBusFromWrappedStore, isRunStoreWrapped, wrapRunStoreWithChangeBus, } from "../runtime/runChangeBus.js";
16
20
  import { createMcpHttpHandler, resolveMcpStateless, } from "../mcp/server.js";
17
21
  import { loadHostConfig, } from "../config/hostConfig.js";
18
22
  import { bootProviderConfig } from "../agent/bootProviderConfig.js";
23
+ import { readCursorApiKey } from "../agent/cursorProvider.js";
19
24
  import { applyPendingRestoreAtBoot, } from "../runstore/restore.js";
20
25
  import { assertTmpdirUsable } from "../runstore/assertTmpdir.js";
21
26
  import { logger as rootLogger } from "../logging/logger.js";
@@ -76,6 +81,11 @@ export async function bootstrapStageflowHost(options) {
76
81
  const ctx = await resolveStageflowContext(invocationCwd);
77
82
  const cwd = ctx.invocationCwd;
78
83
  ensureGlobalHome();
84
+ const cursorKey = readCursorApiKey(env);
85
+ if (cursorKey !== undefined &&
86
+ (env.CURSOR_API_KEY === undefined || env.CURSOR_API_KEY.trim() === "")) {
87
+ process.env.CURSOR_API_KEY = cursorKey;
88
+ }
79
89
  assertTmpdirUsable(env);
80
90
  const hostConfig = options.hostConfig ??
81
91
  (options.skipHostConfig
@@ -149,6 +159,9 @@ export async function bootstrapStageflowHost(options) {
149
159
  close: async () => { },
150
160
  },
151
161
  stopGcInterval: () => { },
162
+ stopScheduleSource: () => { },
163
+ stopGithubPollSource: () => { },
164
+ stopEmailSource: () => { },
152
165
  storeFilesystem,
153
166
  serveBlocked,
154
167
  ...(hostConfig !== undefined ? { hostConfig } : {}),
@@ -231,6 +244,7 @@ export async function bootstrapStageflowHost(options) {
231
244
  });
232
245
  await manager.attachWaitingStages();
233
246
  await manager.reconcileOrphanedStages();
247
+ await manager.sweepBrowserSessions().catch(() => undefined);
234
248
  await manager.autoResumeInterruptedStages();
235
249
  await manager.resumeStalledSchedules();
236
250
  await manager.reenqueuePersistedQueuedRuns();
@@ -240,6 +254,58 @@ export async function bootstrapStageflowHost(options) {
240
254
  if (gcInterval !== undefined)
241
255
  clearInterval(gcInterval);
242
256
  };
257
+ const scheduleSource = new ScheduleSource({
258
+ store,
259
+ cwd,
260
+ intervalMs: triggerTickIntervalMsFromEnv(env),
261
+ logError: (message) => bootLog.error("trigger.schedule_source_failed", message),
262
+ });
263
+ const onTriggerFire = async (event) => {
264
+ try {
265
+ const result = await fireTrigger(event.triggerId, store, manager, {
266
+ cwd,
267
+ task: event.task,
268
+ });
269
+ if (!result.ok) {
270
+ bootLog.error("trigger.fire_failed", `trigger "${event.triggerId}" fire failed: ${result.reason}`);
271
+ }
272
+ }
273
+ catch (err) {
274
+ bootLog.error("trigger.fire_failed", `trigger "${event.triggerId}" fire threw: ${err instanceof Error ? err.message : String(err)}`);
275
+ }
276
+ };
277
+ try {
278
+ // Boot-time catch-up: fire once for any schedule trigger whose next_run_at
279
+ // already passed while the Host was down, then reschedule from now —
280
+ // before starting the periodic ticker so this pass never races it.
281
+ await scheduleSource.tick(onTriggerFire);
282
+ }
283
+ catch (err) {
284
+ bootLog.error("trigger.schedule_catchup_failed", `boot catch-up tick failed: ${err instanceof Error ? err.message : String(err)}`);
285
+ }
286
+ await scheduleSource.start(onTriggerFire);
287
+ const stopScheduleSource = () => {
288
+ void scheduleSource.stop();
289
+ };
290
+ const githubPollSource = new GithubPollSource({
291
+ store,
292
+ cwd,
293
+ intervalMs: githubPollIntervalMsFromEnv(env),
294
+ logError: (message) => bootLog.error("trigger.github_poll_source_failed", message),
295
+ });
296
+ await githubPollSource.start(onTriggerFire);
297
+ const stopGithubPollSource = () => {
298
+ void githubPollSource.stop();
299
+ };
300
+ const emailSource = new EmailSource({
301
+ store,
302
+ cwd,
303
+ logError: (message) => bootLog.error("trigger.email_source_failed", message),
304
+ });
305
+ await emailSource.start(onTriggerFire);
306
+ const stopEmailSource = () => {
307
+ void emailSource.stop();
308
+ };
243
309
  const mcpStateless = resolveMcpStateless({
244
310
  mcpStateless: options.mcpStateless,
245
311
  });
@@ -251,6 +317,7 @@ export async function bootstrapStageflowHost(options) {
251
317
  runChangeBus,
252
318
  providerAuthContext: options.providerAuthContext,
253
319
  projectRoot: rootDir,
320
+ ...(providerBoot !== undefined ? { providerBoot } : {}),
254
321
  }, { mcpStateless });
255
322
  return {
256
323
  a2a: await createA2aHost({
@@ -274,6 +341,9 @@ export async function bootstrapStageflowHost(options) {
274
341
  ...(providerBoot !== undefined ? { providerBoot } : {}),
275
342
  ...(gcInterval !== undefined ? { gcInterval } : {}),
276
343
  stopGcInterval,
344
+ stopScheduleSource,
345
+ stopGithubPollSource,
346
+ stopEmailSource,
277
347
  storeFilesystem,
278
348
  };
279
349
  }
@@ -158,11 +158,24 @@ export class BindRefusedError extends Error {
158
158
  this.name = "BindRefusedError";
159
159
  }
160
160
  }
161
+ /**
162
+ * `POST /api/triggers/:id/webhook` is exempt from bearer auth: an external
163
+ * webhook sender can't present Stageflow's own control token, and the route
164
+ * authenticates the request itself via per-trigger HMAC signature
165
+ * verification instead. It still goes through `assertAllowedHttpAccess`
166
+ * (loopback/allowed-hosts gating) the same as every other `/api/*` route —
167
+ * only the bearer-token requirement is carved out here.
168
+ */
169
+ function isTriggerWebhookRoute(method, pathname) {
170
+ return method === "POST" && /^\/api\/triggers\/[^/]+\/webhook$/.test(pathname);
171
+ }
161
172
  export function requiredScopeFor(method, pathname) {
162
173
  if (pathname === "/mcp")
163
174
  return "drive";
164
175
  if (!pathname.startsWith("/api/"))
165
176
  return null;
177
+ if (isTriggerWebhookRoute(method, pathname))
178
+ return null;
166
179
  if (pathname === "/api/backup" || pathname.startsWith("/api/backup/")) {
167
180
  return "drive";
168
181
  }
@@ -135,6 +135,9 @@ export async function createHttpHost(options) {
135
135
  server.maxConnections = maxConnections;
136
136
  server.on("close", () => {
137
137
  boot.stopGcInterval();
138
+ boot.stopScheduleSource();
139
+ boot.stopGithubPollSource();
140
+ boot.stopEmailSource();
138
141
  void boot.mcpHandler.close();
139
142
  });
140
143
  await new Promise((resolve, reject) => {
@@ -1,3 +1,4 @@
1
+ import { getAuthStatus, } from "../agent/providerAuth.js";
1
2
  import { PACKAGE_VERSION, BUILD_SHA } from "../package-meta.js";
2
3
  import { globalStageflowHome } from "../project/globalHome.js";
3
4
  import { redactHostConfig } from "../config/hostConfig.js";
@@ -6,6 +7,31 @@ import { json } from "./createHttpHost.js";
6
7
  import { livezBody, probeGitVersion, readSchemaHealth, runReadyzChecks, } from "../diagnostics/checks.js";
7
8
  import { buildEgressHealth } from "../net/proxy.js";
8
9
  import { toolchainHealthMap } from "../preflight/toolchain.js";
10
+ function bootProvidersSnapshot(providerBoot) {
11
+ return providerBoot
12
+ ? {
13
+ configured: providerBoot.configured,
14
+ failures: providerBoot.failures,
15
+ }
16
+ : { configured: [], failures: [] };
17
+ }
18
+ async function providersLiveSummary(cwd, authCtx) {
19
+ const note = "Live auth inspect — prefer list_providers / sf providers status. boot_providers is Host boot env only and may be empty when CLI credentials are configured.";
20
+ if (authCtx === undefined) {
21
+ return { configured: [], note };
22
+ }
23
+ try {
24
+ const status = await getAuthStatus(cwd, undefined, authCtx);
25
+ const rows = Array.isArray(status) ? status : [status];
26
+ return {
27
+ configured: rows.filter((r) => r.configured).map((r) => r.providerId),
28
+ note,
29
+ };
30
+ }
31
+ catch {
32
+ return { configured: [], note };
33
+ }
34
+ }
9
35
  export function handleLivez(_req, res) {
10
36
  json(res, 200, livezBody());
11
37
  }
@@ -54,6 +80,8 @@ export async function buildRichHealthPayload(boot) {
54
80
  store: boot.store,
55
81
  bootCwd: boot.cwd,
56
82
  });
83
+ const boot_providers = bootProvidersSnapshot(boot.providerBoot);
84
+ const providers_live = await providersLiveSummary(boot.cwd, boot.providerAuthContext);
57
85
  const payload = {
58
86
  ...capacity,
59
87
  capacity: {
@@ -78,12 +106,9 @@ export async function buildRichHealthPayload(boot) {
78
106
  kind: r.kind,
79
107
  read_only: r.read_only,
80
108
  })),
81
- providers: boot.providerBoot
82
- ? {
83
- configured: boot.providerBoot.configured,
84
- failures: boot.providerBoot.failures,
85
- }
86
- : { configured: [], failures: [] },
109
+ boot_providers,
110
+ providers_live,
111
+ providers: boot_providers,
87
112
  };
88
113
  if (boot.hostConfig) {
89
114
  payload.config = redactHostConfig(boot.hostConfig);
@@ -1,5 +1,7 @@
1
1
  import type { AgentPort } from "../agent/port.js";
2
2
  import { type ProviderAuthContext } from "../agent/providerAuth.js";
3
+ import { WorkshopChatSessionRegistry } from "../workshop/chatTurn.js";
4
+ import { type OperatorAgentHost } from "../operatorAgent/index.js";
3
5
  import type { RunStoreKind } from "../runstore/createStore.js";
4
6
  import type { RunStore } from "../runstore/port.js";
5
7
  import type { RunManager } from "../runtime/runManager.js";
@@ -24,6 +26,8 @@ export type UiServerOptions = {
24
26
  runChangeBus?: RunChangeBus;
25
27
  allowedHosts?: AllowedHosts;
26
28
  controlTokens?: ControlTokens;
29
+ /** Optional Workshop Operator Agent Host (fake in tests). */
30
+ workshopOperatorHost?: OperatorAgentHost;
27
31
  };
28
32
  export declare function defaultUiDistDir(): string;
29
33
  export type OperatorRouteDeps = {
@@ -39,6 +43,17 @@ export type OperatorRouteDeps = {
39
43
  controlTokens?: ControlTokens;
40
44
  /** Live shutdown controller; set after listen so restore can beginDrain. */
41
45
  getShutdown?: () => import("./shutdown.js").ShutdownController | undefined;
46
+ /**
47
+ * Optional Operator Agent Host for Workshop chat (tests inject the fake host).
48
+ * Distinct from stage-execution AgentPort. Defaults to live Pi Workshop Author
49
+ * (fake remains available via createWorkshopOperatorHost / test injection).
50
+ */
51
+ workshopOperatorHost?: OperatorAgentHost;
52
+ /**
53
+ * Optional durable Workshop chat registry (host sessions keyed by session id).
54
+ * Defaults to a registry owned by this route surface.
55
+ */
56
+ workshopChatRegistry?: WorkshopChatSessionRegistry;
42
57
  };
43
58
  /**
44
59
  * The full operator-console route surface: REST API (runs, stages,