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
@@ -0,0 +1,472 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { stat } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { createInterface } from "node:readline";
6
+ import { localAuditLogPath } from "../browser/auditSink.js";
7
+ import { closeBrowserSession, defaultBrowserRunner } from "../browser/browserTeardown.js";
8
+ import { defaultDisplayProbe, noScreenError } from "../browser/humanLogin.js";
9
+ import { createLocalBrowserHost } from "../browser/localBrowserHost.js";
10
+ import { createLocalProfileLock } from "../browser/localProfileLock.js";
11
+ import { createLocalProfileStore } from "../browser/localProfileStore.js";
12
+ import { matchLoginState, runLoginCheck } from "../browser/loginCheck.js";
13
+ // TODO(multi-tenant): every `sf browser` command below uses the fixed local scope.
14
+ // In a hosted service the scope must come from the signed-in user. Search for LOCAL_BROWSER_SCOPE in this file.
15
+ import { InvalidProfileKeyError, LOCAL_BROWSER_SCOPE, validateProfileName, } from "../browser/profileStore.js";
16
+ import { cliRunLive, createRunLiveness } from "../browser/runLiveness.js";
17
+ import { loadHostConfig } from "../config/hostConfig.js";
18
+ import { BlockedSiteError, hostOf, isBlockedHost } from "../browser/sitePolicy.js";
19
+ import { globalStageflowHome } from "../project/globalHome.js";
20
+ import { createRunStore, storeNeedsHostMigration } from "../runstore/createStore.js";
21
+ import { resolveStoreRoot } from "../runstore/paths.js";
22
+ export const BROWSER_USAGE = `Usage:
23
+ sf browser profiles [--json]
24
+ sf browser status <name> [--json]
25
+ sf browser check <name> --url <url> --logged-in <glob> [--logged-out <glob>]... [--headless] [--json]
26
+ sf browser login <name> --url <login-url> --logged-in <glob> [--timeout-sec <n>] [--json]
27
+ sf browser clear <name> [--yes] [--json]
28
+
29
+ Manage saved browser logins (profiles) on this computer. Output never shows profile paths or cookie values.
30
+ Exit codes: 0 ok / logged in; 1 error; 2 profile in use by a live run; 3 logged out; 4 unknown; 5 login not completed (timeout or window closed); 130 interrupted.`;
31
+ export const BROWSER_EXIT = {
32
+ ok: 0,
33
+ error: 1,
34
+ busy: 2,
35
+ loggedOut: 3,
36
+ unknown: 4,
37
+ notCompleted: 5,
38
+ interrupted: 130,
39
+ };
40
+ const DEFAULT_LOGIN_TIMEOUT_SEC = 300;
41
+ const LOGIN_POLL_MS = 1000;
42
+ const LOGIN_CLOSED_AFTER_FAILED_READS = 3;
43
+ const STAGE_ID = "browser-cli";
44
+ class CliError extends Error {
45
+ code;
46
+ exit;
47
+ constructor(message, code, exit) {
48
+ super(message);
49
+ this.code = code;
50
+ this.exit = exit;
51
+ }
52
+ }
53
+ function defaultIsRunLive() {
54
+ let inner;
55
+ return async (runId) => {
56
+ const cli = cliRunLive(runId);
57
+ if (cli !== undefined)
58
+ return cli;
59
+ try {
60
+ const home = globalStageflowHome();
61
+ if (!existsSync(path.join(resolveStoreRoot(home), "state.db")))
62
+ return false;
63
+ if (storeNeedsHostMigration(home))
64
+ return true;
65
+ inner ??= createRunLiveness(createRunStore({ rootDir: home, openerMode: "assert" }));
66
+ return await inner(runId);
67
+ }
68
+ catch {
69
+ return true;
70
+ }
71
+ };
72
+ }
73
+ function defaultSessionOpen(env) {
74
+ const dir = env.AGENT_BROWSER_SOCKET_DIR;
75
+ const session = env.AGENT_BROWSER_SESSION;
76
+ if (!dir || !session)
77
+ return false;
78
+ if (existsSync(path.join(dir, `${session}.sock`)))
79
+ return true;
80
+ try {
81
+ const pid = Number.parseInt(readFileSync(path.join(dir, `${session}.pid`), "utf8").trim(), 10);
82
+ if (!Number.isInteger(pid) || pid <= 0)
83
+ return false;
84
+ process.kill(pid, 0);
85
+ return true;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ async function defaultAuditLastUsed() {
92
+ const out = {};
93
+ let text;
94
+ try {
95
+ text = readFileSync(localAuditLogPath(), "utf8");
96
+ }
97
+ catch {
98
+ return out;
99
+ }
100
+ for (const line of text.split("\n")) {
101
+ if (line.length === 0)
102
+ continue;
103
+ try {
104
+ const rec = JSON.parse(line);
105
+ if (rec.event === "profile_used" &&
106
+ rec.scope === LOCAL_BROWSER_SCOPE &&
107
+ typeof rec.profile === "string" &&
108
+ typeof rec.at === "string" &&
109
+ (out[rec.profile] === undefined || rec.at > out[rec.profile])) {
110
+ out[rec.profile] = rec.at;
111
+ }
112
+ }
113
+ catch {
114
+ // skip bad line
115
+ }
116
+ }
117
+ return out;
118
+ }
119
+ async function defaultConfirm(question) {
120
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
121
+ try {
122
+ const answer = await new Promise((resolve) => rl.question(`${question} [y/N] `, resolve));
123
+ return /^y(es)?$/i.test(answer.trim());
124
+ }
125
+ finally {
126
+ rl.close();
127
+ }
128
+ }
129
+ function defaultDeps() {
130
+ const isRunLive = defaultIsRunLive();
131
+ return {
132
+ log: (line) => console.log(line),
133
+ error: (line) => console.error(line),
134
+ store: createLocalProfileStore(),
135
+ host: createLocalBrowserHost(),
136
+ locks: createLocalProfileLock({ isRunLive }),
137
+ runner: defaultBrowserRunner,
138
+ display: () => defaultDisplayProbe(),
139
+ isRunLive,
140
+ sessionOpen: defaultSessionOpen,
141
+ auditLastUsed: defaultAuditLastUsed,
142
+ confirm: defaultConfirm,
143
+ interactive: Boolean(process.stdin.isTTY),
144
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
145
+ };
146
+ }
147
+ function parse(args) {
148
+ const out = { help: false, json: false, loggedOut: [], headless: false, yes: false };
149
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
150
+ out.help = true;
151
+ return out;
152
+ }
153
+ out.sub = args[0];
154
+ if (!["profiles", "status", "check", "login", "clear"].includes(out.sub)) {
155
+ throw new Error(`Unknown browser subcommand: ${out.sub}`);
156
+ }
157
+ const value = (i, flag) => {
158
+ const v = args[i];
159
+ if (v === undefined || v.length === 0 || v.startsWith("--")) {
160
+ throw new Error(`Missing value for ${flag}`);
161
+ }
162
+ return v;
163
+ };
164
+ for (let i = 1; i < args.length; i++) {
165
+ const a = args[i];
166
+ if (a === "--help" || a === "-h")
167
+ out.help = true;
168
+ else if (a === "--json")
169
+ out.json = true;
170
+ else if (a === "--headless")
171
+ out.headless = true;
172
+ else if (a === "--yes" || a === "-y")
173
+ out.yes = true;
174
+ else if (a === "--url")
175
+ out.url = value(++i, a);
176
+ else if (a === "--logged-in")
177
+ out.loggedIn = value(++i, a);
178
+ else if (a === "--logged-out")
179
+ out.loggedOut.push(value(++i, a));
180
+ else if (a === "--timeout-sec") {
181
+ const n = Number(value(++i, a));
182
+ if (!Number.isFinite(n) || n <= 0)
183
+ throw new Error(`Invalid --timeout-sec: ${args[i]}`);
184
+ out.timeoutSec = n;
185
+ }
186
+ else if (a.startsWith("-"))
187
+ throw new Error(`Unknown flag: ${a}`);
188
+ else if (out.name === undefined && out.sub !== "profiles")
189
+ out.name = a;
190
+ else
191
+ throw new Error(`Unexpected argument: ${a}`);
192
+ }
193
+ return out;
194
+ }
195
+ function requireName(p) {
196
+ if (p.name === undefined)
197
+ throw new Error(`sf browser ${p.sub} needs a profile name`);
198
+ return validateProfileName(p.name);
199
+ }
200
+ function newOwner() {
201
+ return { runId: `cli-${process.pid}-${randomBytes(4).toString("hex")}`, stageId: STAGE_ID };
202
+ }
203
+ async function withProfile(deps, name, fn, options) {
204
+ const names = await deps.store.list(LOCAL_BROWSER_SCOPE);
205
+ if (!options.create && !names.includes(name)) {
206
+ throw new CliError(`Profile "${name}" does not exist. Create it with sf browser login.`, "not_found", BROWSER_EXIT.error);
207
+ }
208
+ const owner = newOwner();
209
+ const got = await deps.locks.acquire({ scope: LOCAL_BROWSER_SCOPE, name }, owner);
210
+ if (got.status === "queued") {
211
+ throw new CliError(`Profile "${name}" is in use by run ${got.holder.runId}${got.holder.stageId !== undefined ? ` stage ${got.holder.stageId}` : ""}. Try again when it finishes.`, "profile_busy", BROWSER_EXIT.busy);
212
+ }
213
+ try {
214
+ const handle = await deps.store.open({ scope: LOCAL_BROWSER_SCOPE, name });
215
+ return await fn(handle);
216
+ }
217
+ finally {
218
+ await got.release().catch(() => undefined);
219
+ }
220
+ }
221
+ async function envFor(deps, handle, browser, humanLogin = false) {
222
+ return deps.host.profileBrowserEnv({
223
+ runId: newOwner().runId,
224
+ browser,
225
+ profile: handle,
226
+ ...(humanLogin ? { humanLogin: true } : {}),
227
+ });
228
+ }
229
+ function emit(deps, json, doc, text) {
230
+ if (json)
231
+ deps.log(JSON.stringify(doc, null, 2));
232
+ else
233
+ for (const line of text)
234
+ deps.log(line);
235
+ }
236
+ function closeOpts(deps) {
237
+ return {
238
+ runner: deps.runner,
239
+ ...(deps.closeWaitMs !== undefined ? { closeWaitMs: deps.closeWaitMs } : {}),
240
+ };
241
+ }
242
+ async function profilesCmd(deps, p) {
243
+ const names = await deps.store.list(LOCAL_BROWSER_SCOPE);
244
+ const audited = await deps.auditLastUsed().catch(() => ({}));
245
+ const profiles = [];
246
+ for (const name of names) {
247
+ let last = audited[name];
248
+ try {
249
+ const handle = await deps.store.open({ scope: LOCAL_BROWSER_SCOPE, name });
250
+ const iso = (await stat(handle.profileDir)).mtime.toISOString();
251
+ if (last === undefined || iso > last)
252
+ last = iso;
253
+ }
254
+ catch {
255
+ // store without a real folder
256
+ }
257
+ profiles.push({ name, last_used: last ?? null });
258
+ }
259
+ emit(deps, p.json, { profiles }, profiles.length === 0
260
+ ? []
261
+ : profiles.map((x) => `${x.name}\t${x.last_used ?? "never"}`));
262
+ return BROWSER_EXIT.ok;
263
+ }
264
+ async function statusCmd(deps, p) {
265
+ const name = requireName(p);
266
+ const exists = (await deps.store.list(LOCAL_BROWSER_SCOPE)).includes(name);
267
+ let lock = null;
268
+ const holder = await deps.locks.holder({ scope: LOCAL_BROWSER_SCOPE, name });
269
+ if (holder !== undefined) {
270
+ lock = {
271
+ run_id: holder.runId,
272
+ stage_id: holder.stageId ?? null,
273
+ live: await deps.isRunLive(holder.runId).catch(() => true),
274
+ };
275
+ }
276
+ let sessionOpen = false;
277
+ if (exists) {
278
+ const handle = await deps.store.open({ scope: LOCAL_BROWSER_SCOPE, name });
279
+ sessionOpen = deps.sessionOpen(await envFor(deps, handle, { profile: name }));
280
+ }
281
+ const doc = { name, exists, locked: lock !== null, lock, session_open: sessionOpen };
282
+ emit(deps, p.json, doc, [
283
+ `profile: ${name}`,
284
+ `exists: ${exists ? "yes" : "no"}`,
285
+ lock === null
286
+ ? "lock: free"
287
+ : `lock: held by run ${lock.run_id}${lock.stage_id !== null ? ` stage ${lock.stage_id}` : ""}${lock.live ? "" : " (stale)"}`,
288
+ `browser session: ${sessionOpen ? "open" : "closed"}`,
289
+ ]);
290
+ return BROWSER_EXIT.ok;
291
+ }
292
+ function assertUrlAllowed(deps, url) {
293
+ const host = hostOf(url);
294
+ if (host === undefined)
295
+ return;
296
+ const blocked = deps.blockedSites ?? loadHostConfig().browserBlockedSites;
297
+ if (isBlockedHost(host, blocked) !== undefined) {
298
+ throw new BlockedSiteError(`sf browser: --url "${host}" is blocked by Host policy (browser.blocked_sites)`);
299
+ }
300
+ }
301
+ async function checkCmd(deps, p) {
302
+ const name = requireName(p);
303
+ if (p.url === undefined || p.loggedIn === undefined) {
304
+ throw new Error("sf browser check needs --url and --logged-in");
305
+ }
306
+ assertUrlAllowed(deps, p.url);
307
+ const check = {
308
+ url: p.url,
309
+ logged_in_url: p.loggedIn,
310
+ ...(p.loggedOut.length > 0 ? { logged_out_url: p.loggedOut } : {}),
311
+ };
312
+ return withProfile(deps, name, async (handle) => {
313
+ const env = await envFor(deps, handle, { profile: name, headed: !p.headless, check });
314
+ try {
315
+ const result = await runLoginCheck(env, check, deps.runner, deps.loginCheck);
316
+ emit(deps, p.json, { logged_in: result.logged_in, url: result.url, state: result.state }, [
317
+ `state: ${result.state}`,
318
+ `logged_in: ${result.logged_in === null ? "unknown" : result.logged_in}`,
319
+ `url: ${result.url}`,
320
+ ]);
321
+ return result.state === "logged_in"
322
+ ? BROWSER_EXIT.ok
323
+ : result.state === "logged_out"
324
+ ? BROWSER_EXIT.loggedOut
325
+ : BROWSER_EXIT.unknown;
326
+ }
327
+ finally {
328
+ await closeBrowserSession(env, closeOpts(deps)).catch(() => undefined);
329
+ }
330
+ }, { create: false });
331
+ }
332
+ async function loginCmd(deps, p) {
333
+ const name = requireName(p);
334
+ if (p.url === undefined || p.loggedIn === undefined) {
335
+ throw new Error("sf browser login needs --url and --logged-in");
336
+ }
337
+ assertUrlAllowed(deps, p.url);
338
+ const screen = deps.display();
339
+ if (!screen.hasDisplay)
340
+ throw noScreenError(screen.docker);
341
+ let signal = deps.signal;
342
+ if (signal === undefined) {
343
+ const controller = new AbortController();
344
+ process.once("SIGINT", () => controller.abort());
345
+ signal = controller.signal;
346
+ }
347
+ const timeoutMs = (p.timeoutSec ?? DEFAULT_LOGIN_TIMEOUT_SEC) * 1000;
348
+ const pollMs = deps.loginPollMs ?? LOGIN_POLL_MS;
349
+ return withProfile(deps, name, async (handle) => {
350
+ const env = await envFor(deps, handle, { profile: name, headed: true }, true);
351
+ let status = "timeout";
352
+ let url = "";
353
+ try {
354
+ const opened = await deps.runner(["open", p.url], env);
355
+ if (opened.code !== 0)
356
+ throw new CliError(`Could not open ${p.url}`, "open_failed", BROWSER_EXIT.error);
357
+ if (!p.json)
358
+ deps.error("Log in in the browser window. Waiting for the login to finish (Ctrl-C to stop).");
359
+ const deadline = Date.now() + timeoutMs;
360
+ let failedReads = 0;
361
+ for (;;) {
362
+ if (signal.aborted) {
363
+ status = "interrupted";
364
+ break;
365
+ }
366
+ const got = await deps.runner(["get", "url"], env).catch(() => ({ code: 1, stdout: "" }));
367
+ if (got.code === 0) {
368
+ failedReads = 0;
369
+ url = (got.stdout ?? "").trim();
370
+ if (matchLoginState({ finalUrl: url, check: { logged_in_url: p.loggedIn } }).state === "logged_in") {
371
+ status = "logged_in";
372
+ break;
373
+ }
374
+ }
375
+ else if (++failedReads >= LOGIN_CLOSED_AFTER_FAILED_READS) {
376
+ status = "closed";
377
+ break;
378
+ }
379
+ if (Date.now() >= deadline)
380
+ break;
381
+ await deps.sleep(pollMs);
382
+ }
383
+ }
384
+ finally {
385
+ await closeBrowserSession(env, closeOpts(deps)).catch(() => undefined);
386
+ }
387
+ const doc = { logged_in: status === "logged_in", url, state: status };
388
+ emit(deps, p.json, doc, [
389
+ status === "logged_in"
390
+ ? `Logged in. Profile "${name}" is saved.`
391
+ : status === "interrupted"
392
+ ? "Stopped before login finished."
393
+ : status === "closed"
394
+ ? "The browser window was closed before login finished."
395
+ : "Timed out before login finished.",
396
+ ]);
397
+ return status === "logged_in"
398
+ ? BROWSER_EXIT.ok
399
+ : status === "interrupted"
400
+ ? BROWSER_EXIT.interrupted
401
+ : BROWSER_EXIT.notCompleted;
402
+ }, { create: true });
403
+ }
404
+ async function clearCmd(deps, p) {
405
+ const name = requireName(p);
406
+ if (!(await deps.store.list(LOCAL_BROWSER_SCOPE)).includes(name)) {
407
+ throw new CliError(`Profile "${name}" does not exist.`, "not_found", BROWSER_EXIT.error);
408
+ }
409
+ if (!p.yes) {
410
+ if (!deps.interactive) {
411
+ throw new CliError(`Refusing to delete profile "${name}" without confirmation. Pass --yes.`, "confirmation_required", BROWSER_EXIT.error);
412
+ }
413
+ if (!(await deps.confirm(`Delete browser profile "${name}" and its saved logins?`))) {
414
+ emit(deps, p.json, { cleared: false, name }, ["Cancelled."]);
415
+ return BROWSER_EXIT.error;
416
+ }
417
+ }
418
+ return withProfile(deps, name, async (handle) => {
419
+ const env = await envFor(deps, handle, { profile: name });
420
+ await closeBrowserSession(env, closeOpts(deps)).catch(() => undefined);
421
+ await deps.store.delete({ scope: LOCAL_BROWSER_SCOPE, name });
422
+ emit(deps, p.json, { cleared: true, name }, [`Cleared profile "${name}".`]);
423
+ return BROWSER_EXIT.ok;
424
+ }, { create: false });
425
+ }
426
+ export async function runBrowserCommand(args, deps) {
427
+ let json = args.includes("--json");
428
+ const io = deps?.log !== undefined && deps?.error !== undefined
429
+ ? { log: deps.log, error: deps.error }
430
+ : { log: (l) => console.log(l), error: (l) => console.error(l) };
431
+ let parsed;
432
+ try {
433
+ parsed = parse(args);
434
+ json = parsed.json;
435
+ }
436
+ catch (err) {
437
+ io.error(err.message);
438
+ io.error(BROWSER_USAGE);
439
+ return BROWSER_EXIT.error;
440
+ }
441
+ if (parsed.help) {
442
+ io.log(BROWSER_USAGE);
443
+ return BROWSER_EXIT.ok;
444
+ }
445
+ const resolved = { ...defaultDeps(), ...deps };
446
+ try {
447
+ switch (parsed.sub) {
448
+ case "profiles":
449
+ return await profilesCmd(resolved, parsed);
450
+ case "status":
451
+ return await statusCmd(resolved, parsed);
452
+ case "check":
453
+ return await checkCmd(resolved, parsed);
454
+ case "login":
455
+ return await loginCmd(resolved, parsed);
456
+ default:
457
+ return await clearCmd(resolved, parsed);
458
+ }
459
+ }
460
+ catch (err) {
461
+ const cli = err instanceof CliError ? err : undefined;
462
+ const code = cli?.code ?? (err instanceof InvalidProfileKeyError || err instanceof BlockedSiteError
463
+ ? err.code
464
+ : "error");
465
+ const message = err.message;
466
+ if (json)
467
+ resolved.log(JSON.stringify({ error: message, code }, null, 2));
468
+ else
469
+ resolved.error(message);
470
+ return cli?.exit ?? BROWSER_EXIT.error;
471
+ }
472
+ }
@@ -3,6 +3,7 @@ import type { AbandonStageResult, CancelRunResult, DecideFeedbackLoopResult, Del
3
3
  import type { DeliverAnswerResult } from "../runtime/stageHitl.js";
4
4
  import type { RetryStageResult } from "../runtime/runRetryCoordinator.js";
5
5
  import type { RunDetail, RunStore } from "../runstore/port.js";
6
+ import type { TriggerListItem } from "../server/triggerRoutes.js";
6
7
  import type { TaskFile } from "../types/task.js";
7
8
  export declare function computeRunDir(runId: string): string;
8
9
  export declare function httpReadRun(base: string, runId: string): Promise<RunDetail>;
@@ -57,5 +58,24 @@ export declare function httpDeleteRun(base: string, runId: string, options?: {
57
58
  export declare function httpGcRuns(base: string, options?: {
58
59
  execute?: boolean;
59
60
  }): Promise<GcRunsResult>;
61
+ export type HttpTriggerLookupResult = {
62
+ ok: true;
63
+ trigger: TriggerListItem;
64
+ } | {
65
+ ok: false;
66
+ reason: string;
67
+ status?: number;
68
+ };
69
+ export type HttpListTriggersResult = {
70
+ ok: true;
71
+ triggers: TriggerListItem[];
72
+ } | {
73
+ ok: false;
74
+ reason: string;
75
+ status?: number;
76
+ };
77
+ export declare function httpFireTrigger(base: string, triggerId: string, task?: Record<string, unknown>): Promise<StartRunResult>;
78
+ export declare function httpListTriggers(base: string): Promise<HttpListTriggersResult>;
79
+ export declare function httpGetTrigger(base: string, triggerId: string): Promise<HttpTriggerLookupResult>;
60
80
  export declare function resolveAbsolute(cwd: string, maybeRelative: string): string;
61
81
  export {};
@@ -160,8 +160,12 @@ export async function httpRerun(base, runId, options) {
160
160
  }
161
161
  export async function httpDeliverAnswer(base, runId, stageId, answer) {
162
162
  const { status, body } = await postJson(base, `/api/runs/${enc(runId)}/stages/${enc(stageId)}/answer`, answer);
163
- if (status === 202)
164
- return { ok: true };
163
+ if (status === 202) {
164
+ const verification = body?.verification;
165
+ return verification === "failed_retrying"
166
+ ? { ok: true, verification }
167
+ : { ok: true };
168
+ }
165
169
  return { ok: false, reason: extractError(body, status), status: status };
166
170
  }
167
171
  export async function httpDecideFeedbackLoop(base, runId, stageId, input) {
@@ -258,6 +262,36 @@ export async function httpGcRuns(base, options) {
258
262
  }
259
263
  return { ok: false, reason: extractError(body, status), status };
260
264
  }
265
+ export async function httpFireTrigger(base, triggerId, task) {
266
+ const { status, body } = await postJson(base, `/api/triggers/${enc(triggerId)}/fire`, task !== undefined ? { task } : {});
267
+ if (status !== 202)
268
+ return toStartFailure(status, body);
269
+ const parsed = body;
270
+ const runId = parsed.runId;
271
+ return {
272
+ ok: true,
273
+ runId,
274
+ ...(parsed.queued === true
275
+ ? { queued: true, queuePosition: parsed.queuePosition }
276
+ : {}),
277
+ done: pollRunUntilTerminal(base, runId).then(runDetailToPipelineRunResult),
278
+ };
279
+ }
280
+ export async function httpListTriggers(base) {
281
+ const { status, body } = await getJson(base, "/api/triggers");
282
+ if (status !== 200) {
283
+ return { ok: false, reason: extractError(body, status), status };
284
+ }
285
+ const parsed = body;
286
+ return { ok: true, triggers: parsed.triggers };
287
+ }
288
+ export async function httpGetTrigger(base, triggerId) {
289
+ const { status, body } = await getJson(base, `/api/triggers/${enc(triggerId)}`);
290
+ if (status !== 200) {
291
+ return { ok: false, reason: extractError(body, status), status };
292
+ }
293
+ return { ok: true, trigger: body };
294
+ }
261
295
  export function resolveAbsolute(cwd, maybeRelative) {
262
296
  return path.resolve(cwd, maybeRelative);
263
297
  }
@@ -6,4 +6,5 @@ export type InitCommandIo = {
6
6
  export declare function runInitCommand(args: string[], options?: {
7
7
  cwd?: string;
8
8
  io?: Partial<InitCommandIo>;
9
+ resolveConfiguredProviderIds?: (cwd: string) => Promise<string[]>;
9
10
  }): Promise<number>;
@@ -1,19 +1,15 @@
1
1
  import { access, mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { getAuthStatus } from "../agent/providerAuth.js";
3
4
  import { findProjectRoot } from "../project/findProjectRoot.js";
4
5
  import { ensureGlobalHome } from "../project/globalHome.js";
5
- import { HELLO_PIPELINE_YAML, HELLO_TASK_YAML, STAGEFLOW_YAML, } from "./initTemplates.js";
6
+ import { HELLO_TASK_YAML, STAGEFLOW_YAML, helloPipelineYaml, resolveInitDefaultModel, } from "./initTemplates.js";
6
7
  export const INIT_USAGE = `Usage:
7
8
  sf init`;
8
9
  const defaultIo = {
9
10
  log: (line) => console.log(line),
10
11
  error: (line) => console.error(line),
11
12
  };
12
- const INIT_TARGETS = [
13
- { relPath: "stageflow.yaml", content: STAGEFLOW_YAML },
14
- { relPath: "pipelines/hello.pipeline.yaml", content: HELLO_PIPELINE_YAML },
15
- { relPath: "tasks/hello.task.yaml", content: HELLO_TASK_YAML },
16
- ];
17
13
  async function fileExists(absPath) {
18
14
  try {
19
15
  await access(absPath);
@@ -35,6 +31,16 @@ function parseInitArgs(args) {
35
31
  }
36
32
  throw new Error(`Unexpected argument: ${args[0]}`);
37
33
  }
34
+ async function defaultConfiguredProviderIds(cwd) {
35
+ try {
36
+ const status = await getAuthStatus(cwd);
37
+ const rows = Array.isArray(status) ? status : [status];
38
+ return rows.filter((r) => r.configured).map((r) => r.providerId);
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ }
38
44
  export async function runInitCommand(args, options = {}) {
39
45
  const cwd = options.cwd ?? process.cwd();
40
46
  const out = { ...defaultIo, ...options.io };
@@ -46,7 +52,17 @@ export async function runInitCommand(args, options = {}) {
46
52
  }
47
53
  const initRoot = findProjectRoot(cwd) ?? path.resolve(cwd);
48
54
  ensureGlobalHome();
49
- for (const target of INIT_TARGETS) {
55
+ const configuredIds = await (options.resolveConfiguredProviderIds ?? defaultConfiguredProviderIds)(cwd);
56
+ const model = resolveInitDefaultModel(configuredIds);
57
+ const targets = [
58
+ { relPath: "stageflow.yaml", content: STAGEFLOW_YAML },
59
+ {
60
+ relPath: "pipelines/hello.pipeline.yaml",
61
+ content: helloPipelineYaml(model),
62
+ },
63
+ { relPath: "tasks/hello.task.yaml", content: HELLO_TASK_YAML },
64
+ ];
65
+ for (const target of targets) {
50
66
  const absPath = path.join(initRoot, target.relPath);
51
67
  if (await fileExists(absPath)) {
52
68
  out.log(`Skipped ${target.relPath} (exists)`);
@@ -1,3 +1,6 @@
1
- export declare const STAGEFLOW_YAML = "version: 1\ncatalog:\n pipelines:\n - pipelines\n tasks:\n - tasks\n patterns:\n pipeline: \"*.pipeline.yaml\"\n task: \"*.task.yaml\"\n";
2
- export declare const HELLO_PIPELINE_YAML = "id: hello\nstages:\n - id: hello\n model: anthropic/claude-sonnet-4-5\n system_prompt: Say hello and emit a success envelope.\n io:\n input:\n schema:\n type: object\n output:\n schema:\n type: object\n";
1
+ export declare const STAGEFLOW_YAML = "version: 1\ncatalog:\n pipelines:\n - pipelines\n - workshop\n tasks:\n - tasks\n - workshop\n patterns:\n pipeline: \"*.pipeline.yaml\"\n task: \"*.task.yaml\"\n";
2
+ export declare const LOUD_MODEL_PLACEHOLDER = "CHANGE_ME/<provider-model>";
3
+ export declare function resolveInitDefaultModel(configuredProviderIds: readonly string[]): string;
4
+ export declare function helloPipelineYaml(model: string): string;
5
+ export declare const HELLO_PIPELINE_YAML: string;
3
6
  export declare const HELLO_TASK_YAML = "id: hello\ngoal: Run the hello pipeline scaffold.\n";
@@ -2,16 +2,37 @@ export const STAGEFLOW_YAML = `version: 1
2
2
  catalog:
3
3
  pipelines:
4
4
  - pipelines
5
+ - workshop
5
6
  tasks:
6
7
  - tasks
8
+ - workshop
7
9
  patterns:
8
10
  pipeline: "*.pipeline.yaml"
9
11
  task: "*.task.yaml"
10
12
  `;
11
- export const HELLO_PIPELINE_YAML = `id: hello
13
+ export const LOUD_MODEL_PLACEHOLDER = "CHANGE_ME/<provider-model>";
14
+ const PROVIDER_DEFAULT_MODELS = {
15
+ anthropic: "anthropic/claude-sonnet-4-5",
16
+ openrouter: "openrouter/auto",
17
+ cursor: "cursor/auto",
18
+ };
19
+ export function resolveInitDefaultModel(configuredProviderIds) {
20
+ const ids = [
21
+ ...new Set(configuredProviderIds
22
+ .map((id) => id.trim().toLowerCase())
23
+ .filter((id) => id.length > 0)),
24
+ ].sort((a, b) => a.localeCompare(b));
25
+ if (ids.length === 1) {
26
+ const id = ids[0];
27
+ return PROVIDER_DEFAULT_MODELS[id] ?? `CHANGE_ME/${id}-model`;
28
+ }
29
+ return LOUD_MODEL_PLACEHOLDER;
30
+ }
31
+ export function helloPipelineYaml(model) {
32
+ return `id: hello
12
33
  stages:
13
34
  - id: hello
14
- model: anthropic/claude-sonnet-4-5
35
+ model: "${model}"
15
36
  system_prompt: Say hello and emit a success envelope.
16
37
  io:
17
38
  input:
@@ -21,6 +42,8 @@ stages:
21
42
  schema:
22
43
  type: object
23
44
  `;
45
+ }
46
+ export const HELLO_PIPELINE_YAML = helloPipelineYaml("anthropic/claude-sonnet-4-5");
24
47
  export const HELLO_TASK_YAML = `id: hello
25
48
  goal: Run the hello pipeline scaffold.
26
49
  `;