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
@@ -5,7 +5,8 @@ import { globalStageflowHome } from "../project/globalHome.js";
5
5
  import { storeRootFor } from "../runstore/paths.js";
6
6
  export const SETTINGS_FILE_NAME = "settings.json";
7
7
  export const INVALID_SLOT_COUNT_MESSAGE = "maxConcurrent must be an integer >= 1";
8
- export const INVALID_CREDENTIAL_SOURCE_MESSAGE = 'credentialSource must be "pi_home" or "sf_owned"';
8
+ export const INVALID_CREDENTIAL_SOURCE_MESSAGE = 'credentialSource must be "sf_owned"';
9
+ export const INVALID_WORKSHOP_MODEL_MESSAGE = "workshopModel must be a non-empty string";
9
10
  export function globalSettingsFilePath() {
10
11
  return path.join(globalStageflowHome(), SETTINGS_FILE_NAME);
11
12
  }
@@ -28,11 +29,20 @@ export function parseSlotCount(value) {
28
29
  return value;
29
30
  }
30
31
  export function parseCredentialSource(value) {
31
- if (value === "pi_home" || value === "sf_owned") {
32
+ if (value === "sf_owned") {
32
33
  return value;
33
34
  }
35
+ if (value === "pi_home") {
36
+ return "sf_owned";
37
+ }
34
38
  return undefined;
35
39
  }
40
+ export function parseWorkshopModelSetting(value) {
41
+ if (typeof value !== "string")
42
+ return undefined;
43
+ const trimmed = value.trim();
44
+ return trimmed.length > 0 ? trimmed : undefined;
45
+ }
36
46
  function readRawSettingsFromFile(filePath) {
37
47
  if (!existsSync(filePath))
38
48
  return {};
@@ -60,8 +70,50 @@ function parseFactorySettingsFromRaw(raw) {
60
70
  if (credentialSource !== undefined) {
61
71
  settings.credentialSource = credentialSource;
62
72
  }
73
+ const workshopModel = parseWorkshopModelSetting(raw.workshopModel);
74
+ if (workshopModel !== undefined) {
75
+ settings.workshopModel = workshopModel;
76
+ }
63
77
  return settings;
64
78
  }
79
+ function serializeFactorySettings(next) {
80
+ const out = {};
81
+ if (next.maxConcurrent !== undefined) {
82
+ out.maxConcurrent = next.maxConcurrent;
83
+ }
84
+ if (next.credentialSource !== undefined) {
85
+ out.credentialSource = next.credentialSource;
86
+ }
87
+ if (next.workshopModel !== undefined) {
88
+ out.workshopModel = next.workshopModel;
89
+ }
90
+ return out;
91
+ }
92
+ function applyFactorySettingsPatch(current, patch) {
93
+ if (patch.credentialSource !== undefined &&
94
+ parseCredentialSource(patch.credentialSource) === undefined) {
95
+ throw new Error(INVALID_CREDENTIAL_SOURCE_MESSAGE);
96
+ }
97
+ if (patch.maxConcurrent !== undefined &&
98
+ parseSlotCount(patch.maxConcurrent) === undefined) {
99
+ throw new Error(INVALID_SLOT_COUNT_MESSAGE);
100
+ }
101
+ if (patch.workshopModel !== undefined &&
102
+ parseWorkshopModelSetting(patch.workshopModel) === undefined) {
103
+ throw new Error(INVALID_WORKSHOP_MODEL_MESSAGE);
104
+ }
105
+ const next = { ...current };
106
+ if (patch.maxConcurrent !== undefined) {
107
+ next.maxConcurrent = patch.maxConcurrent;
108
+ }
109
+ if (patch.credentialSource !== undefined) {
110
+ next.credentialSource = patch.credentialSource;
111
+ }
112
+ if (patch.workshopModel !== undefined) {
113
+ next.workshopModel = parseWorkshopModelSetting(patch.workshopModel);
114
+ }
115
+ return next;
116
+ }
65
117
  export function readCredentialSourceFromContext(ctx) {
66
118
  if (ctx.isGitProject) {
67
119
  const fromProject = parseCredentialSource(readRawSettingsFromFile(settingsFilePathForContext(ctx)).credentialSource);
@@ -84,56 +136,14 @@ function ensureSettingsDirForContext(ctx) {
84
136
  mkdirSync(path.dirname(settingsFilePathForContext(ctx)), { recursive: true });
85
137
  }
86
138
  export function writeFactorySettingsForContext(ctx, patch) {
87
- if (patch.credentialSource !== undefined &&
88
- parseCredentialSource(patch.credentialSource) === undefined) {
89
- throw new Error(INVALID_CREDENTIAL_SOURCE_MESSAGE);
90
- }
91
- if (patch.maxConcurrent !== undefined &&
92
- parseSlotCount(patch.maxConcurrent) === undefined) {
93
- throw new Error(INVALID_SLOT_COUNT_MESSAGE);
94
- }
95
- const current = readFactorySettingsForContext(ctx);
96
- const next = { ...current };
97
- if (patch.maxConcurrent !== undefined) {
98
- next.maxConcurrent = patch.maxConcurrent;
99
- }
100
- if (patch.credentialSource !== undefined) {
101
- next.credentialSource = patch.credentialSource;
102
- }
103
- const out = {};
104
- if (next.maxConcurrent !== undefined) {
105
- out.maxConcurrent = next.maxConcurrent;
106
- }
107
- if (next.credentialSource !== undefined) {
108
- out.credentialSource = next.credentialSource;
109
- }
139
+ const next = applyFactorySettingsPatch(readFactorySettingsForContext(ctx), patch);
140
+ const out = serializeFactorySettings(next);
110
141
  ensureSettingsDirForContext(ctx);
111
142
  writeFileSync(settingsFilePathForContext(ctx), `${JSON.stringify(out, null, 2)}\n`, "utf8");
112
143
  }
113
144
  export function writeFactorySettings(cwd, patch) {
114
- if (patch.credentialSource !== undefined &&
115
- parseCredentialSource(patch.credentialSource) === undefined) {
116
- throw new Error(INVALID_CREDENTIAL_SOURCE_MESSAGE);
117
- }
118
- if (patch.maxConcurrent !== undefined &&
119
- parseSlotCount(patch.maxConcurrent) === undefined) {
120
- throw new Error(INVALID_SLOT_COUNT_MESSAGE);
121
- }
122
- const current = readFactorySettings(cwd);
123
- const next = { ...current };
124
- if (patch.maxConcurrent !== undefined) {
125
- next.maxConcurrent = patch.maxConcurrent;
126
- }
127
- if (patch.credentialSource !== undefined) {
128
- next.credentialSource = patch.credentialSource;
129
- }
130
- const out = {};
131
- if (next.maxConcurrent !== undefined) {
132
- out.maxConcurrent = next.maxConcurrent;
133
- }
134
- if (next.credentialSource !== undefined) {
135
- out.credentialSource = next.credentialSource;
136
- }
145
+ const next = applyFactorySettingsPatch(readFactorySettings(cwd), patch);
146
+ const out = serializeFactorySettings(next);
137
147
  const root = path.resolve(cwd);
138
148
  mkdirSync(storeRootFor(root), { recursive: true });
139
149
  writeFileSync(settingsFilePath(cwd), `${JSON.stringify(out, null, 2)}\n`, "utf8");
@@ -158,17 +168,17 @@ export function writeMaxConcurrentToGlobal(maxConcurrent) {
158
168
  throw new Error(INVALID_SLOT_COUNT_MESSAGE);
159
169
  }
160
170
  const current = parseFactorySettingsFromRaw(readRawSettingsFromFile(globalSettingsFilePath()));
161
- const next = { ...current, maxConcurrent };
162
- const out = {};
163
- if (next.maxConcurrent !== undefined) {
164
- out.maxConcurrent = next.maxConcurrent;
165
- }
166
- if (next.credentialSource !== undefined) {
167
- out.credentialSource = next.credentialSource;
168
- }
171
+ const next = applyFactorySettingsPatch(current, { maxConcurrent });
172
+ const out = serializeFactorySettings(next);
169
173
  mkdirSync(path.dirname(globalSettingsFilePath()), { recursive: true });
170
174
  writeFileSync(globalSettingsFilePath(), `${JSON.stringify(out, null, 2)}\n`, "utf8");
171
175
  }
176
+ export function readWorkshopModelFromFile(cwd) {
177
+ return readFactorySettings(cwd).workshopModel;
178
+ }
179
+ export function writeWorkshopModelToFile(cwd, workshopModel) {
180
+ writeFactorySettings(cwd, { workshopModel });
181
+ }
172
182
  export function readCredentialSourceFromFile(cwd) {
173
183
  return readFactorySettings(cwd).credentialSource;
174
184
  }
@@ -1,4 +1,7 @@
1
1
  import { bindingKindForOrigin, catalogOrSeededOrigin, decideWorkspaceConfigTrust, } from "../config/configOrigin.js";
2
+ import { loginCheckPromptBlock, readStageLoginCheck, } from "../browser/loginCheck.js";
3
+ import { humanLoginPromptBlock, isHumanLoginStage } from "../browser/humanLogin.js";
4
+ import { BROWSER_SKILL_NAME, resolveBuiltinSkillFile, } from "../config/builtinSkills.js";
2
5
  import { resolveSkillByName, } from "../config/listSkills.js";
3
6
  import { loadHostConfig } from "../config/hostConfig.js";
4
7
  import { defaultSeededRoots } from "../config/seededCatalog.js";
@@ -7,6 +10,7 @@ import { mkdir } from "node:fs/promises";
7
10
  import { runSkillsDir } from "./runSkills.js";
8
11
  import { MCP_CATALOG_FILENAME, STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV, StageMcpError, mcpCatalogExists, mcpCatalogPath, resolveStageMcpServers, stampStagePromptArtifactsDir, } from "../config/resolveStageMcpServers.js";
9
12
  import { attemptArtifactsDir, attemptStreamLogPath } from "../runstore/workspaceLayout.js";
13
+ import { redactBrowserActivityEvent, redactBrowserSecrets, } from "../agent/streamLogRedact.js";
10
14
  import { createStageStreamLogWriter, } from "./stageStreamLog.js";
11
15
  import { resolveCloneEmitContext, resolveForkEmitContext } from "../config/resolveForkEmitContext.js";
12
16
  import { createAttemptQaTrailReader } from "../hitl/qaTrail.js";
@@ -68,6 +72,17 @@ async function resolveStageSkillForRun(stage, catalog, options) {
68
72
  skillOrigin: resolved.origin,
69
73
  };
70
74
  }
75
+ /** Browser stages get the `browser` skill (run > checkout > host > built-in). A missing skill is not an error. */
76
+ async function resolveBrowserSkillFile(stage, catalog, options) {
77
+ if (stage.browser === undefined || stage.skill === BROWSER_SKILL_NAME) {
78
+ return undefined;
79
+ }
80
+ const resolved = await resolveStageSkillForRun({ skill: BROWSER_SKILL_NAME }, catalog, options);
81
+ if (resolved.ok && resolved.skillFilePath !== undefined) {
82
+ return resolved.skillFilePath;
83
+ }
84
+ return resolveBuiltinSkillFile(BROWSER_SKILL_NAME);
85
+ }
71
86
  async function resolveAttemptMcpServers(allowlist, factoryCwd, artifactsDir, options) {
72
87
  const names = allowlist ?? [];
73
88
  if (names.length === 0)
@@ -151,6 +166,13 @@ async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, a
151
166
  });
152
167
  if (!skill.ok)
153
168
  return skill;
169
+ const browserSkillFilePath = await resolveBrowserSkillFile(input.stage, catalog, {
170
+ bindingKind: resolveOptions.bindingKind,
171
+ checkoutRoot: resolveOptions.checkoutRoot,
172
+ factoryCwd,
173
+ trustWorkspaceConfig: resolveOptions.trustWorkspaceConfig,
174
+ workspaceDir: resolveOptions.workspaceDir,
175
+ });
154
176
  let resolvedMcpServers;
155
177
  const origins = [];
156
178
  try {
@@ -207,16 +229,41 @@ async function openStageWithOperatorCatalog(agent, input, catalog, factoryCwd, a
207
229
  })).catch(() => null);
208
230
  }
209
231
  }
232
+ let loginCheck;
233
+ const humanLogin = isHumanLoginStage(resolveOptions.dag, input.stage.id, input.stage.browser);
234
+ if (input.stage.browser?.check !== undefined && !humanLogin) {
235
+ loginCheck = await readStageLoginCheck(resolveOptions.workspaceDir, stageIdForManifest);
236
+ if (loginCheck === undefined) {
237
+ return { ok: false, reason: "browser login check result is missing" };
238
+ }
239
+ }
210
240
  try {
211
241
  const handle = agent.openStage({
212
242
  ...input,
213
243
  stage: {
214
244
  ...input.stage,
215
- system_prompt: stampStagePromptArtifactsDir(input.stage.system_prompt, artifactsDir),
245
+ system_prompt: `${stampStagePromptArtifactsDir(input.stage.system_prompt, artifactsDir)}${loginCheck !== undefined ? `\n\n${loginCheckPromptBlock(loginCheck)}` : ""}${humanLogin ? `\n\n${humanLoginPromptBlock(input.stage.browser?.login_url ?? input.stage.browser?.check?.url)}` : ""}`,
246
+ ...(loginCheck !== undefined
247
+ ? {
248
+ pre_emit_checks: [
249
+ ...(input.stage.pre_emit_checks ?? []),
250
+ {
251
+ id: "browser_login_check",
252
+ type: "browser_login_check",
253
+ state: loginCheck.state,
254
+ logged_in: loginCheck.logged_in,
255
+ url: loginCheck.url,
256
+ },
257
+ ],
258
+ }
259
+ : {}),
216
260
  },
217
261
  ...(skill.skillFilePath !== undefined
218
262
  ? { skillFilePath: skill.skillFilePath }
219
263
  : {}),
264
+ ...(browserSkillFilePath !== undefined
265
+ ? { browserSkillFilePath }
266
+ : {}),
220
267
  ...(resolvedMcpServers !== undefined ? { resolvedMcpServers } : {}),
221
268
  onResolvedModel: async (info) => {
222
269
  await patchRunManifest(resolveOptions.store, resolveOptions.runId, (m) => withStageResolvedModel(m, info.stageId, {
@@ -357,7 +404,10 @@ export async function openStageAttempt(input) {
357
404
  return priorInput;
358
405
  const roots = resolveAttemptRoots(input, stageId);
359
406
  const attempt = input.attemptCtx?.attempt ?? 1;
360
- const streamWriter = (input.streamLogWriterFactory ?? createStageStreamLogWriter)(attemptStreamLogPath(input.workspaceDir, stageId, attempt));
407
+ const streamLogPath = attemptStreamLogPath(input.workspaceDir, stageId, attempt);
408
+ const streamWriter = input.streamLogWriterFactory
409
+ ? input.streamLogWriterFactory(streamLogPath)
410
+ : createStageStreamLogWriter(streamLogPath, input.stage.browser !== undefined ? { redact: redactBrowserSecrets } : {});
361
411
  const resumeToken = input.resumeToken ??
362
412
  resumeSessionFilePath(input.workspaceDir, stageId, attempt);
363
413
  const forkEmitContext = resolveForkEmitContext(input.dag, definitionId);
@@ -394,7 +444,9 @@ export async function openStageAttempt(input) {
394
444
  resumeToken,
395
445
  ...(input.sessionMode !== undefined ? { sessionMode: input.sessionMode } : {}),
396
446
  onActivity: (event) => {
397
- input.onActivity?.(event);
447
+ input.onActivity?.(input.stage.browser !== undefined
448
+ ? redactBrowserActivityEvent(event)
449
+ : event);
398
450
  void streamWriter.flush();
399
451
  },
400
452
  onAssistantTextDelta: streamWriter.onDelta,
@@ -25,6 +25,7 @@ export const STAGE_ENV_ALLOWLIST = [
25
25
  "https_proxy",
26
26
  "no_proxy",
27
27
  "STAGEFLOW_HOME",
28
+ "STAGEFLOW_CURSOR_EXTENSION",
28
29
  ];
29
30
  const PROVIDER_AND_CLOUD_NAMES = [
30
31
  "ANTHROPIC_API_KEY",
@@ -4,6 +4,7 @@ import { type StageAttemptContext } from "./stageAttemptContext.js";
4
4
  import { type AskOperatorPrompt } from "../tools/askOperator.js";
5
5
  export type DeliverAnswerResult = {
6
6
  ok: true;
7
+ verification?: "failed_retrying";
7
8
  } | {
8
9
  ok: false;
9
10
  reason: string;
@@ -1,3 +1,4 @@
1
+ import { parseHostGateContext } from "../browser/gateHandoff.js";
1
2
  import { appendOperatorAnswer, appendOperatorPrompt, derivePendingPrompt, } from "../hitl/qaTrail.js";
2
3
  import { deriveExecutionPatchFromEvent } from "../runstore/stageExecution.js";
3
4
  import { attemptContext } from "./stageAttemptContext.js";
@@ -7,7 +8,10 @@ export function tryParsePendingPrompt(pendingRequest) {
7
8
  return undefined;
8
9
  }
9
10
  try {
10
- return normalizePromptIds(parseAskOperatorParams(pendingRequest));
11
+ return {
12
+ ...normalizePromptIds(parseAskOperatorParams(pendingRequest)),
13
+ ...parseHostGateContext(pendingRequest),
14
+ };
11
15
  }
12
16
  catch {
13
17
  return undefined;
@@ -17,6 +17,8 @@ export type StageLaunchInput = {
17
17
  bindingKind?: DerivedBindingKind;
18
18
  grants?: ResolvedStageGrants;
19
19
  attemptHome?: string;
20
+ browserEnv?: Record<string, string>;
21
+ model?: string;
20
22
  };
21
23
  export type StageLaunchResult = {
22
24
  type: "succeeded";
@@ -45,6 +47,7 @@ export declare class StageProcessLauncher {
45
47
  private readonly env;
46
48
  private readonly explicitChildExtras;
47
49
  private readonly cliEntry;
50
+ private readonly workerExecArgv;
48
51
  private readonly logger;
49
52
  private readonly forkFn;
50
53
  private readonly active;
@@ -1,7 +1,10 @@
1
1
  import * as childProcess from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { logger as rootLogger, resolveLogMaxLineBytes, } from "../logging/logger.js";
4
+ import { isCursorModelRef, readCursorApiKey } from "../agent/cursorProvider.js";
5
+ import { BROWSER_ENV_PREFIX } from "../browser/browserHost.js";
4
6
  import { PACKAGE_VERSION } from "../package-meta.js";
7
+ import { PI_HOME_AUTH_PATH_ENV, stageflowAgentAuthPath } from "./credentialBinding.js";
5
8
  import { redactString } from "../logging/redact.js";
6
9
  import { getNamedSecrets } from "../logging/namedSecrets.js";
7
10
  import { readMaxActiveStageProcesses, } from "./stageConcurrency.js";
@@ -10,6 +13,28 @@ import { buildStageEnvironment, isAmbientBlockedEnv, isForeverDeniedSecret, } fr
10
13
  import { ensureStageCacheDirs } from "./stageCacheEnv.js";
11
14
  import { SF_STAGE_WORKER, STAGE_WORKER_EXIT, } from "./stageWorkerProtocol.js";
12
15
  import { overlayStageBindingEnv, } from "./stageRoots.js";
16
+ function defaultStageCliEntry() {
17
+ const source = import.meta.url.split("?")[0]?.endsWith(".ts") === true;
18
+ return fileURLToPath(new URL(source ? "../cli.ts" : "../cli.js", import.meta.url));
19
+ }
20
+ function isTypeScriptEntry(entry) {
21
+ return /\.(?:m|c)?ts$/.test(entry);
22
+ }
23
+ /**
24
+ * A compiled `dist/cli.js` child is plain node. `npm run dev` is TypeScript,
25
+ * and this launcher sets execArgv explicitly so the parent's tsx loader is
26
+ * not inherited. Load that entry with the same tsx hooks the dev CLI uses.
27
+ */
28
+ function tsxLoaderExecArgv() {
29
+ try {
30
+ const preflight = fileURLToPath(import.meta.resolve("tsx/preflight"));
31
+ const loader = import.meta.resolve("tsx");
32
+ return ["--require", preflight, "--import", loader];
33
+ }
34
+ catch {
35
+ return [];
36
+ }
37
+ }
13
38
  function flushCappedPartial(log, event, text, maxLineBytes) {
14
39
  let remaining = redactString(text, { namedSecrets: getNamedSecrets() });
15
40
  while (Buffer.byteLength(remaining, "utf8") > maxLineBytes) {
@@ -109,6 +134,7 @@ export class StageProcessLauncher {
109
134
  env;
110
135
  explicitChildExtras;
111
136
  cliEntry;
137
+ workerExecArgv;
112
138
  logger;
113
139
  forkFn;
114
140
  active = new Map();
@@ -126,9 +152,11 @@ export class StageProcessLauncher {
126
152
  : undefined;
127
153
  this.maxActive = readMaxActiveStageProcesses(this.env, options.maxActiveStageProcesses);
128
154
  this.heapMb = getContainerLimits().maxOldSpaceSizeMb;
129
- this.cliEntry =
130
- options.cliEntry ??
131
- fileURLToPath(new URL("../cli.js", import.meta.url));
155
+ this.cliEntry = options.cliEntry ?? defaultStageCliEntry();
156
+ this.workerExecArgv = [
157
+ `--max-old-space-size=${this.heapMb}`,
158
+ ...(isTypeScriptEntry(this.cliEntry) ? tsxLoaderExecArgv() : []),
159
+ ];
132
160
  this.logger =
133
161
  options.logger ?? rootLogger.child({ component: "runtime" });
134
162
  this.forkFn = options.forkFn ?? childProcess.fork;
@@ -290,6 +318,7 @@ export class StageProcessLauncher {
290
318
  cacheVars: ensureStageCacheDirs(),
291
319
  grants: input.grants,
292
320
  attemptHome: input.attemptHome,
321
+ runVars: input.browserEnv,
293
322
  packageVersion: PACKAGE_VERSION,
294
323
  });
295
324
  for (const warning of built.warnings) {
@@ -301,14 +330,32 @@ export class StageProcessLauncher {
301
330
  const withExtras = this.explicitChildExtras !== undefined
302
331
  ? { ...built.env, ...this.explicitChildExtras }
303
332
  : built.env;
304
- const childEnv = overlayStageBindingEnv(withExtras, input.env ?? {}, input.bindingKind ?? "unbound");
333
+ const overlaid = overlayStageBindingEnv(withExtras, input.env ?? {}, input.bindingKind ?? "unbound");
334
+ const withBrowser = input.browserEnv !== undefined
335
+ ? {
336
+ ...Object.fromEntries(Object.entries(overlaid).filter(([name]) => !name.startsWith(BROWSER_ENV_PREFIX))),
337
+ ...input.browserEnv,
338
+ }
339
+ : overlaid;
340
+ const childEnv = {
341
+ ...withBrowser,
342
+ [PI_HOME_AUTH_PATH_ENV]: stageflowAgentAuthPath(),
343
+ };
344
+ const cursorApiKey = readCursorApiKey(hostEnv);
345
+ if (isCursorModelRef(input.model ?? "") &&
346
+ cursorApiKey !== undefined &&
347
+ cursorApiKey !== "" &&
348
+ childEnv.CURSOR_API_KEY === undefined) {
349
+ childEnv.CURSOR_API_KEY = cursorApiKey;
350
+ }
305
351
  const child = this.forkFn(this.cliEntry, args, {
306
352
  cwd: input.rootDir,
307
353
  env: { ...childEnv, [SF_STAGE_WORKER]: "1" },
308
354
  stdio: ["pipe", "pipe", "pipe", "ipc"],
309
355
  detached: true,
310
356
  // Explicit: do not inherit Host execArgv; set heap from cgroup budget.
311
- execArgv: [`--max-old-space-size=${this.heapMb}`],
357
+ // TypeScript entries also get the tsx loader, since that inheritance is off.
358
+ execArgv: this.workerExecArgv,
312
359
  });
313
360
  const key = activeKey(input.runId, input.stageId);
314
361
  const tracked = {
@@ -1,4 +1,6 @@
1
1
  import type { AgentPort, FeedbackLoopContext, StageHandle, StageRunResult, StageSessionMode } from "../agent/port.js";
2
+ import { type HostGateContext } from "../browser/gateHandoff.js";
3
+ import type { StageBrowserSupport } from "../browser/browserHost.js";
2
4
  import type { StageEnvelope } from "../types/envelope.js";
3
5
  import type { LoadedStageConfig } from "../types/stage.js";
4
6
  import type { TaskFile } from "../types/task.js";
@@ -49,6 +51,8 @@ export type RunStageOptions = {
49
51
  feedbackLoopContext?: FeedbackLoopContext;
50
52
  resumeToken?: string;
51
53
  stageEnv?: Record<string, string>;
54
+ /** Test seam for the Host login check run by a `browser_login` verify item. */
55
+ browser?: Pick<StageBrowserSupport, "runner" | "loginCheck">;
52
56
  };
53
57
  /**
54
58
  * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
@@ -62,5 +66,6 @@ export declare function runStageYieldLoop(options: {
62
66
  store?: RunStore;
63
67
  attemptCtx?: StageAttemptContext;
64
68
  skipGates?: boolean;
69
+ gateContext?: HostGateContext;
65
70
  }): Promise<RunStageYieldLoopResult>;
66
71
  export declare function runStage(options: RunStageOptions): Promise<RunStageOutcome>;
@@ -1,8 +1,11 @@
1
+ import { hostGateContextFor, stampGateRequest, } from "../browser/gateHandoff.js";
1
2
  import { deriveExecutionPatchFromEvent } from "../runstore/stageExecution.js";
2
3
  import { buildStageRoots, withResolvedAuthPath, } from "./stageRoots.js";
3
4
  import { createDefaultHitlQaHooks, recordWaitEnter, } from "./stageHitl.js";
4
5
  import { openStageAttempt, } from "./stageAttemptBootstrap.js";
5
6
  import { createVerifiedStageExecution } from "./verifiedStageExecution.js";
7
+ import { BROWSER_STAGE_PATTERNS, redact } from "../logging/redact.js";
8
+ import { getNamedSecrets } from "../logging/namedSecrets.js";
6
9
  import { isStageTimeoutReason } from "../agent/stageTimeout.js";
7
10
  export function isRunStageWaiting(outcome) {
8
11
  return "waiting" in outcome && outcome.waiting === true;
@@ -44,11 +47,16 @@ async function appendLifecycleEventAndPatchExecution(options) {
44
47
  }
45
48
  }
46
49
  async function finalizeStageResult(options) {
47
- const { store, runId, stageId, result, activityChain, attemptCtx } = options;
50
+ const { store, runId, stageId, result, activityChain, attemptCtx, browser } = options;
48
51
  const attemptOpt = attemptCtx?.eventOptions();
49
52
  await activityChain;
50
53
  if (result.envelope) {
51
- await store.writeEnvelope(runId, stageId, result.envelope, attemptOpt);
54
+ await store.writeEnvelope(runId, stageId, browser
55
+ ? redact(result.envelope, {
56
+ patterns: BROWSER_STAGE_PATTERNS,
57
+ namedSecrets: getNamedSecrets(),
58
+ })
59
+ : result.envelope, attemptOpt);
52
60
  }
53
61
  if (!result.ok) {
54
62
  await appendLifecycleEventAndPatchExecution({
@@ -75,10 +83,11 @@ async function finalizeStageResult(options) {
75
83
  * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
76
84
  */
77
85
  export async function runStageYieldLoop(options) {
78
- const { handle, runId, stageId, hitl, workerMode, store, attemptCtx, skipGates } = options;
86
+ const { handle, runId, stageId, hitl, workerMode, store, attemptCtx, skipGates, gateContext } = options;
79
87
  while (true) {
80
88
  const event = await handle.next();
81
89
  if (event.status === "waiting_for_input") {
90
+ const request = stampGateRequest(event.request, gateContext);
82
91
  if (skipGates) {
83
92
  return {
84
93
  ok: false,
@@ -96,7 +105,7 @@ export async function runStageYieldLoop(options) {
96
105
  store,
97
106
  runId,
98
107
  stageId,
99
- request: event.request,
108
+ request,
100
109
  attemptCtx,
101
110
  qaHooks: createDefaultHitlQaHooks(store),
102
111
  });
@@ -108,14 +117,14 @@ export async function runStageYieldLoop(options) {
108
117
  reason: "stage requested wait but no HITL controller is configured",
109
118
  };
110
119
  }
111
- await hitl.enterWait(runId, stageId, handle, event.request, attemptCtx);
120
+ await hitl.enterWait(runId, stageId, handle, request, attemptCtx);
112
121
  continue;
113
122
  }
114
123
  return event.result;
115
124
  }
116
125
  }
117
126
  export async function runStage(options) {
118
- const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, skipGates, sessionMode, feedbackLoopContext, resumeToken, stageEnv, } = options;
127
+ const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, skipGates, sessionMode, feedbackLoopContext, resumeToken, stageEnv, browser: browserSeams, } = options;
119
128
  const stageId = options.stageId ?? stage.id;
120
129
  const attemptOpt = attemptCtx?.eventOptions();
121
130
  const baseRoots = rootsOverride ??
@@ -144,6 +153,19 @@ export async function runStage(options) {
144
153
  dag,
145
154
  roots,
146
155
  commandEnv: stageEnv ?? process.env,
156
+ ...(stage.browser?.check !== undefined
157
+ ? {
158
+ browserLogin: {
159
+ check: stage.browser.check,
160
+ ...(browserSeams?.runner !== undefined
161
+ ? { runner: browserSeams.runner }
162
+ : {}),
163
+ ...(browserSeams?.loginCheck !== undefined
164
+ ? { options: browserSeams.loginCheck }
165
+ : {}),
166
+ },
167
+ }
168
+ : {}),
147
169
  });
148
170
  await verifiedExecution.prepare();
149
171
  console.error(`Running stage ${stageId} (${stage.model})...`);
@@ -209,6 +231,7 @@ export async function runStage(options) {
209
231
  store: workerMode ? store : undefined,
210
232
  attemptCtx,
211
233
  skipGates,
234
+ gateContext: hostGateContextFor(stage.browser),
212
235
  });
213
236
  }
214
237
  catch (err) {
@@ -253,6 +276,7 @@ export async function runStage(options) {
253
276
  result,
254
277
  activityChain,
255
278
  attemptCtx,
279
+ browser: stage.browser !== undefined,
256
280
  });
257
281
  }
258
282
  finally {
@@ -14,4 +14,5 @@ export type StageStreamLogWriter = {
14
14
  export declare function createStageStreamLogWriter(streamLogPath: string, options?: {
15
15
  flushThrottleMs?: number;
16
16
  capBytes?: number;
17
+ redact?: (text: string) => string;
17
18
  }): StageStreamLogWriter;
@@ -54,6 +54,7 @@ async function appendChunk(streamLogPath, chunk, capBytes) {
54
54
  export function createStageStreamLogWriter(streamLogPath, options = {}) {
55
55
  const flushThrottleMs = options.flushThrottleMs ?? STREAM_FLUSH_THROTTLE_MS;
56
56
  const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
57
+ const scrub = options.redact ?? redactSecrets;
57
58
  let buffer = "";
58
59
  let timer = null;
59
60
  // Serializes disk writes so the throttle timer and an eager flush() never race.
@@ -65,7 +66,7 @@ export function createStageStreamLogWriter(streamLogPath, options = {}) {
65
66
  }
66
67
  if (!buffer)
67
68
  return writeChain;
68
- const chunk = redactSecrets(buffer);
69
+ const chunk = scrub(buffer);
69
70
  buffer = "";
70
71
  writeChain = writeChain.then(() => appendChunk(streamLogPath, chunk, capBytes));
71
72
  return writeChain;
@@ -0,0 +1,6 @@
1
+ import type { TriggerFile } from "../types/trigger.js";
2
+ export declare function findCatalogPathById(paths: string[], id: string): Promise<string | undefined>;
3
+ export declare function findTriggerDefinition(triggerPaths: string[], triggerId: string): Promise<{
4
+ path: string;
5
+ definition: TriggerFile;
6
+ } | undefined>;
@@ -0,0 +1,24 @@
1
+ import { readYamlObject } from "../config/readYamlObject.js";
2
+ import { loadTriggerOutcome } from "../config/loadTrigger.js";
3
+ export async function findCatalogPathById(paths, id) {
4
+ for (const filePath of paths) {
5
+ try {
6
+ const raw = await readYamlObject(filePath);
7
+ if (raw?.id === id)
8
+ return filePath;
9
+ }
10
+ catch {
11
+ // Unreadable/invalid catalog files are surfaced by `sf validate`; skip here.
12
+ }
13
+ }
14
+ return undefined;
15
+ }
16
+ export async function findTriggerDefinition(triggerPaths, triggerId) {
17
+ for (const filePath of triggerPaths) {
18
+ const outcome = await loadTriggerOutcome(filePath);
19
+ if (outcome.ok && outcome.value.id === triggerId) {
20
+ return { path: filePath, definition: outcome.value };
21
+ }
22
+ }
23
+ return undefined;
24
+ }
@@ -0,0 +1,2 @@
1
+ /** Subset-match: every key/value in `match` must equal-match `payload`. No `match` matches everything. */
2
+ export declare function matchesEventFilter(match: Record<string, unknown> | undefined, payload: Record<string, unknown>): boolean;
@@ -0,0 +1,6 @@
1
+ /** Subset-match: every key/value in `match` must equal-match `payload`. No `match` matches everything. */
2
+ export function matchesEventFilter(match, payload) {
3
+ if (!match)
4
+ return true;
5
+ return Object.entries(match).every(([key, value]) => payload[key] === value);
6
+ }
@@ -0,0 +1,14 @@
1
+ import type { TaskFile } from "../types/task.js";
2
+ export type TriggerFireEvent = {
3
+ triggerId: string;
4
+ task?: TaskFile;
5
+ };
6
+ /**
7
+ * Abstract seam for anything that decides "fire trigger X now" — a croner-backed
8
+ * schedule loop, a GitHub/email poller, a webhook receiver. No adapter implements
9
+ * this yet; it exists so those later adapters build against a locked contract.
10
+ */
11
+ export interface TriggerSourcePort {
12
+ start(onFire: (event: TriggerFireEvent) => Promise<void>): Promise<void>;
13
+ stop(): Promise<void>;
14
+ }
@@ -0,0 +1 @@
1
+ export {};