@pasko70/pibo 3.3.0 → 3.4.1

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 (237) hide show
  1. package/dist/agent-runtime/capabilities.js +5 -0
  2. package/dist/agent-runtime/contract.js +2 -0
  3. package/dist/agent-runtime/routed-session.js +70 -11
  4. package/dist/agent-runtimes/codex-native/adapter.js +3 -0
  5. package/dist/agent-runtimes/codex-native/thread.js +26 -4
  6. package/dist/agent-runtimes/omp/adapter.js +1 -0
  7. package/dist/agent-runtimes/pi/adapter.js +4 -0
  8. package/dist/agent-runtimes/pi/routed-session.js +53 -5
  9. package/dist/apps/chat/chat-api-routes.js +11 -37
  10. package/dist/apps/chat/chat-request-normalizers.js +8 -48
  11. package/dist/apps/chat/data/legacy-project-migration.js +492 -0
  12. package/dist/apps/chat/data/room-service.js +2 -2
  13. package/dist/apps/chat/data/workflow-session-model.js +1 -0
  14. package/dist/apps/chat/data/workflow-session-service.js +352 -0
  15. package/dist/apps/chat/rooms.js +1 -1
  16. package/dist/apps/chat/types/rooms.js +30 -0
  17. package/dist/apps/chat/web-app.js +264 -647
  18. package/dist/apps/chat/workflow-catalog.js +18 -18
  19. package/dist/apps/chat/{project-workflow-human-actions.js → workflow-human-actions.js} +14 -14
  20. package/dist/apps/chat/workflow-manual-trigger-runtime.js +5 -1
  21. package/dist/apps/chat/workflow-persistence.js +20 -28
  22. package/dist/apps/chat/{project-workflow-sessions.js → workflow-sessions.js} +18 -17
  23. package/dist/apps/chat-ui/assets/{dist-Kc3zbvQi.js → dist-Abz605MV.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-C9a0_446.js → dist-D0md0xIR.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-CcAkJ8iv.js → dist-DHnaUMlq.js} +1 -1
  26. package/dist/apps/chat-ui/assets/{dist-Bd9gWCbk.js → dist-DnZL1rRp.js} +1 -1
  27. package/dist/apps/chat-ui/assets/{dist-BbCjOLiw.js → dist-ERKABhp3.js} +1 -1
  28. package/dist/apps/chat-ui/assets/index-BWVPNFjU.js +228 -0
  29. package/dist/apps/chat-ui/assets/index-CywOD6EF.css +1 -0
  30. package/dist/apps/chat-ui/index.html +2 -2
  31. package/dist/apps/chat-vscode-web/assets/index-CZmxeSn3.js +43 -0
  32. package/dist/apps/chat-vscode-web/index.html +1 -1
  33. package/dist/compute/pool/cli.js +1 -1
  34. package/dist/compute/pool/seeds.js +2 -3
  35. package/dist/config/config.js +1 -1
  36. package/dist/core/session-router.js +13 -4
  37. package/dist/previews/cli.js +154 -79
  38. package/dist/previews/compute-worker.js +37 -0
  39. package/dist/previews/config.js +2 -1
  40. package/dist/previews/migrations/session-ownership.js +7 -0
  41. package/dist/previews/proxy.js +4 -2
  42. package/dist/previews/public-setup.js +108 -0
  43. package/dist/previews/store.js +20 -13
  44. package/dist/previews/web-app.js +97 -2
  45. package/dist/sessions/workflow-session-kind.js +0 -3
  46. package/npm-shrinkwrap.json +7 -6
  47. package/package.json +6 -2
  48. package/packages/workflows/README.md +21 -0
  49. package/packages/workflows/dist/api/index.d.ts +48 -0
  50. package/packages/workflows/dist/api/index.d.ts.map +1 -0
  51. package/packages/workflows/dist/api/index.js +96 -0
  52. package/packages/workflows/dist/api/index.js.map +1 -0
  53. package/packages/workflows/dist/definition-hash.d.ts +4 -0
  54. package/packages/workflows/dist/definition-hash.d.ts.map +1 -0
  55. package/packages/workflows/dist/definition-hash.js +27 -0
  56. package/packages/workflows/dist/definition-hash.js.map +1 -0
  57. package/packages/workflows/dist/fixtures/index.d.ts +39 -0
  58. package/packages/workflows/dist/fixtures/index.d.ts.map +1 -0
  59. package/packages/workflows/dist/fixtures/index.js +608 -0
  60. package/packages/workflows/dist/fixtures/index.js.map +1 -0
  61. package/packages/workflows/dist/index.d.ts +16 -0
  62. package/packages/workflows/dist/index.d.ts.map +1 -0
  63. package/packages/workflows/dist/index.js +10 -0
  64. package/packages/workflows/dist/index.js.map +1 -0
  65. package/packages/workflows/dist/inspection/index.d.ts +47 -0
  66. package/packages/workflows/dist/inspection/index.d.ts.map +1 -0
  67. package/packages/workflows/dist/inspection/index.js +131 -0
  68. package/packages/workflows/dist/inspection/index.js.map +1 -0
  69. package/packages/workflows/dist/registry/index.d.ts +30 -0
  70. package/packages/workflows/dist/registry/index.d.ts.map +1 -0
  71. package/packages/workflows/dist/registry/index.js +161 -0
  72. package/packages/workflows/dist/registry/index.js.map +1 -0
  73. package/packages/workflows/dist/runtime/adapter-node.d.ts +28 -0
  74. package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -0
  75. package/packages/workflows/dist/runtime/adapter-node.js +172 -0
  76. package/packages/workflows/dist/runtime/adapter-node.js.map +1 -0
  77. package/packages/workflows/dist/runtime/agent-node.d.ts +32 -0
  78. package/packages/workflows/dist/runtime/agent-node.d.ts.map +1 -0
  79. package/packages/workflows/dist/runtime/agent-node.js +231 -0
  80. package/packages/workflows/dist/runtime/agent-node.js.map +1 -0
  81. package/packages/workflows/dist/runtime/agent-runtime.d.ts +25 -0
  82. package/packages/workflows/dist/runtime/agent-runtime.d.ts.map +1 -0
  83. package/packages/workflows/dist/runtime/agent-runtime.js +111 -0
  84. package/packages/workflows/dist/runtime/agent-runtime.js.map +1 -0
  85. package/packages/workflows/dist/runtime/code-node.d.ts +31 -0
  86. package/packages/workflows/dist/runtime/code-node.d.ts.map +1 -0
  87. package/packages/workflows/dist/runtime/code-node.js +223 -0
  88. package/packages/workflows/dist/runtime/code-node.js.map +1 -0
  89. package/packages/workflows/dist/runtime/dispatch-failures.d.ts +36 -0
  90. package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -0
  91. package/packages/workflows/dist/runtime/dispatch-failures.js +80 -0
  92. package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -0
  93. package/packages/workflows/dist/runtime/edge-payloads.d.ts +3 -0
  94. package/packages/workflows/dist/runtime/edge-payloads.d.ts.map +1 -0
  95. package/packages/workflows/dist/runtime/edge-payloads.js +11 -0
  96. package/packages/workflows/dist/runtime/edge-payloads.js.map +1 -0
  97. package/packages/workflows/dist/runtime/edge-transfer.d.ts +39 -0
  98. package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -0
  99. package/packages/workflows/dist/runtime/edge-transfer.js +356 -0
  100. package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -0
  101. package/packages/workflows/dist/runtime/human-action.d.ts +43 -0
  102. package/packages/workflows/dist/runtime/human-action.d.ts.map +1 -0
  103. package/packages/workflows/dist/runtime/human-action.js +231 -0
  104. package/packages/workflows/dist/runtime/human-action.js.map +1 -0
  105. package/packages/workflows/dist/runtime/human-node.d.ts +27 -0
  106. package/packages/workflows/dist/runtime/human-node.d.ts.map +1 -0
  107. package/packages/workflows/dist/runtime/human-node.js +126 -0
  108. package/packages/workflows/dist/runtime/human-node.js.map +1 -0
  109. package/packages/workflows/dist/runtime/ids.d.ts +2 -0
  110. package/packages/workflows/dist/runtime/ids.d.ts.map +1 -0
  111. package/packages/workflows/dist/runtime/ids.js +4 -0
  112. package/packages/workflows/dist/runtime/ids.js.map +1 -0
  113. package/packages/workflows/dist/runtime/index.d.ts +23 -0
  114. package/packages/workflows/dist/runtime/index.d.ts.map +1 -0
  115. package/packages/workflows/dist/runtime/index.js +12 -0
  116. package/packages/workflows/dist/runtime/index.js.map +1 -0
  117. package/packages/workflows/dist/runtime/manual-trigger.d.ts +51 -0
  118. package/packages/workflows/dist/runtime/manual-trigger.d.ts.map +1 -0
  119. package/packages/workflows/dist/runtime/manual-trigger.js +403 -0
  120. package/packages/workflows/dist/runtime/manual-trigger.js.map +1 -0
  121. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts +48 -0
  122. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts.map +1 -0
  123. package/packages/workflows/dist/runtime/nested-workflow-node.js +246 -0
  124. package/packages/workflows/dist/runtime/nested-workflow-node.js.map +1 -0
  125. package/packages/workflows/dist/runtime/one-node-agent.d.ts +34 -0
  126. package/packages/workflows/dist/runtime/one-node-agent.d.ts.map +1 -0
  127. package/packages/workflows/dist/runtime/one-node-agent.js +334 -0
  128. package/packages/workflows/dist/runtime/one-node-agent.js.map +1 -0
  129. package/packages/workflows/dist/runtime/persistence.d.ts +9 -0
  130. package/packages/workflows/dist/runtime/persistence.d.ts.map +1 -0
  131. package/packages/workflows/dist/runtime/persistence.js +67 -0
  132. package/packages/workflows/dist/runtime/persistence.js.map +1 -0
  133. package/packages/workflows/dist/runtime/pibo-routing.d.ts +126 -0
  134. package/packages/workflows/dist/runtime/pibo-routing.d.ts.map +1 -0
  135. package/packages/workflows/dist/runtime/pibo-routing.js +96 -0
  136. package/packages/workflows/dist/runtime/pibo-routing.js.map +1 -0
  137. package/packages/workflows/dist/runtime/prompts.d.ts +20 -0
  138. package/packages/workflows/dist/runtime/prompts.d.ts.map +1 -0
  139. package/packages/workflows/dist/runtime/prompts.js +235 -0
  140. package/packages/workflows/dist/runtime/prompts.js.map +1 -0
  141. package/packages/workflows/dist/runtime/retry.d.ts +35 -0
  142. package/packages/workflows/dist/runtime/retry.d.ts.map +1 -0
  143. package/packages/workflows/dist/runtime/retry.js +80 -0
  144. package/packages/workflows/dist/runtime/retry.js.map +1 -0
  145. package/packages/workflows/dist/runtime/state.d.ts +14 -0
  146. package/packages/workflows/dist/runtime/state.d.ts.map +1 -0
  147. package/packages/workflows/dist/runtime/state.js +139 -0
  148. package/packages/workflows/dist/runtime/state.js.map +1 -0
  149. package/packages/workflows/dist/runtime/time.d.ts +6 -0
  150. package/packages/workflows/dist/runtime/time.d.ts.map +1 -0
  151. package/packages/workflows/dist/runtime/time.js +42 -0
  152. package/packages/workflows/dist/runtime/time.js.map +1 -0
  153. package/packages/workflows/dist/store/catalog-records.d.ts +18 -0
  154. package/packages/workflows/dist/store/catalog-records.d.ts.map +1 -0
  155. package/packages/workflows/dist/store/catalog-records.js +59 -0
  156. package/packages/workflows/dist/store/catalog-records.js.map +1 -0
  157. package/packages/workflows/dist/store/contracts.d.ts +149 -0
  158. package/packages/workflows/dist/store/contracts.d.ts.map +1 -0
  159. package/packages/workflows/dist/store/contracts.js +2 -0
  160. package/packages/workflows/dist/store/contracts.js.map +1 -0
  161. package/packages/workflows/dist/store/draft-writes.d.ts +7 -0
  162. package/packages/workflows/dist/store/draft-writes.d.ts.map +1 -0
  163. package/packages/workflows/dist/store/draft-writes.js +18 -0
  164. package/packages/workflows/dist/store/draft-writes.js.map +1 -0
  165. package/packages/workflows/dist/store/index.d.ts +55 -0
  166. package/packages/workflows/dist/store/index.d.ts.map +1 -0
  167. package/packages/workflows/dist/store/index.js +650 -0
  168. package/packages/workflows/dist/store/index.js.map +1 -0
  169. package/packages/workflows/dist/store/list-query.d.ts +13 -0
  170. package/packages/workflows/dist/store/list-query.d.ts.map +1 -0
  171. package/packages/workflows/dist/store/list-query.js +20 -0
  172. package/packages/workflows/dist/store/list-query.js.map +1 -0
  173. package/packages/workflows/dist/store/row-mappers.d.ts +206 -0
  174. package/packages/workflows/dist/store/row-mappers.d.ts.map +1 -0
  175. package/packages/workflows/dist/store/row-mappers.js +262 -0
  176. package/packages/workflows/dist/store/row-mappers.js.map +1 -0
  177. package/packages/workflows/dist/store/schema.d.ts +13 -0
  178. package/packages/workflows/dist/store/schema.d.ts.map +1 -0
  179. package/packages/workflows/dist/store/schema.js +458 -0
  180. package/packages/workflows/dist/store/schema.js.map +1 -0
  181. package/packages/workflows/dist/store/write-values.d.ts +18 -0
  182. package/packages/workflows/dist/store/write-values.d.ts.map +1 -0
  183. package/packages/workflows/dist/store/write-values.js +227 -0
  184. package/packages/workflows/dist/store/write-values.js.map +1 -0
  185. package/packages/workflows/dist/types/index.d.ts +1202 -0
  186. package/packages/workflows/dist/types/index.d.ts.map +1 -0
  187. package/packages/workflows/dist/types/index.js +8 -0
  188. package/packages/workflows/dist/types/index.js.map +1 -0
  189. package/packages/workflows/dist/validation/diagnostics.d.ts +27 -0
  190. package/packages/workflows/dist/validation/diagnostics.d.ts.map +1 -0
  191. package/packages/workflows/dist/validation/diagnostics.js +104 -0
  192. package/packages/workflows/dist/validation/diagnostics.js.map +1 -0
  193. package/packages/workflows/dist/validation/graph-cycles.d.ts +4 -0
  194. package/packages/workflows/dist/validation/graph-cycles.d.ts.map +1 -0
  195. package/packages/workflows/dist/validation/graph-cycles.js +130 -0
  196. package/packages/workflows/dist/validation/graph-cycles.js.map +1 -0
  197. package/packages/workflows/dist/validation/graph-edges.d.ts +5 -0
  198. package/packages/workflows/dist/validation/graph-edges.d.ts.map +1 -0
  199. package/packages/workflows/dist/validation/graph-edges.js +48 -0
  200. package/packages/workflows/dist/validation/graph-edges.js.map +1 -0
  201. package/packages/workflows/dist/validation/graph-ports.d.ts +5 -0
  202. package/packages/workflows/dist/validation/graph-ports.d.ts.map +1 -0
  203. package/packages/workflows/dist/validation/graph-ports.js +55 -0
  204. package/packages/workflows/dist/validation/graph-ports.js.map +1 -0
  205. package/packages/workflows/dist/validation/index.d.ts +18 -0
  206. package/packages/workflows/dist/validation/index.d.ts.map +1 -0
  207. package/packages/workflows/dist/validation/index.js +175 -0
  208. package/packages/workflows/dist/validation/index.js.map +1 -0
  209. package/packages/workflows/dist/validation/json-schema.d.ts +12 -0
  210. package/packages/workflows/dist/validation/json-schema.d.ts.map +1 -0
  211. package/packages/workflows/dist/validation/json-schema.js +556 -0
  212. package/packages/workflows/dist/validation/json-schema.js.map +1 -0
  213. package/packages/workflows/dist/validation/registry-refs.d.ts +21 -0
  214. package/packages/workflows/dist/validation/registry-refs.d.ts.map +1 -0
  215. package/packages/workflows/dist/validation/registry-refs.js +283 -0
  216. package/packages/workflows/dist/validation/registry-refs.js.map +1 -0
  217. package/packages/workflows/dist/validation/retry-policy.d.ts +3 -0
  218. package/packages/workflows/dist/validation/retry-policy.d.ts.map +1 -0
  219. package/packages/workflows/dist/validation/retry-policy.js +114 -0
  220. package/packages/workflows/dist/validation/retry-policy.js.map +1 -0
  221. package/packages/workflows/dist/validation/schema-declarations.d.ts +5 -0
  222. package/packages/workflows/dist/validation/schema-declarations.d.ts.map +1 -0
  223. package/packages/workflows/dist/validation/schema-declarations.js +36 -0
  224. package/packages/workflows/dist/validation/schema-declarations.js.map +1 -0
  225. package/packages/workflows/dist/validation/state-access.d.ts +4 -0
  226. package/packages/workflows/dist/validation/state-access.d.ts.map +1 -0
  227. package/packages/workflows/dist/validation/state-access.js +121 -0
  228. package/packages/workflows/dist/validation/state-access.js.map +1 -0
  229. package/packages/workflows/dist/xstate/index.d.ts +73 -0
  230. package/packages/workflows/dist/xstate/index.d.ts.map +1 -0
  231. package/packages/workflows/dist/xstate/index.js +728 -0
  232. package/packages/workflows/dist/xstate/index.js.map +1 -0
  233. package/packages/workflows/package.json +23 -0
  234. package/dist/apps/chat/data/project-service.js +0 -1029
  235. package/dist/apps/chat-ui/assets/index-DWJnUye8.css +0 -1
  236. package/dist/apps/chat-ui/assets/index-exohEOUE.js +0 -228
  237. package/dist/apps/chat-vscode-web/assets/index-D1bpAUkR.js +0 -43
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import os from "node:os";
3
+ import { dirname, join } from "node:path";
3
4
  import { monitorEventLoopDelay } from "node:perf_hooks";
4
5
  import { PiboSteeringUnavailableError } from "../../core/events.js";
5
6
  import { OutputRenderSequencer, validRenderSequence } from "../../core/output-render-sequence.js";
@@ -12,7 +13,7 @@ import { summarizeSessionSignalStatus } from "../../signals/status.js";
12
13
  import { PiboWebHttpError, readJsonBody, responseJson } from "../../web/http.js";
13
14
  import { OutputCompactor } from "./output-compactor.js";
14
15
  import { isLiveOnlyOutputEvent, isPersistableOutputEvent, isPiboOutputEvent } from "./output-event-policy.js";
15
- import { chatRoomIdFromMetadata, isDefaultPiboRoom, isPiboRoomArchived, roomWorkspaceFromMetadata, withChatRoomId, withPiboRoomWorkspace, } from "./types/rooms.js";
16
+ import { chatRoomIdFromMetadata, comparePiboRoomsBySidebarOrder, isDefaultPiboRoom, isPiboRoomArchived, isPiboRoomPinned, piboRoomSidebarOrder, roomWorkspaceFromMetadata, withChatRoomId, withPiboRoomPinned, withPiboRoomSidebarOrder, withPiboRoomWorkspace, } from "./types/rooms.js";
16
17
  import { chatStreamFramesFromOutputEvent, createChatStreamState, nextTransientChatStreamFrameId } from "./stream.js";
17
18
  import { buildSessionNodes, buildTraceView, TRACE_PROJECTION_VERSION } from "./trace.js";
18
19
  import { DEFAULT_TRACE_EVENTS_PAGE_SIZE, annotateTracePage, etagForVersion, estimateTraceViewBytes, traceCacheEstimatedBytes, liveSnapshotVersion, requestMatchesVersion, setTraceCache, traceProjectionStatus, traceCacheKey, withLiveSnapshots, withRawTraceTail, } from "./chat-trace-helpers.js";
@@ -39,7 +40,8 @@ import { ChatRoomService, PiboRoomHierarchyCycleError } from "./data/room-servic
39
40
  import { ChatSessionQueryService } from "./data/session-query-service.js";
40
41
  import { ChatTimelineQueryService } from "./data/timeline-query-service.js";
41
42
  import { ChatHistoryQueryService } from "./data/history-query-service.js";
42
- import { ChatProjectService } from "./data/project-service.js";
43
+ import { ChatWorkflowSessionService, } from "./data/workflow-session-service.js";
44
+ import { migrateLegacyProjects } from "./data/legacy-project-migration.js";
43
45
  import { PiboDataStore } from "../../data/pibo-store.js";
44
46
  import { createDefaultPiboCronStore } from "../../cron/store.js";
45
47
  import { createDefaultPiboLoopStore } from "../../loops/store.js";
@@ -56,12 +58,12 @@ import { responseChatSpeechProviders, responseChatSpeechSessionSpeak, responseCh
56
58
  import { chatSettingsRoute, chatSettingsRouteInvalidatesBootstrapCatalog, chatSettingsRouteRequiresSameOrigin, handleChatSettingsRoute, } from "./chat-settings-routes.js";
57
59
  import { chatCapabilityRoute, chatCapabilityRouteRequiresSameOrigin, handleChatCapabilityRoute, } from "./chat-capability-routes.js";
58
60
  import { chatUserSkillRoute, chatUserSkillRouteRequiresSameOrigin, handleChatUserSkillRoute, syncChatUserSkills, } from "./chat-user-skill-routes.js";
59
- import { CHAT_WEB_API_PREFIX, agentFolderResourceId, agentResourceId, projectResourcePath, projectSessionResourceId, projectWorkflowHumanActionsResource, projectWorkflowSessionStartResource, roomResourcePath, sessionActionResource, sessionResourceId, signalResource, workflowArchiveResourceId, workflowCatalogResourceId, workflowDraftActionResource, workflowDraftManualTriggerRunResource, workflowDraftResourceId, workflowDuplicateResourceId, workflowNextDraftResourceId, workflowPickerKind, workflowPromptAssetResourceId, workflowVersionResource, } from "./chat-api-routes.js";
60
- import { assertProjectSessionPatchFields, buildStreamingFixtureSchedule, createAgentInput, createAgentUpdate, createRoomUpdate, createSessionUpdate, normalizeAgentArchived, normalizeAgentFolderName, normalizeClientTxnId, normalizeMessageDelivery, normalizeMessageText, normalizeParentRoomId, normalizeProjectArchived, normalizeProjectDescription, normalizeProjectPath, normalizeProjectSessionArchived, normalizeRoomDeleteConfirmation, normalizeRoomName, normalizeRoomTopic, normalizeRoomType, normalizeRoomWorkspace, normalizeSessionDeleteConfirmation, normalizeSessionTitle, normalizeStreamingFixtureCadenceMs, normalizeStreamingFixtureDeltas, normalizeStreamingFixtureMix, normalizeStreamingFixturePreludeMessages, normalizeStreamingFixturePreludeOnly, normalizeStreamingFixtureProfile, normalizeStreamingFixtureSuppressLiveDeltas, normalizeStreamingFixtureTraceSnapshots, normalizeStreamingFixtureToolInterleaving, resolveCreateSessionProfile, } from "./chat-request-normalizers.js";
61
+ import { CHAT_WEB_API_PREFIX, agentFolderResourceId, agentResourceId, roomResourcePath, sessionActionResource, sessionResourceId, sessionWorkflowResource, signalResource, workflowArchiveResourceId, workflowCatalogResourceId, workflowDraftActionResource, workflowDraftManualTriggerRunResource, workflowDraftResourceId, workflowDuplicateResourceId, workflowNextDraftResourceId, workflowPickerKind, workflowPromptAssetResourceId, workflowVersionResource, } from "./chat-api-routes.js";
62
+ import { buildStreamingFixtureSchedule, createAgentInput, createAgentUpdate, createRoomUpdate, createSessionUpdate, normalizeAgentArchived, normalizeAgentFolderName, normalizeClientTxnId, normalizeMessageDelivery, normalizeMessageText, normalizeParentRoomId, normalizeRoomDeleteConfirmation, normalizeRoomName, normalizeRoomTopic, normalizeRoomType, normalizeRoomWorkspace, normalizeSessionDeleteConfirmation, normalizeSessionTitle, normalizeStreamingFixtureCadenceMs, normalizeStreamingFixtureDeltas, normalizeStreamingFixtureMix, normalizeStreamingFixturePreludeMessages, normalizeStreamingFixturePreludeOnly, normalizeStreamingFixtureProfile, normalizeStreamingFixtureSuppressLiveDeltas, normalizeStreamingFixtureTraceSnapshots, normalizeStreamingFixtureToolInterleaving, resolveCreateSessionProfile, } from "./chat-request-normalizers.js";
61
63
  import { ChatWorkflowArchiveStore, ChatWorkflowDraftStore, ChatWorkflowLifecycleEventStore, ChatWorkflowPromptAssetStore, ChatWorkflowPublishedVersionStore, ChatWorkflowTombstoneStore, hashWorkflowDefinitionJson, normalizeWorkflowPromptAssetLabel, sanitizeWorkflowDiagnostics, } from "./workflow-persistence.js";
62
- import { normalizeProjectWorkflowHumanActionBody, projectWorkflowHumanActionDiagnosticResponse, projectWorkflowHumanActionLifecyclePayload, projectWorkflowHumanActionRuntimeDiagnostic, projectWorkflowHumanActionSubmittedLifecyclePayload, projectWorkflowPendingHumanActionFromToken, validateProjectWorkflowHumanActionRequest, } from "./project-workflow-human-actions.js";
63
- import { createProjectWorkflowRunCurrent, createProjectWorkflowSessionSnapshot, normalizeProjectWorkflowSessionConfiguration, workflowVersionFromSnapshot, } from "./project-workflow-sessions.js";
64
- import { STATIC_WORKFLOW_VERSION_CATALOG, buildProjectWorkflowVersionCatalog, buildProjectWorkflowVersionOptions, buildWorkflowCatalogInspect as buildWorkflowCatalogInspectWithServices, buildWorkflowCatalogList as buildWorkflowCatalogListWithServices, buildWorkflowVersionHistory, buildWorkflowVersionInspect as buildWorkflowVersionInspectWithServices, buildWorkflowVersionList as buildWorkflowVersionListWithServices, buildWorkflowVersionPicker, createPublishedWorkflowDefinition, selectWorkflowCatalogDisplayVersion, workflowDefinitionTags, workflowVersionPickerOptionFromCatalogRecord, } from "./workflow-catalog.js";
64
+ import { normalizeWorkflowHumanActionBody, workflowHumanActionDiagnosticResponse, workflowHumanActionLifecyclePayload, workflowHumanActionRuntimeDiagnostic, workflowHumanActionSubmittedLifecyclePayload, workflowPendingHumanActionFromToken, validateWorkflowHumanActionRequest, } from "./workflow-human-actions.js";
65
+ import { createWorkflowRunCurrent, createWorkflowSessionSnapshot, normalizeWorkflowSessionConfiguration, workflowVersionFromSnapshot, } from "./workflow-sessions.js";
66
+ import { STATIC_WORKFLOW_VERSION_CATALOG, buildWorkflowVersionCatalog, buildWorkflowVersionOptions, buildWorkflowCatalogInspect as buildWorkflowCatalogInspectWithServices, buildWorkflowCatalogList as buildWorkflowCatalogListWithServices, buildWorkflowVersionHistory, buildWorkflowVersionInspect as buildWorkflowVersionInspectWithServices, buildWorkflowVersionList as buildWorkflowVersionListWithServices, buildWorkflowVersionPicker, createPublishedWorkflowDefinition, selectWorkflowCatalogDisplayVersion, workflowDefinitionTags, workflowVersionPickerOptionFromCatalogRecord, } from "./workflow-catalog.js";
65
67
  import { cloneJsonObject, isWorkflowValidationPipelineDiagnostic, normalizeWorkflowValidationTrigger, normalizeWorkflowVersionIntent, parseWorkflowDraftDefinitionFromPatch, summarizeWorkflowDiagnostics, withoutRawWorkflowIrParseDiagnostic, } from "./workflow-validation-helpers.js";
66
68
  import { validateWorkflowEdgeAdapterOutputCompatibilityLike, validateWorkflowEdgeDirectCompatibilityLike, } from "./workflow-edge-compatibility.js";
67
69
  import { runWorkflowManualTextTrigger, } from "./workflow-manual-trigger-runtime.js";
@@ -236,6 +238,13 @@ function createReliabilityStore(path) {
236
238
  function createDataStore(options) {
237
239
  return new PiboDataStore(options.dataStorePath, { payloadRootDir: options.dataPayloadRootDir });
238
240
  }
241
+ function workflowStorePath(options, dataStore) {
242
+ if (options.workflowStorePath)
243
+ return options.workflowStorePath;
244
+ if (dataStore.path === ":memory:")
245
+ return ":memory:";
246
+ return join(dirname(dataStore.path), "pibo-workflows.sqlite");
247
+ }
239
248
  function resolveVscodeWebUrl(value) {
240
249
  const trimmed = value?.trim();
241
250
  if (!trimmed)
@@ -875,17 +884,6 @@ function requireRoom(state, roomId, webSession, action = "read") {
875
884
  }
876
885
  return room;
877
886
  }
878
- function listSharedProjects(state, _webSession, options = {}) {
879
- return state.projectService.listProjects(options);
880
- }
881
- function requireSharedProject(state, _webSession, projectId, options = {}) {
882
- try {
883
- return state.projectService.requireProject(projectId, options);
884
- }
885
- catch {
886
- throw new PiboWebHttpError("Project not found", 404);
887
- }
888
- }
889
887
  function createSharedChatSession(context, webSession, profile, room) {
890
888
  const normalSessions = sessionsInRoom(listSharedSessions(context), room.id)
891
889
  .filter((session) => !session.parentId && !isChatWebSessionArchived(session) && !isChatWebSessionPinned(session));
@@ -897,72 +895,56 @@ function createSharedChatSession(context, webSession, profile, room) {
897
895
  metadata: withChatWebSessionSidebarOrder(withChatRoomId(undefined, room.id), nextChatWebSessionSidebarOrder(normalSessions)),
898
896
  });
899
897
  }
900
- function createProjectChatSession(input) {
901
- const workflowSelection = resolveProjectWorkflowSelection(input.state, input.workflowId, input.workflowVersion);
898
+ function createWorkflowSession(input) {
899
+ const normalSessions = sessionsInRoom(listSharedSessions(input.context), input.room.id)
900
+ .filter((session) => !session.parentId && !isChatWebSessionArchived(session) && !isChatWebSessionPinned(session));
902
901
  const session = input.context.channelContext.createSession({
903
902
  channel: CHAT_WEB_CHANNEL,
904
903
  kind: "chat",
905
904
  profile: input.profile,
906
- workspace: input.project.projectFolder,
905
+ workspace: input.workspace ?? roomWorkspaceFromMetadata(input.room.metadata) ?? getDefaultPiboWorkspace(),
907
906
  ...(input.title ? { title: input.title } : {}),
908
- ...(input.configuration?.model ? { activeModel: input.configuration.model } : {}),
909
- metadata: withWorkflowSessionKind({
910
- projectId: input.project.id,
911
- projectSessionKind: "main",
912
- projectWorkflowId: workflowSelection.id,
913
- projectWorkflowVersion: workflowSelection.version,
914
- ...(input.configuration ? { projectWorkflowConfiguration: input.configuration } : {}),
915
- }, "main_workflow"),
907
+ ...(input.configuration.model ? { activeModel: input.configuration.model } : {}),
908
+ metadata: withWorkflowSessionKind(withChatWebSessionSidebarOrder({
909
+ ...withChatRoomId(undefined, input.room.id),
910
+ workflowId: input.workflow.id,
911
+ workflowVersion: input.workflow.version,
912
+ }, nextChatWebSessionSidebarOrder(normalSessions)), "main_workflow"),
916
913
  });
917
- input.state.projectService.addProjectSession({
918
- projectId: input.project.id,
914
+ const workflowSession = input.state.workflowService.addWorkflowSession({
919
915
  piboSessionId: session.id,
920
- kind: "main",
921
- workflowId: workflowSelection.id,
922
- workflowVersion: workflowSelection.version,
923
- title: session.title,
924
- state: input.configuredWorkflow ? "configured" : undefined,
916
+ workflowId: input.workflow.id,
917
+ workflowVersion: input.workflow.version,
918
+ state: "configured",
925
919
  configuration: input.configuration,
926
920
  });
927
921
  input.state.sessionQuery.upsertSession(session);
928
- return session;
922
+ return { session, workflowSession };
929
923
  }
930
- function resolveProjectWorkflowSelection(state, workflowIdValue, workflowVersionValue, options = {}) {
924
+ function resolveWorkflowSelection(state, workflowIdValue, workflowVersionValue, options = {}) {
931
925
  if (options.requireExplicitWorkflowId && (workflowIdValue === undefined || workflowIdValue === null || workflowIdValue === "")) {
932
926
  throw new PiboWebHttpError("Workflow id is required", 400);
933
927
  }
934
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
935
- const workflowVersion = normalizeProjectWorkflowVersion(workflowVersionValue);
936
- if (options.requireExplicitVersion && !workflowVersion) {
928
+ const workflowId = normalizeWorkflowId(workflowIdValue);
929
+ const workflowVersion = normalizeWorkflowVersion(workflowVersionValue);
930
+ if (options.requireExplicitVersion && !workflowVersion)
937
931
  throw new PiboWebHttpError("Workflow version is required", 400);
938
- }
939
- const candidates = buildProjectWorkflowVersionCatalog(state).filter((option) => option.id === workflowId);
932
+ const candidates = buildWorkflowVersionCatalog(state).filter((option) => option.id === workflowId);
940
933
  const selected = candidates.find((option) => workflowVersion === undefined || option.version === workflowVersion);
941
- if (!selected) {
934
+ if (!selected)
942
935
  throw new PiboWebHttpError(`Unknown workflow version: ${workflowId}${workflowVersion ? `@${workflowVersion}` : ""}`, 400);
943
- }
944
- if (selected.status === "archived") {
945
- throw new PiboWebHttpError(`Workflow version '${selected.id}@${selected.version}' is archived and cannot create a Project session by default`, 400);
946
- }
947
- if (selected.status !== "published") {
936
+ if (selected.status === "archived")
937
+ throw new PiboWebHttpError(`Workflow version '${selected.id}@${selected.version}' is archived and cannot create a Workflow Session`, 400);
938
+ if (selected.status !== "published")
948
939
  throw new PiboWebHttpError(`Workflow version '${selected.id}@${selected.version}' is not published`, 400);
949
- }
950
940
  return workflowVersionPickerOptionFromCatalogRecord({ ...selected, status: "published" });
951
941
  }
952
- function normalizeLegacyProjectWorkflowId(value) {
953
- const workflowId = normalizeProjectWorkflowId(value);
954
- if (workflowId !== "simple-chat")
955
- throw new PiboWebHttpError("Only the simple-chat workflow is available in V1", 400);
956
- return workflowId;
957
- }
958
- function normalizeProjectWorkflowId(value) {
959
- if (value === undefined || value === null || value === "")
960
- return "simple-chat";
942
+ function normalizeWorkflowId(value) {
961
943
  if (typeof value !== "string" || !value.trim())
962
- throw new PiboWebHttpError("Workflow id must be a string", 400);
944
+ throw new PiboWebHttpError("Workflow id is required", 400);
963
945
  return value.trim();
964
946
  }
965
- function normalizeProjectWorkflowVersion(value) {
947
+ function normalizeWorkflowVersion(value) {
966
948
  if (value === undefined || value === null || value === "")
967
949
  return undefined;
968
950
  if (typeof value !== "string" || !value.trim())
@@ -1034,18 +1016,14 @@ function requireWorkflowDeleteConfirmation(value, workflowId) {
1034
1016
  throw new PiboWebHttpError(`Type "${workflowId}" to delete this workflow.`, 400);
1035
1017
  }
1036
1018
  }
1037
- const PROJECT_SESSION_PATCH_FIELDS = new Set([
1038
- "title",
1039
- "archived",
1040
- ]);
1041
- function validateProjectWorkflowSnapshotForStart(snapshot, input) {
1019
+ function validateWorkflowSnapshotForStart(snapshot, input) {
1042
1020
  const diagnostics = sanitizeWorkflowDiagnostics(validateWorkflowDefinitionForV2(snapshot.effectiveDefinition, input));
1043
1021
  return {
1044
1022
  diagnostics,
1045
1023
  validation: summarizeWorkflowDiagnostics(diagnostics, "before_workflow_start"),
1046
1024
  };
1047
1025
  }
1048
- function updateProjectWorkflowRunSessionMetadata(input) {
1026
+ function updateWorkflowRunSessionMetadata(input) {
1049
1027
  if (input.session.metadata?.workflowRunId === input.workflowRunId)
1050
1028
  return;
1051
1029
  const updateSession = input.context.channelContext.updateSession;
@@ -1576,8 +1554,8 @@ function requireValidatedWorkflowDraft(state, context, webSession, draftId, trig
1576
1554
  return serializeWorkflowDraft(record);
1577
1555
  }
1578
1556
  function duplicateWorkflowIntoDraft(state, webSession, workflowIdValue, workflowVersionValue) {
1579
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1580
- const workflowVersion = normalizeProjectWorkflowVersion(workflowVersionValue);
1557
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1558
+ const workflowVersion = normalizeWorkflowVersion(workflowVersionValue);
1581
1559
  const published = selectPublishedWorkflowVersion(state, workflowId, workflowVersion);
1582
1560
  if (!published)
1583
1561
  throw new PiboWebHttpError("Published workflow version not found", 404);
@@ -1628,8 +1606,8 @@ function duplicateWorkflowIntoDraft(state, webSession, workflowIdValue, workflow
1628
1606
  return serializeWorkflowDraft(draft);
1629
1607
  }
1630
1608
  function createNextVersionDraftFromPublishedWorkflow(state, webSession, workflowIdValue, workflowVersionValue) {
1631
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1632
- const workflowVersion = normalizeProjectWorkflowVersion(workflowVersionValue);
1609
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1610
+ const workflowVersion = normalizeWorkflowVersion(workflowVersionValue);
1633
1611
  const published = selectPublishedWorkflowVersion(state, workflowId, workflowVersion);
1634
1612
  if (!published)
1635
1613
  throw new PiboWebHttpError("Published workflow version not found", 404);
@@ -1686,7 +1664,7 @@ function createNextVersionDraftFromPublishedWorkflow(state, webSession, workflow
1686
1664
  return { draft: serializeWorkflowDraft(draft), reused: false };
1687
1665
  }
1688
1666
  function archiveWorkflowIdentity(state, context, webSession, workflowIdValue, reasonValue) {
1689
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1667
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1690
1668
  const catalog = buildWorkflowCatalogList(state, context, webSession, { includeArchived: true });
1691
1669
  const workflow = catalog.workflows.find((record) => record.id === workflowId);
1692
1670
  if (!workflow)
@@ -1716,7 +1694,7 @@ function archiveWorkflowIdentity(state, context, webSession, workflowIdValue, re
1716
1694
  return { workflow: archivedWorkflow, archiveState };
1717
1695
  }
1718
1696
  function deleteWorkflowIdentity(state, context, webSession, workflowIdValue, body) {
1719
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1697
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1720
1698
  const catalog = buildWorkflowCatalogList(state, context, webSession, { includeArchived: true });
1721
1699
  const workflow = catalog.workflows.find((record) => record.id === workflowId);
1722
1700
  if (!workflow)
@@ -1749,7 +1727,7 @@ function deleteWorkflowIdentity(state, context, webSession, workflowIdValue, bod
1749
1727
  return { workflowId, deleted: true, tombstone };
1750
1728
  }
1751
1729
  function selectPublishedWorkflowVersion(state, workflowId, version) {
1752
- const options = buildProjectWorkflowVersionOptions(state).filter((option) => option.id === workflowId);
1730
+ const options = buildWorkflowVersionOptions(state).filter((option) => option.id === workflowId);
1753
1731
  const selected = version ? options.find((option) => option.version === version) : options[0];
1754
1732
  if (!selected)
1755
1733
  return undefined;
@@ -1917,7 +1895,9 @@ function normalizeWorkflowDraftManualTriggerRunBody(body) {
1917
1895
  throw new PiboWebHttpError("Manual trigger node id is required", 400);
1918
1896
  if (typeof body.input !== "string")
1919
1897
  throw new PiboWebHttpError("Manual trigger input must be text", 400);
1920
- return { triggerNodeId, input: body.input };
1898
+ if (body.roomId !== undefined && typeof body.roomId !== "string")
1899
+ throw new PiboWebHttpError("Room id must be a string", 400);
1900
+ return { triggerNodeId, input: body.input, roomId: typeof body.roomId === "string" ? body.roomId.trim() || undefined : undefined, workspace: body.workspace === undefined ? undefined : normalizeRoomWorkspace(body.workspace) };
1921
1901
  }
1922
1902
  function resolveWorkflowRuntimeProfile(input) {
1923
1903
  const profile = input.context.channelContext.getProfiles?.().find((candidate) => candidate.name === input.requestedId || candidate.aliases.includes(input.requestedId));
@@ -2204,7 +2184,7 @@ function validateWorkflowNestedNodeLike(nodeId, node, input, diagnostics) {
2204
2184
  const workflowId = typeof node.workflowId === "string" ? node.workflowId.trim() : undefined;
2205
2185
  const workflowVersion = typeof node.workflowVersion === "string" ? node.workflowVersion.trim() : undefined;
2206
2186
  const selected = workflowId
2207
- ? buildProjectWorkflowVersionOptions(input.state).find((option) => option.id === workflowId && (!workflowVersion || option.version === workflowVersion))
2187
+ ? buildWorkflowVersionOptions(input.state).find((option) => option.id === workflowId && (!workflowVersion || option.version === workflowVersion))
2208
2188
  : undefined;
2209
2189
  if (!selected) {
2210
2190
  const registryRef = workflowId ? `${workflowId}${workflowVersion ? `@${workflowVersion}` : ""}` : undefined;
@@ -2424,7 +2404,7 @@ async function deleteSessionsForAgentProfile(state, context, webSession, profile
2424
2404
  }
2425
2405
  const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2426
2406
  for (const id of orderedIds) {
2427
- await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
2407
+ await deleteSession(id);
2428
2408
  state.sessionQuery.deleteSessions([id]);
2429
2409
  state.eventCommands.deleteSessions([id]);
2430
2410
  }
@@ -2451,7 +2431,7 @@ async function deleteSessionSubtree(state, context, webSession, rootSession) {
2451
2431
  }
2452
2432
  const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2453
2433
  for (const id of orderedIds) {
2454
- await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
2434
+ await deleteSession(id);
2455
2435
  state.sessionQuery.deleteSessions([id]);
2456
2436
  state.eventCommands.deleteSessions([id]);
2457
2437
  }
@@ -2492,7 +2472,7 @@ async function deleteRoomTree(state, context, webSession, room, confirmName) {
2492
2472
  }
2493
2473
  const orderedSessionIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2494
2474
  for (const id of orderedSessionIds) {
2495
- await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
2475
+ await deleteSession(id);
2496
2476
  state.sessionQuery.deleteSessions([id]);
2497
2477
  state.eventCommands.deleteSessions([id]);
2498
2478
  }
@@ -2705,6 +2685,48 @@ async function resolveContextBuildRuntime(context, runtimeInstanceId) {
2705
2685
  function indexSharedSessions(sessionQuery, sessions) {
2706
2686
  sessionQuery.upsertSessionsIfChanged(sessions);
2707
2687
  }
2688
+ function nextChatWebRoomSidebarOrder(rooms) {
2689
+ return rooms.reduce((maximum, room) => {
2690
+ const createdOrder = Date.parse(room.createdAt) || 0;
2691
+ return Math.max(maximum, piboRoomSidebarOrder(room) ?? createdOrder);
2692
+ }, 0) + 1;
2693
+ }
2694
+ function reorderChatWebRoom(input) {
2695
+ if (input.room.parentRoomId || input.targetRoom.parentRoomId) {
2696
+ throw new PiboWebHttpError("Only top-level rooms can be reordered", 400);
2697
+ }
2698
+ if (isDefaultPiboRoom(input.room) || isDefaultPiboRoom(input.targetRoom)) {
2699
+ throw new PiboWebHttpError("Shared Chat cannot be reordered", 400);
2700
+ }
2701
+ if (isPiboRoomArchived(input.room) || isPiboRoomArchived(input.targetRoom)) {
2702
+ throw new PiboWebHttpError("Archived rooms cannot be reordered", 400);
2703
+ }
2704
+ const pinned = isPiboRoomPinned(input.room);
2705
+ if (pinned !== isPiboRoomPinned(input.targetRoom)) {
2706
+ throw new PiboWebHttpError("Pinned and unpinned rooms cannot be reordered together", 400);
2707
+ }
2708
+ const ordered = input.state.roomService.listRooms()
2709
+ .filter((room) => !room.parentRoomId && !isDefaultPiboRoom(room) && !isPiboRoomArchived(room) && isPiboRoomPinned(room) === pinned)
2710
+ .sort(comparePiboRoomsBySidebarOrder);
2711
+ const movedIndex = ordered.findIndex((room) => room.id === input.room.id);
2712
+ const targetIndex = ordered.findIndex((room) => room.id === input.targetRoom.id);
2713
+ if (movedIndex < 0 || targetIndex < 0)
2714
+ throw new PiboWebHttpError("Room ordering target not found", 404);
2715
+ if (movedIndex === targetIndex)
2716
+ return ordered.map((room) => room.id);
2717
+ const [moved] = ordered.splice(movedIndex, 1);
2718
+ const remainingTargetIndex = ordered.findIndex((room) => room.id === input.targetRoom.id);
2719
+ ordered.splice(input.position === "before" ? remainingTargetIndex : remainingTargetIndex + 1, 0, moved);
2720
+ const orderBase = nextChatWebRoomSidebarOrder(ordered) + ordered.length - 1;
2721
+ for (const [index, room] of ordered.entries()) {
2722
+ const updated = input.state.roomService.updateRoom(room.id, {
2723
+ metadata: withPiboRoomSidebarOrder(room.metadata, orderBase - index),
2724
+ });
2725
+ if (!updated)
2726
+ throw new PiboWebHttpError("Room not found", 404);
2727
+ }
2728
+ return ordered.map((room) => room.id);
2729
+ }
2708
2730
  function nextChatWebSessionSidebarOrder(sessions) {
2709
2731
  return sessions.reduce((maximum, session) => {
2710
2732
  const createdOrder = Date.parse(session.createdAt) || 0;
@@ -3221,284 +3243,75 @@ function createEventStream(input) {
3221
3243
  },
3222
3244
  });
3223
3245
  }
3224
- async function buildProjectsBootstrap(input) {
3225
- const sharedDefaultProject = input.state.projectService.ensureSharedDefaultProject();
3226
- const selectedProject = input.projectId ? requireSharedProject(input.state, input.webSession, input.projectId, { includeArchived: true }) : sharedDefaultProject;
3227
- let storedProjectSessions = input.state.projectService
3228
- .listProjectSessions(selectedProject.id, { includeArchived: input.includeArchived })
3229
- .filter((projectSession) => projectSession.workflowId === "simple-chat");
3230
- if (selectedProject.id === sharedDefaultProject.id && storedProjectSessions.length === 0) {
3231
- const session = createProjectChatSession({
3232
- state: input.state,
3233
- context: input.context,
3234
- webSession: input.webSession,
3235
- project: sharedDefaultProject,
3236
- profile: input.defaultProfile,
3237
- workflowId: "simple-chat",
3238
- });
3239
- storedProjectSessions = [input.state.projectService.getProjectSession(session.id)];
3240
- }
3241
- const projectSessions = storedProjectSessions;
3242
- const rootSessions = projectSessions
3243
- .map((projectSession) => input.context.channelContext.getSession(projectSession.piboSessionId))
3244
- .filter((session) => Boolean(session));
3245
- const sessions = collectProjectSessionTreeSessions(rootSessions, listSharedSessions(input.context));
3246
- const requestedSession = input.piboSessionId ? sessions.find((session) => session.id === input.piboSessionId) : undefined;
3247
- const selectedSession = requestedSession ?? sessions.find((session) => session.id === selectedProject.currentMainSessionId) ?? sessions[0];
3248
- indexSharedSessions(input.state.sessionQuery, sessions);
3249
- const nodes = await buildSessionNodes(sessions, input.state.sessionQuery.listSessions(), selectedProject.projectFolder, new Map(), { skipPiMetadataFallback: true });
3250
- applyProjectSessionArchiveState(nodes, new Map(projectSessions.map((projectSession) => [projectSession.piboSessionId, Boolean(projectSession.archived)])));
3251
- return {
3252
- identity: input.webSession.authSession.identity,
3253
- sharedDefaultProject,
3254
- project: selectedProject,
3255
- projects: listSharedProjects(input.state, input.webSession, { includeArchived: input.includeArchived }),
3256
- projectSessions,
3257
- workflowLifecycleEvents: [],
3258
- ...(selectedSession ? { session: selectedSession, selectedPiboSessionId: selectedSession.id } : {}),
3259
- selectedProjectId: selectedProject.id,
3260
- sessions: nodes,
3261
- ...(await loadBootstrapCatalog(input.state, input.context, input.webSession)),
3262
- };
3263
- }
3264
- function collectProjectSessionTreeSessions(rootSessions, candidateSessions) {
3265
- const sessionsById = new Map(rootSessions.map((session) => [session.id, session]));
3266
- let changed = true;
3267
- while (changed) {
3268
- changed = false;
3269
- for (const session of candidateSessions) {
3270
- if (sessionsById.has(session.id))
3271
- continue;
3272
- if (!session.parentId || !sessionsById.has(session.parentId))
3273
- continue;
3274
- sessionsById.set(session.id, session);
3275
- changed = true;
3276
- }
3277
- }
3278
- return [...sessionsById.values()].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
3279
- }
3280
- function enrichProjectSessionWorkflowDefinitionLink(state, projectSession) {
3281
- if (!isProjectWorkflowBackedSession(projectSession))
3282
- return projectSession;
3283
- const snapshot = state.projectService.getWorkflowSessionSnapshotForSession(projectSession.piboSessionId);
3284
- const workflowId = projectSession.workflowId;
3285
- const workflowVersion = projectSession.workflowVersion ?? snapshot?.workflow.version;
3246
+ function enrichWorkflowSession(state, workflowSession) {
3247
+ const snapshot = state.workflowService.getWorkflowSessionSnapshotForSession(workflowSession.piboSessionId);
3248
+ const workflowVersion = workflowSession.workflowVersion ?? snapshot?.workflow.version;
3286
3249
  const catalogRecord = workflowVersion
3287
- ? buildProjectWorkflowVersionCatalog(state).find((record) => record.id === workflowId && record.version === workflowVersion)
3250
+ ? buildWorkflowVersionCatalog(state).find((record) => record.id === workflowSession.workflowId && record.version === workflowVersion)
3288
3251
  : undefined;
3289
3252
  const snapshotHash = snapshot?.workflow.effectiveDefinitionHash ?? snapshot?.deletedDefinitionFallback.effectiveDefinitionHash;
3290
- if (catalogRecord && catalogRecord.status !== "deleted") {
3291
- return {
3292
- ...projectSession,
3293
- workflowDefinitionLink: {
3294
- status: "live",
3295
- workflowId,
3296
- workflowVersion: catalogRecord.version,
3297
- title: catalogRecord.title,
3298
- ...(snapshotHash ? { definitionHash: snapshotHash } : {}),
3299
- href: workflowDefinitionViewerPath(catalogRecord.id, catalogRecord.version),
3300
- },
3301
- };
3302
- }
3303
- const fallback = snapshot?.deletedDefinitionFallback;
3304
- const definitionHash = fallback?.effectiveDefinitionHash ?? snapshotHash;
3305
- return {
3306
- ...projectSession,
3307
- workflowDefinitionLink: {
3308
- status: "snapshot_only_definition_deleted",
3309
- workflowId,
3310
- ...(workflowVersion ? { workflowVersion } : {}),
3311
- title: fallback?.title ?? snapshot?.workflow.title ?? projectSession.title ?? workflowId,
3312
- ...(definitionHash ? { definitionHash } : {}),
3313
- tombstoneLabel: fallback?.tombstoneLabel ?? "Definition deleted or no longer available in the live Workflow catalog.",
3314
- },
3315
- };
3316
- }
3317
- function enrichProjectSessionWorkflowWaitTokens(state, projectSession) {
3318
- if (!projectSession.workflowRunId)
3319
- return projectSession;
3320
- const waitTokens = state.projectService.listProjectWorkflowWaitTokens({
3321
- projectId: projectSession.projectId,
3322
- piboSessionId: projectSession.piboSessionId,
3323
- workflowRunId: projectSession.workflowRunId,
3324
- status: "pending",
3325
- limit: 20,
3326
- });
3327
- if (!waitTokens.length)
3328
- return projectSession;
3253
+ const workflowDefinitionLink = catalogRecord && catalogRecord.status !== "deleted"
3254
+ ? { status: "live", workflowId: workflowSession.workflowId, workflowVersion: catalogRecord.version, title: catalogRecord.title, ...(snapshotHash ? { definitionHash: snapshotHash } : {}), href: workflowDefinitionViewerPath(catalogRecord.id, catalogRecord.version) }
3255
+ : { status: "snapshot_only_definition_deleted", workflowId: workflowSession.workflowId, ...(workflowVersion ? { workflowVersion } : {}), title: snapshot?.deletedDefinitionFallback.title ?? snapshot?.workflow.title ?? workflowSession.workflowId, ...(snapshotHash ? { definitionHash: snapshotHash } : {}), tombstoneLabel: snapshot?.deletedDefinitionFallback.tombstoneLabel ?? "No published catalog version is available. Inspection uses the immutable Workflow snapshot." };
3256
+ const waitTokens = workflowSession.workflowRunId ? state.workflowService.listWorkflowWaitTokens({ piboSessionId: workflowSession.piboSessionId, workflowRunId: workflowSession.workflowRunId, status: "pending", limit: 20 }) : [];
3257
+ return { ...workflowSession, workflowDefinitionLink, ...(waitTokens.length ? { pendingHumanActions: waitTokens.map((token) => workflowPendingHumanActionFromToken(token, WORKFLOW_HUMAN_ACTION_REF_OPTIONS)) } : {}) };
3258
+ }
3259
+ function inspectWorkflowSession(state, context, piboSessionId) {
3260
+ requireSharedSession(context, piboSessionId);
3261
+ const link = state.workflowService.getWorkflowSession(piboSessionId);
3262
+ if (!link)
3263
+ throw new PiboWebHttpError("Workflow session not found", 404);
3264
+ const workflowSession = enrichWorkflowSession(state, link);
3265
+ const snapshot = state.workflowService.getWorkflowSessionSnapshotForSession(piboSessionId);
3266
+ const run = state.workflowService.getWorkflowRunForSession(piboSessionId);
3267
+ const workflowRunId = run?.id ?? workflowSession.workflowRunId;
3268
+ const definitionSnapshot = run?.definitionSnapshotId ? state.workflowService.runtimeStore.getDefinitionSnapshot(run.definitionSnapshotId) : undefined;
3329
3269
  return {
3330
- ...projectSession,
3331
- pendingHumanActions: waitTokens.map((token) => projectWorkflowPendingHumanActionFromToken(token, WORKFLOW_HUMAN_ACTION_REF_OPTIONS)),
3270
+ workflowSession,
3271
+ ...(definitionSnapshot ? { definitionSnapshot } : {}),
3272
+ ...(snapshot ? { snapshot } : {}),
3273
+ ...(run ? { run } : {}),
3274
+ waitTokens: state.workflowService.listWorkflowWaitTokens({ piboSessionId, ...(workflowRunId ? { workflowRunId } : {}), limit: 200 }),
3275
+ humanActions: state.workflowService.listWorkflowHumanActions({ piboSessionId, ...(workflowRunId ? { workflowRunId } : {}), limit: 200 }),
3276
+ nodeAttempts: workflowRunId ? state.workflowService.runtimeStore.listNodeAttempts({ workflowRunId, limit: 200 }) : [],
3277
+ edgeTransfers: workflowRunId ? state.workflowService.runtimeStore.listEdgeTransfers({ workflowRunId, limit: 200 }) : [],
3278
+ lifecycleEvents: [...new Map([
3279
+ ...state.workflowLifecycleEventStore.listEvents({ piboSessionId, limit: 200 }),
3280
+ ...(workflowRunId ? state.workflowLifecycleEventStore.listEvents({ workflowRunId, limit: 200 }) : []),
3281
+ ].map((event) => [event.id, event])).values()].sort((left, right) => right.createdAt.localeCompare(left.createdAt) || right.id.localeCompare(left.id)),
3332
3282
  };
3333
3283
  }
3334
- function submitProjectWorkflowHumanAction(input) {
3335
- const project = requireSharedProject(input.state, input.webSession, input.projectId);
3336
- const session = requireSharedSession(input.context, input.piboSessionId);
3337
- const projectSession = input.state.projectService.getProjectSession(session.id);
3338
- if (!projectSession || projectSession.projectId !== project.id)
3339
- throw new PiboWebHttpError("Project workflow session not found", 404);
3340
- if (!projectSession.workflowRunId) {
3341
- return projectWorkflowHumanActionDiagnosticResponse("Project workflow session has no workflow run to resolve", [{
3342
- code: "WorkflowRuntimeError.workflowRunMissing",
3343
- message: "This Project workflow session has not started a workflow run, so no human wait token can be resolved.",
3344
- severity: "error",
3345
- path: "$.workflowRunId",
3346
- hint: "Start the configured workflow session before submitting human actions.",
3347
- }], 409, undefined, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3348
- }
3349
- const normalized = normalizeProjectWorkflowHumanActionBody(input.body);
3350
- if (normalized.diagnostics.length) {
3351
- return projectWorkflowHumanActionDiagnosticResponse("Human action request is invalid", normalized.diagnostics, 400, undefined, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3352
- }
3284
+ function submitWorkflowHumanAction(input) {
3285
+ requireSharedSession(input.context, input.piboSessionId);
3286
+ const workflowSession = input.state.workflowService.getWorkflowSession(input.piboSessionId);
3287
+ if (!workflowSession)
3288
+ throw new PiboWebHttpError("Workflow session not found", 404);
3289
+ if (!workflowSession.workflowRunId)
3290
+ return workflowHumanActionDiagnosticResponse("Workflow session has no workflow run to resolve", [{ code: "WorkflowRuntimeError.workflowRunMissing", message: "This Workflow session has not started a run.", severity: "error", path: "$.workflowRunId", hint: "Start the configured Workflow session before submitting human actions." }], 409, undefined, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3291
+ const normalized = normalizeWorkflowHumanActionBody(input.body);
3292
+ if (normalized.diagnostics.length)
3293
+ return workflowHumanActionDiagnosticResponse("Human action request is invalid", normalized.diagnostics, 400, undefined, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3353
3294
  const request = normalized.request;
3354
- const waitToken = input.state.projectService.getProjectWorkflowWaitToken(request.waitTokenId);
3355
- const validation = validateProjectWorkflowHumanActionRequest({
3356
- projectSession,
3357
- waitToken,
3358
- request,
3359
- humanActionOptions: WORKFLOW_HUMAN_ACTION_REF_OPTIONS,
3360
- });
3295
+ const waitToken = input.state.workflowService.getWorkflowWaitToken(request.waitTokenId);
3296
+ const validation = validateWorkflowHumanActionRequest({ workflowSession, waitToken, request, humanActionOptions: WORKFLOW_HUMAN_ACTION_REF_OPTIONS });
3361
3297
  if (validation.diagnostics.length) {
3362
- const responseWaitToken = waitToken && validation.expiredAt
3363
- ? input.state.projectService.expireProjectWorkflowWaitToken({
3364
- projectId: project.id,
3365
- piboSessionId: session.id,
3366
- workflowRunId: projectSession.workflowRunId,
3367
- waitTokenId: waitToken.id,
3368
- resolvedAt: validation.checkedAt,
3369
- }).waitToken
3370
- : waitToken;
3371
- recordWorkflowLifecycleEvent(input.state, input.webSession, {
3372
- type: "workflow.human_action.submitted",
3373
- workflowId: projectSession.workflowId,
3374
- ...(projectSession.workflowVersion ? { workflowVersion: projectSession.workflowVersion } : {}),
3375
- projectId: project.id,
3376
- piboSessionId: session.id,
3377
- workflowRunId: projectSession.workflowRunId,
3378
- status: "blocked",
3379
- diagnostics: validation.diagnostics,
3380
- payload: projectWorkflowHumanActionLifecyclePayload(request),
3381
- });
3382
- return projectWorkflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", validation.diagnostics, validation.httpStatus, responseWaitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3298
+ const responseWaitToken = waitToken && validation.expiredAt ? input.state.workflowService.expireWorkflowWaitToken({ piboSessionId: input.piboSessionId, workflowRunId: workflowSession.workflowRunId, waitTokenId: waitToken.id, resolvedAt: validation.checkedAt }).waitToken : waitToken;
3299
+ recordWorkflowLifecycleEvent(input.state, input.webSession, { type: "workflow.human_action.submitted", workflowId: workflowSession.workflowId, workflowVersion: workflowSession.workflowVersion, piboSessionId: input.piboSessionId, workflowRunId: workflowSession.workflowRunId, status: "blocked", diagnostics: validation.diagnostics, payload: workflowHumanActionLifecyclePayload(request) });
3300
+ return workflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", validation.diagnostics, validation.httpStatus, responseWaitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3383
3301
  }
3384
3302
  try {
3385
- const result = input.state.projectService.resolveProjectWorkflowHumanAction({
3386
- projectId: project.id,
3387
- piboSessionId: session.id,
3388
- workflowRunId: projectSession.workflowRunId,
3389
- waitTokenId: request.waitTokenId,
3390
- ...(validation.actionRef?.id ? { actionId: validation.actionRef.id } : {}),
3391
- kind: validation.actionKind,
3392
- actor: {
3393
- userId: input.webSession.authSession.identity.userId,
3394
- ...(input.webSession.authSession.identity.email ? { email: input.webSession.authSession.identity.email } : {}),
3395
- },
3396
- ...(request.payload !== undefined ? { payload: request.payload } : {}),
3397
- });
3398
- const enrichedProjectSession = enrichProjectSessionWorkflowWaitTokens(input.state, enrichProjectSessionWorkflowDefinitionLink(input.state, result.projectSession));
3399
- recordWorkflowLifecycleEvent(input.state, input.webSession, {
3400
- type: "workflow.human_action.submitted",
3401
- workflowId: result.projectSession.workflowId,
3402
- ...(result.projectSession.workflowVersion ? { workflowVersion: result.projectSession.workflowVersion } : {}),
3403
- projectId: project.id,
3404
- piboSessionId: session.id,
3405
- workflowRunId: result.run.id,
3406
- status: "submitted",
3407
- diagnostics: [],
3408
- payload: projectWorkflowHumanActionSubmittedLifecyclePayload(result.waitToken.id, result.action),
3409
- });
3410
- return responseJson({
3411
- ok: true,
3412
- projectSession: enrichedProjectSession,
3413
- waitToken: result.waitToken,
3414
- action: result.action,
3415
- run: result.run,
3416
- diagnostics: [],
3417
- }, { status: result.action.kind === "cancel" ? 200 : 202 });
3303
+ const result = input.state.workflowService.resolveWorkflowHumanAction({ piboSessionId: input.piboSessionId, workflowRunId: workflowSession.workflowRunId, waitTokenId: request.waitTokenId, ...(validation.actionRef?.id ? { actionId: validation.actionRef.id } : {}), kind: validation.actionKind, actor: { userId: input.webSession.authSession.identity.userId, ...(input.webSession.authSession.identity.email ? { email: input.webSession.authSession.identity.email } : {}) }, ...(request.payload !== undefined ? { payload: request.payload } : {}) });
3304
+ recordWorkflowLifecycleEvent(input.state, input.webSession, { type: "workflow.human_action.submitted", workflowId: result.workflowSession.workflowId, workflowVersion: result.workflowSession.workflowVersion, piboSessionId: input.piboSessionId, workflowRunId: result.run.id, status: "submitted", diagnostics: [], payload: workflowHumanActionSubmittedLifecyclePayload(result.waitToken.id, result.action) });
3305
+ return responseJson({ ok: true, workflowSession: enrichWorkflowSession(input.state, result.workflowSession), waitToken: result.waitToken, action: result.action, run: result.run, diagnostics: [] }, { status: result.action.kind === "cancel" ? 200 : 202 });
3418
3306
  }
3419
3307
  catch (error) {
3420
- const diagnostics = [projectWorkflowHumanActionRuntimeDiagnostic(error, request.waitTokenId)];
3421
- recordWorkflowLifecycleEvent(input.state, input.webSession, {
3422
- type: "workflow.human_action.submitted",
3423
- workflowId: projectSession.workflowId,
3424
- ...(projectSession.workflowVersion ? { workflowVersion: projectSession.workflowVersion } : {}),
3425
- projectId: project.id,
3426
- piboSessionId: session.id,
3427
- workflowRunId: projectSession.workflowRunId,
3428
- status: "blocked",
3429
- diagnostics,
3430
- payload: projectWorkflowHumanActionLifecyclePayload(request),
3431
- });
3432
- const responseWaitToken = input.state.projectService.getProjectWorkflowWaitToken(request.waitTokenId) ?? waitToken;
3433
- return projectWorkflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", diagnostics, 409, responseWaitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3308
+ const diagnostics = [workflowHumanActionRuntimeDiagnostic(error, request.waitTokenId)];
3309
+ return workflowHumanActionDiagnosticResponse("Human action was rejected by wait-token validation", diagnostics, 409, input.state.workflowService.getWorkflowWaitToken(request.waitTokenId) ?? waitToken, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3434
3310
  }
3435
3311
  }
3436
- function isProjectWorkflowBackedSession(projectSession) {
3437
- return Boolean(projectSession.workflowRunId) || projectSession.state === "workflow" || projectSession.workflowId !== "simple-chat";
3438
- }
3439
3312
  function workflowDefinitionViewerPath(workflowId, workflowVersion) {
3440
3313
  return `${CHAT_WEB_MOUNT_PATH}/workflows/view/${encodeURIComponent(workflowId)}/${encodeURIComponent(workflowVersion)}`;
3441
3314
  }
3442
- function applyProjectSessionArchiveState(nodes, archivedBySessionId) {
3443
- for (const node of nodes) {
3444
- const archived = archivedBySessionId.get(node.piboSessionId);
3445
- if (archived !== undefined)
3446
- node.archived = archived;
3447
- applyProjectSessionArchiveState(node.children, archivedBySessionId);
3448
- }
3449
- }
3450
- async function sendProjectMessage(input) {
3451
- const text = normalizeMessageText(input.body.text);
3452
- const delivery = normalizeMessageDelivery(input.body.delivery);
3453
- const clientTxnId = normalizeClientTxnId(input.body.clientTxnId);
3454
- if (typeof input.body.piboSessionId !== "string")
3455
- throw new PiboWebHttpError("Project session is required", 400);
3456
- const selectedSession = requireSharedSession(input.context, input.body.piboSessionId);
3457
- const projectSession = input.state.projectService.getProjectSession(selectedSession.id);
3458
- if (!projectSession)
3459
- throw new PiboWebHttpError("Project session not found", 404);
3460
- requireSharedProject(input.state, input.webSession, projectSession.projectId);
3461
- const actorId = auditActorIdFor(input.webSession);
3462
- const duplicate = clientTxnId ? input.state.eventCommands.findByClientTxn(undefined, actorId, clientTxnId) : undefined;
3463
- if (duplicate)
3464
- return responseJson({ duplicate: true, event: duplicate });
3465
- const accepted = input.state.eventCommands.appendEvent({
3466
- piboSessionId: selectedSession.id,
3467
- eventType: "user.message.accepted",
3468
- actorType: "user",
3469
- actorId,
3470
- clientTxnId,
3471
- retentionClass: "chat_message",
3472
- payload: {
3473
- type: "user.message.accepted",
3474
- piboSessionId: selectedSession.id,
3475
- projectId: projectSession.projectId,
3476
- text,
3477
- delivery,
3478
- ...(clientTxnId ? { clientTxnId } : {}),
3479
- },
3480
- });
3481
- for (const listener of input.state.liveListeners)
3482
- listener(accepted);
3483
- const messageId = clientTxnId ?? randomUUID();
3484
- try {
3485
- const output = await input.context.channelContext.emit({
3486
- type: "message",
3487
- piboSessionId: selectedSession.id,
3488
- id: messageId,
3489
- text,
3490
- delivery,
3491
- source: "user",
3492
- });
3493
- return responseJson({ accepted, output });
3494
- }
3495
- catch (error) {
3496
- if (error instanceof PiboSteeringUnavailableError || error instanceof AgentRuntimeBindingMissingError) {
3497
- throw new PiboWebHttpError(error.message, 409);
3498
- }
3499
- throw error;
3500
- }
3501
- }
3502
3315
  function startChatStreamingFixture(input) {
3503
3316
  const requestedRoomId = typeof input.body.roomId === "string" ? input.body.roomId : undefined;
3504
3317
  const selectedSession = resolveRequestedSession(input.state, input.context, input.webSession, input.defaultProfile, typeof input.body.piboSessionId === "string" ? input.body.piboSessionId : undefined, requestedRoomId);
@@ -3727,6 +3540,11 @@ export function createChatWebApp(options = {}) {
3727
3540
  const defaultProfile = options.defaultProfile ?? "base";
3728
3541
  const dataStore = createDataStore(options);
3729
3542
  const reliabilityStore = createReliabilityStore(options.reliabilityStorePath);
3543
+ const workflowService = new ChatWorkflowSessionService(workflowStorePath(options, dataStore));
3544
+ if (dataStore.path !== ":memory:" && workflowService.path !== ":memory:") {
3545
+ migrateLegacyProjects({ dataStore, workflowService });
3546
+ }
3547
+ const workflowCatalogStore = workflowService.catalogDataStore;
3730
3548
  const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
3731
3549
  eventLoopDelay.enable();
3732
3550
  const state = {
@@ -3736,7 +3554,7 @@ export function createChatWebApp(options = {}) {
3736
3554
  eventCommands: new ChatEventCommandService(dataStore),
3737
3555
  readState: new ChatReadStateService(dataStore),
3738
3556
  roomService: new ChatRoomService(dataStore),
3739
- projectService: new ChatProjectService(options.projectStorePath),
3557
+ workflowService,
3740
3558
  agentStore: createAgentStore(options.agentStorePath),
3741
3559
  reliabilityStore,
3742
3560
  cronStore: createDefaultPiboCronStore({ path: options.cronStorePath }),
@@ -3762,12 +3580,12 @@ export function createChatWebApp(options = {}) {
3762
3580
  globalRoot: options.userSkillGlobalRoot ?? os.homedir(),
3763
3581
  workspaceRoot: options.userSkillWorkspaceRoot ?? process.cwd(),
3764
3582
  }),
3765
- workflowDraftStore: new ChatWorkflowDraftStore(dataStore),
3766
- workflowPublishedVersionStore: new ChatWorkflowPublishedVersionStore(dataStore),
3767
- workflowArchiveStore: new ChatWorkflowArchiveStore(dataStore),
3768
- workflowTombstoneStore: new ChatWorkflowTombstoneStore(dataStore),
3769
- workflowLifecycleEventStore: new ChatWorkflowLifecycleEventStore(dataStore),
3770
- workflowPromptAssetStore: new ChatWorkflowPromptAssetStore(dataStore),
3583
+ workflowDraftStore: new ChatWorkflowDraftStore(workflowCatalogStore),
3584
+ workflowPublishedVersionStore: new ChatWorkflowPublishedVersionStore(workflowCatalogStore),
3585
+ workflowArchiveStore: new ChatWorkflowArchiveStore(workflowCatalogStore),
3586
+ workflowTombstoneStore: new ChatWorkflowTombstoneStore(workflowCatalogStore),
3587
+ workflowLifecycleEventStore: new ChatWorkflowLifecycleEventStore(workflowCatalogStore),
3588
+ workflowPromptAssetStore: new ChatWorkflowPromptAssetStore(workflowCatalogStore),
3771
3589
  telemetryRetentionMaintenance: {},
3772
3590
  integrations,
3773
3591
  };
@@ -3788,7 +3606,7 @@ export function createChatWebApp(options = {}) {
3788
3606
  state.subscribedContext = undefined;
3789
3607
  state.eventLoopDelay.disable();
3790
3608
  state.outputPersistenceRetries.dispose();
3791
- state.projectService.close();
3609
+ state.workflowService.close();
3792
3610
  state.agentStore.close();
3793
3611
  state.reliabilityStore.close();
3794
3612
  state.cronStore.close();
@@ -4049,306 +3867,66 @@ export function createChatWebApp(options = {}) {
4049
3867
  if (response)
4050
3868
  return response;
4051
3869
  }
4052
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects/bootstrap` && request.method === "GET") {
4053
- const webSession = await requireSession(request, context);
4054
- return responseJson(await buildProjectsBootstrap({
4055
- state,
4056
- context,
4057
- webSession,
4058
- defaultProfile,
4059
- projectId: url.searchParams.get("projectId") || undefined,
4060
- piboSessionId: url.searchParams.get("piboSessionId") || undefined,
4061
- includeArchived: parseBooleanSearchParam(url, "includeArchived"),
4062
- }));
4063
- }
4064
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects/message` && request.method === "POST") {
3870
+ if (url.pathname === `${CHAT_WEB_API_PREFIX}/workflow-sessions` && request.method === "POST") {
4065
3871
  requireSameOriginJsonRequest(request);
4066
3872
  const webSession = await requireSession(request, context);
4067
3873
  const body = await readJsonBody(request);
4068
- return sendProjectMessage({ state, context, webSession, defaultProfile, body });
4069
- }
4070
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects` && request.method === "GET") {
4071
- const webSession = await requireSession(request, context);
4072
- return responseJson({ projects: listSharedProjects(state, webSession, { includeArchived: parseBooleanSearchParam(url, "includeArchived") }) });
4073
- }
4074
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects` && request.method === "POST") {
4075
- requireSameOriginJsonRequest(request);
4076
- const webSession = await requireSession(request, context);
4077
- const body = await readJsonBody(request);
4078
- try {
4079
- const project = state.projectService.createProject({
4080
- name: normalizeRoomName(body.name),
4081
- description: normalizeProjectDescription(body.description),
4082
- projectFolder: normalizeProjectPath(body.projectFolder),
4083
- createFolder: body.createFolder === true,
4084
- });
4085
- return responseJson({ project }, { status: 201 });
4086
- }
4087
- catch (error) {
4088
- throw new PiboWebHttpError(error instanceof Error ? error.message : String(error), 400);
4089
- }
4090
- }
4091
- const projectResource = projectResourcePath(url.pathname);
4092
- if (projectResource && projectResource.child === undefined && request.method === "PATCH") {
4093
- requireSameOriginJsonRequest(request);
4094
- const webSession = await requireSession(request, context);
4095
- const body = await readJsonBody(request);
4096
- try {
4097
- const existingProject = requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
4098
- if (existingProject.metadata.default === true)
4099
- throw new PiboWebHttpError("Project Manager cannot be changed", 400);
4100
- const project = state.projectService.updateProject(projectResource.projectId, {
4101
- ...(body.name !== undefined ? { name: normalizeRoomName(body.name) } : {}),
4102
- ...(body.description !== undefined ? { description: normalizeProjectDescription(body.description) ?? null } : {}),
4103
- ...(body.archived !== undefined ? { archived: normalizeProjectArchived(body.archived) } : {}),
4104
- });
4105
- if (!project)
4106
- throw new PiboWebHttpError("Project not found", 404);
4107
- return responseJson({ project });
4108
- }
4109
- catch (error) {
4110
- if (error instanceof PiboWebHttpError)
4111
- throw error;
4112
- throw new PiboWebHttpError(error instanceof Error ? error.message : String(error), 400);
4113
- }
4114
- }
4115
- if (projectResource && projectResource.child === undefined && request.method === "DELETE") {
4116
- requireSameOriginJsonRequest(request);
4117
- const webSession = await requireSession(request, context);
4118
- const body = await readJsonBody(request);
4119
- try {
4120
- const project = requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
4121
- if (project.metadata.default === true)
4122
- throw new PiboWebHttpError("Project Manager cannot be deleted", 400);
4123
- return responseJson(state.projectService.deleteProject(projectResource.projectId, {
4124
- confirmName: normalizeRoomDeleteConfirmation(body.confirmName),
4125
- deleteFiles: body.deleteFiles === true,
4126
- }));
4127
- }
4128
- catch (error) {
4129
- if (error instanceof PiboWebHttpError)
4130
- throw error;
4131
- throw new PiboWebHttpError(error instanceof Error ? error.message : String(error), 400);
4132
- }
3874
+ const room = typeof body.roomId === "string" && body.roomId.trim() ? requireRoom(state, body.roomId.trim(), webSession, "write") : state.roomService.ensureDefaultRoom();
3875
+ const workspace = body.workspace === undefined ? undefined : normalizeRoomWorkspace(body.workspace);
3876
+ const profile = resolveCreateSessionProfile(context, defaultProfile, body.profile);
3877
+ const workflow = resolveWorkflowSelection(state, body.workflowId, body.workflowVersion, { requireExplicitWorkflowId: true, requireExplicitVersion: true });
3878
+ const published = resolvePublishedWorkflowDefinitionForProfile(state, workflow, profile);
3879
+ const baseDefinition = cloneJsonObject(published.definition);
3880
+ const configuration = normalizeWorkflowSessionConfiguration(body, baseDefinition);
3881
+ const validation = validatePublishedWorkflowBoundary({ state, context, webSession, definition: baseDefinition, trigger: "before_workflow_session_creation" });
3882
+ recordWorkflowLifecycleEvent(state, webSession, { type: "workflow.validation.completed", workflowId: workflow.id, workflowVersion: workflow.version, status: validation.validation.ok ? "accepted" : "blocked", validation: validation.validation, diagnostics: validation.diagnostics, payload: { trigger: validation.validation.trigger, boundary: "workflow_session_creation" } });
3883
+ if (!validation.validation.ok)
3884
+ return workflowValidationBlockedResponse("Workflow version has validation errors and cannot create a Workflow Session", validation, { workflow });
3885
+ const created = createWorkflowSession({ state, context, room, workspace, profile, workflow, title: normalizeSessionTitle(body.title) ?? undefined, configuration });
3886
+ const snapshot = state.workflowService.saveWorkflowSessionSnapshot(createWorkflowSessionSnapshot({ webSession, session: created.session, workflow: published, baseDefinition, configuration, validation }));
3887
+ recordWorkflowLifecycleEvent(state, webSession, { type: "workflow.session.created", workflowId: workflow.id, workflowVersion: workflow.version, piboSessionId: created.session.id, status: "accepted", validation: validation.validation, diagnostics: validation.diagnostics, payload: { snapshotId: snapshot.id, profile, roomId: room.id } });
3888
+ return responseJson({ session: created.session, workflowSession: enrichWorkflowSession(state, created.workflowSession), workflow, configuration, snapshot, validation: validation.validation, diagnostics: validation.diagnostics }, { status: 201 });
4133
3889
  }
4134
- const workflowHumanActionResource = projectWorkflowHumanActionsResource(url.pathname);
4135
- if (workflowHumanActionResource && request.method === "POST") {
4136
- requireSameOriginJsonRequest(request);
4137
- const webSession = await requireSession(request, context);
4138
- const body = await readJsonBody(request);
4139
- return submitProjectWorkflowHumanAction({
4140
- state,
4141
- context,
4142
- webSession,
4143
- projectId: workflowHumanActionResource.projectId,
4144
- piboSessionId: workflowHumanActionResource.piboSessionId,
4145
- body,
4146
- });
3890
+ const workflowResource = sessionWorkflowResource(url.pathname);
3891
+ if (workflowResource && workflowResource.action === undefined && request.method === "GET") {
3892
+ await requireSession(request, context);
3893
+ return responseJson(inspectWorkflowSession(state, context, workflowResource.piboSessionId));
4147
3894
  }
4148
- if (projectResource && projectResource.child === "workflow-sessions" && request.method === "POST" && !projectWorkflowSessionStartResource(url.pathname) && !workflowHumanActionResource) {
3895
+ if (workflowResource?.action === "human-actions" && request.method === "POST") {
4149
3896
  requireSameOriginJsonRequest(request);
4150
3897
  const webSession = await requireSession(request, context);
4151
- const body = await readJsonBody(request);
4152
- const project = requireSharedProject(state, webSession, projectResource.projectId);
4153
- const profile = resolveCreateSessionProfile(context, defaultProfile, body.profile);
4154
- const workflowSelection = resolveProjectWorkflowSelection(state, body.workflowId, body.workflowVersion, { requireExplicitWorkflowId: true, requireExplicitVersion: true });
4155
- const publishedWorkflow = resolvePublishedWorkflowDefinitionForProfile(state, workflowSelection, profile);
4156
- const baseDefinition = cloneJsonObject(publishedWorkflow.definition);
4157
- const configuration = normalizeProjectWorkflowSessionConfiguration(body, baseDefinition);
4158
- const validation = validatePublishedWorkflowBoundary({
4159
- state,
4160
- context,
4161
- webSession,
4162
- definition: baseDefinition,
4163
- trigger: "before_project_session_creation",
4164
- });
4165
- recordWorkflowLifecycleEvent(state, webSession, {
4166
- type: "workflow.validation.completed",
4167
- workflowId: workflowSelection.id,
4168
- workflowVersion: workflowSelection.version,
4169
- projectId: project.id,
4170
- status: validation.validation.ok ? "accepted" : "blocked",
4171
- validation: validation.validation,
4172
- diagnostics: validation.diagnostics,
4173
- payload: { trigger: validation.validation.trigger, boundary: "project_session_creation" },
4174
- });
4175
- if (!validation.validation.ok) {
4176
- return workflowValidationBlockedResponse("Workflow version has validation errors and cannot be used to create a Project session", validation, { workflow: workflowSelection });
4177
- }
4178
- const session = createProjectChatSession({
4179
- state,
4180
- context,
4181
- webSession,
4182
- project,
4183
- profile,
4184
- workflowId: workflowSelection.id,
4185
- workflowVersion: workflowSelection.version,
4186
- title: normalizeSessionTitle(body.title) ?? undefined,
4187
- configuredWorkflow: true,
4188
- configuration,
4189
- });
4190
- const snapshot = state.projectService.saveWorkflowSessionSnapshot(createProjectWorkflowSessionSnapshot({
4191
- webSession,
4192
- project,
4193
- session,
4194
- workflow: publishedWorkflow,
4195
- baseDefinition,
4196
- configuration,
4197
- validation,
4198
- }));
4199
- const projectSession = state.projectService.getProjectSession(session.id);
4200
- recordWorkflowLifecycleEvent(state, webSession, {
4201
- type: "project.workflow_session.created",
4202
- workflowId: workflowSelection.id,
4203
- workflowVersion: workflowSelection.version,
4204
- projectId: project.id,
4205
- piboSessionId: session.id,
4206
- status: "accepted",
4207
- validation: validation.validation,
4208
- diagnostics: validation.diagnostics,
4209
- payload: { snapshotId: snapshot.id, profile },
4210
- });
4211
- return responseJson({ session, projectSession, workflow: workflowSelection, configuration, snapshot, validation: validation.validation, diagnostics: validation.diagnostics }, { status: 201 });
3898
+ return submitWorkflowHumanAction({ state, context, webSession, piboSessionId: workflowResource.piboSessionId, body: await readJsonBody(request) });
4212
3899
  }
4213
- const workflowSessionStart = projectWorkflowSessionStartResource(url.pathname);
4214
- if (workflowSessionStart && request.method === "POST") {
3900
+ if (workflowResource?.action === "start" && request.method === "POST") {
4215
3901
  requireSameOriginJsonRequest(request);
4216
3902
  const webSession = await requireSession(request, context);
4217
- const session = requireSharedSession(context, workflowSessionStart.piboSessionId);
4218
- const project = requireSharedProject(state, webSession, workflowSessionStart.projectId);
4219
- const projectSession = state.projectService.getProjectSession(session.id);
4220
- if (!projectSession || projectSession.projectId !== project.id)
4221
- throw new PiboWebHttpError("Project workflow session not found", 404);
4222
- const snapshot = state.projectService.getWorkflowSessionSnapshotForSession(session.id);
3903
+ const session = requireSharedSession(context, workflowResource.piboSessionId);
3904
+ const workflowSession = state.workflowService.getWorkflowSession(session.id);
3905
+ if (!workflowSession)
3906
+ throw new PiboWebHttpError("Workflow session not found", 404);
3907
+ const snapshot = state.workflowService.getWorkflowSessionSnapshotForSession(session.id);
4223
3908
  if (!snapshot)
4224
- throw new PiboWebHttpError("Project workflow session snapshot not found", 409);
4225
- const workflowSelection = workflowVersionFromSnapshot(snapshot);
4226
- if (projectSession.workflowRunId) {
4227
- const existingRun = state.projectService.getProjectWorkflowRun(projectSession.workflowRunId);
4228
- if (existingRun) {
4229
- updateProjectWorkflowRunSessionMetadata({ state, context, session, workflowRunId: existingRun.id });
4230
- return responseJson({
4231
- projectSession,
4232
- workflow: workflowSelection,
4233
- snapshot,
4234
- run: existingRun,
4235
- alreadyStarted: true,
4236
- validation: existingRun.validation ?? summarizeWorkflowDiagnostics([], "before_workflow_start"),
4237
- diagnostics: [],
4238
- message: "Workflow run already exists for this Project session.",
4239
- }, { status: 200 });
3909
+ throw new PiboWebHttpError("Workflow session snapshot not found", 409);
3910
+ const workflow = workflowVersionFromSnapshot(snapshot);
3911
+ if (workflowSession.workflowRunId) {
3912
+ const run = state.workflowService.getWorkflowRun(workflowSession.workflowRunId);
3913
+ if (run) {
3914
+ updateWorkflowRunSessionMetadata({ state, context, session, workflowRunId: run.id });
3915
+ return responseJson({ workflowSession: enrichWorkflowSession(state, workflowSession), run, snapshot, workflow, alreadyStarted: true, validation: run.validation ?? summarizeWorkflowDiagnostics([], "before_workflow_start"), diagnostics: [], message: "Workflow run already exists for this Workflow Session." });
4240
3916
  }
4241
3917
  }
4242
- const validation = validateProjectWorkflowSnapshotForStart(snapshot, { state, context, webSession });
4243
- recordWorkflowLifecycleEvent(state, webSession, {
4244
- type: "workflow.validation.completed",
4245
- workflowId: workflowSelection.id,
4246
- workflowVersion: workflowSelection.version,
4247
- projectId: project.id,
4248
- piboSessionId: session.id,
4249
- workflowRunId: projectSession.workflowRunId,
4250
- status: validation.validation.ok ? "accepted" : "blocked",
4251
- validation: validation.validation,
4252
- diagnostics: validation.diagnostics,
4253
- payload: { trigger: validation.validation.trigger, boundary: "workflow_start", snapshotId: snapshot.id },
4254
- });
3918
+ const validation = validateWorkflowSnapshotForStart(snapshot, { state, context, webSession });
3919
+ recordWorkflowLifecycleEvent(state, webSession, { type: "workflow.validation.completed", workflowId: workflow.id, workflowVersion: workflow.version, piboSessionId: session.id, workflowRunId: workflowSession.workflowRunId, status: validation.validation.ok ? "accepted" : "blocked", validation: validation.validation, diagnostics: validation.diagnostics, payload: { trigger: validation.validation.trigger, boundary: "workflow_start", snapshotId: snapshot.id } });
4255
3920
  if (!validation.validation.ok) {
4256
- recordWorkflowLifecycleEvent(state, webSession, {
4257
- type: "project.workflow_start.blocked",
4258
- workflowId: workflowSelection.id,
4259
- workflowVersion: workflowSelection.version,
4260
- projectId: project.id,
4261
- piboSessionId: session.id,
4262
- workflowRunId: projectSession.workflowRunId,
4263
- status: "blocked",
4264
- validation: validation.validation,
4265
- diagnostics: validation.diagnostics,
4266
- payload: { snapshotId: snapshot.id, profile: session.profile },
4267
- });
4268
- return workflowValidationBlockedResponse("Workflow session has validation errors and cannot be started", validation, { projectSession, workflow: workflowSelection });
4269
- }
4270
- const startResult = state.projectService.startWorkflowSessionRun({
4271
- projectId: project.id,
4272
- piboSessionId: session.id,
4273
- runId: `wfr_${randomUUID()}`,
4274
- workflowId: snapshot.workflow.id,
4275
- workflowVersion: snapshot.workflow.version,
4276
- snapshotId: snapshot.id,
4277
- effectiveDefinitionHash: snapshot.workflow.effectiveDefinitionHash,
4278
- current: createProjectWorkflowRunCurrent(snapshot.effectiveDefinition),
4279
- inputValues: snapshot.inputValues,
4280
- validation: validation.validation,
4281
- });
4282
- updateProjectWorkflowRunSessionMetadata({ state, context, session, workflowRunId: startResult.run.id });
4283
- recordWorkflowLifecycleEvent(state, webSession, {
4284
- type: "project.workflow_start.accepted",
4285
- workflowId: workflowSelection.id,
4286
- workflowVersion: workflowSelection.version,
4287
- projectId: project.id,
4288
- piboSessionId: session.id,
4289
- workflowRunId: startResult.run.id,
4290
- status: "accepted",
4291
- validation: validation.validation,
4292
- diagnostics: validation.diagnostics,
4293
- payload: { snapshotId: snapshot.id, profile: session.profile, alreadyStarted: startResult.alreadyStarted },
4294
- });
4295
- if (!startResult.alreadyStarted) {
4296
- recordWorkflowLifecycleEvent(state, webSession, {
4297
- type: "workflow.run.status_changed",
4298
- workflowId: workflowSelection.id,
4299
- workflowVersion: workflowSelection.version,
4300
- projectId: project.id,
4301
- piboSessionId: session.id,
4302
- workflowRunId: startResult.run.id,
4303
- status: "changed",
4304
- validation: validation.validation,
4305
- diagnostics: validation.diagnostics,
4306
- payload: { snapshotId: snapshot.id, state: startResult.run.status, current: startResult.run.current },
4307
- });
4308
- }
4309
- return responseJson({
4310
- projectSession: startResult.projectSession,
4311
- workflow: workflowSelection,
4312
- snapshot,
4313
- run: startResult.run,
4314
- alreadyStarted: startResult.alreadyStarted,
4315
- validation: validation.validation,
4316
- diagnostics: validation.diagnostics,
4317
- message: startResult.alreadyStarted ? "Workflow run already exists for this Project session." : "Workflow run started.",
4318
- }, { status: startResult.alreadyStarted ? 200 : 202 });
4319
- }
4320
- if (projectResource && projectResource.child === "sessions" && request.method === "POST") {
4321
- requireSameOriginJsonRequest(request);
4322
- const webSession = await requireSession(request, context);
4323
- const body = await readJsonBody(request);
4324
- const project = requireSharedProject(state, webSession, projectResource.projectId);
4325
- const profile = resolveCreateSessionProfile(context, defaultProfile, body.profile);
4326
- const workflowId = normalizeLegacyProjectWorkflowId(body.workflowId);
4327
- const session = createProjectChatSession({ state, context, webSession, project, profile, workflowId });
4328
- return responseJson({ session, projectSession: state.projectService.getProjectSession(session.id) }, { status: 201 });
4329
- }
4330
- const projectSessionId = projectSessionResourceId(url.pathname);
4331
- if (projectSessionId && request.method === "PATCH") {
4332
- requireSameOriginJsonRequest(request);
4333
- const webSession = await requireSession(request, context);
4334
- const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, projectSessionId);
4335
- const body = await readJsonBody(request);
4336
- assertProjectSessionPatchFields(body);
4337
- const updateSession = context.channelContext.updateSession;
4338
- if (!updateSession)
4339
- throw new PiboWebHttpError("Session updates are not available", 501);
4340
- const title = normalizeSessionTitle(body.title);
4341
- let updated = selectedSession;
4342
- if (title !== undefined) {
4343
- const next = updateSession(selectedSession.id, { title });
4344
- if (!next)
4345
- throw new PiboWebHttpError("Session not found", 404);
4346
- updated = next;
4347
- state.sessionQuery.upsertSession(updated);
3921
+ recordWorkflowLifecycleEvent(state, webSession, { type: "workflow.start.blocked", workflowId: workflow.id, workflowVersion: workflow.version, piboSessionId: session.id, status: "blocked", validation: validation.validation, diagnostics: validation.diagnostics, payload: { snapshotId: snapshot.id, profile: session.profile } });
3922
+ return workflowValidationBlockedResponse("Workflow Session has validation errors and cannot be started", validation, { workflowSession, workflow });
4348
3923
  }
4349
- const archived = normalizeProjectSessionArchived(body.archived);
4350
- const projectSession = archived === undefined ? state.projectService.getProjectSession(selectedSession.id) : state.projectService.setProjectSessionArchived(selectedSession.id, archived);
4351
- return responseJson({ session: updated, projectSession });
3924
+ const started = state.workflowService.startWorkflowSessionRun({ piboSessionId: session.id, runId: `wfr_${randomUUID()}`, workflowId: snapshot.workflow.id, workflowVersion: snapshot.workflow.version, snapshotId: snapshot.id, effectiveDefinitionHash: snapshot.workflow.effectiveDefinitionHash, current: createWorkflowRunCurrent(snapshot.effectiveDefinition), inputValues: snapshot.inputValues, validation: validation.validation });
3925
+ updateWorkflowRunSessionMetadata({ state, context, session, workflowRunId: started.run.id });
3926
+ recordWorkflowLifecycleEvent(state, webSession, { type: "workflow.start.accepted", workflowId: workflow.id, workflowVersion: workflow.version, piboSessionId: session.id, workflowRunId: started.run.id, status: "accepted", validation: validation.validation, diagnostics: validation.diagnostics, payload: { snapshotId: snapshot.id, profile: session.profile, alreadyStarted: started.alreadyStarted } });
3927
+ if (!started.alreadyStarted)
3928
+ recordWorkflowLifecycleEvent(state, webSession, { type: "workflow.run.status_changed", workflowId: workflow.id, workflowVersion: workflow.version, piboSessionId: session.id, workflowRunId: started.run.id, status: "changed", validation: validation.validation, diagnostics: validation.diagnostics, payload: { snapshotId: snapshot.id, state: started.run.status, current: started.run.current } });
3929
+ return responseJson({ workflowSession: enrichWorkflowSession(state, started.workflowSession), run: started.run, snapshot, workflow, alreadyStarted: started.alreadyStarted, validation: validation.validation, diagnostics: validation.diagnostics, message: started.alreadyStarted ? "Workflow run already exists for this Workflow Session." : "Workflow run recorded. General graph execution is not connected to this surface; supported manual triggers run from the editor." }, { status: started.alreadyStarted ? 200 : 202 });
4352
3930
  }
4353
3931
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/signals/statuses` && request.method === "GET") {
4354
3932
  await requireSession(request, context);
@@ -4524,6 +4102,7 @@ export function createChatWebApp(options = {}) {
4524
4102
  });
4525
4103
  return workflowValidationBlockedResponse("Workflow draft has validation errors and cannot be test-run", validation, { draft: serializeWorkflowDraft(record) });
4526
4104
  }
4105
+ const room = body.roomId ? requireRoom(state, body.roomId, webSession, "write") : state.roomService.ensureDefaultRoom();
4527
4106
  const result = await runWorkflowManualTextTrigger({
4528
4107
  definition: record.definition,
4529
4108
  triggerNodeId: body.triggerNodeId,
@@ -4532,11 +4111,31 @@ export function createChatWebApp(options = {}) {
4532
4111
  draftId: record.draftId,
4533
4112
  channelContext: context.channelContext,
4534
4113
  channel: CHAT_WEB_CHANNEL,
4535
- defaultWorkspace: getDefaultPiboWorkspace(),
4114
+ roomId: room.id,
4115
+ defaultWorkspace: body.workspace ?? room.workspace ?? getDefaultPiboWorkspace(),
4536
4116
  onSessionCreated: (session) => state.sessionQuery.upsertSession(session),
4537
4117
  resolveProfile: (profileId) => resolveWorkflowRuntimeProfile({ context, requestedId: profileId }),
4538
4118
  });
4539
4119
  const diagnostics = result.ok ? validation.diagnostics : sanitizeWorkflowDiagnostics([...validation.diagnostics, ...result.diagnostics]);
4120
+ if (result.run) {
4121
+ const run = result.run;
4122
+ const store = state.workflowService.runtimeStore;
4123
+ store.transaction(() => {
4124
+ const hash = hashWorkflowDefinitionJson(record.definition);
4125
+ const existing = store.listDefinitionSnapshots({ workflowId: run.workflowId, workflowVersion: run.workflowVersion, hash, limit: 1 })[0];
4126
+ const definitionSnapshotId = existing?.id ?? `wfds_${run.id}`;
4127
+ if (!existing)
4128
+ store.saveDefinitionSnapshot({ id: definitionSnapshotId, workflowId: run.workflowId, workflowVersion: run.workflowVersion, hash, definition: record.definition, createdAt: run.createdAt });
4129
+ store.saveRun({ ...run, piboSessionId: result.nodeAttempts.find((attempt) => attempt.piboSessionId)?.piboSessionId, definitionSnapshotId, workflowDefinitionHash: hash, current: { status: run.status, nodeId: result.nodeAttempts.at(-1)?.nodeId }, state: { global: {} } });
4130
+ for (const attempt of result.nodeAttempts) {
4131
+ store.saveNodeAttempt({ ...attempt, attempt: 1 });
4132
+ if (attempt.piboSessionId)
4133
+ state.workflowService.addWorkflowSession({ piboSessionId: attempt.piboSessionId, workflowId: run.workflowId, workflowVersion: run.workflowVersion, workflowRunId: run.id, state: run.status });
4134
+ }
4135
+ for (const transfer of result.edgeTransfers)
4136
+ store.saveEdgeTransfer(transfer);
4137
+ });
4138
+ }
4540
4139
  recordWorkflowLifecycleEvent(state, webSession, {
4541
4140
  type: result.ok ? "workflow.editor_test_run.completed" : "workflow.editor_test_run.failed",
4542
4141
  workflowId: record.workflowId,
@@ -4673,13 +4272,12 @@ export function createChatWebApp(options = {}) {
4673
4272
  return responseJson(archiveWorkflowIdentity(state, context, webSession, archiveWorkflowId, body.reason));
4674
4273
  }
4675
4274
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/workflows/lifecycle-events` && request.method === "GET") {
4676
- const webSession = await requireSession(request, context);
4275
+ await requireSession(request, context);
4677
4276
  return responseJson({
4678
4277
  events: state.workflowLifecycleEventStore.listEvents({
4679
4278
  type: url.searchParams.get("type") ?? undefined,
4680
4279
  workflowId: url.searchParams.get("workflowId") ?? undefined,
4681
4280
  draftId: url.searchParams.get("draftId") ?? undefined,
4682
- projectId: url.searchParams.get("projectId") ?? undefined,
4683
4281
  piboSessionId: url.searchParams.get("piboSessionId") ?? undefined,
4684
4282
  workflowRunId: url.searchParams.get("workflowRunId") ?? undefined,
4685
4283
  limit: parsePositiveIntSearchParam(url, "limit", 100, 500),
@@ -4991,12 +4589,42 @@ export function createChatWebApp(options = {}) {
4991
4589
  sessions: await buildSessionNodes(ownedSessions, state.sessionQuery.listSessions(), process.cwd(), new Map(), sessionNodeHistoryOptions(context)),
4992
4590
  });
4993
4591
  }
4592
+ if (roomResource?.child === "order" && request.method === "PATCH") {
4593
+ requireSameOriginJsonRequest(request);
4594
+ const webSession = await requireSession(request, context);
4595
+ const room = requireRoom(state, roomResource.roomId, webSession, "admin");
4596
+ const body = await readJsonBody(request);
4597
+ if (typeof body.targetRoomId !== "string" || !body.targetRoomId) {
4598
+ throw new PiboWebHttpError("targetRoomId is required", 400);
4599
+ }
4600
+ if (body.position !== "before" && body.position !== "after") {
4601
+ throw new PiboWebHttpError("position must be before or after", 400);
4602
+ }
4603
+ const targetRoom = requireRoom(state, body.targetRoomId, webSession, "admin");
4604
+ return responseJson({
4605
+ orderedRoomIds: reorderChatWebRoom({ state, room, targetRoom, position: body.position }),
4606
+ });
4607
+ }
4994
4608
  if (roomResource && roomResource.child === undefined && request.method === "PATCH") {
4995
4609
  requireSameOriginJsonRequest(request);
4996
4610
  const webSession = await requireSession(request, context);
4997
4611
  const existingRoom = requireRoom(state, roomResource.roomId, webSession, "admin");
4998
4612
  const body = await readJsonBody(request);
4613
+ if (body.pinned !== undefined && existingRoom.parentRoomId) {
4614
+ throw new PiboWebHttpError("Only top-level rooms can be pinned", 400);
4615
+ }
4616
+ if (body.pinned === true && (body.archived === true || isPiboRoomArchived(existingRoom))) {
4617
+ throw new PiboWebHttpError("Archived rooms cannot be pinned", 400);
4618
+ }
4999
4619
  const update = createRoomUpdate(existingRoom, body);
4620
+ if (typeof body.pinned === "boolean") {
4621
+ const targetGroup = state.roomService.listRooms().filter((room) => !room.parentRoomId
4622
+ && !isDefaultPiboRoom(room)
4623
+ && !isPiboRoomArchived(room)
4624
+ && room.id !== existingRoom.id
4625
+ && isPiboRoomPinned(room) === body.pinned);
4626
+ update.metadata = withPiboRoomPinned(update.metadata ?? existingRoom.metadata, body.pinned, nextChatWebRoomSidebarOrder(targetGroup));
4627
+ }
5000
4628
  if (update.parentRoomId)
5001
4629
  requireRoom(state, update.parentRoomId, webSession, "admin");
5002
4630
  let room;
@@ -5796,17 +5424,6 @@ export function createChatWebApp(options = {}) {
5796
5424
  const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, requestedPiboSessionId, requestedRoomId);
5797
5425
  const cursor = parseSseCursor(url.searchParams.get("since")) ?? parseSseCursor(request.headers.get("last-event-id"));
5798
5426
  const transientReplayCursor = parseTransientReplayCursor(url.searchParams.get("liveSince"));
5799
- if (!requestedRoomId && state.projectService.getProjectSession(selectedSession.id)) {
5800
- return createEventStream({
5801
- piboSessionId: selectedSession.id,
5802
- activePiboSessionId: selectedSession.id,
5803
- mode: streamMode,
5804
- context,
5805
- state,
5806
- cursor,
5807
- transientReplayCursor,
5808
- });
5809
- }
5810
5427
  const roomId = requestedRoomId ?? selectedRoomIdForSession(state, context, selectedSession);
5811
5428
  requireRoom(state, roomId, webSession, "read");
5812
5429
  const streamPiboSessionId = requestedPiboSessionId || !requestedRoomId ? selectedSession.id : undefined;