@pasko70/pibo 3.2.7 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) 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 +68 -11
  4. package/dist/agent-runtimes/codex-native/adapter.js +4 -1
  5. package/dist/agent-runtimes/codex-native/process.js +1 -1
  6. package/dist/agent-runtimes/codex-native/protocol-version.js +6 -6
  7. package/dist/agent-runtimes/codex-native/thread.js +26 -4
  8. package/dist/agent-runtimes/omp/adapter.js +1 -0
  9. package/dist/agent-runtimes/omp/process.js +23 -3
  10. package/dist/agent-runtimes/omp/protocol-version.js +2 -0
  11. package/dist/agent-runtimes/pi/adapter.js +5 -1
  12. package/dist/agent-runtimes/pi/model-catalog.js +2 -2
  13. package/dist/agent-runtimes/pi/routed-session.js +53 -5
  14. package/dist/agent-runtimes/pi/runtime.js +6 -3
  15. package/dist/apps/chat/chat-api-routes.js +12 -38
  16. package/dist/apps/chat/chat-request-normalizers.js +15 -50
  17. package/dist/apps/chat/data/legacy-project-migration.js +492 -0
  18. package/dist/apps/chat/data/room-service.js +2 -2
  19. package/dist/apps/chat/data/workflow-session-model.js +1 -0
  20. package/dist/apps/chat/data/workflow-session-service.js +352 -0
  21. package/dist/apps/chat/rooms.js +1 -1
  22. package/dist/apps/chat/session-metadata.js +30 -0
  23. package/dist/apps/chat/trace.js +15 -2
  24. package/dist/apps/chat/types/rooms.js +30 -0
  25. package/dist/apps/chat/web-app.js +355 -651
  26. package/dist/apps/chat/workflow-catalog.js +18 -18
  27. package/dist/apps/chat/{project-workflow-human-actions.js → workflow-human-actions.js} +14 -14
  28. package/dist/apps/chat/workflow-manual-trigger-runtime.js +5 -1
  29. package/dist/apps/chat/workflow-persistence.js +20 -28
  30. package/dist/apps/chat/{project-workflow-sessions.js → workflow-sessions.js} +18 -17
  31. package/dist/apps/chat-ui/assets/{dist-ENXYFYda.js → dist-AhtdAETR.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-CGgUuVB6.js → dist-Bk_L1qbJ.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-DG6T1maU.js → dist-Dyp6sLQn.js} +1 -1
  34. package/dist/apps/chat-ui/assets/{dist-CFtZTGHa.js → dist-jVaiGXFD.js} +1 -1
  35. package/dist/apps/chat-ui/assets/{dist-BikaGikn.js → dist-t25moyJS.js} +1 -1
  36. package/dist/apps/chat-ui/assets/index-CZjNw-aK.js +228 -0
  37. package/dist/apps/chat-ui/assets/index-CcEjFITM.css +1 -0
  38. package/dist/apps/chat-ui/index.html +2 -2
  39. package/dist/apps/chat-vscode-web/assets/index-Spj6M0tn.js +43 -0
  40. package/dist/apps/chat-vscode-web/index.html +1 -1
  41. package/dist/compute/pool/cli.js +1 -1
  42. package/dist/compute/pool/seeds.js +2 -3
  43. package/dist/config/config.js +1 -1
  44. package/dist/core/session-router.js +19 -10
  45. package/dist/debug/agents.js +9 -3
  46. package/dist/plugins/builtin.js +2 -0
  47. package/dist/plugins/registry.js +1 -0
  48. package/dist/previews/cli.js +154 -79
  49. package/dist/previews/compute-worker.js +37 -0
  50. package/dist/previews/config.js +2 -1
  51. package/dist/previews/migrations/session-ownership.js +7 -0
  52. package/dist/previews/proxy.js +4 -2
  53. package/dist/previews/public-setup.js +108 -0
  54. package/dist/previews/store.js +20 -13
  55. package/dist/previews/web-app.js +12 -2
  56. package/dist/providers/openai-gpt56.js +41 -3
  57. package/dist/sessions/workflow-session-kind.js +0 -3
  58. package/dist/subagents/context.js +2 -2
  59. package/dist/subagents/observation-query.js +49 -6
  60. package/dist/subagents/observation-text-regex.js +124 -0
  61. package/dist/subagents/tool.js +3 -2
  62. package/dist/tools/hashline.js +64 -0
  63. package/npm-shrinkwrap.json +786 -284
  64. package/package.json +11 -6
  65. package/packages/workflows/README.md +21 -0
  66. package/packages/workflows/dist/api/index.d.ts +48 -0
  67. package/packages/workflows/dist/api/index.d.ts.map +1 -0
  68. package/packages/workflows/dist/api/index.js +96 -0
  69. package/packages/workflows/dist/api/index.js.map +1 -0
  70. package/packages/workflows/dist/definition-hash.d.ts +4 -0
  71. package/packages/workflows/dist/definition-hash.d.ts.map +1 -0
  72. package/packages/workflows/dist/definition-hash.js +27 -0
  73. package/packages/workflows/dist/definition-hash.js.map +1 -0
  74. package/packages/workflows/dist/fixtures/index.d.ts +39 -0
  75. package/packages/workflows/dist/fixtures/index.d.ts.map +1 -0
  76. package/packages/workflows/dist/fixtures/index.js +608 -0
  77. package/packages/workflows/dist/fixtures/index.js.map +1 -0
  78. package/packages/workflows/dist/index.d.ts +16 -0
  79. package/packages/workflows/dist/index.d.ts.map +1 -0
  80. package/packages/workflows/dist/index.js +10 -0
  81. package/packages/workflows/dist/index.js.map +1 -0
  82. package/packages/workflows/dist/inspection/index.d.ts +47 -0
  83. package/packages/workflows/dist/inspection/index.d.ts.map +1 -0
  84. package/packages/workflows/dist/inspection/index.js +131 -0
  85. package/packages/workflows/dist/inspection/index.js.map +1 -0
  86. package/packages/workflows/dist/registry/index.d.ts +30 -0
  87. package/packages/workflows/dist/registry/index.d.ts.map +1 -0
  88. package/packages/workflows/dist/registry/index.js +161 -0
  89. package/packages/workflows/dist/registry/index.js.map +1 -0
  90. package/packages/workflows/dist/runtime/adapter-node.d.ts +28 -0
  91. package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -0
  92. package/packages/workflows/dist/runtime/adapter-node.js +172 -0
  93. package/packages/workflows/dist/runtime/adapter-node.js.map +1 -0
  94. package/packages/workflows/dist/runtime/agent-node.d.ts +32 -0
  95. package/packages/workflows/dist/runtime/agent-node.d.ts.map +1 -0
  96. package/packages/workflows/dist/runtime/agent-node.js +231 -0
  97. package/packages/workflows/dist/runtime/agent-node.js.map +1 -0
  98. package/packages/workflows/dist/runtime/agent-runtime.d.ts +25 -0
  99. package/packages/workflows/dist/runtime/agent-runtime.d.ts.map +1 -0
  100. package/packages/workflows/dist/runtime/agent-runtime.js +111 -0
  101. package/packages/workflows/dist/runtime/agent-runtime.js.map +1 -0
  102. package/packages/workflows/dist/runtime/code-node.d.ts +31 -0
  103. package/packages/workflows/dist/runtime/code-node.d.ts.map +1 -0
  104. package/packages/workflows/dist/runtime/code-node.js +223 -0
  105. package/packages/workflows/dist/runtime/code-node.js.map +1 -0
  106. package/packages/workflows/dist/runtime/dispatch-failures.d.ts +36 -0
  107. package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -0
  108. package/packages/workflows/dist/runtime/dispatch-failures.js +80 -0
  109. package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -0
  110. package/packages/workflows/dist/runtime/edge-payloads.d.ts +3 -0
  111. package/packages/workflows/dist/runtime/edge-payloads.d.ts.map +1 -0
  112. package/packages/workflows/dist/runtime/edge-payloads.js +11 -0
  113. package/packages/workflows/dist/runtime/edge-payloads.js.map +1 -0
  114. package/packages/workflows/dist/runtime/edge-transfer.d.ts +39 -0
  115. package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -0
  116. package/packages/workflows/dist/runtime/edge-transfer.js +356 -0
  117. package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -0
  118. package/packages/workflows/dist/runtime/human-action.d.ts +43 -0
  119. package/packages/workflows/dist/runtime/human-action.d.ts.map +1 -0
  120. package/packages/workflows/dist/runtime/human-action.js +231 -0
  121. package/packages/workflows/dist/runtime/human-action.js.map +1 -0
  122. package/packages/workflows/dist/runtime/human-node.d.ts +27 -0
  123. package/packages/workflows/dist/runtime/human-node.d.ts.map +1 -0
  124. package/packages/workflows/dist/runtime/human-node.js +126 -0
  125. package/packages/workflows/dist/runtime/human-node.js.map +1 -0
  126. package/packages/workflows/dist/runtime/ids.d.ts +2 -0
  127. package/packages/workflows/dist/runtime/ids.d.ts.map +1 -0
  128. package/packages/workflows/dist/runtime/ids.js +4 -0
  129. package/packages/workflows/dist/runtime/ids.js.map +1 -0
  130. package/packages/workflows/dist/runtime/index.d.ts +23 -0
  131. package/packages/workflows/dist/runtime/index.d.ts.map +1 -0
  132. package/packages/workflows/dist/runtime/index.js +12 -0
  133. package/packages/workflows/dist/runtime/index.js.map +1 -0
  134. package/packages/workflows/dist/runtime/manual-trigger.d.ts +51 -0
  135. package/packages/workflows/dist/runtime/manual-trigger.d.ts.map +1 -0
  136. package/packages/workflows/dist/runtime/manual-trigger.js +403 -0
  137. package/packages/workflows/dist/runtime/manual-trigger.js.map +1 -0
  138. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts +48 -0
  139. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts.map +1 -0
  140. package/packages/workflows/dist/runtime/nested-workflow-node.js +246 -0
  141. package/packages/workflows/dist/runtime/nested-workflow-node.js.map +1 -0
  142. package/packages/workflows/dist/runtime/one-node-agent.d.ts +34 -0
  143. package/packages/workflows/dist/runtime/one-node-agent.d.ts.map +1 -0
  144. package/packages/workflows/dist/runtime/one-node-agent.js +334 -0
  145. package/packages/workflows/dist/runtime/one-node-agent.js.map +1 -0
  146. package/packages/workflows/dist/runtime/persistence.d.ts +9 -0
  147. package/packages/workflows/dist/runtime/persistence.d.ts.map +1 -0
  148. package/packages/workflows/dist/runtime/persistence.js +67 -0
  149. package/packages/workflows/dist/runtime/persistence.js.map +1 -0
  150. package/packages/workflows/dist/runtime/pibo-routing.d.ts +126 -0
  151. package/packages/workflows/dist/runtime/pibo-routing.d.ts.map +1 -0
  152. package/packages/workflows/dist/runtime/pibo-routing.js +96 -0
  153. package/packages/workflows/dist/runtime/pibo-routing.js.map +1 -0
  154. package/packages/workflows/dist/runtime/prompts.d.ts +20 -0
  155. package/packages/workflows/dist/runtime/prompts.d.ts.map +1 -0
  156. package/packages/workflows/dist/runtime/prompts.js +235 -0
  157. package/packages/workflows/dist/runtime/prompts.js.map +1 -0
  158. package/packages/workflows/dist/runtime/retry.d.ts +35 -0
  159. package/packages/workflows/dist/runtime/retry.d.ts.map +1 -0
  160. package/packages/workflows/dist/runtime/retry.js +80 -0
  161. package/packages/workflows/dist/runtime/retry.js.map +1 -0
  162. package/packages/workflows/dist/runtime/state.d.ts +14 -0
  163. package/packages/workflows/dist/runtime/state.d.ts.map +1 -0
  164. package/packages/workflows/dist/runtime/state.js +139 -0
  165. package/packages/workflows/dist/runtime/state.js.map +1 -0
  166. package/packages/workflows/dist/runtime/time.d.ts +6 -0
  167. package/packages/workflows/dist/runtime/time.d.ts.map +1 -0
  168. package/packages/workflows/dist/runtime/time.js +42 -0
  169. package/packages/workflows/dist/runtime/time.js.map +1 -0
  170. package/packages/workflows/dist/store/catalog-records.d.ts +18 -0
  171. package/packages/workflows/dist/store/catalog-records.d.ts.map +1 -0
  172. package/packages/workflows/dist/store/catalog-records.js +59 -0
  173. package/packages/workflows/dist/store/catalog-records.js.map +1 -0
  174. package/packages/workflows/dist/store/contracts.d.ts +149 -0
  175. package/packages/workflows/dist/store/contracts.d.ts.map +1 -0
  176. package/packages/workflows/dist/store/contracts.js +2 -0
  177. package/packages/workflows/dist/store/contracts.js.map +1 -0
  178. package/packages/workflows/dist/store/draft-writes.d.ts +7 -0
  179. package/packages/workflows/dist/store/draft-writes.d.ts.map +1 -0
  180. package/packages/workflows/dist/store/draft-writes.js +18 -0
  181. package/packages/workflows/dist/store/draft-writes.js.map +1 -0
  182. package/packages/workflows/dist/store/index.d.ts +55 -0
  183. package/packages/workflows/dist/store/index.d.ts.map +1 -0
  184. package/packages/workflows/dist/store/index.js +650 -0
  185. package/packages/workflows/dist/store/index.js.map +1 -0
  186. package/packages/workflows/dist/store/list-query.d.ts +13 -0
  187. package/packages/workflows/dist/store/list-query.d.ts.map +1 -0
  188. package/packages/workflows/dist/store/list-query.js +20 -0
  189. package/packages/workflows/dist/store/list-query.js.map +1 -0
  190. package/packages/workflows/dist/store/row-mappers.d.ts +206 -0
  191. package/packages/workflows/dist/store/row-mappers.d.ts.map +1 -0
  192. package/packages/workflows/dist/store/row-mappers.js +262 -0
  193. package/packages/workflows/dist/store/row-mappers.js.map +1 -0
  194. package/packages/workflows/dist/store/schema.d.ts +13 -0
  195. package/packages/workflows/dist/store/schema.d.ts.map +1 -0
  196. package/packages/workflows/dist/store/schema.js +458 -0
  197. package/packages/workflows/dist/store/schema.js.map +1 -0
  198. package/packages/workflows/dist/store/write-values.d.ts +18 -0
  199. package/packages/workflows/dist/store/write-values.d.ts.map +1 -0
  200. package/packages/workflows/dist/store/write-values.js +227 -0
  201. package/packages/workflows/dist/store/write-values.js.map +1 -0
  202. package/packages/workflows/dist/types/index.d.ts +1202 -0
  203. package/packages/workflows/dist/types/index.d.ts.map +1 -0
  204. package/packages/workflows/dist/types/index.js +8 -0
  205. package/packages/workflows/dist/types/index.js.map +1 -0
  206. package/packages/workflows/dist/validation/diagnostics.d.ts +27 -0
  207. package/packages/workflows/dist/validation/diagnostics.d.ts.map +1 -0
  208. package/packages/workflows/dist/validation/diagnostics.js +104 -0
  209. package/packages/workflows/dist/validation/diagnostics.js.map +1 -0
  210. package/packages/workflows/dist/validation/graph-cycles.d.ts +4 -0
  211. package/packages/workflows/dist/validation/graph-cycles.d.ts.map +1 -0
  212. package/packages/workflows/dist/validation/graph-cycles.js +130 -0
  213. package/packages/workflows/dist/validation/graph-cycles.js.map +1 -0
  214. package/packages/workflows/dist/validation/graph-edges.d.ts +5 -0
  215. package/packages/workflows/dist/validation/graph-edges.d.ts.map +1 -0
  216. package/packages/workflows/dist/validation/graph-edges.js +48 -0
  217. package/packages/workflows/dist/validation/graph-edges.js.map +1 -0
  218. package/packages/workflows/dist/validation/graph-ports.d.ts +5 -0
  219. package/packages/workflows/dist/validation/graph-ports.d.ts.map +1 -0
  220. package/packages/workflows/dist/validation/graph-ports.js +55 -0
  221. package/packages/workflows/dist/validation/graph-ports.js.map +1 -0
  222. package/packages/workflows/dist/validation/index.d.ts +18 -0
  223. package/packages/workflows/dist/validation/index.d.ts.map +1 -0
  224. package/packages/workflows/dist/validation/index.js +175 -0
  225. package/packages/workflows/dist/validation/index.js.map +1 -0
  226. package/packages/workflows/dist/validation/json-schema.d.ts +12 -0
  227. package/packages/workflows/dist/validation/json-schema.d.ts.map +1 -0
  228. package/packages/workflows/dist/validation/json-schema.js +556 -0
  229. package/packages/workflows/dist/validation/json-schema.js.map +1 -0
  230. package/packages/workflows/dist/validation/registry-refs.d.ts +21 -0
  231. package/packages/workflows/dist/validation/registry-refs.d.ts.map +1 -0
  232. package/packages/workflows/dist/validation/registry-refs.js +283 -0
  233. package/packages/workflows/dist/validation/registry-refs.js.map +1 -0
  234. package/packages/workflows/dist/validation/retry-policy.d.ts +3 -0
  235. package/packages/workflows/dist/validation/retry-policy.d.ts.map +1 -0
  236. package/packages/workflows/dist/validation/retry-policy.js +114 -0
  237. package/packages/workflows/dist/validation/retry-policy.js.map +1 -0
  238. package/packages/workflows/dist/validation/schema-declarations.d.ts +5 -0
  239. package/packages/workflows/dist/validation/schema-declarations.d.ts.map +1 -0
  240. package/packages/workflows/dist/validation/schema-declarations.js +36 -0
  241. package/packages/workflows/dist/validation/schema-declarations.js.map +1 -0
  242. package/packages/workflows/dist/validation/state-access.d.ts +4 -0
  243. package/packages/workflows/dist/validation/state-access.d.ts.map +1 -0
  244. package/packages/workflows/dist/validation/state-access.js +121 -0
  245. package/packages/workflows/dist/validation/state-access.js.map +1 -0
  246. package/packages/workflows/dist/xstate/index.d.ts +73 -0
  247. package/packages/workflows/dist/xstate/index.d.ts.map +1 -0
  248. package/packages/workflows/dist/xstate/index.js +728 -0
  249. package/packages/workflows/dist/xstate/index.js.map +1 -0
  250. package/packages/workflows/package.json +23 -0
  251. package/dist/apps/chat/data/project-service.js +0 -1029
  252. package/dist/apps/chat-ui/assets/index-BwT5grLv.css +0 -1
  253. package/dist/apps/chat-ui/assets/index-PTJanD3E.js +0 -228
  254. 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,12 +13,12 @@ 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";
19
20
  import { TRACE_V2_DEFAULT_TIMELINE_LIMIT, TRACE_V2_MAX_TIMELINE_LIMIT, TRACE_V2_PAYLOAD_DEFAULT_LIMIT_BYTES, TRACE_V2_PAYLOAD_MAX_LIMIT_BYTES, TRACE_V2_RAW_EVENTS_DEFAULT_LIMIT, TRACE_V2_RAW_EVENTS_MAX_LIMIT, TRACE_V2_TIMELINE_HARD_BYTES, TRACE_IMAGE_MAX_COUNT, parseTracePayloadRef, readTraceImagePayload, readTracePayloadChunk, traceRawEventsPageFromEvents, traceTimelinePageFromView, } from "./trace-v2.js";
20
- import { isChatWebSessionArchived } from "./session-metadata.js";
21
+ import { chatWebSessionSidebarOrder, compareChatWebSessionsBySidebarOrder, isChatWebSessionArchived, isChatWebSessionPinned, withChatWebSessionPinned, withChatWebSessionSidebarOrder, } from "./session-metadata.js";
21
22
  import { withWorkflowSessionKind } from "../../sessions/workflow-session-kind.js";
22
23
  import { CustomAgentTargetReferenceError, CustomAgentStore, createDefaultCustomAgentStore, previewCustomAgentCreate, previewCustomAgentUpdate, } from "./agent-store.js";
23
24
  import { loadPiboModelDefaults, } from "../../core/model-defaults.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)
@@ -750,7 +759,7 @@ function listSharedSessions(context) {
750
759
  const profiles = context.channelContext.getProfiles?.();
751
760
  return sessions
752
761
  .map((session) => canonicalizeSessionProfile(context, session, profiles))
753
- .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
762
+ .sort(compareChatWebSessionsBySidebarOrder);
754
763
  }
755
764
  function canonicalizeSessionProfile(context, session, profiles = context.channelContext.getProfiles?.()) {
756
765
  const canonicalProfile = canonicalProfileName(profiles, session.profile);
@@ -818,7 +827,7 @@ function paginateSessionNodes(nodes, input) {
818
827
  }
819
828
  function sessionTreeVersion(nodes) {
820
829
  return nodes
821
- .map((node) => `${node.piboSessionId}:${node.lastActivityAt ?? ""}:${node.status}:${node.archived ? "1" : "0"}`)
830
+ .map((node) => `${node.piboSessionId}:${node.lastActivityAt ?? ""}:${node.status}:${node.archived ? "1" : "0"}:${node.pinned ? "1" : "0"}:${node.sidebarOrder ?? ""}`)
822
831
  .join("|");
823
832
  }
824
833
  function selectedRoomIdForSession(state, context, session) {
@@ -875,92 +884,67 @@ 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) {
888
+ const normalSessions = sessionsInRoom(listSharedSessions(context), room.id)
889
+ .filter((session) => !session.parentId && !isChatWebSessionArchived(session) && !isChatWebSessionPinned(session));
890
890
  return context.channelContext.createSession({
891
891
  channel: CHAT_WEB_CHANNEL,
892
892
  kind: "chat",
893
893
  profile,
894
894
  workspace: roomWorkspaceFromMetadata(room.metadata) ?? getDefaultPiboWorkspace(),
895
- metadata: withChatRoomId(undefined, room.id),
895
+ metadata: withChatWebSessionSidebarOrder(withChatRoomId(undefined, room.id), nextChatWebSessionSidebarOrder(normalSessions)),
896
896
  });
897
897
  }
898
- function createProjectChatSession(input) {
899
- 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));
900
901
  const session = input.context.channelContext.createSession({
901
902
  channel: CHAT_WEB_CHANNEL,
902
903
  kind: "chat",
903
904
  profile: input.profile,
904
- workspace: input.project.projectFolder,
905
+ workspace: input.workspace ?? roomWorkspaceFromMetadata(input.room.metadata) ?? getDefaultPiboWorkspace(),
905
906
  ...(input.title ? { title: input.title } : {}),
906
- ...(input.configuration?.model ? { activeModel: input.configuration.model } : {}),
907
- metadata: withWorkflowSessionKind({
908
- projectId: input.project.id,
909
- projectSessionKind: "main",
910
- projectWorkflowId: workflowSelection.id,
911
- projectWorkflowVersion: workflowSelection.version,
912
- ...(input.configuration ? { projectWorkflowConfiguration: input.configuration } : {}),
913
- }, "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"),
914
913
  });
915
- input.state.projectService.addProjectSession({
916
- projectId: input.project.id,
914
+ const workflowSession = input.state.workflowService.addWorkflowSession({
917
915
  piboSessionId: session.id,
918
- kind: "main",
919
- workflowId: workflowSelection.id,
920
- workflowVersion: workflowSelection.version,
921
- title: session.title,
922
- state: input.configuredWorkflow ? "configured" : undefined,
916
+ workflowId: input.workflow.id,
917
+ workflowVersion: input.workflow.version,
918
+ state: "configured",
923
919
  configuration: input.configuration,
924
920
  });
925
921
  input.state.sessionQuery.upsertSession(session);
926
- return session;
922
+ return { session, workflowSession };
927
923
  }
928
- function resolveProjectWorkflowSelection(state, workflowIdValue, workflowVersionValue, options = {}) {
924
+ function resolveWorkflowSelection(state, workflowIdValue, workflowVersionValue, options = {}) {
929
925
  if (options.requireExplicitWorkflowId && (workflowIdValue === undefined || workflowIdValue === null || workflowIdValue === "")) {
930
926
  throw new PiboWebHttpError("Workflow id is required", 400);
931
927
  }
932
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
933
- const workflowVersion = normalizeProjectWorkflowVersion(workflowVersionValue);
934
- if (options.requireExplicitVersion && !workflowVersion) {
928
+ const workflowId = normalizeWorkflowId(workflowIdValue);
929
+ const workflowVersion = normalizeWorkflowVersion(workflowVersionValue);
930
+ if (options.requireExplicitVersion && !workflowVersion)
935
931
  throw new PiboWebHttpError("Workflow version is required", 400);
936
- }
937
- const candidates = buildProjectWorkflowVersionCatalog(state).filter((option) => option.id === workflowId);
932
+ const candidates = buildWorkflowVersionCatalog(state).filter((option) => option.id === workflowId);
938
933
  const selected = candidates.find((option) => workflowVersion === undefined || option.version === workflowVersion);
939
- if (!selected) {
934
+ if (!selected)
940
935
  throw new PiboWebHttpError(`Unknown workflow version: ${workflowId}${workflowVersion ? `@${workflowVersion}` : ""}`, 400);
941
- }
942
- if (selected.status === "archived") {
943
- throw new PiboWebHttpError(`Workflow version '${selected.id}@${selected.version}' is archived and cannot create a Project session by default`, 400);
944
- }
945
- 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")
946
939
  throw new PiboWebHttpError(`Workflow version '${selected.id}@${selected.version}' is not published`, 400);
947
- }
948
940
  return workflowVersionPickerOptionFromCatalogRecord({ ...selected, status: "published" });
949
941
  }
950
- function normalizeLegacyProjectWorkflowId(value) {
951
- const workflowId = normalizeProjectWorkflowId(value);
952
- if (workflowId !== "simple-chat")
953
- throw new PiboWebHttpError("Only the simple-chat workflow is available in V1", 400);
954
- return workflowId;
955
- }
956
- function normalizeProjectWorkflowId(value) {
957
- if (value === undefined || value === null || value === "")
958
- return "simple-chat";
942
+ function normalizeWorkflowId(value) {
959
943
  if (typeof value !== "string" || !value.trim())
960
- throw new PiboWebHttpError("Workflow id must be a string", 400);
944
+ throw new PiboWebHttpError("Workflow id is required", 400);
961
945
  return value.trim();
962
946
  }
963
- function normalizeProjectWorkflowVersion(value) {
947
+ function normalizeWorkflowVersion(value) {
964
948
  if (value === undefined || value === null || value === "")
965
949
  return undefined;
966
950
  if (typeof value !== "string" || !value.trim())
@@ -1032,18 +1016,14 @@ function requireWorkflowDeleteConfirmation(value, workflowId) {
1032
1016
  throw new PiboWebHttpError(`Type "${workflowId}" to delete this workflow.`, 400);
1033
1017
  }
1034
1018
  }
1035
- const PROJECT_SESSION_PATCH_FIELDS = new Set([
1036
- "title",
1037
- "archived",
1038
- ]);
1039
- function validateProjectWorkflowSnapshotForStart(snapshot, input) {
1019
+ function validateWorkflowSnapshotForStart(snapshot, input) {
1040
1020
  const diagnostics = sanitizeWorkflowDiagnostics(validateWorkflowDefinitionForV2(snapshot.effectiveDefinition, input));
1041
1021
  return {
1042
1022
  diagnostics,
1043
1023
  validation: summarizeWorkflowDiagnostics(diagnostics, "before_workflow_start"),
1044
1024
  };
1045
1025
  }
1046
- function updateProjectWorkflowRunSessionMetadata(input) {
1026
+ function updateWorkflowRunSessionMetadata(input) {
1047
1027
  if (input.session.metadata?.workflowRunId === input.workflowRunId)
1048
1028
  return;
1049
1029
  const updateSession = input.context.channelContext.updateSession;
@@ -1574,8 +1554,8 @@ function requireValidatedWorkflowDraft(state, context, webSession, draftId, trig
1574
1554
  return serializeWorkflowDraft(record);
1575
1555
  }
1576
1556
  function duplicateWorkflowIntoDraft(state, webSession, workflowIdValue, workflowVersionValue) {
1577
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1578
- const workflowVersion = normalizeProjectWorkflowVersion(workflowVersionValue);
1557
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1558
+ const workflowVersion = normalizeWorkflowVersion(workflowVersionValue);
1579
1559
  const published = selectPublishedWorkflowVersion(state, workflowId, workflowVersion);
1580
1560
  if (!published)
1581
1561
  throw new PiboWebHttpError("Published workflow version not found", 404);
@@ -1626,8 +1606,8 @@ function duplicateWorkflowIntoDraft(state, webSession, workflowIdValue, workflow
1626
1606
  return serializeWorkflowDraft(draft);
1627
1607
  }
1628
1608
  function createNextVersionDraftFromPublishedWorkflow(state, webSession, workflowIdValue, workflowVersionValue) {
1629
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1630
- const workflowVersion = normalizeProjectWorkflowVersion(workflowVersionValue);
1609
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1610
+ const workflowVersion = normalizeWorkflowVersion(workflowVersionValue);
1631
1611
  const published = selectPublishedWorkflowVersion(state, workflowId, workflowVersion);
1632
1612
  if (!published)
1633
1613
  throw new PiboWebHttpError("Published workflow version not found", 404);
@@ -1684,7 +1664,7 @@ function createNextVersionDraftFromPublishedWorkflow(state, webSession, workflow
1684
1664
  return { draft: serializeWorkflowDraft(draft), reused: false };
1685
1665
  }
1686
1666
  function archiveWorkflowIdentity(state, context, webSession, workflowIdValue, reasonValue) {
1687
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1667
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1688
1668
  const catalog = buildWorkflowCatalogList(state, context, webSession, { includeArchived: true });
1689
1669
  const workflow = catalog.workflows.find((record) => record.id === workflowId);
1690
1670
  if (!workflow)
@@ -1714,7 +1694,7 @@ function archiveWorkflowIdentity(state, context, webSession, workflowIdValue, re
1714
1694
  return { workflow: archivedWorkflow, archiveState };
1715
1695
  }
1716
1696
  function deleteWorkflowIdentity(state, context, webSession, workflowIdValue, body) {
1717
- const workflowId = normalizeProjectWorkflowId(workflowIdValue);
1697
+ const workflowId = normalizeWorkflowId(workflowIdValue);
1718
1698
  const catalog = buildWorkflowCatalogList(state, context, webSession, { includeArchived: true });
1719
1699
  const workflow = catalog.workflows.find((record) => record.id === workflowId);
1720
1700
  if (!workflow)
@@ -1747,7 +1727,7 @@ function deleteWorkflowIdentity(state, context, webSession, workflowIdValue, bod
1747
1727
  return { workflowId, deleted: true, tombstone };
1748
1728
  }
1749
1729
  function selectPublishedWorkflowVersion(state, workflowId, version) {
1750
- const options = buildProjectWorkflowVersionOptions(state).filter((option) => option.id === workflowId);
1730
+ const options = buildWorkflowVersionOptions(state).filter((option) => option.id === workflowId);
1751
1731
  const selected = version ? options.find((option) => option.version === version) : options[0];
1752
1732
  if (!selected)
1753
1733
  return undefined;
@@ -1915,7 +1895,9 @@ function normalizeWorkflowDraftManualTriggerRunBody(body) {
1915
1895
  throw new PiboWebHttpError("Manual trigger node id is required", 400);
1916
1896
  if (typeof body.input !== "string")
1917
1897
  throw new PiboWebHttpError("Manual trigger input must be text", 400);
1918
- 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) };
1919
1901
  }
1920
1902
  function resolveWorkflowRuntimeProfile(input) {
1921
1903
  const profile = input.context.channelContext.getProfiles?.().find((candidate) => candidate.name === input.requestedId || candidate.aliases.includes(input.requestedId));
@@ -2202,7 +2184,7 @@ function validateWorkflowNestedNodeLike(nodeId, node, input, diagnostics) {
2202
2184
  const workflowId = typeof node.workflowId === "string" ? node.workflowId.trim() : undefined;
2203
2185
  const workflowVersion = typeof node.workflowVersion === "string" ? node.workflowVersion.trim() : undefined;
2204
2186
  const selected = workflowId
2205
- ? 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))
2206
2188
  : undefined;
2207
2189
  if (!selected) {
2208
2190
  const registryRef = workflowId ? `${workflowId}${workflowVersion ? `@${workflowVersion}` : ""}` : undefined;
@@ -2422,7 +2404,7 @@ async function deleteSessionsForAgentProfile(state, context, webSession, profile
2422
2404
  }
2423
2405
  const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2424
2406
  for (const id of orderedIds) {
2425
- await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
2407
+ await deleteSession(id);
2426
2408
  state.sessionQuery.deleteSessions([id]);
2427
2409
  state.eventCommands.deleteSessions([id]);
2428
2410
  }
@@ -2449,7 +2431,7 @@ async function deleteSessionSubtree(state, context, webSession, rootSession) {
2449
2431
  }
2450
2432
  const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2451
2433
  for (const id of orderedIds) {
2452
- await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
2434
+ await deleteSession(id);
2453
2435
  state.sessionQuery.deleteSessions([id]);
2454
2436
  state.eventCommands.deleteSessions([id]);
2455
2437
  }
@@ -2490,7 +2472,7 @@ async function deleteRoomTree(state, context, webSession, room, confirmName) {
2490
2472
  }
2491
2473
  const orderedSessionIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2492
2474
  for (const id of orderedSessionIds) {
2493
- await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
2475
+ await deleteSession(id);
2494
2476
  state.sessionQuery.deleteSessions([id]);
2495
2477
  state.eventCommands.deleteSessions([id]);
2496
2478
  }
@@ -2703,6 +2685,95 @@ async function resolveContextBuildRuntime(context, runtimeInstanceId) {
2703
2685
  function indexSharedSessions(sessionQuery, sessions) {
2704
2686
  sessionQuery.upsertSessionsIfChanged(sessions);
2705
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
+ }
2730
+ function nextChatWebSessionSidebarOrder(sessions) {
2731
+ return sessions.reduce((maximum, session) => {
2732
+ const createdOrder = Date.parse(session.createdAt) || 0;
2733
+ return Math.max(maximum, chatWebSessionSidebarOrder(session) ?? createdOrder);
2734
+ }, 0) + 1;
2735
+ }
2736
+ function reorderChatWebSession(input) {
2737
+ if (input.session.parentId || input.targetSession.parentId) {
2738
+ throw new PiboWebHttpError("Only top-level sessions can be reordered", 400);
2739
+ }
2740
+ if (isChatWebSessionArchived(input.session) || isChatWebSessionArchived(input.targetSession)) {
2741
+ throw new PiboWebHttpError("Archived sessions cannot be reordered", 400);
2742
+ }
2743
+ const roomId = chatRoomIdFromMetadata(input.session.metadata);
2744
+ const targetRoomId = chatRoomIdFromMetadata(input.targetSession.metadata);
2745
+ if (!roomId || roomId !== targetRoomId)
2746
+ throw new PiboWebHttpError("Sessions must belong to the same room", 400);
2747
+ const pinned = isChatWebSessionPinned(input.session);
2748
+ if (pinned !== isChatWebSessionPinned(input.targetSession)) {
2749
+ throw new PiboWebHttpError("Pinned and unpinned sessions cannot be reordered together", 400);
2750
+ }
2751
+ const updateSession = input.context.channelContext.updateSession;
2752
+ if (!updateSession)
2753
+ throw new PiboWebHttpError("Session updates are not available", 501);
2754
+ const ordered = sessionsInRoom(listSharedSessions(input.context), roomId)
2755
+ .filter((session) => !session.parentId && !isChatWebSessionArchived(session) && isChatWebSessionPinned(session) === pinned)
2756
+ .sort(compareChatWebSessionsBySidebarOrder);
2757
+ const movedIndex = ordered.findIndex((session) => session.id === input.session.id);
2758
+ const targetIndex = ordered.findIndex((session) => session.id === input.targetSession.id);
2759
+ if (movedIndex < 0 || targetIndex < 0)
2760
+ throw new PiboWebHttpError("Session ordering target not found", 404);
2761
+ if (movedIndex === targetIndex)
2762
+ return ordered.map((session) => session.id);
2763
+ const [moved] = ordered.splice(movedIndex, 1);
2764
+ const remainingTargetIndex = ordered.findIndex((session) => session.id === input.targetSession.id);
2765
+ ordered.splice(input.position === "before" ? remainingTargetIndex : remainingTargetIndex + 1, 0, moved);
2766
+ const orderBase = nextChatWebSessionSidebarOrder(ordered) + ordered.length - 1;
2767
+ for (const [index, session] of ordered.entries()) {
2768
+ const updated = updateSession(session.id, {
2769
+ metadata: withChatWebSessionSidebarOrder(session.metadata, orderBase - index),
2770
+ });
2771
+ if (!updated)
2772
+ throw new PiboWebHttpError("Session not found", 404);
2773
+ input.state.sessionQuery.upsertSession(updated);
2774
+ }
2775
+ return ordered.map((session) => session.id);
2776
+ }
2706
2777
  function markSessionsRead(state, sessions) {
2707
2778
  for (const session of sessions) {
2708
2779
  const latestStreamId = state.timelineQuery.getLatestStreamId({ piboSessionId: session.id });
@@ -3172,284 +3243,75 @@ function createEventStream(input) {
3172
3243
  },
3173
3244
  });
3174
3245
  }
3175
- async function buildProjectsBootstrap(input) {
3176
- const sharedDefaultProject = input.state.projectService.ensureSharedDefaultProject();
3177
- const selectedProject = input.projectId ? requireSharedProject(input.state, input.webSession, input.projectId, { includeArchived: true }) : sharedDefaultProject;
3178
- let storedProjectSessions = input.state.projectService
3179
- .listProjectSessions(selectedProject.id, { includeArchived: input.includeArchived })
3180
- .filter((projectSession) => projectSession.workflowId === "simple-chat");
3181
- if (selectedProject.id === sharedDefaultProject.id && storedProjectSessions.length === 0) {
3182
- const session = createProjectChatSession({
3183
- state: input.state,
3184
- context: input.context,
3185
- webSession: input.webSession,
3186
- project: sharedDefaultProject,
3187
- profile: input.defaultProfile,
3188
- workflowId: "simple-chat",
3189
- });
3190
- storedProjectSessions = [input.state.projectService.getProjectSession(session.id)];
3191
- }
3192
- const projectSessions = storedProjectSessions;
3193
- const rootSessions = projectSessions
3194
- .map((projectSession) => input.context.channelContext.getSession(projectSession.piboSessionId))
3195
- .filter((session) => Boolean(session));
3196
- const sessions = collectProjectSessionTreeSessions(rootSessions, listSharedSessions(input.context));
3197
- const requestedSession = input.piboSessionId ? sessions.find((session) => session.id === input.piboSessionId) : undefined;
3198
- const selectedSession = requestedSession ?? sessions.find((session) => session.id === selectedProject.currentMainSessionId) ?? sessions[0];
3199
- indexSharedSessions(input.state.sessionQuery, sessions);
3200
- const nodes = await buildSessionNodes(sessions, input.state.sessionQuery.listSessions(), selectedProject.projectFolder, new Map(), { skipPiMetadataFallback: true });
3201
- applyProjectSessionArchiveState(nodes, new Map(projectSessions.map((projectSession) => [projectSession.piboSessionId, Boolean(projectSession.archived)])));
3202
- return {
3203
- identity: input.webSession.authSession.identity,
3204
- sharedDefaultProject,
3205
- project: selectedProject,
3206
- projects: listSharedProjects(input.state, input.webSession, { includeArchived: input.includeArchived }),
3207
- projectSessions,
3208
- workflowLifecycleEvents: [],
3209
- ...(selectedSession ? { session: selectedSession, selectedPiboSessionId: selectedSession.id } : {}),
3210
- selectedProjectId: selectedProject.id,
3211
- sessions: nodes,
3212
- ...(await loadBootstrapCatalog(input.state, input.context, input.webSession)),
3213
- };
3214
- }
3215
- function collectProjectSessionTreeSessions(rootSessions, candidateSessions) {
3216
- const sessionsById = new Map(rootSessions.map((session) => [session.id, session]));
3217
- let changed = true;
3218
- while (changed) {
3219
- changed = false;
3220
- for (const session of candidateSessions) {
3221
- if (sessionsById.has(session.id))
3222
- continue;
3223
- if (!session.parentId || !sessionsById.has(session.parentId))
3224
- continue;
3225
- sessionsById.set(session.id, session);
3226
- changed = true;
3227
- }
3228
- }
3229
- return [...sessionsById.values()].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
3230
- }
3231
- function enrichProjectSessionWorkflowDefinitionLink(state, projectSession) {
3232
- if (!isProjectWorkflowBackedSession(projectSession))
3233
- return projectSession;
3234
- const snapshot = state.projectService.getWorkflowSessionSnapshotForSession(projectSession.piboSessionId);
3235
- const workflowId = projectSession.workflowId;
3236
- 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;
3237
3249
  const catalogRecord = workflowVersion
3238
- ? buildProjectWorkflowVersionCatalog(state).find((record) => record.id === workflowId && record.version === workflowVersion)
3250
+ ? buildWorkflowVersionCatalog(state).find((record) => record.id === workflowSession.workflowId && record.version === workflowVersion)
3239
3251
  : undefined;
3240
3252
  const snapshotHash = snapshot?.workflow.effectiveDefinitionHash ?? snapshot?.deletedDefinitionFallback.effectiveDefinitionHash;
3241
- if (catalogRecord && catalogRecord.status !== "deleted") {
3242
- return {
3243
- ...projectSession,
3244
- workflowDefinitionLink: {
3245
- status: "live",
3246
- workflowId,
3247
- workflowVersion: catalogRecord.version,
3248
- title: catalogRecord.title,
3249
- ...(snapshotHash ? { definitionHash: snapshotHash } : {}),
3250
- href: workflowDefinitionViewerPath(catalogRecord.id, catalogRecord.version),
3251
- },
3252
- };
3253
- }
3254
- const fallback = snapshot?.deletedDefinitionFallback;
3255
- const definitionHash = fallback?.effectiveDefinitionHash ?? snapshotHash;
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;
3256
3269
  return {
3257
- ...projectSession,
3258
- workflowDefinitionLink: {
3259
- status: "snapshot_only_definition_deleted",
3260
- workflowId,
3261
- ...(workflowVersion ? { workflowVersion } : {}),
3262
- title: fallback?.title ?? snapshot?.workflow.title ?? projectSession.title ?? workflowId,
3263
- ...(definitionHash ? { definitionHash } : {}),
3264
- tombstoneLabel: fallback?.tombstoneLabel ?? "Definition deleted or no longer available in the live Workflow catalog.",
3265
- },
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)),
3266
3282
  };
3267
3283
  }
3268
- function enrichProjectSessionWorkflowWaitTokens(state, projectSession) {
3269
- if (!projectSession.workflowRunId)
3270
- return projectSession;
3271
- const waitTokens = state.projectService.listProjectWorkflowWaitTokens({
3272
- projectId: projectSession.projectId,
3273
- piboSessionId: projectSession.piboSessionId,
3274
- workflowRunId: projectSession.workflowRunId,
3275
- status: "pending",
3276
- limit: 20,
3277
- });
3278
- if (!waitTokens.length)
3279
- return projectSession;
3280
- return {
3281
- ...projectSession,
3282
- pendingHumanActions: waitTokens.map((token) => projectWorkflowPendingHumanActionFromToken(token, WORKFLOW_HUMAN_ACTION_REF_OPTIONS)),
3283
- };
3284
- }
3285
- function submitProjectWorkflowHumanAction(input) {
3286
- const project = requireSharedProject(input.state, input.webSession, input.projectId);
3287
- const session = requireSharedSession(input.context, input.piboSessionId);
3288
- const projectSession = input.state.projectService.getProjectSession(session.id);
3289
- if (!projectSession || projectSession.projectId !== project.id)
3290
- throw new PiboWebHttpError("Project workflow session not found", 404);
3291
- if (!projectSession.workflowRunId) {
3292
- return projectWorkflowHumanActionDiagnosticResponse("Project workflow session has no workflow run to resolve", [{
3293
- code: "WorkflowRuntimeError.workflowRunMissing",
3294
- message: "This Project workflow session has not started a workflow run, so no human wait token can be resolved.",
3295
- severity: "error",
3296
- path: "$.workflowRunId",
3297
- hint: "Start the configured workflow session before submitting human actions.",
3298
- }], 409, undefined, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3299
- }
3300
- const normalized = normalizeProjectWorkflowHumanActionBody(input.body);
3301
- if (normalized.diagnostics.length) {
3302
- return projectWorkflowHumanActionDiagnosticResponse("Human action request is invalid", normalized.diagnostics, 400, undefined, WORKFLOW_HUMAN_ACTION_REF_OPTIONS);
3303
- }
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);
3304
3294
  const request = normalized.request;
3305
- const waitToken = input.state.projectService.getProjectWorkflowWaitToken(request.waitTokenId);
3306
- const validation = validateProjectWorkflowHumanActionRequest({
3307
- projectSession,
3308
- waitToken,
3309
- request,
3310
- humanActionOptions: WORKFLOW_HUMAN_ACTION_REF_OPTIONS,
3311
- });
3295
+ const waitToken = input.state.workflowService.getWorkflowWaitToken(request.waitTokenId);
3296
+ const validation = validateWorkflowHumanActionRequest({ workflowSession, waitToken, request, humanActionOptions: WORKFLOW_HUMAN_ACTION_REF_OPTIONS });
3312
3297
  if (validation.diagnostics.length) {
3313
- const responseWaitToken = waitToken && validation.expiredAt
3314
- ? input.state.projectService.expireProjectWorkflowWaitToken({
3315
- projectId: project.id,
3316
- piboSessionId: session.id,
3317
- workflowRunId: projectSession.workflowRunId,
3318
- waitTokenId: waitToken.id,
3319
- resolvedAt: validation.checkedAt,
3320
- }).waitToken
3321
- : waitToken;
3322
- recordWorkflowLifecycleEvent(input.state, input.webSession, {
3323
- type: "workflow.human_action.submitted",
3324
- workflowId: projectSession.workflowId,
3325
- ...(projectSession.workflowVersion ? { workflowVersion: projectSession.workflowVersion } : {}),
3326
- projectId: project.id,
3327
- piboSessionId: session.id,
3328
- workflowRunId: projectSession.workflowRunId,
3329
- status: "blocked",
3330
- diagnostics: validation.diagnostics,
3331
- payload: projectWorkflowHumanActionLifecyclePayload(request),
3332
- });
3333
- 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);
3334
3301
  }
3335
3302
  try {
3336
- const result = input.state.projectService.resolveProjectWorkflowHumanAction({
3337
- projectId: project.id,
3338
- piboSessionId: session.id,
3339
- workflowRunId: projectSession.workflowRunId,
3340
- waitTokenId: request.waitTokenId,
3341
- ...(validation.actionRef?.id ? { actionId: validation.actionRef.id } : {}),
3342
- kind: validation.actionKind,
3343
- actor: {
3344
- userId: input.webSession.authSession.identity.userId,
3345
- ...(input.webSession.authSession.identity.email ? { email: input.webSession.authSession.identity.email } : {}),
3346
- },
3347
- ...(request.payload !== undefined ? { payload: request.payload } : {}),
3348
- });
3349
- const enrichedProjectSession = enrichProjectSessionWorkflowWaitTokens(input.state, enrichProjectSessionWorkflowDefinitionLink(input.state, result.projectSession));
3350
- recordWorkflowLifecycleEvent(input.state, input.webSession, {
3351
- type: "workflow.human_action.submitted",
3352
- workflowId: result.projectSession.workflowId,
3353
- ...(result.projectSession.workflowVersion ? { workflowVersion: result.projectSession.workflowVersion } : {}),
3354
- projectId: project.id,
3355
- piboSessionId: session.id,
3356
- workflowRunId: result.run.id,
3357
- status: "submitted",
3358
- diagnostics: [],
3359
- payload: projectWorkflowHumanActionSubmittedLifecyclePayload(result.waitToken.id, result.action),
3360
- });
3361
- return responseJson({
3362
- ok: true,
3363
- projectSession: enrichedProjectSession,
3364
- waitToken: result.waitToken,
3365
- action: result.action,
3366
- run: result.run,
3367
- diagnostics: [],
3368
- }, { 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 });
3369
3306
  }
3370
3307
  catch (error) {
3371
- const diagnostics = [projectWorkflowHumanActionRuntimeDiagnostic(error, request.waitTokenId)];
3372
- recordWorkflowLifecycleEvent(input.state, input.webSession, {
3373
- type: "workflow.human_action.submitted",
3374
- workflowId: projectSession.workflowId,
3375
- ...(projectSession.workflowVersion ? { workflowVersion: projectSession.workflowVersion } : {}),
3376
- projectId: project.id,
3377
- piboSessionId: session.id,
3378
- workflowRunId: projectSession.workflowRunId,
3379
- status: "blocked",
3380
- diagnostics,
3381
- payload: projectWorkflowHumanActionLifecyclePayload(request),
3382
- });
3383
- const responseWaitToken = input.state.projectService.getProjectWorkflowWaitToken(request.waitTokenId) ?? waitToken;
3384
- 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);
3385
3310
  }
3386
3311
  }
3387
- function isProjectWorkflowBackedSession(projectSession) {
3388
- return Boolean(projectSession.workflowRunId) || projectSession.state === "workflow" || projectSession.workflowId !== "simple-chat";
3389
- }
3390
3312
  function workflowDefinitionViewerPath(workflowId, workflowVersion) {
3391
3313
  return `${CHAT_WEB_MOUNT_PATH}/workflows/view/${encodeURIComponent(workflowId)}/${encodeURIComponent(workflowVersion)}`;
3392
3314
  }
3393
- function applyProjectSessionArchiveState(nodes, archivedBySessionId) {
3394
- for (const node of nodes) {
3395
- const archived = archivedBySessionId.get(node.piboSessionId);
3396
- if (archived !== undefined)
3397
- node.archived = archived;
3398
- applyProjectSessionArchiveState(node.children, archivedBySessionId);
3399
- }
3400
- }
3401
- async function sendProjectMessage(input) {
3402
- const text = normalizeMessageText(input.body.text);
3403
- const delivery = normalizeMessageDelivery(input.body.delivery);
3404
- const clientTxnId = normalizeClientTxnId(input.body.clientTxnId);
3405
- if (typeof input.body.piboSessionId !== "string")
3406
- throw new PiboWebHttpError("Project session is required", 400);
3407
- const selectedSession = requireSharedSession(input.context, input.body.piboSessionId);
3408
- const projectSession = input.state.projectService.getProjectSession(selectedSession.id);
3409
- if (!projectSession)
3410
- throw new PiboWebHttpError("Project session not found", 404);
3411
- requireSharedProject(input.state, input.webSession, projectSession.projectId);
3412
- const actorId = auditActorIdFor(input.webSession);
3413
- const duplicate = clientTxnId ? input.state.eventCommands.findByClientTxn(undefined, actorId, clientTxnId) : undefined;
3414
- if (duplicate)
3415
- return responseJson({ duplicate: true, event: duplicate });
3416
- const accepted = input.state.eventCommands.appendEvent({
3417
- piboSessionId: selectedSession.id,
3418
- eventType: "user.message.accepted",
3419
- actorType: "user",
3420
- actorId,
3421
- clientTxnId,
3422
- retentionClass: "chat_message",
3423
- payload: {
3424
- type: "user.message.accepted",
3425
- piboSessionId: selectedSession.id,
3426
- projectId: projectSession.projectId,
3427
- text,
3428
- delivery,
3429
- ...(clientTxnId ? { clientTxnId } : {}),
3430
- },
3431
- });
3432
- for (const listener of input.state.liveListeners)
3433
- listener(accepted);
3434
- const messageId = clientTxnId ?? randomUUID();
3435
- try {
3436
- const output = await input.context.channelContext.emit({
3437
- type: "message",
3438
- piboSessionId: selectedSession.id,
3439
- id: messageId,
3440
- text,
3441
- delivery,
3442
- source: "user",
3443
- });
3444
- return responseJson({ accepted, output });
3445
- }
3446
- catch (error) {
3447
- if (error instanceof PiboSteeringUnavailableError || error instanceof AgentRuntimeBindingMissingError) {
3448
- throw new PiboWebHttpError(error.message, 409);
3449
- }
3450
- throw error;
3451
- }
3452
- }
3453
3315
  function startChatStreamingFixture(input) {
3454
3316
  const requestedRoomId = typeof input.body.roomId === "string" ? input.body.roomId : undefined;
3455
3317
  const selectedSession = resolveRequestedSession(input.state, input.context, input.webSession, input.defaultProfile, typeof input.body.piboSessionId === "string" ? input.body.piboSessionId : undefined, requestedRoomId);
@@ -3678,6 +3540,11 @@ export function createChatWebApp(options = {}) {
3678
3540
  const defaultProfile = options.defaultProfile ?? "base";
3679
3541
  const dataStore = createDataStore(options);
3680
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;
3681
3548
  const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
3682
3549
  eventLoopDelay.enable();
3683
3550
  const state = {
@@ -3687,7 +3554,7 @@ export function createChatWebApp(options = {}) {
3687
3554
  eventCommands: new ChatEventCommandService(dataStore),
3688
3555
  readState: new ChatReadStateService(dataStore),
3689
3556
  roomService: new ChatRoomService(dataStore),
3690
- projectService: new ChatProjectService(options.projectStorePath),
3557
+ workflowService,
3691
3558
  agentStore: createAgentStore(options.agentStorePath),
3692
3559
  reliabilityStore,
3693
3560
  cronStore: createDefaultPiboCronStore({ path: options.cronStorePath }),
@@ -3713,12 +3580,12 @@ export function createChatWebApp(options = {}) {
3713
3580
  globalRoot: options.userSkillGlobalRoot ?? os.homedir(),
3714
3581
  workspaceRoot: options.userSkillWorkspaceRoot ?? process.cwd(),
3715
3582
  }),
3716
- workflowDraftStore: new ChatWorkflowDraftStore(dataStore),
3717
- workflowPublishedVersionStore: new ChatWorkflowPublishedVersionStore(dataStore),
3718
- workflowArchiveStore: new ChatWorkflowArchiveStore(dataStore),
3719
- workflowTombstoneStore: new ChatWorkflowTombstoneStore(dataStore),
3720
- workflowLifecycleEventStore: new ChatWorkflowLifecycleEventStore(dataStore),
3721
- 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),
3722
3589
  telemetryRetentionMaintenance: {},
3723
3590
  integrations,
3724
3591
  };
@@ -3739,7 +3606,7 @@ export function createChatWebApp(options = {}) {
3739
3606
  state.subscribedContext = undefined;
3740
3607
  state.eventLoopDelay.disable();
3741
3608
  state.outputPersistenceRetries.dispose();
3742
- state.projectService.close();
3609
+ state.workflowService.close();
3743
3610
  state.agentStore.close();
3744
3611
  state.reliabilityStore.close();
3745
3612
  state.cronStore.close();
@@ -4000,306 +3867,66 @@ export function createChatWebApp(options = {}) {
4000
3867
  if (response)
4001
3868
  return response;
4002
3869
  }
4003
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects/bootstrap` && request.method === "GET") {
4004
- const webSession = await requireSession(request, context);
4005
- return responseJson(await buildProjectsBootstrap({
4006
- state,
4007
- context,
4008
- webSession,
4009
- defaultProfile,
4010
- projectId: url.searchParams.get("projectId") || undefined,
4011
- piboSessionId: url.searchParams.get("piboSessionId") || undefined,
4012
- includeArchived: parseBooleanSearchParam(url, "includeArchived"),
4013
- }));
4014
- }
4015
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects/message` && request.method === "POST") {
4016
- requireSameOriginJsonRequest(request);
4017
- const webSession = await requireSession(request, context);
4018
- const body = await readJsonBody(request);
4019
- return sendProjectMessage({ state, context, webSession, defaultProfile, body });
4020
- }
4021
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects` && request.method === "GET") {
4022
- const webSession = await requireSession(request, context);
4023
- return responseJson({ projects: listSharedProjects(state, webSession, { includeArchived: parseBooleanSearchParam(url, "includeArchived") }) });
4024
- }
4025
- if (url.pathname === `${CHAT_WEB_API_PREFIX}/projects` && request.method === "POST") {
4026
- requireSameOriginJsonRequest(request);
4027
- const webSession = await requireSession(request, context);
4028
- const body = await readJsonBody(request);
4029
- try {
4030
- const project = state.projectService.createProject({
4031
- name: normalizeRoomName(body.name),
4032
- description: normalizeProjectDescription(body.description),
4033
- projectFolder: normalizeProjectPath(body.projectFolder),
4034
- createFolder: body.createFolder === true,
4035
- });
4036
- return responseJson({ project }, { status: 201 });
4037
- }
4038
- catch (error) {
4039
- throw new PiboWebHttpError(error instanceof Error ? error.message : String(error), 400);
4040
- }
4041
- }
4042
- const projectResource = projectResourcePath(url.pathname);
4043
- if (projectResource && projectResource.child === undefined && request.method === "PATCH") {
3870
+ if (url.pathname === `${CHAT_WEB_API_PREFIX}/workflow-sessions` && request.method === "POST") {
4044
3871
  requireSameOriginJsonRequest(request);
4045
3872
  const webSession = await requireSession(request, context);
4046
3873
  const body = await readJsonBody(request);
4047
- try {
4048
- const existingProject = requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
4049
- if (existingProject.metadata.default === true)
4050
- throw new PiboWebHttpError("Project Manager cannot be changed", 400);
4051
- const project = state.projectService.updateProject(projectResource.projectId, {
4052
- ...(body.name !== undefined ? { name: normalizeRoomName(body.name) } : {}),
4053
- ...(body.description !== undefined ? { description: normalizeProjectDescription(body.description) ?? null } : {}),
4054
- ...(body.archived !== undefined ? { archived: normalizeProjectArchived(body.archived) } : {}),
4055
- });
4056
- if (!project)
4057
- throw new PiboWebHttpError("Project not found", 404);
4058
- return responseJson({ project });
4059
- }
4060
- catch (error) {
4061
- if (error instanceof PiboWebHttpError)
4062
- throw error;
4063
- throw new PiboWebHttpError(error instanceof Error ? error.message : String(error), 400);
4064
- }
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 });
4065
3889
  }
4066
- if (projectResource && projectResource.child === undefined && request.method === "DELETE") {
4067
- requireSameOriginJsonRequest(request);
4068
- const webSession = await requireSession(request, context);
4069
- const body = await readJsonBody(request);
4070
- try {
4071
- const project = requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
4072
- if (project.metadata.default === true)
4073
- throw new PiboWebHttpError("Project Manager cannot be deleted", 400);
4074
- return responseJson(state.projectService.deleteProject(projectResource.projectId, {
4075
- confirmName: normalizeRoomDeleteConfirmation(body.confirmName),
4076
- deleteFiles: body.deleteFiles === true,
4077
- }));
4078
- }
4079
- catch (error) {
4080
- if (error instanceof PiboWebHttpError)
4081
- throw error;
4082
- throw new PiboWebHttpError(error instanceof Error ? error.message : String(error), 400);
4083
- }
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));
4084
3894
  }
4085
- const workflowHumanActionResource = projectWorkflowHumanActionsResource(url.pathname);
4086
- if (workflowHumanActionResource && request.method === "POST") {
3895
+ if (workflowResource?.action === "human-actions" && request.method === "POST") {
4087
3896
  requireSameOriginJsonRequest(request);
4088
3897
  const webSession = await requireSession(request, context);
4089
- const body = await readJsonBody(request);
4090
- return submitProjectWorkflowHumanAction({
4091
- state,
4092
- context,
4093
- webSession,
4094
- projectId: workflowHumanActionResource.projectId,
4095
- piboSessionId: workflowHumanActionResource.piboSessionId,
4096
- body,
4097
- });
3898
+ return submitWorkflowHumanAction({ state, context, webSession, piboSessionId: workflowResource.piboSessionId, body: await readJsonBody(request) });
4098
3899
  }
4099
- if (projectResource && projectResource.child === "workflow-sessions" && request.method === "POST" && !projectWorkflowSessionStartResource(url.pathname) && !workflowHumanActionResource) {
3900
+ if (workflowResource?.action === "start" && request.method === "POST") {
4100
3901
  requireSameOriginJsonRequest(request);
4101
3902
  const webSession = await requireSession(request, context);
4102
- const body = await readJsonBody(request);
4103
- const project = requireSharedProject(state, webSession, projectResource.projectId);
4104
- const profile = resolveCreateSessionProfile(context, defaultProfile, body.profile);
4105
- const workflowSelection = resolveProjectWorkflowSelection(state, body.workflowId, body.workflowVersion, { requireExplicitWorkflowId: true, requireExplicitVersion: true });
4106
- const publishedWorkflow = resolvePublishedWorkflowDefinitionForProfile(state, workflowSelection, profile);
4107
- const baseDefinition = cloneJsonObject(publishedWorkflow.definition);
4108
- const configuration = normalizeProjectWorkflowSessionConfiguration(body, baseDefinition);
4109
- const validation = validatePublishedWorkflowBoundary({
4110
- state,
4111
- context,
4112
- webSession,
4113
- definition: baseDefinition,
4114
- trigger: "before_project_session_creation",
4115
- });
4116
- recordWorkflowLifecycleEvent(state, webSession, {
4117
- type: "workflow.validation.completed",
4118
- workflowId: workflowSelection.id,
4119
- workflowVersion: workflowSelection.version,
4120
- projectId: project.id,
4121
- status: validation.validation.ok ? "accepted" : "blocked",
4122
- validation: validation.validation,
4123
- diagnostics: validation.diagnostics,
4124
- payload: { trigger: validation.validation.trigger, boundary: "project_session_creation" },
4125
- });
4126
- if (!validation.validation.ok) {
4127
- return workflowValidationBlockedResponse("Workflow version has validation errors and cannot be used to create a Project session", validation, { workflow: workflowSelection });
4128
- }
4129
- const session = createProjectChatSession({
4130
- state,
4131
- context,
4132
- webSession,
4133
- project,
4134
- profile,
4135
- workflowId: workflowSelection.id,
4136
- workflowVersion: workflowSelection.version,
4137
- title: normalizeSessionTitle(body.title) ?? undefined,
4138
- configuredWorkflow: true,
4139
- configuration,
4140
- });
4141
- const snapshot = state.projectService.saveWorkflowSessionSnapshot(createProjectWorkflowSessionSnapshot({
4142
- webSession,
4143
- project,
4144
- session,
4145
- workflow: publishedWorkflow,
4146
- baseDefinition,
4147
- configuration,
4148
- validation,
4149
- }));
4150
- const projectSession = state.projectService.getProjectSession(session.id);
4151
- recordWorkflowLifecycleEvent(state, webSession, {
4152
- type: "project.workflow_session.created",
4153
- workflowId: workflowSelection.id,
4154
- workflowVersion: workflowSelection.version,
4155
- projectId: project.id,
4156
- piboSessionId: session.id,
4157
- status: "accepted",
4158
- validation: validation.validation,
4159
- diagnostics: validation.diagnostics,
4160
- payload: { snapshotId: snapshot.id, profile },
4161
- });
4162
- return responseJson({ session, projectSession, workflow: workflowSelection, configuration, snapshot, validation: validation.validation, diagnostics: validation.diagnostics }, { status: 201 });
4163
- }
4164
- const workflowSessionStart = projectWorkflowSessionStartResource(url.pathname);
4165
- if (workflowSessionStart && request.method === "POST") {
4166
- requireSameOriginJsonRequest(request);
4167
- const webSession = await requireSession(request, context);
4168
- const session = requireSharedSession(context, workflowSessionStart.piboSessionId);
4169
- const project = requireSharedProject(state, webSession, workflowSessionStart.projectId);
4170
- const projectSession = state.projectService.getProjectSession(session.id);
4171
- if (!projectSession || projectSession.projectId !== project.id)
4172
- throw new PiboWebHttpError("Project workflow session not found", 404);
4173
- 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);
4174
3908
  if (!snapshot)
4175
- throw new PiboWebHttpError("Project workflow session snapshot not found", 409);
4176
- const workflowSelection = workflowVersionFromSnapshot(snapshot);
4177
- if (projectSession.workflowRunId) {
4178
- const existingRun = state.projectService.getProjectWorkflowRun(projectSession.workflowRunId);
4179
- if (existingRun) {
4180
- updateProjectWorkflowRunSessionMetadata({ state, context, session, workflowRunId: existingRun.id });
4181
- return responseJson({
4182
- projectSession,
4183
- workflow: workflowSelection,
4184
- snapshot,
4185
- run: existingRun,
4186
- alreadyStarted: true,
4187
- validation: existingRun.validation ?? summarizeWorkflowDiagnostics([], "before_workflow_start"),
4188
- diagnostics: [],
4189
- message: "Workflow run already exists for this Project session.",
4190
- }, { 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." });
4191
3916
  }
4192
3917
  }
4193
- const validation = validateProjectWorkflowSnapshotForStart(snapshot, { state, context, webSession });
4194
- recordWorkflowLifecycleEvent(state, webSession, {
4195
- type: "workflow.validation.completed",
4196
- workflowId: workflowSelection.id,
4197
- workflowVersion: workflowSelection.version,
4198
- projectId: project.id,
4199
- piboSessionId: session.id,
4200
- workflowRunId: projectSession.workflowRunId,
4201
- status: validation.validation.ok ? "accepted" : "blocked",
4202
- validation: validation.validation,
4203
- diagnostics: validation.diagnostics,
4204
- payload: { trigger: validation.validation.trigger, boundary: "workflow_start", snapshotId: snapshot.id },
4205
- });
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 } });
4206
3920
  if (!validation.validation.ok) {
4207
- recordWorkflowLifecycleEvent(state, webSession, {
4208
- type: "project.workflow_start.blocked",
4209
- workflowId: workflowSelection.id,
4210
- workflowVersion: workflowSelection.version,
4211
- projectId: project.id,
4212
- piboSessionId: session.id,
4213
- workflowRunId: projectSession.workflowRunId,
4214
- status: "blocked",
4215
- validation: validation.validation,
4216
- diagnostics: validation.diagnostics,
4217
- payload: { snapshotId: snapshot.id, profile: session.profile },
4218
- });
4219
- return workflowValidationBlockedResponse("Workflow session has validation errors and cannot be started", validation, { projectSession, workflow: workflowSelection });
4220
- }
4221
- const startResult = state.projectService.startWorkflowSessionRun({
4222
- projectId: project.id,
4223
- piboSessionId: session.id,
4224
- runId: `wfr_${randomUUID()}`,
4225
- workflowId: snapshot.workflow.id,
4226
- workflowVersion: snapshot.workflow.version,
4227
- snapshotId: snapshot.id,
4228
- effectiveDefinitionHash: snapshot.workflow.effectiveDefinitionHash,
4229
- current: createProjectWorkflowRunCurrent(snapshot.effectiveDefinition),
4230
- inputValues: snapshot.inputValues,
4231
- validation: validation.validation,
4232
- });
4233
- updateProjectWorkflowRunSessionMetadata({ state, context, session, workflowRunId: startResult.run.id });
4234
- recordWorkflowLifecycleEvent(state, webSession, {
4235
- type: "project.workflow_start.accepted",
4236
- workflowId: workflowSelection.id,
4237
- workflowVersion: workflowSelection.version,
4238
- projectId: project.id,
4239
- piboSessionId: session.id,
4240
- workflowRunId: startResult.run.id,
4241
- status: "accepted",
4242
- validation: validation.validation,
4243
- diagnostics: validation.diagnostics,
4244
- payload: { snapshotId: snapshot.id, profile: session.profile, alreadyStarted: startResult.alreadyStarted },
4245
- });
4246
- if (!startResult.alreadyStarted) {
4247
- recordWorkflowLifecycleEvent(state, webSession, {
4248
- type: "workflow.run.status_changed",
4249
- workflowId: workflowSelection.id,
4250
- workflowVersion: workflowSelection.version,
4251
- projectId: project.id,
4252
- piboSessionId: session.id,
4253
- workflowRunId: startResult.run.id,
4254
- status: "changed",
4255
- validation: validation.validation,
4256
- diagnostics: validation.diagnostics,
4257
- payload: { snapshotId: snapshot.id, state: startResult.run.status, current: startResult.run.current },
4258
- });
4259
- }
4260
- return responseJson({
4261
- projectSession: startResult.projectSession,
4262
- workflow: workflowSelection,
4263
- snapshot,
4264
- run: startResult.run,
4265
- alreadyStarted: startResult.alreadyStarted,
4266
- validation: validation.validation,
4267
- diagnostics: validation.diagnostics,
4268
- message: startResult.alreadyStarted ? "Workflow run already exists for this Project session." : "Workflow run started.",
4269
- }, { status: startResult.alreadyStarted ? 200 : 202 });
4270
- }
4271
- if (projectResource && projectResource.child === "sessions" && request.method === "POST") {
4272
- requireSameOriginJsonRequest(request);
4273
- const webSession = await requireSession(request, context);
4274
- const body = await readJsonBody(request);
4275
- const project = requireSharedProject(state, webSession, projectResource.projectId);
4276
- const profile = resolveCreateSessionProfile(context, defaultProfile, body.profile);
4277
- const workflowId = normalizeLegacyProjectWorkflowId(body.workflowId);
4278
- const session = createProjectChatSession({ state, context, webSession, project, profile, workflowId });
4279
- return responseJson({ session, projectSession: state.projectService.getProjectSession(session.id) }, { status: 201 });
4280
- }
4281
- const projectSessionId = projectSessionResourceId(url.pathname);
4282
- if (projectSessionId && request.method === "PATCH") {
4283
- requireSameOriginJsonRequest(request);
4284
- const webSession = await requireSession(request, context);
4285
- const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, projectSessionId);
4286
- const body = await readJsonBody(request);
4287
- assertProjectSessionPatchFields(body);
4288
- const updateSession = context.channelContext.updateSession;
4289
- if (!updateSession)
4290
- throw new PiboWebHttpError("Session updates are not available", 501);
4291
- const title = normalizeSessionTitle(body.title);
4292
- let updated = selectedSession;
4293
- if (title !== undefined) {
4294
- const next = updateSession(selectedSession.id, { title });
4295
- if (!next)
4296
- throw new PiboWebHttpError("Session not found", 404);
4297
- updated = next;
4298
- 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 });
4299
3923
  }
4300
- const archived = normalizeProjectSessionArchived(body.archived);
4301
- const projectSession = archived === undefined ? state.projectService.getProjectSession(selectedSession.id) : state.projectService.setProjectSessionArchived(selectedSession.id, archived);
4302
- 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 });
4303
3930
  }
4304
3931
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/signals/statuses` && request.method === "GET") {
4305
3932
  await requireSession(request, context);
@@ -4475,6 +4102,7 @@ export function createChatWebApp(options = {}) {
4475
4102
  });
4476
4103
  return workflowValidationBlockedResponse("Workflow draft has validation errors and cannot be test-run", validation, { draft: serializeWorkflowDraft(record) });
4477
4104
  }
4105
+ const room = body.roomId ? requireRoom(state, body.roomId, webSession, "write") : state.roomService.ensureDefaultRoom();
4478
4106
  const result = await runWorkflowManualTextTrigger({
4479
4107
  definition: record.definition,
4480
4108
  triggerNodeId: body.triggerNodeId,
@@ -4483,11 +4111,31 @@ export function createChatWebApp(options = {}) {
4483
4111
  draftId: record.draftId,
4484
4112
  channelContext: context.channelContext,
4485
4113
  channel: CHAT_WEB_CHANNEL,
4486
- defaultWorkspace: getDefaultPiboWorkspace(),
4114
+ roomId: room.id,
4115
+ defaultWorkspace: body.workspace ?? room.workspace ?? getDefaultPiboWorkspace(),
4487
4116
  onSessionCreated: (session) => state.sessionQuery.upsertSession(session),
4488
4117
  resolveProfile: (profileId) => resolveWorkflowRuntimeProfile({ context, requestedId: profileId }),
4489
4118
  });
4490
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
+ }
4491
4139
  recordWorkflowLifecycleEvent(state, webSession, {
4492
4140
  type: result.ok ? "workflow.editor_test_run.completed" : "workflow.editor_test_run.failed",
4493
4141
  workflowId: record.workflowId,
@@ -4624,13 +4272,12 @@ export function createChatWebApp(options = {}) {
4624
4272
  return responseJson(archiveWorkflowIdentity(state, context, webSession, archiveWorkflowId, body.reason));
4625
4273
  }
4626
4274
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/workflows/lifecycle-events` && request.method === "GET") {
4627
- const webSession = await requireSession(request, context);
4275
+ await requireSession(request, context);
4628
4276
  return responseJson({
4629
4277
  events: state.workflowLifecycleEventStore.listEvents({
4630
4278
  type: url.searchParams.get("type") ?? undefined,
4631
4279
  workflowId: url.searchParams.get("workflowId") ?? undefined,
4632
4280
  draftId: url.searchParams.get("draftId") ?? undefined,
4633
- projectId: url.searchParams.get("projectId") ?? undefined,
4634
4281
  piboSessionId: url.searchParams.get("piboSessionId") ?? undefined,
4635
4282
  workflowRunId: url.searchParams.get("workflowRunId") ?? undefined,
4636
4283
  limit: parsePositiveIntSearchParam(url, "limit", 100, 500),
@@ -4942,12 +4589,42 @@ export function createChatWebApp(options = {}) {
4942
4589
  sessions: await buildSessionNodes(ownedSessions, state.sessionQuery.listSessions(), process.cwd(), new Map(), sessionNodeHistoryOptions(context)),
4943
4590
  });
4944
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
+ }
4945
4608
  if (roomResource && roomResource.child === undefined && request.method === "PATCH") {
4946
4609
  requireSameOriginJsonRequest(request);
4947
4610
  const webSession = await requireSession(request, context);
4948
4611
  const existingRoom = requireRoom(state, roomResource.roomId, webSession, "admin");
4949
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
+ }
4950
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
+ }
4951
4628
  if (update.parentRoomId)
4952
4629
  requireRoom(state, update.parentRoomId, webSession, "admin");
4953
4630
  let room;
@@ -5082,6 +4759,28 @@ export function createChatWebApp(options = {}) {
5082
4759
  throw new PiboWebHttpError(message, status);
5083
4760
  }
5084
4761
  }
4762
+ if (sessionAction?.action === "order" && request.method === "PATCH") {
4763
+ requireSameOriginJsonRequest(request);
4764
+ const webSession = await requireSession(request, context);
4765
+ const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, sessionAction.piboSessionId);
4766
+ const body = await readJsonBody(request);
4767
+ if (typeof body.targetPiboSessionId !== "string" || !body.targetPiboSessionId) {
4768
+ throw new PiboWebHttpError("targetPiboSessionId is required", 400);
4769
+ }
4770
+ if (body.position !== "before" && body.position !== "after") {
4771
+ throw new PiboWebHttpError("position must be before or after", 400);
4772
+ }
4773
+ const targetSession = resolveRequestedSession(state, context, webSession, defaultProfile, body.targetPiboSessionId);
4774
+ return responseJson({
4775
+ orderedSessionIds: reorderChatWebSession({
4776
+ state,
4777
+ context,
4778
+ session: selectedSession,
4779
+ targetSession,
4780
+ position: body.position,
4781
+ }),
4782
+ });
4783
+ }
5085
4784
  const patchSessionId = sessionResourceId(url.pathname);
5086
4785
  if (patchSessionId && request.method === "PATCH") {
5087
4786
  requireSameOriginJsonRequest(request);
@@ -5095,7 +4794,23 @@ export function createChatWebApp(options = {}) {
5095
4794
  if (body.profile !== undefined && (state.activeTraceSessions.has(selectedSession.id) || state.sessionQuery.hasSessionActivity(selectedSession.id))) {
5096
4795
  throw new PiboWebHttpError("Session profile can only be changed before the first message.", 400);
5097
4796
  }
4797
+ if (body.pinned !== undefined && selectedSession.parentId) {
4798
+ throw new PiboWebHttpError("Only top-level sessions can be pinned", 400);
4799
+ }
4800
+ if (body.pinned === true && (body.archived === true || isChatWebSessionArchived(selectedSession))) {
4801
+ throw new PiboWebHttpError("Archived sessions cannot be pinned", 400);
4802
+ }
5098
4803
  const update = createSessionUpdate(context, selectedSession, body);
4804
+ if (typeof body.pinned === "boolean") {
4805
+ const roomId = chatRoomIdFromMetadata(selectedSession.metadata);
4806
+ const targetGroup = roomId
4807
+ ? sessionsInRoom(listSharedSessions(context), roomId).filter((session) => !session.parentId
4808
+ && !isChatWebSessionArchived(session)
4809
+ && session.id !== selectedSession.id
4810
+ && isChatWebSessionPinned(session) === body.pinned)
4811
+ : [];
4812
+ update.metadata = withChatWebSessionPinned(update.metadata ?? selectedSession.metadata, body.pinned, nextChatWebSessionSidebarOrder(targetGroup));
4813
+ }
5099
4814
  if ("activeModel" in update) {
5100
4815
  try {
5101
4816
  await context.channelContext.setLiveSessionActiveModel?.(selectedSession.id, update.activeModel ?? undefined);
@@ -5709,17 +5424,6 @@ export function createChatWebApp(options = {}) {
5709
5424
  const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, requestedPiboSessionId, requestedRoomId);
5710
5425
  const cursor = parseSseCursor(url.searchParams.get("since")) ?? parseSseCursor(request.headers.get("last-event-id"));
5711
5426
  const transientReplayCursor = parseTransientReplayCursor(url.searchParams.get("liveSince"));
5712
- if (!requestedRoomId && state.projectService.getProjectSession(selectedSession.id)) {
5713
- return createEventStream({
5714
- piboSessionId: selectedSession.id,
5715
- activePiboSessionId: selectedSession.id,
5716
- mode: streamMode,
5717
- context,
5718
- state,
5719
- cursor,
5720
- transientReplayCursor,
5721
- });
5722
- }
5723
5427
  const roomId = requestedRoomId ?? selectedRoomIdForSession(state, context, selectedSession);
5724
5428
  requireRoom(state, roomId, webSession, "read");
5725
5429
  const streamPiboSessionId = requestedPiboSessionId || !requestedRoomId ? selectedSession.id : undefined;