stageflow 0.32.0 → 0.35.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 (335) hide show
  1. package/README.md +4 -3
  2. package/dist/agent/adapterSpill.d.ts +14 -0
  3. package/dist/agent/adapterSpill.js +83 -0
  4. package/dist/agent/agentBackend.d.ts +2 -2
  5. package/dist/agent/agentBackend.js +2 -2
  6. package/dist/agent/claudeAdapter.d.ts +13 -2
  7. package/dist/agent/claudeAdapter.js +50 -6
  8. package/dist/agent/cursorProvider.d.ts +3 -0
  9. package/dist/agent/cursorProvider.js +23 -6
  10. package/dist/agent/fakeAgent.d.ts +3 -3
  11. package/dist/agent/fakeAgent.js +0 -6
  12. package/dist/agent/piAdapter.d.ts +2 -2
  13. package/dist/agent/piAdapter.js +116 -15
  14. package/dist/agent/piIsolatedMcp.d.ts +1 -0
  15. package/dist/agent/piIsolatedMcp.js +19 -1
  16. package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
  17. package/dist/agent/piIsolatedMcpProbe.js +20 -0
  18. package/dist/agent/port.d.ts +2 -2
  19. package/dist/agent/providerAuth.d.ts +6 -0
  20. package/dist/agent/providerAuth.js +86 -17
  21. package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -6
  22. package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +13 -9
  23. package/dist/agent/stageTurnDriver.d.ts +33 -0
  24. package/dist/agent/stageTurnDriver.js +64 -0
  25. package/dist/agent/toolOutputBudget.d.ts +46 -0
  26. package/dist/agent/toolOutputBudget.js +97 -0
  27. package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
  28. package/dist/agent/toolOutputBudgetExtension.js +141 -0
  29. package/dist/agent/toolOutputSpill.d.ts +34 -0
  30. package/dist/agent/toolOutputSpill.js +85 -0
  31. package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
  32. package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
  33. package/dist/browser/anchor.d.ts +2 -0
  34. package/dist/browser/anchor.js +2 -1
  35. package/dist/browser/auditSink.d.ts +8 -0
  36. package/dist/browser/browserHost.d.ts +29 -0
  37. package/dist/browser/browserSweep.d.ts +4 -1
  38. package/dist/browser/browserSweep.js +7 -0
  39. package/dist/browser/browserTeardown.d.ts +1 -0
  40. package/dist/browser/browserTeardown.js +32 -2
  41. package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
  42. package/dist/browser/containerBrowserEndpoint.js +57 -0
  43. package/dist/browser/containerBrowserHost.d.ts +24 -0
  44. package/dist/browser/containerBrowserHost.js +147 -0
  45. package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
  46. package/dist/browser/dockerSandboxOrchestrator.js +142 -0
  47. package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
  48. package/dist/browser/fakeLiveViewRelay.js +94 -0
  49. package/dist/browser/fakeLiveViewSource.d.ts +6 -0
  50. package/dist/browser/fakeLiveViewSource.js +16 -0
  51. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
  52. package/dist/browser/fakeRemoteBrowserHost.js +2 -1
  53. package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
  54. package/dist/browser/fakeSandboxOrchestrator.js +44 -0
  55. package/dist/browser/gateHandoff.d.ts +0 -2
  56. package/dist/browser/gateHandoff.js +5 -11
  57. package/dist/browser/hostCapabilities.d.ts +23 -0
  58. package/dist/browser/hostCapabilities.js +23 -0
  59. package/dist/browser/humanLogin.d.ts +2 -2
  60. package/dist/browser/humanLogin.js +11 -2
  61. package/dist/browser/liveViewPersisted.d.ts +11 -0
  62. package/dist/browser/liveViewPersisted.js +22 -0
  63. package/dist/browser/liveViewRelay.d.ts +76 -0
  64. package/dist/browser/liveViewRelay.js +1 -0
  65. package/dist/browser/liveViewSessions.d.ts +25 -0
  66. package/dist/browser/liveViewSessions.js +139 -0
  67. package/dist/browser/liveViewSource.d.ts +25 -0
  68. package/dist/browser/liveViewSource.js +7 -0
  69. package/dist/browser/liveViewTickets.d.ts +46 -0
  70. package/dist/browser/liveViewTickets.js +122 -0
  71. package/dist/browser/localBrowserHost.d.ts +10 -0
  72. package/dist/browser/localBrowserHost.js +53 -7
  73. package/dist/browser/ownerScope.d.ts +10 -0
  74. package/dist/browser/ownerScope.js +6 -0
  75. package/dist/browser/profileStore.d.ts +5 -2
  76. package/dist/browser/profileStore.js +3 -5
  77. package/dist/browser/sandboxOrchestrator.d.ts +60 -0
  78. package/dist/browser/sandboxOrchestrator.js +8 -0
  79. package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
  80. package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
  81. package/dist/browser/stageBrowserEnv.d.ts +7 -0
  82. package/dist/browser/stageBrowserEnv.js +53 -15
  83. package/dist/browser/stageHandoff.d.ts +47 -0
  84. package/dist/browser/stageHandoff.js +101 -0
  85. package/dist/browser/stageProfileLock.d.ts +1 -0
  86. package/dist/browser/stageProfileLock.js +1 -3
  87. package/dist/browser/volumeProfileStore.d.ts +7 -0
  88. package/dist/browser/volumeProfileStore.js +35 -0
  89. package/dist/cli/browserCommand.d.ts +3 -1
  90. package/dist/cli/browserCommand.js +25 -20
  91. package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
  92. package/dist/cli/resolveCliProjectRoot.js +27 -0
  93. package/dist/cli/runCommand.d.ts +1 -1
  94. package/dist/cli/runCommand.js +59 -17
  95. package/dist/cli/skillsCommand.d.ts +20 -0
  96. package/dist/cli/skillsCommand.js +74 -31
  97. package/dist/cli/validateCommand.d.ts +1 -1
  98. package/dist/cli/validateCommand.js +22 -4
  99. package/dist/cli.js +4 -4
  100. package/dist/config/browseCatalog.d.ts +2 -0
  101. package/dist/config/browseCatalog.js +6 -0
  102. package/dist/config/catalogRelativePath.d.ts +4 -0
  103. package/dist/config/catalogRelativePath.js +37 -9
  104. package/dist/config/createStage.d.ts +1 -0
  105. package/dist/config/createStage.js +11 -2
  106. package/dist/config/createTask.d.ts +30 -0
  107. package/dist/config/createTask.js +161 -0
  108. package/dist/config/createTrigger.d.ts +15 -0
  109. package/dist/config/createTrigger.js +32 -25
  110. package/dist/config/draftPackage.d.ts +41 -0
  111. package/dist/config/draftPackage.js +216 -23
  112. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  113. package/dist/config/ensureCatalogScanRoot.js +6 -2
  114. package/dist/config/findTaskInCatalog.d.ts +33 -0
  115. package/dist/config/findTaskInCatalog.js +69 -0
  116. package/dist/config/hostConfig.d.ts +21 -0
  117. package/dist/config/hostConfig.js +113 -1
  118. package/dist/config/importTask.d.ts +19 -0
  119. package/dist/config/importTask.js +133 -0
  120. package/dist/config/installSkillRequest.d.ts +40 -0
  121. package/dist/config/installSkillRequest.js +286 -0
  122. package/dist/config/listConfig.d.ts +0 -16
  123. package/dist/config/listConfig.js +0 -3
  124. package/dist/config/loadOutcome.d.ts +11 -6
  125. package/dist/config/loadPipeline.js +0 -1
  126. package/dist/config/loadStage.js +2 -0
  127. package/dist/config/loadStageflowManifest.js +4 -1
  128. package/dist/config/loadTask.js +2 -0
  129. package/dist/config/loadTrigger.js +2 -0
  130. package/dist/config/mergePipelineIncludes.js +3 -0
  131. package/dist/config/normalizePipelineStageEntry.js +10 -1
  132. package/dist/config/printTargetYaml.js +0 -1
  133. package/dist/config/publishDraftPackage.d.ts +5 -1
  134. package/dist/config/publishDraftPackage.js +4 -1
  135. package/dist/config/resolveCatalogRoots.d.ts +4 -1
  136. package/dist/config/resolveCatalogRoots.js +18 -1
  137. package/dist/config/skillUsageIndex.d.ts +6 -0
  138. package/dist/config/skillUsageIndex.js +39 -0
  139. package/dist/config/stageBrowser.js +13 -0
  140. package/dist/config/triggerAdapterStatus.d.ts +11 -0
  141. package/dist/config/triggerAdapterStatus.js +90 -0
  142. package/dist/config/triggerCatalog.d.ts +41 -2
  143. package/dist/config/triggerCatalog.js +107 -15
  144. package/dist/config/updateTask.d.ts +30 -0
  145. package/dist/config/updateTask.js +119 -0
  146. package/dist/config/updateTrigger.d.ts +38 -0
  147. package/dist/config/updateTrigger.js +220 -0
  148. package/dist/config/validateCatalog.d.ts +6 -2
  149. package/dist/config/validateCatalog.js +48 -7
  150. package/dist/config/workshopCatalogFilenames.d.ts +37 -0
  151. package/dist/config/workshopCatalogFilenames.js +125 -0
  152. package/dist/config/workshopCatalogRepair.d.ts +7 -0
  153. package/dist/config/workshopCatalogRepair.js +144 -0
  154. package/dist/config/yamlParsePosition.d.ts +4 -0
  155. package/dist/config/yamlParsePosition.js +9 -0
  156. package/dist/hosted/containment.d.ts +3 -0
  157. package/dist/hosted/containment.js +36 -0
  158. package/dist/hosted/errors.d.ts +21 -0
  159. package/dist/hosted/errors.js +30 -0
  160. package/dist/hosted/packageSkills.d.ts +3 -0
  161. package/dist/hosted/packageSkills.js +80 -0
  162. package/dist/hosted/runtime.d.ts +12 -0
  163. package/dist/hosted/runtime.js +596 -0
  164. package/dist/hosted/sequencer.d.ts +28 -0
  165. package/dist/hosted/sequencer.js +129 -0
  166. package/dist/hosted/translate.d.ts +3 -0
  167. package/dist/hosted/translate.js +43 -0
  168. package/dist/hosted/types.d.ts +108 -0
  169. package/dist/hosted/types.js +10 -0
  170. package/dist/index.d.ts +9 -1
  171. package/dist/index.js +6 -0
  172. package/dist/mcp/getStartedTool.js +15 -6
  173. package/dist/mcp/triggerTools.js +2 -8
  174. package/dist/operatorAgent/agentPort.d.ts +11 -0
  175. package/dist/operatorAgent/agentPort.js +12 -0
  176. package/dist/operatorAgent/index.d.ts +1 -0
  177. package/dist/operatorAgent/index.js +1 -0
  178. package/dist/operatorAgent/piModel.js +26 -6
  179. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  180. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
  181. package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
  182. package/dist/package-meta.d.ts +1 -1
  183. package/dist/package-meta.js +1 -1
  184. package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
  185. package/dist/project/ensureProjectRootsFromEnv.js +22 -0
  186. package/dist/project/globalHome.d.ts +1 -0
  187. package/dist/project/globalHome.js +18 -0
  188. package/dist/project/workspaceCatalog.d.ts +10 -0
  189. package/dist/project/workspaceCatalog.js +91 -0
  190. package/dist/projection/projectRun.d.ts +0 -1
  191. package/dist/projection/projectRun.js +0 -1
  192. package/dist/runstore/backup.js +14 -4
  193. package/dist/runstore/port.d.ts +11 -1
  194. package/dist/runstore/restore.js +5 -2
  195. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
  196. package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
  197. package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
  198. package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
  199. package/dist/runstore/sqlite/migrations/index.js +2 -0
  200. package/dist/runstore/stageSnapshot.d.ts +3 -1
  201. package/dist/runstore/stageSnapshot.js +19 -0
  202. package/dist/runstore/workspaceLayout.js +27 -0
  203. package/dist/runstore/writeContainedArtifact.d.ts +1 -0
  204. package/dist/runstore/writeContainedArtifact.js +52 -0
  205. package/dist/runtime/containerLimits.d.ts +0 -3
  206. package/dist/runtime/containerLimits.js +0 -5
  207. package/dist/runtime/credentialBinding.d.ts +3 -0
  208. package/dist/runtime/credentialBinding.js +23 -6
  209. package/dist/runtime/emailSource.js +20 -28
  210. package/dist/runtime/githubPollSource.js +20 -28
  211. package/dist/runtime/pipelineRunner.d.ts +4 -4
  212. package/dist/runtime/pipelineScheduler.d.ts +2 -2
  213. package/dist/runtime/pipelineScheduler.js +3 -0
  214. package/dist/runtime/resumeReconstruct.d.ts +2 -2
  215. package/dist/runtime/resumeTimedOut.d.ts +2 -2
  216. package/dist/runtime/runManager.d.ts +18 -2
  217. package/dist/runtime/runManager.js +95 -30
  218. package/dist/runtime/runRetentionSweep.d.ts +0 -2
  219. package/dist/runtime/runRetentionSweep.js +0 -8
  220. package/dist/runtime/runRetryCoordinator.d.ts +2 -2
  221. package/dist/runtime/scheduleSource.js +15 -24
  222. package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
  223. package/dist/runtime/stageAttemptBootstrap.js +10 -1
  224. package/dist/runtime/stageProcessLauncher.js +2 -2
  225. package/dist/runtime/stageRoots.js +12 -1
  226. package/dist/runtime/stageRunner.d.ts +2 -2
  227. package/dist/runtime/stageRunner.js +8 -2
  228. package/dist/runtime/stageWorker.js +2 -2
  229. package/dist/runtime/triggerPort.d.ts +5 -3
  230. package/dist/runtime/triggerRunner.d.ts +1 -0
  231. package/dist/runtime/triggerRunner.js +27 -20
  232. package/dist/runtime/webhookAccept.d.ts +25 -0
  233. package/dist/runtime/webhookAccept.js +56 -0
  234. package/dist/server/bootstrap.d.ts +7 -2
  235. package/dist/server/bootstrap.js +15 -1
  236. package/dist/server/controlToken.d.ts +1 -1
  237. package/dist/server/controlToken.js +18 -2
  238. package/dist/server/healthSurfaces.d.ts +12 -0
  239. package/dist/server/healthSurfaces.js +27 -6
  240. package/dist/server/http.d.ts +19 -10
  241. package/dist/server/http.js +605 -85
  242. package/dist/server/liveViewPath.d.ts +9 -0
  243. package/dist/server/liveViewPath.js +8 -0
  244. package/dist/server/liveViewRoutes.d.ts +58 -0
  245. package/dist/server/liveViewRoutes.js +565 -0
  246. package/dist/server/mcpHost.d.ts +4 -2
  247. package/dist/server/mcpHost.js +2 -0
  248. package/dist/server/readCatalogFile.d.ts +29 -0
  249. package/dist/server/readCatalogFile.js +215 -0
  250. package/dist/server/triggerRoutes.d.ts +1 -0
  251. package/dist/server/triggerRoutes.js +200 -73
  252. package/dist/tools/readEmail.d.ts +3 -3
  253. package/dist/tools/writeStageArtifact.d.ts +9 -0
  254. package/dist/tools/writeStageArtifact.js +2 -47
  255. package/dist/types/pipeline.d.ts +1 -1
  256. package/dist/types/stage.d.ts +1 -1
  257. package/dist/types/stageflowManifest.d.ts +1 -1
  258. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  259. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  260. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  261. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  262. package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  263. package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  264. package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  265. package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  266. package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  267. package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  268. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  269. package/dist/ui/assets/index-BoFPkKFg.js +256 -0
  270. package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.css} +1 -1
  271. package/dist/ui/index.html +4 -4
  272. package/dist/workshop/autosave.d.ts +2 -2
  273. package/dist/workshop/autosave.js +4 -6
  274. package/dist/workshop/buildStore.d.ts +2 -0
  275. package/dist/workshop/chatAttachments.d.ts +27 -0
  276. package/dist/workshop/chatAttachments.js +98 -0
  277. package/dist/workshop/chatTurn.d.ts +5 -2
  278. package/dist/workshop/chatTurn.js +22 -18
  279. package/dist/workshop/sessionMutations.d.ts +17 -0
  280. package/dist/workshop/sessionMutations.js +107 -0
  281. package/dist/workshop/sessionStore.d.ts +10 -0
  282. package/dist/workshop/sessionStore.js +52 -0
  283. package/dist/workshop/workshopPin.d.ts +4 -3
  284. package/dist/workshop/workshopPin.js +26 -6
  285. package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
  286. package/dist/workshop/workshopTranscriptTurn.js +46 -0
  287. package/examples/README.md +1 -1
  288. package/examples/archify-on-pr/README.md +1 -1
  289. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
  290. package/examples/archify-on-pr/author-diagrams.yaml +1 -0
  291. package/examples/conditional-fork/branch-a.yaml +1 -1
  292. package/examples/conditional-fork/decide.yaml +2 -1
  293. package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
  294. package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
  295. package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
  296. package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
  297. package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
  298. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
  299. package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
  300. package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
  301. package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
  302. package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
  303. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
  304. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
  305. package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
  306. package/examples/hello-world/hello.pipeline.yaml +2 -2
  307. package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
  308. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
  309. package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
  310. package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
  311. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
  312. package/examples/stageflow.yaml +6 -0
  313. package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
  314. package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
  315. package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
  316. package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
  317. package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
  318. package/examples/triggers-readiness/README.md +48 -0
  319. package/examples/triggers-readiness/readiness-emit.yaml +18 -0
  320. package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
  321. package/examples/triggers-readiness/readiness.task.yaml +6 -0
  322. package/examples/triggers-readiness/stageflow.yaml +13 -0
  323. package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
  324. package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
  325. package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
  326. package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
  327. package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
  328. package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
  329. package/package.json +9 -1
  330. package/skills/stageflow/references/control-surface.md +1 -1
  331. package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
  332. package/skills/stageflow-run/SKILL.md +1 -1
  333. package/skills/stageflow-run/references/mcp-call.md +1 -1
  334. package/skills/stageflow-setup/SKILL.md +1 -1
  335. package/dist/ui/assets/index-59cjF0OA.js +0 -133
@@ -0,0 +1,129 @@
1
+ import { redactString } from "../logging/redact.js";
2
+ import { HostedRuntimeError } from "./errors.js";
3
+ import { HOSTED_EVENT_SCHEMA_VERSION, } from "./types.js";
4
+ export class HostedEventSequencer {
5
+ sink;
6
+ context;
7
+ timeoutMs;
8
+ attempts;
9
+ chain = Promise.resolve();
10
+ held = [];
11
+ holding = true;
12
+ sequence = 0;
13
+ runId = "";
14
+ failed = false;
15
+ failure;
16
+ constructor(sink, context, timeoutMs, attempts) {
17
+ this.sink = sink;
18
+ this.context = context;
19
+ this.timeoutMs = timeoutMs;
20
+ this.attempts = attempts;
21
+ }
22
+ setRunId(runId) {
23
+ this.runId = runId;
24
+ }
25
+ emit(body) {
26
+ if (this.failed)
27
+ return Promise.reject(this.failureError());
28
+ if (this.holding) {
29
+ return new Promise((resolve, reject) => {
30
+ this.held.push({ body, resolve, reject });
31
+ });
32
+ }
33
+ return this.enqueue(body);
34
+ }
35
+ emitNow(body) {
36
+ if (this.failed)
37
+ return Promise.reject(this.failureError());
38
+ return this.enqueue(body);
39
+ }
40
+ release() {
41
+ if (!this.holding)
42
+ return;
43
+ this.holding = false;
44
+ const queued = this.held;
45
+ this.held = [];
46
+ for (const item of queued) {
47
+ const run = this.chain.then(() => this.deliver(item.body));
48
+ this.chain = run.then(() => undefined, () => undefined);
49
+ void run.then(item.resolve, item.reject);
50
+ }
51
+ }
52
+ rejectHeld(error) {
53
+ this.holding = false;
54
+ const queued = this.held;
55
+ this.held = [];
56
+ for (const item of queued)
57
+ item.reject(error);
58
+ }
59
+ enqueue(body) {
60
+ const run = this.chain.then(() => this.deliver(body));
61
+ this.chain = run.then(() => undefined, () => undefined);
62
+ return run;
63
+ }
64
+ async deliver(body) {
65
+ if (this.failed)
66
+ throw this.failureError();
67
+ const sequence = ++this.sequence;
68
+ const event = this.envelope(body, sequence);
69
+ let last;
70
+ for (let attempt = 1; attempt <= this.attempts; attempt += 1) {
71
+ try {
72
+ await withTimeout(this.sink.onEvent(event), this.timeoutMs);
73
+ return;
74
+ }
75
+ catch (error) {
76
+ last = error;
77
+ }
78
+ }
79
+ const message = redactString(errorMessage(last));
80
+ this.failed = true;
81
+ this.failure = { eventType: event.type, sequence, message };
82
+ throw this.failureError();
83
+ }
84
+ envelope(body, sequence) {
85
+ return {
86
+ schemaVersion: HOSTED_EVENT_SCHEMA_VERSION,
87
+ eventId: `${this.context.externalAttemptId}:${sequence}`,
88
+ sequence,
89
+ occurredAt: new Date().toISOString(),
90
+ runId: this.runId,
91
+ externalRunId: this.context.externalRunId,
92
+ externalAttemptId: this.context.externalAttemptId,
93
+ packageRevision: this.context.packageRevision,
94
+ ...(this.context.attributes !== undefined
95
+ ? { attributes: this.context.attributes }
96
+ : {}),
97
+ ...body,
98
+ };
99
+ }
100
+ failureError() {
101
+ const failure = this.failure;
102
+ return new HostedRuntimeError(failure?.message ?? "hosted event sink failed", {
103
+ code: "event_sink_failed",
104
+ externalRunId: this.context.externalRunId,
105
+ externalAttemptId: this.context.externalAttemptId,
106
+ ...(this.runId !== "" ? { runId: this.runId } : {}),
107
+ ...(failure !== undefined
108
+ ? { eventType: failure.eventType, sequence: failure.sequence }
109
+ : {}),
110
+ });
111
+ }
112
+ }
113
+ function errorMessage(error) {
114
+ return error instanceof Error ? error.message : String(error);
115
+ }
116
+ function withTimeout(work, timeoutMs) {
117
+ return new Promise((resolve, reject) => {
118
+ const timer = setTimeout(() => {
119
+ reject(new Error("hosted event sink timed out"));
120
+ }, timeoutMs);
121
+ work.then(() => {
122
+ clearTimeout(timer);
123
+ resolve();
124
+ }, (error) => {
125
+ clearTimeout(timer);
126
+ reject(error);
127
+ });
128
+ });
129
+ }
@@ -0,0 +1,3 @@
1
+ import type { StageLogLine } from "../agent/activity.js";
2
+ import type { HostedEventBody } from "./types.js";
3
+ export declare function translateStageEvent(stageId: string, event: StageLogLine, attempt: number): HostedEventBody | undefined;
@@ -0,0 +1,43 @@
1
+ import { redactString } from "../logging/redact.js";
2
+ const ACTIVITY_EVENTS = new Set([
3
+ "agent_start",
4
+ "agent_end",
5
+ "turn_start",
6
+ "tool_start",
7
+ "tool_end",
8
+ "tool_progress",
9
+ "message",
10
+ "operator_prompt",
11
+ "operator_answer",
12
+ ]);
13
+ function redactActivity(event) {
14
+ return JSON.parse(redactString(JSON.stringify(event)));
15
+ }
16
+ export function translateStageEvent(stageId, event, attempt) {
17
+ if (ACTIVITY_EVENTS.has(event.event)) {
18
+ return {
19
+ type: "stage.activity",
20
+ stageId,
21
+ attempt,
22
+ activity: redactActivity(event),
23
+ };
24
+ }
25
+ switch (event.event) {
26
+ case "started":
27
+ return { type: "stage.started", stageId, attempt };
28
+ case "waiting_for_input":
29
+ return { type: "stage.waiting", stageId, attempt };
30
+ case "succeeded":
31
+ return { type: "stage.succeeded", stageId, attempt };
32
+ case "failed":
33
+ case "interrupted":
34
+ return {
35
+ type: "stage.failed",
36
+ stageId,
37
+ attempt,
38
+ reason: redactString(event.reason),
39
+ };
40
+ default:
41
+ return undefined;
42
+ }
43
+ }
@@ -0,0 +1,108 @@
1
+ import type { StageActivityEvent } from "../agent/activity.js";
2
+ import type { HostedErrorCode } from "./errors.js";
3
+ export declare const HOSTED_EVENT_SCHEMA_VERSION: 1;
4
+ /** One sink call is attempted this many times. Delivery is at least once, not exactly once. */
5
+ export declare const HOSTED_DELIVERY_ATTEMPTS = 2;
6
+ /** Bound for a single sink call. The private sink and Cloud worker own further retries. */
7
+ export declare const DEFAULT_EVENT_DELIVERY_TIMEOUT_MS = 10000;
8
+ /**
9
+ * After cancellation or timeout, Core waits at most this long for in-flight
10
+ * stage work to stop. `StagePort` handles must stop inside `close()`.
11
+ */
12
+ export declare const HOSTED_CANCEL_GRACE_MS = 5000;
13
+ export type HostedRunContext = {
14
+ /** Opaque Cloud run id. Core copies it and does not interpret it. */
15
+ externalRunId: string;
16
+ /** Opaque Cloud attempt id. Core copies it and does not interpret it. */
17
+ externalAttemptId: string;
18
+ /** Revision of the package Cloud already materialized. */
19
+ packageRevision: string;
20
+ /** Extra non-secret correlation strings. */
21
+ attributes?: Readonly<Record<string, string>>;
22
+ };
23
+ export type HostedPipelineInput = {
24
+ packageRoot: string;
25
+ pipeline: string;
26
+ taskYaml: string;
27
+ context: HostedRunContext;
28
+ signal?: AbortSignal;
29
+ maxActiveStagesPerRun?: number;
30
+ /** Wall-clock limit for the hosted run. Omit for no hosted-level limit. */
31
+ timeoutMs?: number;
32
+ };
33
+ export type HostedExecutionReceipt = {
34
+ coreVersion: string;
35
+ eventSchemaVersion: typeof HOSTED_EVENT_SCHEMA_VERSION;
36
+ packageRevision: string;
37
+ executionMode: "inline";
38
+ startedAt: string;
39
+ finishedAt: string;
40
+ };
41
+ export type HostedPipelineResult = {
42
+ /** Null when the run failed before Core created a local run. */
43
+ runId: string | null;
44
+ externalRunId: string;
45
+ externalAttemptId: string;
46
+ status: "succeeded" | "failed" | "cancelled" | "waiting";
47
+ code?: HostedErrorCode;
48
+ reason?: string;
49
+ /** Present once a local run exists. Caller exports artifacts from here. */
50
+ workspaceDir?: string;
51
+ receipt: HostedExecutionReceipt;
52
+ };
53
+ export type HostedEventEnvelope = {
54
+ schemaVersion: typeof HOSTED_EVENT_SCHEMA_VERSION;
55
+ eventId: string;
56
+ sequence: number;
57
+ occurredAt: string;
58
+ runId: string;
59
+ externalRunId: string;
60
+ externalAttemptId: string;
61
+ packageRevision: string;
62
+ attributes?: Readonly<Record<string, string>>;
63
+ };
64
+ export type HostedRunEvent = HostedEventEnvelope & ({
65
+ type: "run.created";
66
+ } | {
67
+ type: "run.started";
68
+ } | {
69
+ type: "stage.started";
70
+ stageId: string;
71
+ attempt: number;
72
+ } | {
73
+ type: "stage.activity";
74
+ stageId: string;
75
+ attempt: number;
76
+ activity: StageActivityEvent;
77
+ } | {
78
+ type: "stage.waiting";
79
+ stageId: string;
80
+ attempt: number;
81
+ } | {
82
+ type: "stage.succeeded";
83
+ stageId: string;
84
+ attempt: number;
85
+ } | {
86
+ type: "stage.failed";
87
+ stageId: string;
88
+ attempt: number;
89
+ reason?: string;
90
+ } | {
91
+ type: "run.succeeded";
92
+ receipt: HostedExecutionReceipt;
93
+ } | {
94
+ type: "run.failed";
95
+ code?: HostedErrorCode;
96
+ reason?: string;
97
+ receipt: HostedExecutionReceipt;
98
+ } | {
99
+ type: "run.cancelled";
100
+ reason?: string;
101
+ receipt: HostedExecutionReceipt;
102
+ });
103
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
104
+ export type HostedEventBody = DistributiveOmit<HostedRunEvent, keyof HostedEventEnvelope>;
105
+ export interface HostedEventSink {
106
+ onEvent(event: HostedRunEvent): Promise<void>;
107
+ }
108
+ export {};
@@ -0,0 +1,10 @@
1
+ export const HOSTED_EVENT_SCHEMA_VERSION = 1;
2
+ /** One sink call is attempted this many times. Delivery is at least once, not exactly once. */
3
+ export const HOSTED_DELIVERY_ATTEMPTS = 2;
4
+ /** Bound for a single sink call. The private sink and Cloud worker own further retries. */
5
+ export const DEFAULT_EVENT_DELIVERY_TIMEOUT_MS = 10_000;
6
+ /**
7
+ * After cancellation or timeout, Core waits at most this long for in-flight
8
+ * stage work to stop. `StagePort` handles must stop inside `close()`.
9
+ */
10
+ export const HOSTED_CANCEL_GRACE_MS = 5_000;
package/dist/index.d.ts CHANGED
@@ -5,10 +5,18 @@ export { resolveStageflowContext, projectContextFromStageflow, type StageflowCon
5
5
  export { PACKAGE_NAME, PACKAGE_VERSION } from "./package-meta.js";
6
6
  export type { StageEnvelope } from "./types/envelope.js";
7
7
  export { assertRequiredEnvelope, isAdvancingEnvelope, } from "./envelope/check.js";
8
- export type { AgentPort } from "./agent/port.js";
8
+ export type { StagePort, StageHandle, StageHandleEvent, StageRunInput, StageRunResult, } from "./agent/port.js";
9
+ export { createPiStagePort } from "./agent/resolveStagePort.js";
10
+ export { createPiAgentPort, resolveAgentPort, type AgentPort, } from "./operatorAgent/agentPort.js";
11
+ export { configurePiProviderApiKey } from "./agent/providerAuth.js";
9
12
  export type { StageActivityEvent, StageLogLine } from "./agent/activity.js";
10
13
  export type { StageRoots } from "./runtime/stageRoots.js";
11
14
  export { runPipeline, startPipeline } from "./runtime/pipelineRunner.js";
15
+ export { createHostedRuntime, type HostedRuntime, } from "./hosted/runtime.js";
16
+ export { HostedRuntimeError, HOSTED_ERROR_CODES } from "./hosted/errors.js";
17
+ export type { HostedErrorCode } from "./hosted/errors.js";
18
+ export { HOSTED_EVENT_SCHEMA_VERSION, HOSTED_DELIVERY_ATTEMPTS, DEFAULT_EVENT_DELIVERY_TIMEOUT_MS, HOSTED_CANCEL_GRACE_MS, } from "./hosted/types.js";
19
+ export type { HostedRunContext, HostedPipelineInput, HostedPipelineResult, HostedExecutionReceipt, HostedEventEnvelope, HostedRunEvent, HostedEventSink, } from "./hosted/types.js";
12
20
  export { createRunStore } from "./runstore/createStore.js";
13
21
  export type { RunStoreConfig, RunStoreKind } from "./runstore/createStore.js";
14
22
  export type { RunStore, RunSummary, RunDetail, RunMeta, RunStatus, StageSnapshot, StageLogEvent, CreatedRun, } from "./runstore/port.js";
package/dist/index.js CHANGED
@@ -4,7 +4,13 @@ export { resolveProjectContext, legacyProjectContext, } from "./project/resolveP
4
4
  export { resolveStageflowContext, projectContextFromStageflow, } from "./project/resolveStageflowContext.js";
5
5
  export { PACKAGE_NAME, PACKAGE_VERSION } from "./package-meta.js";
6
6
  export { assertRequiredEnvelope, isAdvancingEnvelope, } from "./envelope/check.js";
7
+ export { createPiStagePort } from "./agent/resolveStagePort.js";
8
+ export { createPiAgentPort, resolveAgentPort, } from "./operatorAgent/agentPort.js";
9
+ export { configurePiProviderApiKey } from "./agent/providerAuth.js";
7
10
  export { runPipeline, startPipeline } from "./runtime/pipelineRunner.js";
11
+ export { createHostedRuntime, } from "./hosted/runtime.js";
12
+ export { HostedRuntimeError, HOSTED_ERROR_CODES } from "./hosted/errors.js";
13
+ export { HOSTED_EVENT_SCHEMA_VERSION, HOSTED_DELIVERY_ATTEMPTS, DEFAULT_EVENT_DELIVERY_TIMEOUT_MS, HOSTED_CANCEL_GRACE_MS, } from "./hosted/types.js";
8
14
  export { createRunStore } from "./runstore/createStore.js";
9
15
  export { deriveStatusFromStages } from "./runstore/port.js";
10
16
  export { RunManager } from "./runtime/runManager.js";
@@ -29,9 +29,12 @@ export function registerGetStartedTool(server, deps) {
29
29
  task_count: tasks.items.filter((t) => t.project_root === root.project_root)
30
30
  .length,
31
31
  }));
32
- const firstRoot = catalog_roots.find((r) => r.kind === "seeded") ?? catalog_roots[0];
33
- const samplePipeline = pipelines.items.find((p) => p.project_root === firstRoot?.project_root);
34
- const sampleTask = tasks.items.find((t) => t.project_root === firstRoot?.project_root);
32
+ const authoringRoot = catalog_roots.find((r) => r.kind === "workspace" && r.read_only !== true) ??
33
+ catalog_roots.find((r) => r.kind === "registered") ??
34
+ catalog_roots[0];
35
+ const browseRoot = catalog_roots.find((r) => r.kind === "seeded") ?? authoringRoot;
36
+ const samplePipeline = pipelines.items.find((p) => p.project_root === browseRoot?.project_root);
37
+ const sampleTask = tasks.items.find((t) => t.project_root === browseRoot?.project_root);
35
38
  return textResult({
36
39
  host: { version: PACKAGE_VERSION },
37
40
  providers: deps.providerAuthContext
@@ -42,16 +45,22 @@ export function registerGetStartedTool(server, deps) {
42
45
  next_steps: [
43
46
  {
44
47
  tool: "list_pipelines",
45
- args: firstRoot ? { project_root: firstRoot.project_root } : {},
48
+ args: authoringRoot
49
+ ? { project_root: authoringRoot.project_root }
50
+ : {},
46
51
  },
47
52
  {
48
53
  tool: "list_tasks",
49
- args: firstRoot ? { project_root: firstRoot.project_root } : {},
54
+ args: authoringRoot
55
+ ? { project_root: authoringRoot.project_root }
56
+ : {},
50
57
  },
51
58
  {
52
59
  tool: "start_run",
53
60
  args: {
54
- ...(firstRoot ? { project_root: firstRoot.project_root } : {}),
61
+ ...(authoringRoot
62
+ ? { project_root: authoringRoot.project_root }
63
+ : {}),
55
64
  pipeline: samplePipeline?.path ?? "pipelines/demo.pipeline.yaml",
56
65
  task_path: sampleTask?.path ?? "tasks/demo.task.yaml",
57
66
  },
@@ -63,10 +63,7 @@ export function registerTriggerTools(server, deps) {
63
63
  description: "List catalog-declared triggers (same source as GET /api/triggers): manual/schedule/event kind, enabled, and store-recorded last_fired_at/last_run_id/next_run_at when present.",
64
64
  inputSchema: z.object({}),
65
65
  }, async () => {
66
- const items = await loadCatalogTriggers(cwd, store);
67
- if (items === undefined) {
68
- return textResult({ error: "No Stageflow catalog found", status: 404 }, true);
69
- }
66
+ const items = await loadCatalogTriggers({ store, bootCwd: cwd });
70
67
  return textResult({ triggers: items });
71
68
  });
72
69
  server.registerTool("get_trigger", {
@@ -75,10 +72,7 @@ export function registerTriggerTools(server, deps) {
75
72
  id: z.string(),
76
73
  }),
77
74
  }, async ({ id }) => {
78
- const items = await loadCatalogTriggers(cwd, store);
79
- if (items === undefined) {
80
- return textResult({ error: "No Stageflow catalog found", status: 404 }, true);
81
- }
75
+ const items = await loadCatalogTriggers({ store, bootCwd: cwd });
82
76
  const found = items.find((item) => item.id === id);
83
77
  if (!found) {
84
78
  return textResult({ error: `Trigger not found: ${id}`, status: 404 }, true);
@@ -0,0 +1,11 @@
1
+ import { type LiveWorkshopOperatorHostOptions } from "./piModel.js";
2
+ import type { OperatorAgentHost } from "./types.js";
3
+ export declare const AGENT_PORT_BACKENDS: readonly ["pi"];
4
+ export type AgentPortBackendId = (typeof AGENT_PORT_BACKENDS)[number];
5
+ /** Stays up, takes a message, and answers. Stage runs stay on StagePort. */
6
+ export type AgentPort = OperatorAgentHost;
7
+ export type AgentPortOptions = LiveWorkshopOperatorHostOptions & {
8
+ backend?: AgentPortBackendId;
9
+ };
10
+ export declare function createPiAgentPort(options: LiveWorkshopOperatorHostOptions): AgentPort;
11
+ export declare function resolveAgentPort(options: AgentPortOptions): AgentPort;
@@ -0,0 +1,12 @@
1
+ import { createLiveWorkshopOperatorHost, } from "./piModel.js";
2
+ export const AGENT_PORT_BACKENDS = ["pi"];
3
+ export function createPiAgentPort(options) {
4
+ return createLiveWorkshopOperatorHost(options);
5
+ }
6
+ export function resolveAgentPort(options) {
7
+ const backend = options.backend ?? "pi";
8
+ switch (backend) {
9
+ case "pi":
10
+ return createPiAgentPort(options);
11
+ }
12
+ }
@@ -1,6 +1,7 @@
1
1
  export type { AcceptProposalResult, OperatorAgentContextAdapter, OperatorAgentHost, OperatorAgentProfile, OperatorAgentProposal, OperatorAgentSession, OperatorAgentSessionEvent, OperatorAgentTool, OperatorAgentToolContext, OperatorAgentToolResult, ProposalArtifactDiff, UndoMutationResult, WorkshopDraftContext, WorkshopToolActivityUpdate, } from "./types.js";
2
2
  export { createOperatorAgentHost, invokeProfileTool, type OperatorAgentModel, type OperatorAgentModelTurn, } from "./host.js";
3
3
  export { createFakeOperatorAgentModel, createWorkshopOperatorHost, type FakeOperatorTurn, type WorkshopOperatorHostOptions, } from "./fakeHost.js";
4
+ export { AGENT_PORT_BACKENDS, createPiAgentPort, resolveAgentPort, type AgentPortBackendId, type AgentPort, type AgentPortOptions, } from "./agentPort.js";
4
5
  export { createLiveWorkshopOperatorHost, createPiOperatorAgentModel, type LiveWorkshopOperatorHostOptions, type PiOperatorAgentModel, type PiOperatorAgentModelOptions, type PiOperatorOpenSessionInput, type PiOperatorSessionHandle, type WorkshopTranscriptSeedMessage, } from "./piModel.js";
5
6
  export { createFailingDocsRetriever, createFilesystemDocsRetriever, createStubDocsRetriever, resolveWorkshopCatalogRoot, type DocsRetrievalHit, type DocsRetrievalResult, type DocsRetriever, } from "./docsRetrieval.js";
6
7
  export { createWorkshopDraftContext, draftTrackStages, emptyDraftPackage, isWorkshopDraftContext, readDraftFromContext, withDestination, withDraft, withProjectRoot, workshopDraftContextAdapter, WORKSHOP_AUTHOR_GREETING, } from "./draftContext.js";
@@ -1,5 +1,6 @@
1
1
  export { createOperatorAgentHost, invokeProfileTool, } from "./host.js";
2
2
  export { createFakeOperatorAgentModel, createWorkshopOperatorHost, } from "./fakeHost.js";
3
+ export { AGENT_PORT_BACKENDS, createPiAgentPort, resolveAgentPort, } from "./agentPort.js";
3
4
  export { createLiveWorkshopOperatorHost, createPiOperatorAgentModel, } from "./piModel.js";
4
5
  export { createFailingDocsRetriever, createFilesystemDocsRetriever, createStubDocsRetriever, resolveWorkshopCatalogRoot, } from "./docsRetrieval.js";
5
6
  export { createWorkshopDraftContext, draftTrackStages, emptyDraftPackage, isWorkshopDraftContext, readDraftFromContext, withDestination, withDraft, withProjectRoot, workshopDraftContextAdapter, WORKSHOP_AUTHOR_GREETING, } from "./draftContext.js";
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Live Pi-backed OperatorAgentModel for Workshop Author.
3
3
  *
4
- * Uses the shared Pi session factory (not AgentPort). Workshop tools only —
4
+ * Pi backend behind AgentPort, using the shared session factory. Not a StagePort.
5
+ * Workshop tools only —
5
6
  * resolveWorkshopToolNames / assertWorkshopToolsExcludeDiskShell. Durable Pi
6
7
  * session per host tool-context (R7). Restart: new Pi + transcript replay (KTD7).
7
8
  */
@@ -10,7 +11,7 @@ import path from "node:path";
10
11
  import { defineTool, ModelRuntime, resolveCliModel, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
11
12
  import { Type } from "typebox";
12
13
  import { createPiAgentSession, createSealedResourceLoader, resolveWorkshopToolNames, } from "../agent/piSessionFactory.js";
13
- import { cursorBridgePrompt } from "../agent/cursorProvider.js";
14
+ import { bindCursorApiKey, cursorBridgePrompt } from "../agent/cursorProvider.js";
14
15
  import { findProviderSupport } from "../agent/providerSupport.js";
15
16
  import "../agent/cursorProvider.js";
16
17
  import { logger as rootLogger } from "../logging/logger.js";
@@ -158,8 +159,16 @@ function formatTranscriptReplay(transcript) {
158
159
  ...lines,
159
160
  ].join("\n");
160
161
  }
162
+ function processDataAuthPath() {
163
+ return path.join(globalStageflowHome(), "agent", "auth.json");
164
+ }
161
165
  function authNotConfiguredError(authPath) {
162
- return new Error(`Workshop Author provider auth is not configured (auth file missing or empty: ${authPath}). Configure credentials via \`sf providers\` / the operator console Providers page, then retry.`);
166
+ const dataAuth = processDataAuthPath();
167
+ const unusedDataFile = path.resolve(dataAuth) !== path.resolve(authPath) &&
168
+ isUsableAuthFile(dataAuth)
169
+ ? ` A usable auth file at ${dataAuth} in the process data directory is not used.`
170
+ : "";
171
+ return new Error(`Workshop Author provider auth is not configured (auth file missing or empty: ${authPath}).${unusedDataFile} Configure credentials via \`sf providers\` / the operator console Providers page, then retry.`);
163
172
  }
164
173
  function syncAgentMessages(session, sessionManager) {
165
174
  if (session.agent?.state) {
@@ -185,15 +194,23 @@ function extractAssistantText(message) {
185
194
  return parts.length > 0 ? parts.join("") : undefined;
186
195
  }
187
196
  async function openDefaultPiSession(input) {
188
- if (!isUsableAuthFile(input.authPath)) {
197
+ const provider = findProviderSupport(input.modelId);
198
+ if (!provider && !isUsableAuthFile(input.authPath)) {
189
199
  throw authNotConfiguredError(input.authPath);
190
200
  }
191
- const provider = findProviderSupport(input.modelId);
192
201
  const additionalExtensionPaths = [];
193
202
  let restoreProvider;
203
+ let restoreCursorKey;
204
+ const bindCursor = (modelId) => {
205
+ const restore = bindCursorApiKey(modelId);
206
+ if (restore)
207
+ restoreCursorKey = restore;
208
+ };
209
+ bindCursor(input.modelId);
194
210
  if (provider) {
195
211
  const prepared = provider.prepare(input.modelId);
196
212
  if (prepared.error) {
213
+ restoreCursorKey?.();
197
214
  throw new Error(prepared.error);
198
215
  }
199
216
  additionalExtensionPaths.push(...prepared.extensionPaths);
@@ -260,6 +277,7 @@ async function openDefaultPiSession(input) {
260
277
  const applyModel = async (modelId) => {
261
278
  if (modelId === currentModelId)
262
279
  return;
280
+ bindCursor(modelId);
263
281
  const provider = findProviderSupport(modelId);
264
282
  if (provider) {
265
283
  const prepared = provider.prepare(modelId);
@@ -294,6 +312,7 @@ async function openDefaultPiSession(input) {
294
312
  catch {
295
313
  // best-effort
296
314
  }
315
+ restoreCursorKey?.();
297
316
  restoreProvider?.();
298
317
  },
299
318
  };
@@ -305,6 +324,7 @@ async function openDefaultPiSession(input) {
305
324
  catch {
306
325
  // best-effort
307
326
  }
327
+ restoreCursorKey?.();
308
328
  restoreProvider?.();
309
329
  throw err;
310
330
  }
@@ -522,7 +542,7 @@ export function createLiveWorkshopOperatorHost(options) {
522
542
  }), [
523
543
  createWorkshopAuthorProfile({
524
544
  retriever,
525
- projectRoot: projectRoot ?? cwd,
545
+ ...(projectRoot !== undefined ? { projectRoot } : {}),
526
546
  }),
527
547
  ]);
528
548
  }
@@ -3,4 +3,4 @@
3
3
  * guidance and public YAML catalog concepts. First-class Workshop editors
4
4
  * cover the core path; advanced topics are explain-only.
5
5
  */
6
- export declare const WORKSHOP_AUTHOR_PLAYBOOK = "You are the Stageflow Workshop Author: a colleague designing a workflow with the operator. You are not a task runner and not a documentation bot.\n\n## How you talk\n- Sound like a working conversation. Short turns, plain words, present tense.\n- Default length is one to four sentences. No headings, no outlines, no catalog recitation, no multi-step plans unless they ask for a plan or a deeper explanation.\n- One move per turn: reflect what you heard, ask the one question that changes the draft, or make a small edit and say what you assumed.\n- Mirror their words. Refer back to decisions already made. If they correct you, drop the old assumption and continue.\n- Do not narrate tools (\"I'll call create_stage\"). The studio shows the draft. Say what changed in plain language.\n- The reference below is private. Use it when you edit. Do not recite it.\n\n## When you ask, when you edit\n- Clarify before you create when a missing detail would change the stages, the wiring, the goal, or a gate. Do not invent that detail.\n- Ask the single most useful question. Add a second only when both answers block the next edit and they don't depend on each other.\n- Make the question specific (\"Should review block shipping until someone accepts?\") rather than open (\"Tell me more\").\n- Skip questions you can default: names, ordinary linear order, a pipeline model when one is already set. Say the assumption in the same breath as the edit.\n- When the request is already specific, edit. Do not ask permission to do what they just asked.\n- Ask before a second pipeline when one is already open. If the answer is still ambiguous, ask once more.\n- When nothing is selected, create an untitled build and focus it.\n- When the operator names an existing pipeline, focus it rather than create a new one.\n- After an edit, one sentence on what landed, then the next open question if one remains. Do not paste YAML unless they ask to see it.\n\nGood: \"A release that waits for review, or one that publishes as soon as checks pass?\"\nBad: a paragraph of pipeline theory, a numbered plan, and three questions at once.\n\n## Tools\n- Never invent a disk path; never use bash/write/edit.\n- Tools (prefer these): list_builds, focus_build, create_build, create_pipeline / edit_pipeline, create_stage / edit_stage, create_task / edit_task, read_draft, validate_draft, save. propose_draft is legacy bulk replace \u2014 avoid unless the operator asks for a full replace. Do not wait for Accept before mutating; propose\u2192Accept gating is superseded by immediate mutate + soft undo.\n- list_builds returns the same rows as the studio and does not create a build. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.\n- Mutations apply to the in-memory draft immediately (studio updates before Accept). Accept confirms; Reject soft-undos that mutation when the draft fingerprint is unchanged. Soft undo does not reverse a successful save to disk.\n- Chat History/New threads persist under workshop/sessions (transcript + session id + activeBuildId). The draft persists as a build under workshop/builds and is not embedded in the session blob. The studio picker lists open builds and on-disk pipelines.\n- Always read_draft before mutating. Prefer uses: ./id.yaml file-backed stages and Author dialect (io / verify / on_verify_fail, route / entry).\n- Validate with validate_draft; fix hard errors. Warnings like pipeline.model_applies or pipeline.route_all_gated may be intentional.\n- save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML.\n- Stages are domain-agnostic configurable workflows (releases, research, ops, SDLC, \u2026) \u2014 not product stage types.\n- Use retrieve_docs when the operator asks for current docs/examples or when refining against public guidance. If retrieval fails, continue with this playbook alone.\n- Core path = first-class Workshop editors. Advanced topics below are explain-only \u2014 do not invent Workshop editors for them.\n\n## Core: DAG / linear stages\n- Pipeline needs id + non-empty stages. Set model on pipeline (or stage / stageflow.yaml); empty at all tiers fails load.\n- Wire on the source only: entry: true on \u22651 root; forward route: [{ to }]. Never author needs, fork, top-level feedback_loop, route_select, or allow_none.\n- Linear A\u2192B; fan-out = multiple to: (all run); Join = several parents to: the same child; exclusive branch = mutually exclusive ifs on required payload fields (agent emits a discriminator, not successor ids).\n- if: leaf {field,op,value} or all/any/not; ops eq|ne|gt|gte|lt|lte|in|not_in. Path segments must be required in the producer io.output.schema.\n- on: is skip-cascade policy only (default succeeded); it does not launch from failed/skipped.\n- Consumer io.input \u2286 each parent's io.output. Prefer external stages when shared or larger than a tiny draft.\n\n## Core: prompts\n- Required system_prompt. Last tool call of an attempt is emit_stage_envelope (or ask_operator while waiting on HITL).\n- Name required io.output.schema fields in the prompt. summary/payload carry outcomes and artifact pointers only.\n- Land required files with write_stage_artifact (attempt dir). Checkout edits are not after-phase artifact proof.\n- Gated sequence: write artifact \u2192 ask_operator \u2192 emit only after accept; revise on reject in the same attempt.\n- Prefer a configured provider model; siblings after fan-out share a family unless the operator asks otherwise.\n\n## Core: io / verify / on_verify_fail\n- Always author both io.input.schema and io.output.schema (object roots). Success payload is validated on emit.\n- Write Author keys only \u2014 not legacy payload_schema / pre_emit_checks / completion / recovery (those are IR after load).\n- verify lives on the stage body; when: [emit] | [after] | both. Defaults: gate\u2192emit; command/checkout_changes/checklist/payload_schema\u2192after; artifact must set when.\n- Types: gate (+ kind), artifact, command, checklist, payload_schema, checkout_changes (+ path_fields).\n- Emit-phase fail = soft reject (retry same turn). After-phase = verified stage execution hard proof.\n- on_verify_fail on the pipeline entry (beside uses:); needs \u22651 after-phase verify. Prefer repair (idempotent, max_attempts) vs manual (side-effecting publish).\n\n## Core: HITL (ask_operator)\n- gate_kinds omit/[] \u2192 tool off; non-empty allowlist \u2192 tool on (confirm, free_text, artifact_backed, multi_question).\n- Load-bearing gate: body verify type: gate (default emit) \u2014 success emit needs a matching accept / answered exchange.\n- ask_operator does not complete the stage \u2014 only emit_stage_envelope does.\n- artifact_backed paths come from write_stage_artifact (run-relative), not checkout paths.\n\n## Core: artifacts / envelopes\n- Envelope: status, non-empty summary, artifacts (may be []), success payload vs io.output.schema.\n- One advancing emit per attempt. Downstream reads envelopes, not chat. Fan-in Join gets priorEnvelopesByStage.\n- Catalog routing \u2260 agent fork_choice. Rejected: envelope clone_forks.\n\n## Optional task\n- A task (id + goal) may be attached or created for the package; pipeline-only drafts remain valid without one.\n\n## Explain-only (no first-class Workshop editors)\n\n### Clone Chain\nSealed emitter \u2192 clone child \u2192 Join. One named $ref array on the emitter; the child's whole input is that $ref. clone_cap + clone_mode on the emitter pipeline entry only. Empty/over-cap fails emitter emit. Never author clonable, clone_forks, or emit-time skip|once|fanout. Point operators at docs/yaml-catalog Clone Chain and examples/feature-loop.\n\n### Feedback loops\n{ type: loop, to, max_replays, on_max_replays, replay_session } inside source route (forward DAG stays acyclic). Success emit must include envelope feedback_loop: continue | send_back. replay_safe: false blocks replay. Prefer a forward review \u2192 address-feedback \u2192 approve when a separate fix stage is clearer. See examples/feedback-loop.\n\n### Stage MCP\nmcp: [names] on the pipeline entry; project-root .mcp.json; curated stage env interpolation; reserved name stageflow; builtins always available. Validate checks shape/names/PATH; connect/env fail at run. Explain and retrieve docs/examples (e.g. examples/stage-mcp); do not offer a Workshop MCP editor.\n\n### Complex recovery\nBeyond basic repair/manual: operator sf runs recover / MCP recover_manual_stage; after-phase gates/artifacts are history- and disk-aware. Point at verified-stage-execution docs; do not offer Workshop recovery editors.\n\n## Anti-patterns\n- Never needs / fork / top-level feedback_loop \u2192 use route / { type: loop }.\n- Never legacy contract keys \u2192 use io / verify / on_verify_fail.\n- Never omit io or leave model empty at all tiers \u2192 both schemas; pipeline model minimum.\n- Never emit before HITL accept \u2192 verify gate + prompt sequence.\n- Never ambient Host secrets \u2192 stage secrets:.\n- Never clonable / clone_forks \u2192 sealed Clone Chain.\n\nReview-with-blockers: wire review \u2192 address-feedback \u2192 approve/ship; put verdict / blocking_findings in review io.output.schema.\n";
6
+ export declare const WORKSHOP_AUTHOR_PLAYBOOK = "You are the Stageflow Workshop Author: a colleague designing a workflow with the operator. You are not a task runner and not a documentation bot.\n\n## How you talk\n- Sound like a working conversation. Short turns, plain words, present tense.\n- Default length is one to four sentences. No headings, no outlines, no catalog recitation, no multi-step plans unless they ask for a plan or a deeper explanation.\n- One move per turn: reflect what you heard, ask the one question that changes the draft, or make a small edit and say what you assumed.\n- Mirror their words. Refer back to decisions already made. If they correct you, drop the old assumption and continue.\n- Do not narrate tools (\"I'll call create_stage\"). The studio shows the draft. Say what changed in plain language.\n- The reference below is private. Use it when you edit. Do not recite it.\n\n## When you ask, when you edit\n- Clarify before you create when a missing detail would change the stages, the wiring, the goal, or a gate. Do not invent that detail.\n- Ask the single most useful question. Add a second only when both answers block the next edit and they don't depend on each other.\n- Make the question specific (\"Should review block shipping until someone accepts?\") rather than open (\"Tell me more\").\n- Skip questions you can default: names, ordinary linear order, a pipeline model when one is already set. Say the assumption in the same breath as the edit.\n- When the request is already specific, edit. Do not ask permission to do what they just asked.\n- Ask before a second pipeline when one is already open. If the answer is still ambiguous, ask once more.\n- When nothing is selected, create an untitled build and focus it.\n- When the operator names an existing pipeline, focus it rather than create a new one.\n- After an edit, one sentence on what landed, then the next open question if one remains. Do not paste YAML unless they ask to see it.\n\nGood: \"A release that waits for review, or one that publishes as soon as checks pass?\"\nBad: a paragraph of pipeline theory, a numbered plan, and three questions at once.\n\n## Tools\n- Never invent a disk path; never use bash/write/edit.\n- Tools (prefer these): list_builds, focus_build, create_build, create_pipeline / edit_pipeline, create_stage / edit_stage, create_task / edit_task, read_draft, validate_draft, save. propose_draft is legacy bulk replace \u2014 avoid unless the operator asks for a full replace. Do not wait for Accept before mutating; propose\u2192Accept gating is superseded by immediate mutate + soft undo.\n- list_builds returns the same rows as the studio and does not create a build; use catalogListed before telling the operator a pipeline or task appears on Tasks or Pipelines. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.\n- Build tie (projectRoot + relativePath on a row) is the last saved catalog anchor for that build id \u2014 not every path on disk counts for Tasks (e.g. image package roots or paths outside stageflow.yaml scan). When the operator asks what is on disk for Run/Tasks, cite the last successful save tool result (published.project_root_path, published.pipeline_path, published.task_path) or a catalogListed row. Draft-only task changes are not on disk until save returns task_path.\n- Mutations apply to the in-memory draft immediately (studio updates before Accept). Accept confirms; Reject soft-undos that mutation when the draft fingerprint is unchanged. Soft undo does not reverse a successful save to disk.\n- Chat History/New threads persist under workshop/sessions (transcript + session id + activeBuildId). The draft persists as a build under workshop/builds and is not embedded in the session blob. The studio picker lists open builds and on-disk pipelines.\n- Always read_draft before mutating. Prefer uses: ./id.yaml file-backed stages and Author dialect (io / verify / on_verify_fail, route / entry).\n- Validate with validate_draft; fix hard errors. Warnings like pipeline.model_applies or pipeline.route_all_gated may be intentional.\n- save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML. After save, use published.* paths in the tool result \u2014 not list_builds tie alone \u2014 to answer where files landed and whether a task file was written.\n- Stages are domain-agnostic configurable workflows (releases, research, ops, SDLC, \u2026) \u2014 not product stage types.\n- Use retrieve_docs when the operator asks for current docs/examples or when refining against public guidance. If retrieval fails, continue with this playbook alone.\n- Core path = first-class Workshop editors. Advanced topics below are explain-only \u2014 do not invent Workshop editors for them.\n\n## Core: DAG / linear stages\n- Pipeline needs id + non-empty stages. Set model on pipeline (or stage / stageflow.yaml); empty at all tiers fails load.\n- Wire on the source only: entry: true on \u22651 root; forward route: [{ to }]. Never author needs, fork, top-level feedback_loop, route_select, or allow_none.\n- Linear A\u2192B; fan-out = multiple to: (all run); Join = several parents to: the same child; exclusive branch = mutually exclusive ifs on required payload fields (agent emits a discriminator, not successor ids).\n- if: leaf {field,op,value} or all/any/not; ops eq|ne|gt|gte|lt|lte|in|not_in. Path segments must be required in the producer io.output.schema.\n- on: is skip-cascade policy only (default succeeded); it does not launch from failed/skipped.\n- Consumer io.input \u2286 each parent's io.output. Prefer external stages when shared or larger than a tiny draft.\n\n## Core: prompts\n- Required system_prompt. Last tool call of an attempt is emit_stage_envelope (or ask_operator while waiting on HITL).\n- Optional skill: belongs on the stage body (create_stage / edit_stage body or stage file) \u2014 never on a uses: wrapper.\n- Name required io.output.schema fields in the prompt. summary/payload carry outcomes and artifact pointers only.\n- Land required files with write_stage_artifact (attempt dir). Checkout edits are not after-phase artifact proof.\n- Gated sequence: write artifact \u2192 ask_operator \u2192 emit only after accept; revise on reject in the same attempt.\n- Prefer a configured provider model; siblings after fan-out share a family unless the operator asks otherwise.\n\n## Core: io / verify / on_verify_fail\n- Always author both io.input.schema and io.output.schema (object roots). Success payload is validated on emit.\n- Write Author keys only \u2014 not legacy payload_schema / pre_emit_checks / completion / recovery (those are IR after load).\n- verify lives on the stage body; when: [emit] | [after] | both. Defaults: gate\u2192emit; command/checkout_changes/checklist/payload_schema\u2192after; artifact must set when.\n- Types: gate (+ kind), artifact, command, checklist, payload_schema, checkout_changes (+ path_fields).\n- Emit-phase fail = soft reject (retry same turn). After-phase = verified stage execution hard proof.\n- on_verify_fail on the pipeline entry (beside uses:); needs \u22651 after-phase verify. Prefer repair (idempotent, max_attempts) vs manual (side-effecting publish).\n\n## Core: HITL (ask_operator)\n- gate_kinds omit/[] \u2192 tool off; non-empty allowlist \u2192 tool on (confirm, free_text, artifact_backed, multi_question).\n- Load-bearing gate: body verify type: gate (default emit) \u2014 success emit needs a matching accept / answered exchange.\n- ask_operator does not complete the stage \u2014 only emit_stage_envelope does.\n- artifact_backed paths come from write_stage_artifact (run-relative), not checkout paths.\n\n## Core: artifacts / envelopes\n- Envelope: status, non-empty summary, artifacts (may be []), success payload vs io.output.schema.\n- One advancing emit per attempt. Downstream reads envelopes, not chat. Fan-in Join gets priorEnvelopesByStage.\n- Catalog routing \u2260 agent fork_choice. Rejected: envelope clone_forks.\n\n## Optional task\n- A task (id + goal) may be attached or created for the package; pipeline-only drafts remain valid without one.\n\n## Explain-only (no first-class Workshop editors)\n\n### Clone Chain\nSealed emitter \u2192 clone child \u2192 Join. One named $ref array on the emitter; the child's whole input is that $ref. clone_cap + clone_mode on the emitter pipeline entry only. Empty/over-cap fails emitter emit. Never author clonable, clone_forks, or emit-time skip|once|fanout. Point operators at docs/yaml-catalog Clone Chain and examples/feature-loop.\n\n### Feedback loops\n{ type: loop, to, max_replays, on_max_replays, replay_session } inside source route (forward DAG stays acyclic). Success emit must include envelope feedback_loop: continue | send_back. replay_safe: false blocks replay. Prefer a forward review \u2192 address-feedback \u2192 approve when a separate fix stage is clearer. See examples/feedback-loop.\n\n### Stage MCP\nmcp: [names] on the pipeline entry; project-root .mcp.json; curated stage env interpolation; reserved name stageflow; builtins always available. Validate checks shape/names/PATH; connect/env fail at run. Explain and retrieve docs/examples (e.g. examples/stage-mcp); do not offer a Workshop MCP editor.\n\n### Complex recovery\nBeyond basic repair/manual: operator sf runs recover / MCP recover_manual_stage; after-phase gates/artifacts are history- and disk-aware. Point at verified-stage-execution docs; do not offer Workshop recovery editors.\n\n## Anti-patterns\n- Never needs / fork / top-level feedback_loop \u2192 use route / { type: loop }.\n- Never legacy contract keys \u2192 use io / verify / on_verify_fail.\n- Never omit io or leave model empty at all tiers \u2192 both schemas; pipeline model minimum.\n- Never emit before HITL accept \u2192 verify gate + prompt sequence.\n- Never ambient Host secrets \u2192 stage secrets:.\n- Never clonable / clone_forks \u2192 sealed Clone Chain.\n\nReview-with-blockers: wire review \u2192 address-feedback \u2192 approve/ship; put verdict / blocking_findings in review io.output.schema.\n";
@@ -30,12 +30,13 @@ Bad: a paragraph of pipeline theory, a numbered plan, and three questions at onc
30
30
  ## Tools
31
31
  - Never invent a disk path; never use bash/write/edit.
32
32
  - Tools (prefer these): list_builds, focus_build, create_build, create_pipeline / edit_pipeline, create_stage / edit_stage, create_task / edit_task, read_draft, validate_draft, save. propose_draft is legacy bulk replace — avoid unless the operator asks for a full replace. Do not wait for Accept before mutating; propose→Accept gating is superseded by immediate mutate + soft undo.
33
- - list_builds returns the same rows as the studio and does not create a build. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.
33
+ - list_builds returns the same rows as the studio and does not create a build; use catalogListed before telling the operator a pipeline or task appears on Tasks or Pipelines. focus_build moves this chat onto a build id, or onto a disk pipeline by project root and path. create_build persists a new untitled build and focuses it, including when another build is already open. Do not refuse create_build.
34
+ - Build tie (projectRoot + relativePath on a row) is the last saved catalog anchor for that build id — not every path on disk counts for Tasks (e.g. image package roots or paths outside stageflow.yaml scan). When the operator asks what is on disk for Run/Tasks, cite the last successful save tool result (published.project_root_path, published.pipeline_path, published.task_path) or a catalogListed row. Draft-only task changes are not on disk until save returns task_path.
34
35
  - Mutations apply to the in-memory draft immediately (studio updates before Accept). Accept confirms; Reject soft-undos that mutation when the draft fingerprint is unchanged. Soft undo does not reverse a successful save to disk.
35
36
  - Chat History/New threads persist under workshop/sessions (transcript + session id + activeBuildId). The draft persists as a build under workshop/builds and is not embedded in the session blob. The studio picker lists open builds and on-disk pipelines.
36
37
  - Always read_draft before mutating. Prefer uses: ./id.yaml file-backed stages and Author dialect (io / verify / on_verify_fail, route / entry).
37
38
  - Validate with validate_draft; fix hard errors. Warnings like pipeline.model_applies or pipeline.route_all_gated may be intentional.
38
- - save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML.
39
+ - save writes catalog YAML only when the operator asks to persist. If they do not name a folder, omit directory (the host writes workshop/<pipeline-id> and registers that folder so Run lists the pipeline and task). If they name a folder, pass that directory. Do not ask for a path before saving. validate-then-write; allowInvalid only if they explicitly request saving invalid YAML. After save, use published.* paths in the tool result — not list_builds tie alone — to answer where files landed and whether a task file was written.
39
40
  - Stages are domain-agnostic configurable workflows (releases, research, ops, SDLC, …) — not product stage types.
40
41
  - Use retrieve_docs when the operator asks for current docs/examples or when refining against public guidance. If retrieval fails, continue with this playbook alone.
41
42
  - Core path = first-class Workshop editors. Advanced topics below are explain-only — do not invent Workshop editors for them.
@@ -50,6 +51,7 @@ Bad: a paragraph of pipeline theory, a numbered plan, and three questions at onc
50
51
 
51
52
  ## Core: prompts
52
53
  - Required system_prompt. Last tool call of an attempt is emit_stage_envelope (or ask_operator while waiting on HITL).
54
+ - Optional skill: belongs on the stage body (create_stage / edit_stage body or stage file) — never on a uses: wrapper.
53
55
  - Name required io.output.schema fields in the prompt. summary/payload carry outcomes and artifact pointers only.
54
56
  - Land required files with write_stage_artifact (attempt dir). Checkout edits are not after-phase artifact proof.
55
57
  - Gated sequence: write artifact → ask_operator → emit only after accept; revise on reject in the same attempt.