stageflow 0.33.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 (313) hide show
  1. package/README.md +3 -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/piAdapter.d.ts +2 -2
  12. package/dist/agent/piAdapter.js +116 -15
  13. package/dist/agent/piIsolatedMcp.d.ts +1 -0
  14. package/dist/agent/piIsolatedMcp.js +19 -1
  15. package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
  16. package/dist/agent/piIsolatedMcpProbe.js +20 -0
  17. package/dist/agent/port.d.ts +2 -2
  18. package/dist/agent/providerAuth.js +36 -3
  19. package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -8
  20. package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +11 -11
  21. package/dist/agent/stageTurnDriver.d.ts +33 -0
  22. package/dist/agent/stageTurnDriver.js +64 -0
  23. package/dist/agent/toolOutputBudget.d.ts +46 -0
  24. package/dist/agent/toolOutputBudget.js +97 -0
  25. package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
  26. package/dist/agent/toolOutputBudgetExtension.js +141 -0
  27. package/dist/agent/toolOutputSpill.d.ts +34 -0
  28. package/dist/agent/toolOutputSpill.js +85 -0
  29. package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
  30. package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
  31. package/dist/browser/anchor.d.ts +2 -0
  32. package/dist/browser/anchor.js +2 -1
  33. package/dist/browser/auditSink.d.ts +8 -0
  34. package/dist/browser/browserHost.d.ts +29 -0
  35. package/dist/browser/browserSweep.d.ts +4 -1
  36. package/dist/browser/browserSweep.js +7 -0
  37. package/dist/browser/browserTeardown.d.ts +1 -0
  38. package/dist/browser/browserTeardown.js +32 -2
  39. package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
  40. package/dist/browser/containerBrowserEndpoint.js +57 -0
  41. package/dist/browser/containerBrowserHost.d.ts +24 -0
  42. package/dist/browser/containerBrowserHost.js +147 -0
  43. package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
  44. package/dist/browser/dockerSandboxOrchestrator.js +142 -0
  45. package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
  46. package/dist/browser/fakeLiveViewRelay.js +94 -0
  47. package/dist/browser/fakeLiveViewSource.d.ts +6 -0
  48. package/dist/browser/fakeLiveViewSource.js +16 -0
  49. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
  50. package/dist/browser/fakeRemoteBrowserHost.js +2 -1
  51. package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
  52. package/dist/browser/fakeSandboxOrchestrator.js +44 -0
  53. package/dist/browser/gateHandoff.d.ts +0 -2
  54. package/dist/browser/gateHandoff.js +5 -11
  55. package/dist/browser/hostCapabilities.d.ts +23 -0
  56. package/dist/browser/hostCapabilities.js +23 -0
  57. package/dist/browser/humanLogin.d.ts +2 -2
  58. package/dist/browser/humanLogin.js +11 -2
  59. package/dist/browser/liveViewPersisted.d.ts +11 -0
  60. package/dist/browser/liveViewPersisted.js +22 -0
  61. package/dist/browser/liveViewRelay.d.ts +76 -0
  62. package/dist/browser/liveViewRelay.js +1 -0
  63. package/dist/browser/liveViewSessions.d.ts +25 -0
  64. package/dist/browser/liveViewSessions.js +139 -0
  65. package/dist/browser/liveViewSource.d.ts +25 -0
  66. package/dist/browser/liveViewSource.js +7 -0
  67. package/dist/browser/liveViewTickets.d.ts +46 -0
  68. package/dist/browser/liveViewTickets.js +122 -0
  69. package/dist/browser/localBrowserHost.d.ts +10 -0
  70. package/dist/browser/localBrowserHost.js +53 -7
  71. package/dist/browser/ownerScope.d.ts +10 -0
  72. package/dist/browser/ownerScope.js +6 -0
  73. package/dist/browser/profileStore.d.ts +5 -2
  74. package/dist/browser/profileStore.js +3 -5
  75. package/dist/browser/sandboxOrchestrator.d.ts +60 -0
  76. package/dist/browser/sandboxOrchestrator.js +8 -0
  77. package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
  78. package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
  79. package/dist/browser/stageBrowserEnv.d.ts +7 -0
  80. package/dist/browser/stageBrowserEnv.js +53 -15
  81. package/dist/browser/stageHandoff.d.ts +47 -0
  82. package/dist/browser/stageHandoff.js +101 -0
  83. package/dist/browser/stageProfileLock.d.ts +1 -0
  84. package/dist/browser/stageProfileLock.js +1 -3
  85. package/dist/browser/volumeProfileStore.d.ts +7 -0
  86. package/dist/browser/volumeProfileStore.js +35 -0
  87. package/dist/cli/browserCommand.d.ts +3 -1
  88. package/dist/cli/browserCommand.js +25 -20
  89. package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
  90. package/dist/cli/resolveCliProjectRoot.js +27 -0
  91. package/dist/cli/runCommand.d.ts +1 -1
  92. package/dist/cli/runCommand.js +59 -17
  93. package/dist/cli/skillsCommand.d.ts +20 -0
  94. package/dist/cli/skillsCommand.js +74 -31
  95. package/dist/cli/validateCommand.d.ts +1 -1
  96. package/dist/cli/validateCommand.js +22 -4
  97. package/dist/cli.js +4 -4
  98. package/dist/config/browseCatalog.d.ts +2 -0
  99. package/dist/config/browseCatalog.js +6 -0
  100. package/dist/config/catalogRelativePath.d.ts +4 -0
  101. package/dist/config/catalogRelativePath.js +37 -9
  102. package/dist/config/createStage.d.ts +1 -0
  103. package/dist/config/createStage.js +11 -2
  104. package/dist/config/createTask.d.ts +30 -0
  105. package/dist/config/createTask.js +161 -0
  106. package/dist/config/createTrigger.d.ts +15 -0
  107. package/dist/config/createTrigger.js +32 -25
  108. package/dist/config/draftPackage.d.ts +41 -0
  109. package/dist/config/draftPackage.js +216 -23
  110. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  111. package/dist/config/ensureCatalogScanRoot.js +6 -2
  112. package/dist/config/findTaskInCatalog.d.ts +33 -0
  113. package/dist/config/findTaskInCatalog.js +69 -0
  114. package/dist/config/hostConfig.d.ts +21 -0
  115. package/dist/config/hostConfig.js +113 -1
  116. package/dist/config/importTask.d.ts +19 -0
  117. package/dist/config/importTask.js +133 -0
  118. package/dist/config/installSkillRequest.d.ts +40 -0
  119. package/dist/config/installSkillRequest.js +286 -0
  120. package/dist/config/loadOutcome.d.ts +11 -6
  121. package/dist/config/loadPipeline.js +0 -1
  122. package/dist/config/loadStage.js +2 -0
  123. package/dist/config/loadStageflowManifest.js +4 -1
  124. package/dist/config/loadTask.js +2 -0
  125. package/dist/config/loadTrigger.js +2 -0
  126. package/dist/config/mergePipelineIncludes.js +3 -0
  127. package/dist/config/normalizePipelineStageEntry.js +10 -1
  128. package/dist/config/printTargetYaml.js +0 -1
  129. package/dist/config/publishDraftPackage.d.ts +5 -1
  130. package/dist/config/publishDraftPackage.js +4 -1
  131. package/dist/config/resolveCatalogRoots.d.ts +4 -1
  132. package/dist/config/resolveCatalogRoots.js +18 -1
  133. package/dist/config/skillUsageIndex.d.ts +6 -0
  134. package/dist/config/skillUsageIndex.js +39 -0
  135. package/dist/config/stageBrowser.js +13 -0
  136. package/dist/config/triggerAdapterStatus.d.ts +11 -0
  137. package/dist/config/triggerAdapterStatus.js +90 -0
  138. package/dist/config/triggerCatalog.d.ts +41 -2
  139. package/dist/config/triggerCatalog.js +107 -15
  140. package/dist/config/updateTask.d.ts +30 -0
  141. package/dist/config/updateTask.js +119 -0
  142. package/dist/config/updateTrigger.d.ts +38 -0
  143. package/dist/config/updateTrigger.js +220 -0
  144. package/dist/config/validateCatalog.d.ts +6 -2
  145. package/dist/config/validateCatalog.js +48 -7
  146. package/dist/config/workshopCatalogFilenames.d.ts +37 -0
  147. package/dist/config/workshopCatalogFilenames.js +125 -0
  148. package/dist/config/workshopCatalogRepair.d.ts +7 -0
  149. package/dist/config/workshopCatalogRepair.js +144 -0
  150. package/dist/config/yamlParsePosition.d.ts +4 -0
  151. package/dist/config/yamlParsePosition.js +9 -0
  152. package/dist/hosted/runtime.d.ts +2 -2
  153. package/dist/hosted/types.d.ts +1 -1
  154. package/dist/hosted/types.js +1 -1
  155. package/dist/index.d.ts +3 -2
  156. package/dist/index.js +2 -1
  157. package/dist/mcp/getStartedTool.js +15 -6
  158. package/dist/mcp/triggerTools.js +2 -8
  159. package/dist/operatorAgent/agentPort.d.ts +11 -0
  160. package/dist/operatorAgent/agentPort.js +12 -0
  161. package/dist/operatorAgent/index.d.ts +1 -0
  162. package/dist/operatorAgent/index.js +1 -0
  163. package/dist/operatorAgent/piModel.js +26 -6
  164. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  165. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
  166. package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
  167. package/dist/package-meta.d.ts +1 -1
  168. package/dist/package-meta.js +1 -1
  169. package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
  170. package/dist/project/ensureProjectRootsFromEnv.js +22 -0
  171. package/dist/project/globalHome.d.ts +1 -0
  172. package/dist/project/globalHome.js +18 -0
  173. package/dist/project/workspaceCatalog.d.ts +10 -0
  174. package/dist/project/workspaceCatalog.js +91 -0
  175. package/dist/runstore/backup.js +14 -4
  176. package/dist/runstore/port.d.ts +11 -1
  177. package/dist/runstore/restore.js +5 -2
  178. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
  179. package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
  180. package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
  181. package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
  182. package/dist/runstore/sqlite/migrations/index.js +2 -0
  183. package/dist/runstore/stageSnapshot.d.ts +3 -1
  184. package/dist/runstore/stageSnapshot.js +19 -0
  185. package/dist/runstore/workspaceLayout.js +27 -0
  186. package/dist/runstore/writeContainedArtifact.d.ts +1 -0
  187. package/dist/runstore/writeContainedArtifact.js +52 -0
  188. package/dist/runtime/credentialBinding.js +14 -6
  189. package/dist/runtime/emailSource.js +20 -28
  190. package/dist/runtime/githubPollSource.js +20 -28
  191. package/dist/runtime/pipelineRunner.d.ts +4 -4
  192. package/dist/runtime/pipelineScheduler.d.ts +2 -2
  193. package/dist/runtime/pipelineScheduler.js +3 -0
  194. package/dist/runtime/resumeReconstruct.d.ts +2 -2
  195. package/dist/runtime/resumeTimedOut.d.ts +2 -2
  196. package/dist/runtime/runManager.d.ts +18 -2
  197. package/dist/runtime/runManager.js +95 -30
  198. package/dist/runtime/runRetryCoordinator.d.ts +2 -2
  199. package/dist/runtime/scheduleSource.js +15 -24
  200. package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
  201. package/dist/runtime/stageAttemptBootstrap.js +10 -1
  202. package/dist/runtime/stageProcessLauncher.js +2 -2
  203. package/dist/runtime/stageRoots.js +8 -1
  204. package/dist/runtime/stageRunner.d.ts +2 -2
  205. package/dist/runtime/stageRunner.js +8 -2
  206. package/dist/runtime/stageWorker.js +2 -2
  207. package/dist/runtime/triggerPort.d.ts +5 -3
  208. package/dist/runtime/triggerRunner.d.ts +1 -0
  209. package/dist/runtime/triggerRunner.js +27 -20
  210. package/dist/runtime/webhookAccept.d.ts +25 -0
  211. package/dist/runtime/webhookAccept.js +56 -0
  212. package/dist/server/bootstrap.d.ts +7 -2
  213. package/dist/server/bootstrap.js +15 -1
  214. package/dist/server/controlToken.d.ts +1 -1
  215. package/dist/server/controlToken.js +18 -2
  216. package/dist/server/healthSurfaces.d.ts +12 -0
  217. package/dist/server/healthSurfaces.js +27 -6
  218. package/dist/server/http.d.ts +19 -10
  219. package/dist/server/http.js +605 -85
  220. package/dist/server/liveViewPath.d.ts +9 -0
  221. package/dist/server/liveViewPath.js +8 -0
  222. package/dist/server/liveViewRoutes.d.ts +58 -0
  223. package/dist/server/liveViewRoutes.js +565 -0
  224. package/dist/server/mcpHost.d.ts +4 -2
  225. package/dist/server/mcpHost.js +2 -0
  226. package/dist/server/readCatalogFile.d.ts +29 -0
  227. package/dist/server/readCatalogFile.js +215 -0
  228. package/dist/server/triggerRoutes.d.ts +1 -0
  229. package/dist/server/triggerRoutes.js +200 -73
  230. package/dist/tools/readEmail.d.ts +3 -3
  231. package/dist/tools/writeStageArtifact.d.ts +9 -0
  232. package/dist/tools/writeStageArtifact.js +2 -47
  233. package/dist/types/pipeline.d.ts +1 -1
  234. package/dist/types/stage.d.ts +1 -1
  235. package/dist/types/stageflowManifest.d.ts +1 -1
  236. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  237. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  238. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  239. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  240. package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  241. package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  242. package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  243. package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  244. package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  245. package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  246. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  247. package/dist/ui/assets/index-BoFPkKFg.js +256 -0
  248. package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.css} +1 -1
  249. package/dist/ui/index.html +4 -4
  250. package/dist/workshop/autosave.d.ts +2 -2
  251. package/dist/workshop/autosave.js +4 -6
  252. package/dist/workshop/buildStore.d.ts +2 -0
  253. package/dist/workshop/chatAttachments.d.ts +27 -0
  254. package/dist/workshop/chatAttachments.js +98 -0
  255. package/dist/workshop/chatTurn.d.ts +5 -2
  256. package/dist/workshop/chatTurn.js +22 -18
  257. package/dist/workshop/sessionMutations.d.ts +17 -0
  258. package/dist/workshop/sessionMutations.js +107 -0
  259. package/dist/workshop/sessionStore.d.ts +10 -0
  260. package/dist/workshop/sessionStore.js +52 -0
  261. package/dist/workshop/workshopPin.d.ts +4 -3
  262. package/dist/workshop/workshopPin.js +26 -6
  263. package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
  264. package/dist/workshop/workshopTranscriptTurn.js +46 -0
  265. package/examples/README.md +1 -1
  266. package/examples/archify-on-pr/README.md +1 -1
  267. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
  268. package/examples/archify-on-pr/author-diagrams.yaml +1 -0
  269. package/examples/conditional-fork/branch-a.yaml +1 -1
  270. package/examples/conditional-fork/decide.yaml +2 -1
  271. package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
  272. package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
  273. package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
  274. package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
  275. package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
  276. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
  277. package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
  278. package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
  279. package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
  280. package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
  281. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
  282. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
  283. package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
  284. package/examples/hello-world/hello.pipeline.yaml +2 -2
  285. package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
  286. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
  287. package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
  288. package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
  289. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
  290. package/examples/stageflow.yaml +6 -0
  291. package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
  292. package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
  293. package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
  294. package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
  295. package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
  296. package/examples/triggers-readiness/README.md +48 -0
  297. package/examples/triggers-readiness/readiness-emit.yaml +18 -0
  298. package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
  299. package/examples/triggers-readiness/readiness.task.yaml +6 -0
  300. package/examples/triggers-readiness/stageflow.yaml +13 -0
  301. package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
  302. package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
  303. package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
  304. package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
  305. package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
  306. package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
  307. package/package.json +1 -1
  308. package/skills/stageflow/references/control-surface.md +1 -1
  309. package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
  310. package/skills/stageflow-run/SKILL.md +1 -1
  311. package/skills/stageflow-run/references/mcp-call.md +1 -1
  312. package/skills/stageflow-setup/SKILL.md +1 -1
  313. package/dist/ui/assets/index-59cjF0OA.js +0 -133
@@ -5,16 +5,16 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <script>
7
7
  try {
8
- var t = localStorage.getItem("sf-theme");
9
- if (t && t !== "system") document.documentElement.setAttribute("data-theme", t);
8
+ document.documentElement.setAttribute("data-theme", "dark");
9
+ document.documentElement.setAttribute("data-redesign", "on");
10
10
  } catch (e) {}
11
11
  </script>
12
12
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13
13
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap" />
14
14
  <link rel="icon" type="image/svg+xml" href="/stageflow-icon.svg" />
15
15
  <title>Stageflow</title>
16
- <script type="module" crossorigin src="/assets/index-59cjF0OA.js"></script>
17
- <link rel="stylesheet" crossorigin href="/assets/index-Bv4p1aPK.css">
16
+ <script type="module" crossorigin src="/assets/index-BoFPkKFg.js"></script>
17
+ <link rel="stylesheet" crossorigin href="/assets/index-CIPbbBUi.css">
18
18
  </head>
19
19
  <body>
20
20
  <div id="root"></div>
@@ -22,11 +22,11 @@ export type WorkshopAutosaveRecord = {
22
22
  savedPath?: string | null;
23
23
  savedTaskPath?: string | null;
24
24
  diskFingerprints?: Record<string, string>;
25
+ projectRoot?: string | null;
25
26
  };
26
27
  export declare function workshopAutosaveSlotKey(pipelinePath: string | null | undefined): string;
27
- export declare function resolveWorkshopAutosaveStoreRoot(options: {
28
+ export declare function resolveWorkshopAutosaveStoreRoot(_options?: {
28
29
  projectRoot?: string | null;
29
- isGitProject?: boolean;
30
30
  }): string;
31
31
  export declare function workshopAutosaveDir(storeRoot: string): string;
32
32
  export declare function workshopAutosaveFilePath(storeRoot: string, key: string): string;
@@ -1,7 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { globalStageflowHome } from "../project/globalHome.js";
4
- import { storeRootFor } from "../runstore/paths.js";
5
4
  export const WORKSHOP_UNTITLED_AUTOSAVE_KEY = "__untitled__";
6
5
  export function workshopAutosaveSlotKey(pipelinePath) {
7
6
  const trimmed = pipelinePath?.trim();
@@ -9,11 +8,7 @@ export function workshopAutosaveSlotKey(pipelinePath) {
9
8
  return WORKSHOP_UNTITLED_AUTOSAVE_KEY;
10
9
  return trimmed.replace(/\\/g, "/");
11
10
  }
12
- export function resolveWorkshopAutosaveStoreRoot(options) {
13
- const root = options.projectRoot?.trim();
14
- if (root && options.isGitProject !== false) {
15
- return storeRootFor(path.resolve(root));
16
- }
11
+ export function resolveWorkshopAutosaveStoreRoot(_options) {
17
12
  return globalStageflowHome();
18
13
  }
19
14
  export function workshopAutosaveDir(storeRoot) {
@@ -114,6 +109,9 @@ export function parseWorkshopAutosaveRecord(value) {
114
109
  }
115
110
  record.diskFingerprints = fps;
116
111
  }
112
+ if (value.projectRoot === null || typeof value.projectRoot === "string") {
113
+ record.projectRoot = value.projectRoot;
114
+ }
117
115
  return record;
118
116
  }
119
117
  export function readWorkshopAutosave(storeRoot, key) {
@@ -55,6 +55,8 @@ export type WorkshopPickerRow = {
55
55
  name: string;
56
56
  projectRoot: string | null;
57
57
  relativePath: string | null;
58
+ /** True when this row's pipeline path appears in the catalog scan (Tasks/Pipelines lists). */
59
+ catalogListed?: boolean;
58
60
  };
59
61
  export type FocusUnboundWorkshopBuildInput = {
60
62
  projectRoot: string;
@@ -0,0 +1,27 @@
1
+ export declare const WORKSHOP_CHAT_ATTACHMENT_MAX_FILES = 5;
2
+ export declare const WORKSHOP_CHAT_ATTACHMENT_MAX_BYTES: number;
3
+ export declare const WORKSHOP_DOCS_REFERENCE_PATHS: readonly ["docs/yaml-catalog.md", "skills/stageflow-author/references/catalog-mapping.md"];
4
+ export type WorkshopDocsReference = {
5
+ path: string;
6
+ text: string;
7
+ };
8
+ export type WorkshopChatAttachment = {
9
+ name: string;
10
+ mediaType: string;
11
+ size: number;
12
+ content: string;
13
+ };
14
+ export type WorkshopChatContext = {
15
+ docs?: boolean;
16
+ };
17
+ export type ParseWorkshopChatAttachmentsResult = {
18
+ ok: true;
19
+ attachments: WorkshopChatAttachment[];
20
+ } | {
21
+ ok: false;
22
+ error: string;
23
+ };
24
+ export declare function parseWorkshopChatAttachments(value: unknown): ParseWorkshopChatAttachmentsResult;
25
+ export declare function parseWorkshopChatContext(value: unknown): WorkshopChatContext;
26
+ export declare function readWorkshopDocsReference(rootDir?: string): Promise<WorkshopDocsReference | null>;
27
+ export declare function buildWorkshopChatPrompt(message: string, attachments: readonly WorkshopChatAttachment[], docsReference: WorkshopDocsReference | null): string;
@@ -0,0 +1,98 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { resolveWorkshopCatalogRoot } from "../operatorAgent/docsRetrieval.js";
4
+ export const WORKSHOP_CHAT_ATTACHMENT_MAX_FILES = 5;
5
+ export const WORKSHOP_CHAT_ATTACHMENT_MAX_BYTES = 256 * 1024;
6
+ export const WORKSHOP_DOCS_REFERENCE_PATHS = [
7
+ "docs/yaml-catalog.md",
8
+ "skills/stageflow-author/references/catalog-mapping.md",
9
+ ];
10
+ function isPlainObject(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value);
12
+ }
13
+ export function parseWorkshopChatAttachments(value) {
14
+ if (value === undefined || value === null) {
15
+ return { ok: true, attachments: [] };
16
+ }
17
+ if (!Array.isArray(value)) {
18
+ return { ok: false, error: "attachments must be an array" };
19
+ }
20
+ if (value.length > WORKSHOP_CHAT_ATTACHMENT_MAX_FILES) {
21
+ return {
22
+ ok: false,
23
+ error: `At most ${WORKSHOP_CHAT_ATTACHMENT_MAX_FILES} attachments per message`,
24
+ };
25
+ }
26
+ const attachments = [];
27
+ for (const [index, entry] of value.entries()) {
28
+ if (!isPlainObject(entry)) {
29
+ return { ok: false, error: `attachments[${index}] must be an object` };
30
+ }
31
+ const name = typeof entry.name === "string" ? entry.name.replace(/\s+/g, " ").trim() : "";
32
+ if (!name) {
33
+ return { ok: false, error: `attachments[${index}].name is required` };
34
+ }
35
+ if (typeof entry.content !== "string") {
36
+ return {
37
+ ok: false,
38
+ error: `attachments[${index}].content must be a string (${name})`,
39
+ };
40
+ }
41
+ if (entry.mediaType !== undefined && typeof entry.mediaType !== "string") {
42
+ return {
43
+ ok: false,
44
+ error: `attachments[${index}].mediaType must be a string (${name})`,
45
+ };
46
+ }
47
+ const size = Buffer.byteLength(entry.content, "utf8");
48
+ if (size > WORKSHOP_CHAT_ATTACHMENT_MAX_BYTES) {
49
+ return {
50
+ ok: false,
51
+ error: `Attachment ${name} exceeds ${WORKSHOP_CHAT_ATTACHMENT_MAX_BYTES / 1024} KB`,
52
+ };
53
+ }
54
+ attachments.push({
55
+ name,
56
+ mediaType: entry.mediaType?.trim() || "text/plain",
57
+ size,
58
+ content: entry.content,
59
+ });
60
+ }
61
+ return { ok: true, attachments };
62
+ }
63
+ export function parseWorkshopChatContext(value) {
64
+ return isPlainObject(value) && value.docs === true ? { docs: true } : {};
65
+ }
66
+ function fenceFor(content) {
67
+ const longest = (content.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
68
+ return "`".repeat(Math.max(3, longest + 1));
69
+ }
70
+ function fencedBlock(heading, content) {
71
+ const fence = fenceFor(content);
72
+ const body = content.endsWith("\n") ? content : `${content}\n`;
73
+ return `${heading}\n${fence}\n${body}${fence}`;
74
+ }
75
+ export async function readWorkshopDocsReference(rootDir) {
76
+ const root = resolveWorkshopCatalogRoot(rootDir);
77
+ for (const relPath of WORKSHOP_DOCS_REFERENCE_PATHS) {
78
+ try {
79
+ const text = await readFile(path.join(root, relPath), "utf8");
80
+ if (text.trim())
81
+ return { path: relPath, text };
82
+ }
83
+ catch {
84
+ continue;
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+ export function buildWorkshopChatPrompt(message, attachments, docsReference) {
90
+ const sections = [message];
91
+ for (const attachment of attachments) {
92
+ sections.push(fencedBlock(`Attached file: ${attachment.name}`, attachment.content));
93
+ }
94
+ if (docsReference) {
95
+ sections.push(fencedBlock(`Stageflow YAML authoring reference (${docsReference.path}):`, docsReference.text));
96
+ }
97
+ return sections.join("\n\n");
98
+ }
@@ -1,5 +1,6 @@
1
1
  import type { DraftPackage } from "../config/draftPackage.js";
2
2
  import { type OperatorAgentHost, type OperatorAgentProposal, type OperatorAgentSession, type OperatorAgentSessionEvent, type ProposalArtifactDiff, type WorkshopToolActivityUpdate } from "../operatorAgent/index.js";
3
+ import { type WorkshopChatAttachment, type WorkshopChatContext } from "./chatAttachments.js";
3
4
  import { WorkshopSessionStoreError } from "./sessionStore.js";
4
5
  import { type WorkshopPointerChange } from "./workshopPin.js";
5
6
  export type { WorkshopPointerChange };
@@ -51,8 +52,9 @@ export type WorkshopChatTurnInput = {
51
52
  sessionId: string;
52
53
  draft: DraftPackage;
53
54
  message: string;
54
- /** @deprecated Ignored — mutations apply immediately (Accept = soft undo). */
55
55
  autoApply?: boolean;
56
+ attachments?: WorkshopChatAttachment[];
57
+ context?: WorkshopChatContext;
56
58
  /** Session model override from Workshop chrome. */
57
59
  model?: string | null;
58
60
  /** Settings default from factory settings /api/settings. */
@@ -75,6 +77,8 @@ export type WorkshopChatTurnInput = {
75
77
  onActivity?: (update: WorkshopToolActivityUpdate) => void;
76
78
  /** Fired when this turn changes the session pointer. */
77
79
  onPointerChange?: (frame: WorkshopPointerChange) => void;
80
+ /** Writable catalog root (absolute path) for agent save; host-resolved. */
81
+ projectRoot?: string;
78
82
  };
79
83
  export type WorkshopChatTurnResult = {
80
84
  sessionId: string;
@@ -86,7 +90,6 @@ export type WorkshopChatTurnResult = {
86
90
  * Accept/undo. Name is historical — not a pending gated apply.
87
91
  */
88
92
  pending: WorkshopChatProposalPayload | null;
89
- /** Always false — auto-apply chrome removed; mutations apply immediately. */
90
93
  autoApply: boolean;
91
94
  model: string;
92
95
  /** Build this turn edited. Null when the chat stayed unlinked. */
@@ -1,17 +1,28 @@
1
- import { createWorkshopDraftContext, createWorkshopOperatorHost, isWorkshopDraftContext, readDraftFromContext, withDraft, WORKSHOP_AUTHOR_PROFILE_ID, } from "../operatorAgent/index.js";
1
+ import { createWorkshopDraftContext, createWorkshopOperatorHost, isWorkshopDraftContext, readDraftFromContext, withDraft, withProjectRoot, WORKSHOP_AUTHOR_PROFILE_ID, } from "../operatorAgent/index.js";
2
2
  import { resolveWorkshopModel } from "./modelSettings.js";
3
3
  import { getWorkshopBuild, updateWorkshopBuild } from "./buildStore.js";
4
+ import { buildWorkshopChatPrompt, readWorkshopDocsReference, } from "./chatAttachments.js";
5
+ import { transcriptMessagesForTurn } from "./workshopTranscriptTurn.js";
4
6
  import { appendWorkshopSessionMessages, getWorkshopSession, resolveWorkshopSessionStoreRoot, WorkshopSessionStoreError, } from "./sessionStore.js";
5
7
  import { bindPinnedBuildContext, enqueueSessionWrite, registerLiveWorkshopSession, resetWorkshopPinStateForTests, unregisterLiveWorkshopSession, } from "./workshopPin.js";
6
- function markUnlinkedChatContext(current, sessionId, draft) {
8
+ function markUnlinkedChatContext(current, sessionId, draft, projectRoot) {
7
9
  const base = isWorkshopDraftContext(current)
8
10
  ? { ...current, draft }
9
11
  : { draft };
10
12
  delete base.buildId;
11
13
  base.chatSessionId = sessionId;
12
14
  base.draft = draft;
15
+ if (projectRoot?.trim()) {
16
+ base.projectRoot = projectRoot.trim();
17
+ }
13
18
  return base;
14
19
  }
20
+ function bindChatProjectRoot(context, projectRoot) {
21
+ const trimmed = projectRoot?.trim();
22
+ if (!trimmed || context.projectRoot)
23
+ return context;
24
+ return withProjectRoot(context, trimmed);
25
+ }
15
26
  /**
16
27
  * In-memory map of Workshop session id → open OperatorAgentSession.
17
28
  * Survives across chat turns until process restart (disk transcript remains).
@@ -207,17 +218,6 @@ export function toWorkshopChatWireEvent(event, fallbackDraft) {
207
218
  }
208
219
  return { type: "error", message: event.message };
209
220
  }
210
- function transcriptMessagesForTurn(userMessage, events) {
211
- const messages = [
212
- { role: "user", text: userMessage },
213
- ];
214
- for (const event of events) {
215
- if (event.type === "message") {
216
- messages.push({ role: event.role, text: event.text });
217
- }
218
- }
219
- return messages;
220
- }
221
221
  /**
222
222
  * One Workshop Author turn on a durable Operator Agent Host session.
223
223
  * Requires an existing session store record (New / POST sessions first).
@@ -240,6 +240,8 @@ export async function runWorkshopChatTurn(input) {
240
240
  sessionOverride: input.model,
241
241
  settingsDefault: input.settingsDefault,
242
242
  });
243
+ const attachments = input.attachments ?? [];
244
+ const prompt = buildWorkshopChatPrompt(message, attachments, input.context?.docs === true ? await readWorkshopDocsReference() : null);
243
245
  const registry = input.registry ?? getDefaultRegistry(input.host);
244
246
  if (input.host && registry.host !== input.host) {
245
247
  throw new Error("host does not match workshop chat session registry");
@@ -253,10 +255,10 @@ export async function runWorkshopChatTurn(input) {
253
255
  const draftForOpen = pinnedBuild?.draft ?? input.draft;
254
256
  const { session: agentSession, created } = registry.getOrOpen(sessionId, draftForOpen);
255
257
  if (pinnedBuild) {
256
- agentSession.setContext(bindPinnedBuildContext(agentSession.getContext(), pinnedBuild, sessionId));
258
+ agentSession.setContext(bindChatProjectRoot(bindPinnedBuildContext(agentSession.getContext(), pinnedBuild, sessionId), input.projectRoot));
257
259
  }
258
260
  else {
259
- agentSession.setContext(markUnlinkedChatContext(agentSession.getContext(), sessionId, draftForOpen));
261
+ agentSession.setContext(bindChatProjectRoot(markUnlinkedChatContext(agentSession.getContext(), sessionId, draftForOpen, input.projectRoot), input.projectRoot));
260
262
  }
261
263
  registry.bindTurn(sessionId, {
262
264
  storeRoot,
@@ -272,7 +274,7 @@ export async function runWorkshopChatTurn(input) {
272
274
  typeof agentSession.prepareRestart === "function") {
273
275
  await agentSession.prepareRestart(storeRecord.transcript);
274
276
  }
275
- rawEvents = await agentSession.send(message, {
277
+ rawEvents = await agentSession.send(prompt, {
276
278
  onDelta: input.onDelta,
277
279
  onActivity: (update) => {
278
280
  const buildId = registry.getPinnedBuildId(sessionId);
@@ -302,7 +304,9 @@ export async function runWorkshopChatTurn(input) {
302
304
  const pendingRaw = agentSession.getPendingProposal();
303
305
  const events = rawEvents.map((event) => toWorkshopChatWireEvent(event, draft));
304
306
  await enqueueSessionWrite(sessionId, () => {
305
- appendWorkshopSessionMessages(storeRoot, sessionId, transcriptMessagesForTurn(message, events));
307
+ appendWorkshopSessionMessages(storeRoot, sessionId, transcriptMessagesForTurn(message, attachments, events, {
308
+ autoApply: input.autoApply === true,
309
+ }));
306
310
  });
307
311
  return {
308
312
  sessionId,
@@ -311,7 +315,7 @@ export async function runWorkshopChatTurn(input) {
311
315
  pending: pendingRaw
312
316
  ? serializeWorkshopProposal(pendingRaw, draft)
313
317
  : null,
314
- autoApply: false,
318
+ autoApply: input.autoApply === true,
315
319
  model,
316
320
  buildId: resultBuildId,
317
321
  };
@@ -0,0 +1,17 @@
1
+ import type { ProposalArtifactDiff } from "../operatorAgent/proposals.js";
2
+ import type { WorkshopChatProposalPayload } from "./chatTurn.js";
3
+ type SnapshotDraft = WorkshopChatProposalPayload["baseDraft"];
4
+ export type WorkshopSessionMutationSnapshot = {
5
+ id: string;
6
+ summary: string;
7
+ affectedStageIds: string[];
8
+ artifacts: ProposalArtifactDiff[];
9
+ status: "accepted" | "rejected";
10
+ autoApplied?: boolean;
11
+ baseDraft?: SnapshotDraft;
12
+ nextDraft?: SnapshotDraft;
13
+ baseFingerprint?: string;
14
+ };
15
+ export declare function parseWorkshopSessionMutationSnapshot(value: unknown): WorkshopSessionMutationSnapshot | null;
16
+ export declare function mutationSnapshotsFromProposals(proposals: readonly WorkshopChatProposalPayload[], autoApply: boolean): WorkshopSessionMutationSnapshot[];
17
+ export {};
@@ -0,0 +1,107 @@
1
+ function isPlainObject(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function parseDraft(value) {
5
+ if (!isPlainObject(value))
6
+ return undefined;
7
+ const pipeline = value.pipeline;
8
+ if (!isPlainObject(pipeline))
9
+ return undefined;
10
+ if (typeof pipeline.id !== "string")
11
+ return undefined;
12
+ if (!Array.isArray(pipeline.stages))
13
+ return undefined;
14
+ if (pipeline.stages.some((stage) => !isPlainObject(stage)))
15
+ return undefined;
16
+ if (value.stages !== undefined) {
17
+ if (!Array.isArray(value.stages))
18
+ return undefined;
19
+ for (const entry of value.stages) {
20
+ if (!isPlainObject(entry) ||
21
+ typeof entry.path !== "string" ||
22
+ !isPlainObject(entry.body)) {
23
+ return undefined;
24
+ }
25
+ }
26
+ }
27
+ if (value.task !== undefined) {
28
+ if (!isPlainObject(value.task) ||
29
+ typeof value.task.filename !== "string" ||
30
+ !isPlainObject(value.task.body)) {
31
+ return undefined;
32
+ }
33
+ }
34
+ return structuredClone(value);
35
+ }
36
+ function parseArtifact(value) {
37
+ if (!isPlainObject(value))
38
+ return null;
39
+ if (typeof value.path !== "string" || !value.path)
40
+ return null;
41
+ if (value.kind !== "added" &&
42
+ value.kind !== "removed" &&
43
+ value.kind !== "modified") {
44
+ return null;
45
+ }
46
+ const artifact = {
47
+ path: value.path,
48
+ kind: value.kind,
49
+ };
50
+ if (typeof value.before === "string")
51
+ artifact.before = value.before;
52
+ if (typeof value.after === "string")
53
+ artifact.after = value.after;
54
+ return artifact;
55
+ }
56
+ export function parseWorkshopSessionMutationSnapshot(value) {
57
+ if (!isPlainObject(value))
58
+ return null;
59
+ if (typeof value.id !== "string" || !value.id)
60
+ return null;
61
+ if (typeof value.summary !== "string")
62
+ return null;
63
+ if (!Array.isArray(value.affectedStageIds))
64
+ return null;
65
+ const affectedStageIds = value.affectedStageIds.filter((id) => typeof id === "string" && id.length > 0);
66
+ if (!Array.isArray(value.artifacts))
67
+ return null;
68
+ const artifacts = [];
69
+ for (const entry of value.artifacts) {
70
+ const parsed = parseArtifact(entry);
71
+ if (!parsed)
72
+ return null;
73
+ artifacts.push(parsed);
74
+ }
75
+ const status = value.status === "rejected" ? "rejected" : "accepted";
76
+ const baseDraft = parseDraft(value.baseDraft);
77
+ const nextDraft = parseDraft(value.nextDraft);
78
+ const baseFingerprint = typeof value.baseFingerprint === "string" && value.baseFingerprint
79
+ ? value.baseFingerprint
80
+ : undefined;
81
+ return {
82
+ id: value.id,
83
+ summary: value.summary,
84
+ affectedStageIds,
85
+ artifacts,
86
+ status,
87
+ ...(value.autoApplied === true ? { autoApplied: true } : {}),
88
+ ...(baseDraft ? { baseDraft } : {}),
89
+ ...(nextDraft ? { nextDraft } : {}),
90
+ ...(baseFingerprint ? { baseFingerprint } : {}),
91
+ };
92
+ }
93
+ export function mutationSnapshotsFromProposals(proposals, autoApply) {
94
+ return proposals.map((proposal) => ({
95
+ id: proposal.id,
96
+ summary: proposal.summary,
97
+ affectedStageIds: [...proposal.affectedStageIds],
98
+ artifacts: proposal.artifacts.map((artifact) => ({ ...artifact })),
99
+ status: "accepted",
100
+ baseDraft: structuredClone(proposal.baseDraft),
101
+ nextDraft: structuredClone(proposal.nextDraft),
102
+ ...(proposal.baseFingerprint
103
+ ? { baseFingerprint: proposal.baseFingerprint }
104
+ : {}),
105
+ ...(autoApply ? { autoApplied: true } : {}),
106
+ }));
107
+ }
@@ -1,10 +1,18 @@
1
+ import { type WorkshopSessionMutationSnapshot } from "./sessionMutations.js";
1
2
  export declare const WORKSHOP_SESSION_TITLE_MAX_LENGTH = 80;
2
3
  export type WorkshopSessionMessageRole = "assistant" | "user" | "system";
4
+ export type WorkshopSessionAttachment = {
5
+ name: string;
6
+ size: number;
7
+ mediaType: string;
8
+ };
3
9
  export type WorkshopSessionMessage = {
4
10
  id: string;
5
11
  role: WorkshopSessionMessageRole;
6
12
  text: string;
7
13
  createdAt: string;
14
+ attachments?: WorkshopSessionAttachment[];
15
+ mutations?: WorkshopSessionMutationSnapshot[];
8
16
  };
9
17
  export type WorkshopSessionRecord = {
10
18
  version: 1;
@@ -28,6 +36,8 @@ export type WorkshopSessionAppendMessage = {
28
36
  role: WorkshopSessionMessageRole;
29
37
  text: string;
30
38
  createdAt?: string;
39
+ attachments?: WorkshopSessionAttachment[];
40
+ mutations?: WorkshopSessionMutationSnapshot[];
31
41
  };
32
42
  export type WorkshopSessionStoreErrorCode = "workshop_session_not_found";
33
43
  export declare class WorkshopSessionStoreError extends Error {
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { globalStageflowHome } from "../project/globalHome.js";
5
+ import { parseWorkshopSessionMutationSnapshot, } from "./sessionMutations.js";
5
6
  export const WORKSHOP_SESSION_TITLE_MAX_LENGTH = 80;
6
7
  export class WorkshopSessionStoreError extends Error {
7
8
  code;
@@ -63,13 +64,46 @@ function parseMessage(value) {
63
64
  return null;
64
65
  if (typeof value.createdAt !== "string" || !value.createdAt)
65
66
  return null;
67
+ const attachments = Array.isArray(value.attachments)
68
+ ? value.attachments.flatMap((entry) => {
69
+ const parsed = parseAttachment(entry);
70
+ return parsed ? [parsed] : [];
71
+ })
72
+ : [];
73
+ let mutations;
74
+ if (value.mutations !== undefined) {
75
+ if (!Array.isArray(value.mutations))
76
+ return null;
77
+ const parsedMutations = [];
78
+ for (const entry of value.mutations) {
79
+ const parsed = parseWorkshopSessionMutationSnapshot(entry);
80
+ if (!parsed)
81
+ return null;
82
+ parsedMutations.push(parsed);
83
+ }
84
+ if (parsedMutations.length > 0)
85
+ mutations = parsedMutations;
86
+ }
66
87
  return {
67
88
  id: value.id,
68
89
  role: value.role,
69
90
  text: value.text,
70
91
  createdAt: value.createdAt,
92
+ ...(attachments.length > 0 ? { attachments } : {}),
93
+ ...(mutations ? { mutations } : {}),
71
94
  };
72
95
  }
96
+ function parseAttachment(value) {
97
+ if (!isPlainObject(value))
98
+ return null;
99
+ if (typeof value.name !== "string" || !value.name)
100
+ return null;
101
+ if (typeof value.size !== "number" || !Number.isFinite(value.size))
102
+ return null;
103
+ if (typeof value.mediaType !== "string")
104
+ return null;
105
+ return { name: value.name, size: value.size, mediaType: value.mediaType };
106
+ }
73
107
  export function parseWorkshopSessionRecord(value) {
74
108
  if (!isPlainObject(value))
75
109
  return null;
@@ -211,6 +245,24 @@ export function appendWorkshopSessionMessages(storeRoot, sessionId, messages, op
211
245
  role: msg.role,
212
246
  text: msg.text,
213
247
  createdAt: msg.createdAt || now,
248
+ ...(msg.attachments && msg.attachments.length > 0
249
+ ? {
250
+ attachments: msg.attachments.map(({ name, size, mediaType }) => ({
251
+ name,
252
+ size,
253
+ mediaType,
254
+ })),
255
+ }
256
+ : {}),
257
+ ...(msg.mutations && msg.mutations.length > 0
258
+ ? {
259
+ mutations: msg.mutations.map((mutation) => ({
260
+ ...mutation,
261
+ affectedStageIds: [...mutation.affectedStageIds],
262
+ artifacts: mutation.artifacts.map((artifact) => ({ ...artifact })),
263
+ })),
264
+ }
265
+ : {}),
214
266
  };
215
267
  appended.push(entry);
216
268
  if (!title && entry.role === "user" && entry.text.trim()) {
@@ -35,17 +35,18 @@ export declare function resolveLiveWorkshopBinding(context: unknown): {
35
35
  * An unlinked chat turn fails the tool and does not create a build.
36
36
  */
37
37
  export declare function rejectUnlinkedWorkshopDraftTool(context: unknown, toolName: string): OperatorAgentToolResult | null;
38
- /** Tied builds save to the stored path. Untitled builds keep the caller's destination. */
39
- export declare function resolvePinnedSaveDestination(context: unknown): {
38
+ /** Tied builds save to the stored path when the write root matches the build tie. */
39
+ export declare function resolvePinnedSaveDestination(context: unknown, writeProjectRoot?: string | null): {
40
40
  directory: string;
41
41
  pipelineFilename: string;
42
42
  } | null;
43
- /** A successful save of an untitled build records the operator destination on that id. */
43
+ /** A successful catalog save updates the build tie and in-memory pointer. */
44
44
  export declare function recordPinnedWorkshopSave(context: unknown, saved: {
45
45
  directory: string;
46
46
  pipelineFilename?: string;
47
47
  projectRoot: string;
48
48
  draft: DraftPackage;
49
+ relativePath?: string;
49
50
  }): void;
50
51
  /**
51
52
  * Create an untitled build and move this turn's pin onto it.
@@ -117,8 +117,8 @@ export function rejectUnlinkedWorkshopDraftTool(context, toolName) {
117
117
  error: WORKSHOP_UNLINKED_DRAFT_TOOL_ERROR,
118
118
  };
119
119
  }
120
- /** Tied builds save to the stored path. Untitled builds keep the caller's destination. */
121
- export function resolvePinnedSaveDestination(context) {
120
+ /** Tied builds save to the stored path when the write root matches the build tie. */
121
+ export function resolvePinnedSaveDestination(context, writeProjectRoot) {
122
122
  if (!isWorkshopDraftContext(context) ||
123
123
  !context.buildId ||
124
124
  !context.chatSessionId) {
@@ -130,9 +130,21 @@ export function resolvePinnedSaveDestination(context) {
130
130
  const build = readWorkshopBuild(live.storeRoot, context.buildId);
131
131
  if (!build?.projectRoot || !build.relativePath)
132
132
  return null;
133
+ if (writeProjectRoot?.trim()) {
134
+ const tieRoot = path.resolve(build.projectRoot);
135
+ const writeRoot = path.resolve(writeProjectRoot.trim());
136
+ if (tieRoot !== writeRoot)
137
+ return null;
138
+ }
133
139
  return destinationFromRelativePath(build.relativePath);
134
140
  }
135
- /** A successful save of an untitled build records the operator destination on that id. */
141
+ function normalizeSavedRelativePath(value) {
142
+ let trimmed = value.trim().replace(/\\/g, "/");
143
+ while (trimmed.startsWith("./"))
144
+ trimmed = trimmed.slice(2);
145
+ return trimmed;
146
+ }
147
+ /** A successful catalog save updates the build tie and in-memory pointer. */
136
148
  export function recordPinnedWorkshopSave(context, saved) {
137
149
  if (!isWorkshopDraftContext(context) ||
138
150
  !context.buildId ||
@@ -143,15 +155,23 @@ export function recordPinnedWorkshopSave(context, saved) {
143
155
  if (!live)
144
156
  return;
145
157
  const build = readWorkshopBuild(live.storeRoot, context.buildId);
146
- if (!build || (build.projectRoot && build.relativePath))
158
+ if (!build)
147
159
  return;
148
160
  const pipelineFilename = saved.pipelineFilename?.trim() ||
149
161
  `${saved.draft.pipeline.id}.pipeline.yaml`;
150
- updateWorkshopBuild(live.storeRoot, build.id, {
162
+ const relativePath = saved.relativePath?.trim()
163
+ ? normalizeSavedRelativePath(saved.relativePath)
164
+ : relativePathFromDestination(saved.directory, pipelineFilename);
165
+ const updated = updateWorkshopBuild(live.storeRoot, build.id, {
151
166
  draft: saved.draft,
152
167
  projectRoot: path.resolve(saved.projectRoot),
153
- relativePath: relativePathFromDestination(saved.directory, pipelineFilename),
168
+ relativePath,
154
169
  });
170
+ const session = live.registry.get(context.chatSessionId);
171
+ if (session) {
172
+ session.setContext(bindPinnedBuildContext(session.getContext(), updated, context.chatSessionId));
173
+ }
174
+ emitPointerChange(live.registry, context.chatSessionId, updated);
155
175
  }
156
176
  /**
157
177
  * Create an untitled build and move this turn's pin onto it.
@@ -0,0 +1,8 @@
1
+ import type { WorkshopChatAttachment } from "./chatAttachments.js";
2
+ import type { WorkshopChatProposalPayload, WorkshopChatWireEvent } from "./chatTurn.js";
3
+ import type { WorkshopSessionAppendMessage } from "./sessionStore.js";
4
+ export declare function assistantTranscriptTextFromEvents(events: readonly WorkshopChatWireEvent[]): string;
5
+ export declare function proposalPayloadsFromEvents(events: readonly WorkshopChatWireEvent[]): WorkshopChatProposalPayload[];
6
+ export declare function transcriptMessagesForTurn(userMessage: string, attachments: readonly WorkshopChatAttachment[], events: readonly WorkshopChatWireEvent[], options?: {
7
+ autoApply?: boolean;
8
+ }): WorkshopSessionAppendMessage[];