@plurnk/plurnk-service 4.2.1 → 4.3.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 (438) hide show
  1. package/INSTALL.md +7 -2
  2. package/README.md +3 -1
  3. package/SPEC.md +1977 -1397
  4. package/dist/build-info.d.ts.map +1 -1
  5. package/dist/build-info.js +25 -3
  6. package/dist/build-info.js.map +1 -1
  7. package/dist/build-info.json +1 -1
  8. package/dist/completions/_plurnk-service +1 -1
  9. package/dist/completions/plurnk-service.bash +1 -1
  10. package/dist/completions/plurnk-service.fish +1 -0
  11. package/dist/content/body-preview.js.map +1 -1
  12. package/dist/content/byte-view.d.ts.map +1 -1
  13. package/dist/content/byte-view.js +3 -2
  14. package/dist/content/byte-view.js.map +1 -1
  15. package/dist/content/edit-collision.d.ts +2 -1
  16. package/dist/content/edit-collision.d.ts.map +1 -1
  17. package/dist/content/edit-collision.js.map +1 -1
  18. package/dist/content/edit-receipt.d.ts.map +1 -1
  19. package/dist/content/edit-receipt.js +28 -23
  20. package/dist/content/edit-receipt.js.map +1 -1
  21. package/dist/content/edited-span.js +1 -1
  22. package/dist/content/edited-span.js.map +1 -1
  23. package/dist/content/line-anchors.d.ts +3 -2
  24. package/dist/content/line-anchors.d.ts.map +1 -1
  25. package/dist/content/line-anchors.js +33 -19
  26. package/dist/content/line-anchors.js.map +1 -1
  27. package/dist/content/line-selection.js.map +1 -1
  28. package/dist/content/pattern-edits.js +1 -1
  29. package/dist/content/pattern-edits.js.map +1 -1
  30. package/dist/content/read-projector.d.ts +1 -1
  31. package/dist/content/read-projector.d.ts.map +1 -1
  32. package/dist/content/read-projector.js +10 -8
  33. package/dist/content/read-projector.js.map +1 -1
  34. package/dist/content/read-resolve.d.ts.map +1 -1
  35. package/dist/content/read-resolve.js +6 -5
  36. package/dist/content/read-resolve.js.map +1 -1
  37. package/dist/core/AdmittedTurnExecutor.d.ts +4 -4
  38. package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
  39. package/dist/core/AdmittedTurnExecutor.js +20 -18
  40. package/dist/core/AdmittedTurnExecutor.js.map +1 -1
  41. package/dist/core/AmbientMembers.d.ts +1 -1
  42. package/dist/core/AmbientMembers.d.ts.map +1 -1
  43. package/dist/core/AmbientMembers.js +5 -2
  44. package/dist/core/AmbientMembers.js.map +1 -1
  45. package/dist/core/BareBatchRunner.d.ts +1 -1
  46. package/dist/core/BareBatchRunner.d.ts.map +1 -1
  47. package/dist/core/BareBatchRunner.js +2 -2
  48. package/dist/core/BareBatchRunner.js.map +1 -1
  49. package/dist/core/CapabilityPolicies.js.map +1 -1
  50. package/dist/core/CapabilityResolver.d.ts +1 -2
  51. package/dist/core/CapabilityResolver.d.ts.map +1 -1
  52. package/dist/core/CapabilityResolver.js +11 -7
  53. package/dist/core/CapabilityResolver.js.map +1 -1
  54. package/dist/core/ChannelWrite.d.ts +2 -2
  55. package/dist/core/ChannelWrite.d.ts.map +1 -1
  56. package/dist/core/ChannelWrite.js.map +1 -1
  57. package/dist/core/ChannelWrite.sql +27 -0
  58. package/dist/core/ClientInteractions.js.map +1 -1
  59. package/dist/core/ContextFit.js +1 -1
  60. package/dist/core/ContextFit.js.map +1 -1
  61. package/dist/core/CoreSchemeServices.d.ts.map +1 -1
  62. package/dist/core/CoreSchemeServices.js +4 -4
  63. package/dist/core/CoreSchemeServices.js.map +1 -1
  64. package/dist/core/DataStatementRunner.d.ts.map +1 -1
  65. package/dist/core/DataStatementRunner.js +15 -15
  66. package/dist/core/DataStatementRunner.js.map +1 -1
  67. package/dist/core/Dispatcher.d.ts +29 -5
  68. package/dist/core/Dispatcher.d.ts.map +1 -1
  69. package/dist/core/Dispatcher.js +210 -130
  70. package/dist/core/Dispatcher.js.map +1 -1
  71. package/dist/core/EditMutations.d.ts.map +1 -1
  72. package/dist/core/EditMutations.js +12 -6
  73. package/dist/core/EditMutations.js.map +1 -1
  74. package/dist/core/EditSequence.js.map +1 -1
  75. package/dist/core/Engine.d.ts +12 -6
  76. package/dist/core/Engine.d.ts.map +1 -1
  77. package/dist/core/Engine.js +29 -7
  78. package/dist/core/Engine.js.map +1 -1
  79. package/dist/core/ExecutionOutputs.js.map +1 -1
  80. package/dist/core/ExecutorRegistry.d.ts +1 -2
  81. package/dist/core/ExecutorRegistry.d.ts.map +1 -1
  82. package/dist/core/ExecutorRegistry.js +2 -2
  83. package/dist/core/ExecutorRegistry.js.map +1 -1
  84. package/dist/core/FileAddress.js.map +1 -1
  85. package/dist/core/FilePublication.d.ts.map +1 -1
  86. package/dist/core/FilePublication.js +6 -3
  87. package/dist/core/FilePublication.js.map +1 -1
  88. package/dist/core/FileSnapshot.d.ts.map +1 -1
  89. package/dist/core/FileSnapshot.js +8 -5
  90. package/dist/core/FileSnapshot.js.map +1 -1
  91. package/dist/core/KillHandler.d.ts.map +1 -1
  92. package/dist/core/KillHandler.js +7 -0
  93. package/dist/core/KillHandler.js.map +1 -1
  94. package/dist/core/LiveSubscription.d.ts +1 -0
  95. package/dist/core/LiveSubscription.d.ts.map +1 -1
  96. package/dist/core/LiveSubscription.js +3 -2
  97. package/dist/core/LiveSubscription.js.map +1 -1
  98. package/dist/core/LiveSubscriptions.d.ts +4 -1
  99. package/dist/core/LiveSubscriptions.d.ts.map +1 -1
  100. package/dist/core/LiveSubscriptions.js +42 -1
  101. package/dist/core/LiveSubscriptions.js.map +1 -1
  102. package/dist/core/LogBody.d.ts.map +1 -1
  103. package/dist/core/LogBody.js +22 -19
  104. package/dist/core/LogBody.js.map +1 -1
  105. package/dist/core/LogVisibility.d.ts +1 -1
  106. package/dist/core/LogVisibility.d.ts.map +1 -1
  107. package/dist/core/LogVisibility.js +4 -6
  108. package/dist/core/LogVisibility.js.map +1 -1
  109. package/dist/core/LogWriter.d.ts.map +1 -1
  110. package/dist/core/LogWriter.js +6 -9
  111. package/dist/core/LogWriter.js.map +1 -1
  112. package/dist/core/LoopDriver.d.ts +4 -4
  113. package/dist/core/LoopDriver.d.ts.map +1 -1
  114. package/dist/core/LoopDriver.js +4 -5
  115. package/dist/core/LoopDriver.js.map +1 -1
  116. package/dist/core/LoopLifecycle.d.ts +2 -0
  117. package/dist/core/LoopLifecycle.d.ts.map +1 -1
  118. package/dist/core/LoopLifecycle.js +13 -3
  119. package/dist/core/LoopLifecycle.js.map +1 -1
  120. package/dist/core/LoopLifecycle.sql +4 -4
  121. package/dist/core/LoopOutcome.d.ts.map +1 -1
  122. package/dist/core/LoopOutcome.js +2 -2
  123. package/dist/core/LoopOutcome.js.map +1 -1
  124. package/dist/core/MembershipMaterialization.d.ts.map +1 -1
  125. package/dist/core/MembershipMaterialization.js +29 -29
  126. package/dist/core/MembershipMaterialization.js.map +1 -1
  127. package/dist/core/MessageResources.d.ts +5 -1
  128. package/dist/core/MessageResources.d.ts.map +1 -1
  129. package/dist/core/MessageResources.js +12 -0
  130. package/dist/core/MessageResources.js.map +1 -1
  131. package/dist/core/MessageResources.sql +8 -0
  132. package/dist/core/MutationEffects.d.ts +1 -0
  133. package/dist/core/MutationEffects.d.ts.map +1 -1
  134. package/dist/core/MutationEffects.js +14 -10
  135. package/dist/core/MutationEffects.js.map +1 -1
  136. package/dist/core/OperationHistory.d.ts.map +1 -1
  137. package/dist/core/OperationHistory.js +4 -3
  138. package/dist/core/OperationHistory.js.map +1 -1
  139. package/dist/core/OperatorConfig.d.ts.map +1 -1
  140. package/dist/core/OperatorConfig.js +3 -2
  141. package/dist/core/OperatorConfig.js.map +1 -1
  142. package/dist/core/PacketBuilder.d.ts +1 -1
  143. package/dist/core/PacketBuilder.d.ts.map +1 -1
  144. package/dist/core/PacketBuilder.js +7 -5
  145. package/dist/core/PacketBuilder.js.map +1 -1
  146. package/dist/core/ProblemLog.d.ts +2 -5
  147. package/dist/core/ProblemLog.d.ts.map +1 -1
  148. package/dist/core/ProblemLog.js +2 -3
  149. package/dist/core/ProblemLog.js.map +1 -1
  150. package/dist/core/ProposalLifecycle.d.ts +12 -11
  151. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  152. package/dist/core/ProposalLifecycle.js +92 -84
  153. package/dist/core/ProposalLifecycle.js.map +1 -1
  154. package/dist/core/ReasoningEvent.d.ts +2 -0
  155. package/dist/core/ReasoningEvent.d.ts.map +1 -1
  156. package/dist/core/ReplyMetadata.d.ts.map +1 -1
  157. package/dist/core/ReplyMetadata.js +4 -3
  158. package/dist/core/ReplyMetadata.js.map +1 -1
  159. package/dist/core/ResourceBindings.d.ts +2 -2
  160. package/dist/core/ResourceBindings.d.ts.map +1 -1
  161. package/dist/core/ResourceBindings.js.map +1 -1
  162. package/dist/core/ResourceMutations.d.ts +1 -1
  163. package/dist/core/ResourceMutations.d.ts.map +1 -1
  164. package/dist/core/ResourceMutations.js +1 -2
  165. package/dist/core/ResourceMutations.js.map +1 -1
  166. package/dist/core/ResourceSelector.js +2 -2
  167. package/dist/core/ResourceSelector.js.map +1 -1
  168. package/dist/core/ResourceTransfers.d.ts.map +1 -1
  169. package/dist/core/ResourceTransfers.js +162 -120
  170. package/dist/core/ResourceTransfers.js.map +1 -1
  171. package/dist/core/SchemeRegistry.d.ts +6 -5
  172. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  173. package/dist/core/SchemeRegistry.js +32 -26
  174. package/dist/core/SchemeRegistry.js.map +1 -1
  175. package/dist/core/ServiceHelp.d.ts.map +1 -1
  176. package/dist/core/ServiceHelp.js +2 -1
  177. package/dist/core/ServiceHelp.js.map +1 -1
  178. package/dist/core/StoredPacket.d.ts.map +1 -1
  179. package/dist/core/StoredPacket.js +26 -19
  180. package/dist/core/StoredPacket.js.map +1 -1
  181. package/dist/core/StrikeRail.d.ts +1 -2
  182. package/dist/core/StrikeRail.d.ts.map +1 -1
  183. package/dist/core/StrikeRail.js +4 -4
  184. package/dist/core/StrikeRail.js.map +1 -1
  185. package/dist/core/TerminalResult.d.ts +0 -2
  186. package/dist/core/TerminalResult.d.ts.map +1 -1
  187. package/dist/core/TerminalResult.js +0 -18
  188. package/dist/core/TerminalResult.js.map +1 -1
  189. package/dist/core/ToolInputSchema.d.ts.map +1 -1
  190. package/dist/core/ToolInputSchema.js +2 -2
  191. package/dist/core/ToolInputSchema.js.map +1 -1
  192. package/dist/core/ToolResources.d.ts +1 -0
  193. package/dist/core/ToolResources.d.ts.map +1 -1
  194. package/dist/core/ToolResources.js +7 -6
  195. package/dist/core/ToolResources.js.map +1 -1
  196. package/dist/core/TurnDispositionHandler.d.ts +1 -1
  197. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  198. package/dist/core/TurnDispositionHandler.js +0 -1
  199. package/dist/core/TurnDispositionHandler.js.map +1 -1
  200. package/dist/core/TurnMaterialization.d.ts.map +1 -1
  201. package/dist/core/TurnMaterialization.js +3 -2
  202. package/dist/core/TurnMaterialization.js.map +1 -1
  203. package/dist/core/TurnRunner.d.ts +13 -12
  204. package/dist/core/TurnRunner.d.ts.map +1 -1
  205. package/dist/core/TurnRunner.js +174 -164
  206. package/dist/core/TurnRunner.js.map +1 -1
  207. package/dist/core/TurnRunner.sql +3 -3
  208. package/dist/core/WorkerOwners.js.map +1 -1
  209. package/dist/core/caps/DbEntryCaps.js.map +1 -1
  210. package/dist/core/caps/DbMessageCaps.d.ts +3 -3
  211. package/dist/core/caps/DbMessageCaps.d.ts.map +1 -1
  212. package/dist/core/caps/DbProjectionCaps.js +1 -1
  213. package/dist/core/caps/DbProjectionCaps.js.map +1 -1
  214. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  215. package/dist/core/caps/DbSubscriptionCaps.js +1 -0
  216. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  217. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  218. package/dist/core/caps/SchemeCtxImpl.js +4 -1
  219. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  220. package/dist/core/env-catalog.js.map +1 -1
  221. package/dist/core/env-defaults.d.ts.map +1 -1
  222. package/dist/core/env-defaults.js +5 -3
  223. package/dist/core/env-defaults.js.map +1 -1
  224. package/dist/core/file-creation-policy.d.ts.map +1 -1
  225. package/dist/core/file-creation-policy.js +2 -1
  226. package/dist/core/file-creation-policy.js.map +1 -1
  227. package/dist/core/file-materialization.d.ts.map +1 -1
  228. package/dist/core/file-materialization.js +9 -11
  229. package/dist/core/file-materialization.js.map +1 -1
  230. package/dist/core/git-env.d.ts +1 -0
  231. package/dist/core/git-env.d.ts.map +1 -1
  232. package/dist/core/git-env.js +34 -3
  233. package/dist/core/git-env.js.map +1 -1
  234. package/dist/core/git-membership.d.ts +5 -0
  235. package/dist/core/git-membership.d.ts.map +1 -1
  236. package/dist/core/git-membership.js +55 -21
  237. package/dist/core/git-membership.js.map +1 -1
  238. package/dist/core/git-state.d.ts.map +1 -1
  239. package/dist/core/git-state.js +1 -0
  240. package/dist/core/git-state.js.map +1 -1
  241. package/dist/core/packet-wire.d.ts +26 -21
  242. package/dist/core/packet-wire.d.ts.map +1 -1
  243. package/dist/core/packet-wire.js +150 -112
  244. package/dist/core/packet-wire.js.map +1 -1
  245. package/dist/core/results.d.ts +9 -4
  246. package/dist/core/results.d.ts.map +1 -1
  247. package/dist/core/results.js +14 -1
  248. package/dist/core/results.js.map +1 -1
  249. package/dist/core/runtime-floor.d.ts +8 -0
  250. package/dist/core/runtime-floor.d.ts.map +1 -0
  251. package/dist/core/runtime-floor.js +40 -0
  252. package/dist/core/runtime-floor.js.map +1 -0
  253. package/dist/core/runtime-guard.d.ts +2 -0
  254. package/dist/core/runtime-guard.d.ts.map +1 -0
  255. package/dist/core/runtime-guard.js +5 -0
  256. package/dist/core/runtime-guard.js.map +1 -0
  257. package/dist/core/sqlite-synchronous.d.ts +1 -1
  258. package/dist/core/sqlite-synchronous.d.ts.map +1 -1
  259. package/dist/core/sqlite-synchronous.js.map +1 -1
  260. package/dist/core/statement-primary.d.ts +2 -2
  261. package/dist/core/statement-primary.d.ts.map +1 -1
  262. package/dist/core/statement-primary.js.map +1 -1
  263. package/dist/core/workspace-settings.js +1 -1
  264. package/dist/core/workspace-settings.js.map +1 -1
  265. package/dist/evidence/EvidenceReader.d.ts.map +1 -1
  266. package/dist/evidence/EvidenceReader.js +20 -6
  267. package/dist/evidence/EvidenceReader.js.map +1 -1
  268. package/dist/launch/Launch.d.ts +1 -0
  269. package/dist/launch/Launch.d.ts.map +1 -1
  270. package/dist/launch/Launch.js +28 -12
  271. package/dist/launch/Launch.js.map +1 -1
  272. package/dist/man/plurnk-service.1 +6 -2
  273. package/dist/schemes/EffectPolicy.js +1 -1
  274. package/dist/schemes/EffectPolicy.js.map +1 -1
  275. package/dist/schemes/Env.d.ts +1 -1
  276. package/dist/schemes/Env.d.ts.map +1 -1
  277. package/dist/schemes/Env.js +7 -1
  278. package/dist/schemes/Env.js.map +1 -1
  279. package/dist/schemes/Exec.d.ts +10 -12
  280. package/dist/schemes/Exec.d.ts.map +1 -1
  281. package/dist/schemes/Exec.js +337 -437
  282. package/dist/schemes/Exec.js.map +1 -1
  283. package/dist/schemes/ExecOutputScheme.d.ts +4 -6
  284. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  285. package/dist/schemes/ExecOutputScheme.js +17 -17
  286. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  287. package/dist/schemes/ExecScheduler.d.ts.map +1 -1
  288. package/dist/schemes/ExecScheduler.js +1 -2
  289. package/dist/schemes/ExecScheduler.js.map +1 -1
  290. package/dist/schemes/ExecutionInput.js +3 -3
  291. package/dist/schemes/ExecutionInput.js.map +1 -1
  292. package/dist/schemes/File.d.ts +3 -18
  293. package/dist/schemes/File.d.ts.map +1 -1
  294. package/dist/schemes/File.js +136 -79
  295. package/dist/schemes/File.js.map +1 -1
  296. package/dist/schemes/Log.d.ts.map +1 -1
  297. package/dist/schemes/Log.js +193 -161
  298. package/dist/schemes/Log.js.map +1 -1
  299. package/dist/schemes/QuestionTool.d.ts.map +1 -1
  300. package/dist/schemes/QuestionTool.js +6 -8
  301. package/dist/schemes/QuestionTool.js.map +1 -1
  302. package/dist/schemes/ResourceTreeScheme.d.ts.map +1 -1
  303. package/dist/schemes/ResourceTreeScheme.js +2 -2
  304. package/dist/schemes/ResourceTreeScheme.js.map +1 -1
  305. package/dist/schemes/TurnSource.js +3 -3
  306. package/dist/schemes/TurnSource.js.map +1 -1
  307. package/dist/schemes/Worker.js.map +1 -1
  308. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  309. package/dist/schemes/_entry-crud.js +4 -2
  310. package/dist/schemes/_entry-crud.js.map +1 -1
  311. package/dist/schemes/_entry-find.d.ts.map +1 -1
  312. package/dist/schemes/_entry-find.js +187 -139
  313. package/dist/schemes/_entry-find.js.map +1 -1
  314. package/dist/schemes/_entry-manifest.js +1 -1
  315. package/dist/schemes/_entry-manifest.js.map +1 -1
  316. package/dist/schemes/_entry-ops.js.map +1 -1
  317. package/dist/schemes/_search-index.js.map +1 -1
  318. package/dist/schemes/exec-entry-sink.d.ts +30 -0
  319. package/dist/schemes/exec-entry-sink.d.ts.map +1 -0
  320. package/dist/schemes/exec-entry-sink.js +238 -0
  321. package/dist/schemes/exec-entry-sink.js.map +1 -0
  322. package/dist/schemes/exec-lifetime.d.ts.map +1 -1
  323. package/dist/schemes/exec-lifetime.js +5 -2
  324. package/dist/schemes/exec-lifetime.js.map +1 -1
  325. package/dist/server/ClientOperations.d.ts +45 -0
  326. package/dist/server/ClientOperations.d.ts.map +1 -0
  327. package/dist/server/ClientOperations.js +192 -0
  328. package/dist/server/ClientOperations.js.map +1 -0
  329. package/dist/server/ClientReads.d.ts +13 -5
  330. package/dist/server/ClientReads.d.ts.map +1 -1
  331. package/dist/server/ClientReads.js +108 -21
  332. package/dist/server/ClientReads.js.map +1 -1
  333. package/dist/server/Credentials.js +2 -2
  334. package/dist/server/Credentials.js.map +1 -1
  335. package/dist/server/Daemon.d.ts +40 -28
  336. package/dist/server/Daemon.d.ts.map +1 -1
  337. package/dist/server/Daemon.js +250 -839
  338. package/dist/server/Daemon.js.map +1 -1
  339. package/dist/server/DaemonLock.d.ts +2 -0
  340. package/dist/server/DaemonLock.d.ts.map +1 -1
  341. package/dist/server/DaemonLock.js +10 -1
  342. package/dist/server/DaemonLock.js.map +1 -1
  343. package/dist/server/DaemonModules.d.ts +39 -0
  344. package/dist/server/DaemonModules.d.ts.map +1 -0
  345. package/dist/server/DaemonModules.js +219 -0
  346. package/dist/server/DaemonModules.js.map +1 -0
  347. package/dist/server/DrainSupervisor.d.ts +4 -2
  348. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  349. package/dist/server/DrainSupervisor.js +318 -290
  350. package/dist/server/DrainSupervisor.js.map +1 -1
  351. package/dist/server/EnvFunctionality.d.ts +10 -7
  352. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  353. package/dist/server/EnvFunctionality.js +6 -5
  354. package/dist/server/EnvFunctionality.js.map +1 -1
  355. package/dist/server/Functionality.d.ts +4 -5
  356. package/dist/server/Functionality.d.ts.map +1 -1
  357. package/dist/server/Functionality.js +19 -15
  358. package/dist/server/Functionality.js.map +1 -1
  359. package/dist/server/HttpListener.d.ts.map +1 -1
  360. package/dist/server/HttpListener.js +28 -8
  361. package/dist/server/HttpListener.js.map +1 -1
  362. package/dist/server/MembersFunctionality.d.ts +19 -7
  363. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  364. package/dist/server/MembersFunctionality.js +30 -16
  365. package/dist/server/MembersFunctionality.js.map +1 -1
  366. package/dist/server/MessageDelivery.d.ts +43 -0
  367. package/dist/server/MessageDelivery.d.ts.map +1 -0
  368. package/dist/server/MessageDelivery.js +186 -0
  369. package/dist/server/MessageDelivery.js.map +1 -0
  370. package/dist/server/ModuleHost.d.ts +3 -3
  371. package/dist/server/ModuleHost.d.ts.map +1 -1
  372. package/dist/server/Places.d.ts +19 -0
  373. package/dist/server/Places.d.ts.map +1 -0
  374. package/dist/server/Places.js +76 -0
  375. package/dist/server/Places.js.map +1 -0
  376. package/dist/server/PluginSources.d.ts.map +1 -1
  377. package/dist/server/PluginSources.js +2 -1
  378. package/dist/server/PluginSources.js.map +1 -1
  379. package/dist/server/PlurnkSkill.d.ts.map +1 -1
  380. package/dist/server/PlurnkSkill.js +5 -4
  381. package/dist/server/PlurnkSkill.js.map +1 -1
  382. package/dist/server/Rollback.d.ts.map +1 -1
  383. package/dist/server/Rollback.js +2 -1
  384. package/dist/server/Rollback.js.map +1 -1
  385. package/dist/server/WorkerModels.d.ts +83 -0
  386. package/dist/server/WorkerModels.d.ts.map +1 -0
  387. package/dist/server/WorkerModels.js +500 -0
  388. package/dist/server/WorkerModels.js.map +1 -0
  389. package/dist/server/WorkerOverview.d.ts +22 -0
  390. package/dist/server/WorkerOverview.d.ts.map +1 -0
  391. package/dist/server/WorkerOverview.js +42 -0
  392. package/dist/server/WorkerOverview.js.map +1 -0
  393. package/dist/server/WorkspacePlugins.d.ts +1 -1
  394. package/dist/server/WorkspacePlugins.d.ts.map +1 -1
  395. package/dist/server/WorkspacePlugins.js +0 -1
  396. package/dist/server/WorkspacePlugins.js.map +1 -1
  397. package/dist/server/WorkspaceResidency.d.ts +3 -3
  398. package/dist/server/WorkspaceResidency.d.ts.map +1 -1
  399. package/dist/server/WorkspaceResidency.js.map +1 -1
  400. package/dist/server/client-input.d.ts +2 -2
  401. package/dist/server/client-input.d.ts.map +1 -1
  402. package/dist/server/client-input.js +19 -12
  403. package/dist/server/client-input.js.map +1 -1
  404. package/dist/server/envelope.d.ts +4 -1
  405. package/dist/server/envelope.d.ts.map +1 -1
  406. package/dist/server/envelope.js +7 -3
  407. package/dist/server/envelope.js.map +1 -1
  408. package/dist/server/envelope.sql +10 -6
  409. package/dist/server/logEntry.d.ts +38 -1
  410. package/dist/server/logEntry.d.ts.map +1 -1
  411. package/dist/server/logEntry.js +9 -9
  412. package/dist/server/logEntry.js.map +1 -1
  413. package/dist/server/logEntry.sql +4 -2
  414. package/dist/server/loopDocs.d.ts.map +1 -1
  415. package/dist/server/loopDocs.js +11 -2
  416. package/dist/server/loopDocs.js.map +1 -1
  417. package/dist/server/module-discovery.d.ts +2 -3
  418. package/dist/server/module-discovery.d.ts.map +1 -1
  419. package/dist/server/module-discovery.js +6 -6
  420. package/dist/server/module-discovery.js.map +1 -1
  421. package/dist/server/places.sql +83 -0
  422. package/dist/server/seam-loop.sql +8 -0
  423. package/dist/server/worker-overview.sql +50 -0
  424. package/dist/service.d.ts +1 -0
  425. package/dist/service.d.ts.map +1 -1
  426. package/dist/service.js +35 -9
  427. package/dist/service.js.map +1 -1
  428. package/docs/members.md +11 -21
  429. package/migrations/033_hidden_places.sql +10 -0
  430. package/package.json +48 -57
  431. package/dist/observe/init.d.ts +0 -8
  432. package/dist/observe/init.d.ts.map +0 -1
  433. package/dist/observe/init.js +0 -163
  434. package/dist/observe/init.js.map +0 -1
  435. package/dist/server/WorkerModelResolver.d.ts +0 -20
  436. package/dist/server/WorkerModelResolver.d.ts.map +0 -1
  437. package/dist/server/WorkerModelResolver.js +0 -200
  438. package/dist/server/WorkerModelResolver.js.map +0 -1
@@ -3,7 +3,6 @@ import { aggregateProviderAccounting } from "@plurnk/plurnk-providers";
3
3
  import { routeForSpec } from "./model-route.js";
4
4
  import ChannelWrite from "../core/ChannelWrite.js";
5
5
  import ErrorDetail from "../core/ErrorDetail.js";
6
- import LoopLifecycle from "../core/LoopLifecycle.js";
7
6
  import MessageResources from "../core/MessageResources.js";
8
7
  import { Knob } from "@plurnk/plurnk-meta";
9
8
  import Results, { OperationFailureError } from "../core/results.js";
@@ -23,13 +22,13 @@ export default class DrainSupervisor {
23
22
  #loopAttributions;
24
23
  #discardLoopNotices;
25
24
  #cancelSubscription;
25
+ #subscriptionConcluded;
26
+ #reapLoopStreams;
26
27
  #hasActiveStreams;
27
28
  #readSystemPrompt;
28
29
  #emitLogEntry;
29
30
  #emit;
30
31
  #completeAbandonedMessages;
31
- // The handle is the drain identity. Start/exit compare by reference so an
32
- // exiting drain cannot clobber a successor that raced in.
33
32
  #activeDrains = new Map();
34
33
  #settlementTasks = new Set();
35
34
  #settlementFailures = [];
@@ -40,8 +39,10 @@ export default class DrainSupervisor {
40
39
  #drainLocks = new Map();
41
40
  #admissionLocks = new Map();
42
41
  #completionWakeGates = new Map();
42
+ // {§notifications-stream-concluded} A closed subscription's `stream/concluded`, until it is published.
43
+ #streamAnnouncements = new Map();
43
44
  #acceptingWork = false;
44
- constructor({ db, lifecycle, injectPrompt, assertInjectionCompatibility, runLoop, loopUsage, loopAttributions, discardLoopNotices, cancelSubscription, hasActiveStreams, readSystemPrompt, emitLogEntry, emit, completeAbandonedMessages, }) {
45
+ constructor({ db, lifecycle, injectPrompt, assertInjectionCompatibility, runLoop, loopUsage, loopAttributions, discardLoopNotices, cancelSubscription, subscriptionConcluded, reapLoopStreams, hasActiveStreams, readSystemPrompt, emitLogEntry, emit, completeAbandonedMessages, }) {
45
46
  this.#completeAbandonedMessages = completeAbandonedMessages ?? (async () => { });
46
47
  this.#db = db;
47
48
  this.#lifecycle = lifecycle;
@@ -52,6 +53,8 @@ export default class DrainSupervisor {
52
53
  this.#loopAttributions = loopAttributions;
53
54
  this.#discardLoopNotices = discardLoopNotices;
54
55
  this.#cancelSubscription = cancelSubscription;
56
+ this.#subscriptionConcluded = subscriptionConcluded;
57
+ this.#reapLoopStreams = reapLoopStreams;
55
58
  this.#hasActiveStreams = hasActiveStreams;
56
59
  this.#readSystemPrompt = readSystemPrompt;
57
60
  this.#emitLogEntry = emitLogEntry;
@@ -153,7 +156,7 @@ export default class DrainSupervisor {
153
156
  return { action: "enqueued_new_loop", ...accepted };
154
157
  }));
155
158
  const started = await this.ensureDrain({ workspaceId, workerId, systemPrompt: args.systemPrompt });
156
- return { ...delivery, ...(started ?? {}) };
159
+ return { ...delivery, ...started };
157
160
  }
158
161
  async #enqueueFreshLoop(args) {
159
162
  // {§worker-model-selection} — resolve the complete route before persistence;
@@ -190,254 +193,247 @@ export default class DrainSupervisor {
190
193
  // The drain runs under the worker's cancellation scope (shared with the
191
194
  // execs its loops spawn), so loop.cancel/shutdown abort it as a unit.
192
195
  const controller = this.#workerSignal(workerId);
193
- const handle = {
194
- controller, promise: Promise.resolve(),
195
- };
196
- let resolveFirst = () => { };
197
- let rejectFirst = () => { };
198
- const firstLoopPromise = new Promise((res, rej) => {
199
- resolveFirst = res;
200
- rejectFirst = rej;
201
- });
202
- let firstSettled = false;
203
- const claim = () => this.#db.drain_claim_next_loop.get({ worker_id: workerId });
204
- const drainPromise = (async () => {
205
- let loopsDrained = 0;
206
- let lastResult = null;
207
- let currentLoopId = null; // the loop being drained — for abort→499 settlement
208
- try {
209
- while (true) {
210
- controller.signal.throwIfAborted();
211
- let loopRow = await claim();
212
- if (loopRow === undefined) {
213
- // {§worker-lifecycle-no-lost-loop}
214
- // Queue empty → teardown UNDER the per-worker drain lock (R4 / I1),
215
- // serialized against ensureDrain so a concurrent inject can't
216
- // start a 2nd drain in the gap. Re-claim while holding the lock;
217
- // relinquish the registry slot only if it's empty too. A loop
218
- // that raced in is returned and run — we stay registered, so
219
- // there's no transient delete for ensureDrain to catch.
220
- loopRow = await this.#withDrainLock(workerId, async () => {
221
- const claimed = await claim();
222
- if (claimed === undefined && this.#activeDrains.get(workerId) === handle) {
223
- this.#activeDrains.delete(workerId);
224
- }
225
- return claimed;
226
- });
227
- if (loopRow === undefined)
228
- break;
229
- }
230
- currentLoopId = loopRow.id;
231
- this.#clearLoopTimer(loopRow.id);
232
- await this.#emitLoopStatus(workspaceId, workerId, loopRow.id, 102);
233
- const onSettled = async (logEntryId) => {
234
- await this.#emitLogEntry(workspaceId, logEntryId).catch((error) => {
235
- console.error("log/entry broadcast failed:", error instanceof Error ? error.message : String(error));
236
- });
237
- };
238
- const result = await observed("loop.run", { workspaceId, workerId, "loop.id": loopRow.id }, async (span) => {
239
- const loopResult = await this.#runLoop({
240
- workspaceId,
241
- workerId,
242
- loopId: loopRow.id,
243
- maxTurns: loopRow.max_turns,
244
- prompt: loopRow.prompt,
245
- systemPrompt,
246
- signal: controller.signal,
247
- onSettled,
248
- });
249
- span.setAttribute("status", loopResult.result.status);
250
- recordCounter(LOOP_TERMINALS, { status: loopResult.result.status });
251
- return loopResult;
252
- });
253
- if (result.result.status === 202) {
254
- await this.#withDrainLock(workerId, () => this.#emitLoopStatus(workspaceId, workerId, loopRow.id, 202));
255
- // The loop parked — suspended, not terminated. Leave it at 202
256
- // (resumable); no loop/terminated, no orphan-reconcile. A stream conclusion
257
- // through handleWakeWorker re-queues it; the durable wait deadline also
258
- // wakes it to inspect ({§worker-wait-timing}). {§worker-lifecycle-wake-liveness}.
259
- await this.scheduleWakes(workspaceId, workerId, systemPrompt);
260
- // Serialize the park boundary against message injection.
261
- // Whichever side arrives first owns a wake edge: injection wakes
262
- // an already-parked loop, while this check wakes a message written
263
- // just before runLoop finished parking.
264
- const messageWaiting = await this.#withDrainLock(workerId, async () => {
265
- const unpublished = await this.#db.drain_unpublished_messages_for_loop.get({ loop_id: loopRow.id });
266
- if (unpublished === undefined)
267
- return false;
268
- return this.#wakeLoop(workspaceId, workerId, loopRow.id);
269
- });
270
- if (messageWaiting) {
271
- currentLoopId = null;
272
- continue;
273
- }
274
- // {§loop-wake-identity}: events observed by another loop
275
- // cannot consume this loop's completion wake.
276
- if (await this.#withDrainLock(workerId, () => this.#wakeLoop(workspaceId, workerId, loopRow.id, { eventOnly: true }))) {
277
- currentLoopId = null;
278
- continue;
279
- }
280
- // {§loop-wake-identity}: the wait now belongs to its completion
281
- // obligations and inherited observation, not this drain.
282
- currentLoopId = null;
283
- continue;
284
- }
285
- const [usage, attributions, turnIds] = await Promise.all([
286
- this.#loopUsage(loopRow.id),
287
- this.#loopAttributions(loopRow.id),
288
- this.#lifecycle.turnIds(loopRow.id),
289
- ]);
290
- await this.#publishTermination(workspaceId, {
291
- workerId,
292
- loopId: loopRow.id,
293
- result: result.result,
294
- hitMaxTurns: result.hitMaxTurns,
295
- turnIds,
296
- usage,
297
- attributions,
298
- });
299
- loopsDrained++;
300
- const loopResult = {
301
- loopId: loopRow.id,
302
- turnIds,
303
- result: result.result,
304
- hitMaxTurns: result.hitMaxTurns,
305
- usage,
306
- attributions,
307
- };
308
- lastResult = loopResult;
309
- if (!firstSettled) {
310
- firstSettled = true;
311
- resolveFirst(loopResult);
312
- }
313
- // A message this loop ended before publishing (a wake conclusion or a
314
- // runLoop-while-active arrival) is promoted to a fresh queued loop so it's
315
- // never silently dropped ({§message-loop-containment}).
316
- await this.reconcileOrphanedMessages(workerId, loopRow.id);
317
- await this.#abandonUnanswered(workspaceId, workerId, loopRow.id, result.result.status);
196
+ const handle = { controller, promise: Promise.resolve() };
197
+ const firstLoop = Promise.withResolvers();
198
+ const first = { settled: false, resolve: firstLoop.resolve, reject: firstLoop.reject };
199
+ const drainPromise = this.#drain({ workspaceId, workerId, systemPrompt, controller, handle, first });
200
+ handle.promise = drainPromise;
201
+ this.#activeDrains.set(workerId, handle);
202
+ // Swallow unhandled rejections (drain aborts with no awaiter); the
203
+ // error already surfaced via firstLoopPromise or was logged inside.
204
+ drainPromise.catch(() => { });
205
+ firstLoop.promise.catch(() => { });
206
+ return { firstLoopPromise: firstLoop.promise, drainPromise };
207
+ }
208
+ // The drain itself: claim, run, then park or conclude, until the queue is empty under the lock. A
209
+ // cancellation or a failure settles the loop in flight before the drain exits, the first loop's
210
+ // settlement is delivered exactly once, and the exit always schedules the worker's wakes.
211
+ async #drain(scope) {
212
+ const { workspaceId, workerId, systemPrompt, controller, handle, first } = scope;
213
+ let loopsDrained = 0;
214
+ let lastResult = null;
215
+ let currentLoopId = null; // the loop being drained — for abort→499 settlement
216
+ try {
217
+ while (true) {
218
+ controller.signal.throwIfAborted();
219
+ const loopRow = await this.#claimLoop(workerId, handle);
220
+ if (loopRow === undefined)
221
+ break;
222
+ currentLoopId = loopRow.id;
223
+ const result = await this.#runClaimedLoop(scope, loopRow);
224
+ if (result.result.status === 202) {
225
+ await this.#parkLoop(scope, loopRow.id);
318
226
  currentLoopId = null;
227
+ continue;
319
228
  }
320
- }
321
- catch (err) {
322
- if (controller.signal.aborted) {
323
- // {§methods-loop-cancel} — loop.cancel / shutdown aborted the live drain. A cancellation
324
- // is the loop's TERMINAL state (499), delivered via loop/terminated (runLoop no
325
- // longer blocks to return it). A genuine error rejects firstLoopPromise.
326
- let usage = {
327
- accounting: aggregateProviderAccounting([]),
328
- curationWeight: null,
329
- curationBudget: null,
330
- contextTokens: null,
331
- contextCapacity: null,
332
- meta: {},
333
- };
334
- let attributions = [];
335
- const message = ErrorDetail.preview(controller.signal.reason ?? "user_cancelled")
336
- || "no reason was supplied";
337
- if (currentLoopId !== null) {
338
- // {§methods-loop-cancel}/{§worker-lifecycle-terminal-result} —
339
- // persist the exact 499 cancellation result before broadcasting it.
340
- const cancelled = await this.#lifecycle.finish(currentLoopId, Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
341
- reason: message,
342
- stage: "loop",
343
- retryable: false,
344
- }), { terminatedBy: "cancel" });
345
- [usage, attributions] = await Promise.all([
346
- this.#loopUsage(currentLoopId),
347
- this.#loopAttributions(currentLoopId),
348
- ]);
349
- if (cancelled !== null) {
350
- await this.#publishTermination(workspaceId, {
351
- workerId,
352
- loopId: currentLoopId,
353
- result: cancelled,
354
- hitMaxTurns: false,
355
- turnIds: await this.#lifecycle.turnIds(currentLoopId),
356
- usage,
357
- attributions,
358
- });
359
- await this.#abandonUnanswered(workspaceId, workerId, currentLoopId, cancelled.status);
360
- }
361
- }
362
- if (!firstSettled) {
363
- firstSettled = true;
364
- resolveFirst({
365
- loopId: currentLoopId ?? 0,
366
- turnIds: [],
367
- result: currentLoopId === null
368
- ? Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
369
- reason: message,
370
- stage: "loop",
371
- retryable: false,
372
- })
373
- : await this.#lifecycle.result(currentLoopId)
374
- ?? Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
375
- reason: message,
376
- stage: "loop",
377
- retryable: false,
378
- }),
379
- hitMaxTurns: false,
380
- usage,
381
- });
382
- }
383
- }
384
- else {
385
- // {§worker-lifecycle-terminal-result} — a non-abort drain
386
- // failure becomes an exact durable 500 and terminal notification;
387
- // daemon diagnostics retain the complete caught error.
388
- console.error(`drain error (workspace ${workspaceId}, worker ${workerId}, loop ${currentLoopId ?? "?"}):`, err);
389
- if (currentLoopId !== null) {
390
- const failure = err instanceof OperationFailureError
391
- ? err.result
392
- : Results.failure("daemon:drain", "loop-threw", 500, "The loop failed outside its operation result contract.", {}, {
393
- stage: "loop",
394
- retryable: false,
395
- });
396
- const settled = await this.#lifecycle.finish(currentLoopId, failure)
397
- ?? await this.#lifecycle.result(currentLoopId);
398
- if (settled === null) {
399
- throw new Error(`drain could not settle loop ${currentLoopId}`, { cause: err });
400
- }
401
- const [usage, attributions] = await Promise.all([
402
- this.#loopUsage(currentLoopId),
403
- this.#loopAttributions(currentLoopId),
404
- ]);
405
- await this.#publishTermination(workspaceId, {
406
- workerId,
407
- loopId: currentLoopId,
408
- result: settled,
409
- hitMaxTurns: false,
410
- turnIds: await this.#lifecycle.turnIds(currentLoopId),
411
- usage,
412
- attributions,
413
- });
414
- await this.#abandonUnanswered(workspaceId, workerId, currentLoopId, settled.status);
415
- }
416
- if (!firstSettled) {
417
- firstSettled = true;
418
- rejectFirst(err);
419
- }
229
+ const { loopResult, announced } = await this.#concludeLoop(scope, loopRow.id, result);
230
+ loopsDrained++;
231
+ lastResult = loopResult;
232
+ if (!first.settled) {
233
+ first.settled = true;
234
+ first.resolve(loopResult);
420
235
  }
421
- throw err;
236
+ // A message this loop ended before publishing (a wake conclusion or a
237
+ // runLoop-while-active arrival) is promoted to a fresh queued loop so it's
238
+ // never silently dropped ({§message-loop-containment}).
239
+ await this.reconcileOrphanedMessages(workerId, loopRow.id);
240
+ if (announced)
241
+ await this.#abandonUnanswered(workspaceId, workerId, loopRow.id, result.result.status);
242
+ currentLoopId = null;
243
+ }
244
+ }
245
+ catch (err) {
246
+ if (controller.signal.aborted)
247
+ await this.#settleCancelledDrain(scope, currentLoopId);
248
+ else
249
+ await this.#settleFailedDrain(scope, currentLoopId, err);
250
+ throw err;
251
+ }
252
+ finally {
253
+ if (!first.settled) {
254
+ first.settled = true;
255
+ first.reject(new Error("drain exited without producing a result"));
256
+ }
257
+ if (this.#activeDrains.get(workerId) === handle)
258
+ this.#activeDrains.delete(workerId);
259
+ await this.scheduleWakes(workspaceId, workerId, systemPrompt);
260
+ }
261
+ return { loopsDrained, lastResult };
262
+ }
263
+ // {§worker-lifecycle-no-lost-loop} The next queued loop. An empty queue is re-claimed UNDER the per-worker
264
+ // drain lock (R4 / I1), serialized against ensureDrain so a concurrent inject can't start a 2nd drain
265
+ // in the gap; the registry slot is relinquished only if it is empty there too, and a loop that raced in
266
+ // is returned and run — the drain stays registered, so there's no transient delete for ensureDrain to
267
+ // catch.
268
+ async #claimLoop(workerId, handle) {
269
+ const claim = () => this.#db.drain_claim_next_loop.get({ worker_id: workerId });
270
+ const claimed = await claim();
271
+ if (claimed !== undefined)
272
+ return claimed;
273
+ return this.#withDrainLock(workerId, async () => {
274
+ const reclaimed = await claim();
275
+ if (reclaimed === undefined && this.#activeDrains.get(workerId) === handle) {
276
+ this.#activeDrains.delete(workerId);
277
+ }
278
+ return reclaimed;
279
+ });
280
+ }
281
+ // One claimed loop under observation: its timer cleared, 102 announced, every settled row broadcast.
282
+ async #runClaimedLoop(scope, loopRow) {
283
+ const { workspaceId, workerId, systemPrompt, controller } = scope;
284
+ this.#clearLoopTimer(loopRow.id);
285
+ await this.#emitLoopStatus(workspaceId, workerId, loopRow.id, 102);
286
+ const onSettled = async (logEntryId) => {
287
+ await this.#emitLogEntry(workspaceId, logEntryId).catch((error) => {
288
+ console.error("log/entry broadcast failed:", error instanceof Error ? error.message : String(error));
289
+ });
290
+ };
291
+ return observed("loop.run", { workspaceId, workerId, "loop.id": loopRow.id }, async (span) => {
292
+ const loopResult = await this.#runLoop({
293
+ workspaceId,
294
+ workerId,
295
+ loopId: loopRow.id,
296
+ maxTurns: loopRow.max_turns,
297
+ prompt: loopRow.prompt,
298
+ systemPrompt,
299
+ signal: controller.signal,
300
+ onSettled,
301
+ });
302
+ span.setAttribute("status", loopResult.result.status);
303
+ recordCounter(LOOP_TERMINALS, { status: loopResult.result.status });
304
+ return loopResult;
305
+ });
306
+ }
307
+ // A parked loop (202) is suspended, not terminated: it stays resumable, with no loop/terminated and no
308
+ // orphan reconcile. A stream conclusion through handleWakeWorker re-queues it; the durable wait deadline
309
+ // also wakes it to inspect ({§worker-wait-timing}). {§worker-lifecycle-wake-liveness}
310
+ async #parkLoop(scope, loopId) {
311
+ const { workspaceId, workerId, systemPrompt } = scope;
312
+ await this.#withDrainLock(workerId, () => this.#emitLoopStatus(workspaceId, workerId, loopId, 202));
313
+ await this.scheduleWakes(workspaceId, workerId, systemPrompt);
314
+ // Serialize the park boundary against message injection.
315
+ // Whichever side arrives first owns a wake edge: injection wakes
316
+ // an already-parked loop, while this check wakes a message written
317
+ // just before runLoop finished parking.
318
+ const messageWaiting = await this.#withDrainLock(workerId, async () => {
319
+ const unpublished = await this.#db.drain_unpublished_messages_for_loop.get({ loop_id: loopId });
320
+ if (unpublished === undefined)
321
+ return false;
322
+ return this.#wakeLoop(workspaceId, workerId, loopId);
323
+ });
324
+ if (messageWaiting)
325
+ return;
326
+ // {§loop-wake-identity}: events observed by another loop cannot consume this loop's completion
327
+ // wake. Whether or not one wakes it here, the wait now belongs to its completion obligations and
328
+ // inherited observation, not this drain.
329
+ await this.#withDrainLock(workerId, () => this.#wakeLoop(workspaceId, workerId, loopId, { eventOnly: true }));
330
+ }
331
+ // A loop's terminal: its streams reaped, usage, attributions and turns gathered, loop/terminated published, and
332
+ // the result the drain reports. A loop a cancellation retired is reaped and announced by that cancellation instead.
333
+ async #concludeLoop(scope, loopId, result) {
334
+ const { workspaceId, workerId } = scope;
335
+ const cancelled = await this.#lifecycle.cancelled(loopId);
336
+ // {§notifications-loop-terminated} {§exec-lifetime} The streams the loop's terminal reaps conclude before it is announced.
337
+ if (!cancelled)
338
+ await this.#reapLoop(loopId);
339
+ const [usage, attributions, turnIds] = await Promise.all([
340
+ this.#loopUsage(loopId),
341
+ this.#loopAttributions(loopId),
342
+ this.#lifecycle.turnIds(loopId),
343
+ ]);
344
+ const loopResult = {
345
+ loopId: loopId,
346
+ turnIds,
347
+ result: result.result,
348
+ hitMaxTurns: result.hitMaxTurns,
349
+ usage,
350
+ attributions,
351
+ };
352
+ if (!cancelled)
353
+ await this.#publishTermination(workspaceId, { workerId, ...loopResult });
354
+ return { loopResult, announced: !cancelled };
355
+ }
356
+ // {§methods-loop-cancel} loop.cancel / shutdown aborted the live drain. A cancellation is the loop's
357
+ // TERMINAL state (499), delivered via loop/terminated (runLoop no longer blocks to return it), and the
358
+ // exact 499 is persisted before it is broadcast ({§worker-lifecycle-terminal-result}).
359
+ async #settleCancelledDrain(scope, currentLoopId) {
360
+ const { workspaceId, workerId, controller, first } = scope;
361
+ const message = ErrorDetail.preview(controller.signal.reason ?? "user_cancelled")
362
+ || "no reason was supplied";
363
+ const cancelledResult = () => Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
364
+ reason: message,
365
+ stage: "loop",
366
+ retryable: false,
367
+ });
368
+ let usage = {
369
+ accounting: aggregateProviderAccounting([]),
370
+ curationWeight: null,
371
+ curationBudget: null,
372
+ contextTokens: null,
373
+ contextCapacity: null,
374
+ meta: {},
375
+ };
376
+ let attributions = [];
377
+ if (currentLoopId !== null) {
378
+ const cancelled = await this.#lifecycle.finish(currentLoopId, cancelledResult(), { terminatedBy: "cancel" });
379
+ [usage, attributions] = await Promise.all([
380
+ this.#loopUsage(currentLoopId),
381
+ this.#loopAttributions(currentLoopId),
382
+ ]);
383
+ if (cancelled !== null) {
384
+ await this.#reapLoop(currentLoopId);
385
+ await this.#publishTermination(workspaceId, {
386
+ workerId,
387
+ loopId: currentLoopId,
388
+ result: cancelled,
389
+ hitMaxTurns: false,
390
+ turnIds: await this.#lifecycle.turnIds(currentLoopId),
391
+ usage,
392
+ attributions,
393
+ });
394
+ await this.#abandonUnanswered(workspaceId, workerId, currentLoopId, cancelled.status);
422
395
  }
423
- finally {
424
- if (!firstSettled) {
425
- firstSettled = true;
426
- rejectFirst(new Error("drain exited without producing a result"));
396
+ }
397
+ if (!first.settled) {
398
+ first.settled = true;
399
+ first.resolve({
400
+ loopId: currentLoopId ?? 0,
401
+ turnIds: [],
402
+ result: currentLoopId === null
403
+ ? cancelledResult()
404
+ : await this.#lifecycle.result(currentLoopId) ?? cancelledResult(),
405
+ hitMaxTurns: false,
406
+ usage,
407
+ });
408
+ }
409
+ }
410
+ // {§worker-lifecycle-terminal-result} A non-abort drain failure becomes an exact durable 500 and terminal
411
+ // notification; daemon diagnostics retain the complete caught error.
412
+ async #settleFailedDrain(scope, currentLoopId, err) {
413
+ const { workspaceId, workerId, first } = scope;
414
+ console.error(`drain error (workspace ${workspaceId}, worker ${workerId}, loop ${currentLoopId ?? "?"}):`, err);
415
+ if (currentLoopId !== null) {
416
+ const failure = err instanceof OperationFailureError
417
+ ? err.result
418
+ : Results.failure("daemon:drain", "loop-threw", 500, "The loop failed outside its operation result contract.", {}, {
419
+ stage: "loop",
420
+ retryable: false,
421
+ });
422
+ const finished = await this.#lifecycle.finish(currentLoopId, failure);
423
+ // A loop a cancellation retired is that cancellation's to announce.
424
+ if (finished !== null || !await this.#lifecycle.cancelled(currentLoopId)) {
425
+ const settled = finished ?? await this.#lifecycle.result(currentLoopId);
426
+ if (settled === null) {
427
+ throw new Error(`drain could not settle loop ${currentLoopId}`, { cause: err });
427
428
  }
428
- if (this.#activeDrains.get(workerId) === handle)
429
- this.#activeDrains.delete(workerId);
430
- await this.scheduleWakes(workspaceId, workerId, systemPrompt);
429
+ await this.#reapLoop(currentLoopId);
430
+ await this.#announce(workspaceId, workerId, currentLoopId, settled);
431
431
  }
432
- return { loopsDrained, lastResult };
433
- })();
434
- handle.promise = drainPromise;
435
- this.#activeDrains.set(workerId, handle);
436
- // Swallow unhandled rejections (drain aborts with no awaiter); the
437
- // error already surfaced via firstLoopPromise or was logged inside.
438
- drainPromise.catch(() => { });
439
- firstLoopPromise.catch(() => { });
440
- return { firstLoopPromise, drainPromise };
432
+ }
433
+ if (!first.settled) {
434
+ first.settled = true;
435
+ first.reject(err);
436
+ }
441
437
  }
442
438
  // Per-worker drain-transition lock (R4 / {§worker-lifecycle-single-drain}). ensureDrain's
443
439
  // start and a drain's teardown relinquish both run under it, serialized, so the two
@@ -523,10 +519,11 @@ export default class DrainSupervisor {
523
519
  const owner = await this.#db.drain_get_worker_workspace.get({ worker_id: workerId });
524
520
  if (owner === undefined)
525
521
  throw new Error(`cancellation worker ${workerId} does not exist`);
526
- const { cancelled, subscriptions } = await this.#withAdmissionLock(owner.workspace_id, async () => {
527
- const cancelled = await this.#lifecycle.cancelTree(workerId, reason, includeRoot);
528
- const subscriptions = (await Promise.all(cancelled.workerIds.map((id) => ChannelWrite.findOpenSubscriptionsForWorker(this.#db, id)))).flat();
529
- for (const targetWorkerId of cancelled.workerIds) {
522
+ const { cancelled, subscriptions, drains } = await this.#withAdmissionLock(owner.workspace_id, async () => {
523
+ const tree = await this.#lifecycle.cancelTree(workerId, reason, includeRoot);
524
+ const open = await Promise.all(tree.workerIds.map(async (id) => [id, await ChannelWrite.findOpenSubscriptionsForWorker(this.#db, id)]));
525
+ const draining = new Map();
526
+ for (const targetWorkerId of tree.workerIds) {
530
527
  for (const [loopId, timer] of this.#loopTimers) {
531
528
  if (timer.workerId === targetWorkerId)
532
529
  this.#clearLoopTimer(loopId);
@@ -534,32 +531,43 @@ export default class DrainSupervisor {
534
531
  const scope = this.#workerAborts.get(targetWorkerId);
535
532
  if (scope !== undefined && !scope.signal.aborted)
536
533
  scope.abort(reason);
534
+ const drain = this.#activeDrains.get(targetWorkerId);
535
+ if (drain !== undefined)
536
+ draining.set(targetWorkerId, drain.promise);
537
537
  }
538
- return { cancelled, subscriptions };
538
+ return { cancelled: tree, subscriptions: new Map(open), drains: draining };
539
539
  });
540
- await Promise.all(subscriptions.map(({ id }) => this.#cancelSubscription(id)));
540
+ await Promise.all([...subscriptions.values()].flat().map(({ id }) => this.#cancelSubscription(id)));
541
541
  for (const { loopId, workerId: targetWorkerId, result } of cancelled.loops) {
542
542
  const row = await this.#db.drain_get_worker_workspace.get({
543
543
  worker_id: targetWorkerId,
544
544
  });
545
545
  if (row === undefined)
546
546
  continue;
547
- const [usage, attributions] = await Promise.all([
548
- this.#loopUsage(loopId),
549
- this.#loopAttributions(loopId),
550
- ]);
551
- await this.#publishTermination(row.workspace_id, {
552
- workerId: targetWorkerId,
553
- loopId,
554
- result,
555
- hitMaxTurns: false,
556
- turnIds: await this.#lifecycle.turnIds(loopId),
557
- usage,
558
- attributions,
559
- });
560
- await this.#abandonUnanswered(row.workspace_id, targetWorkerId, loopId, result.status);
547
+ const announce = () => this.#announce(row.workspace_id, targetWorkerId, loopId, result);
548
+ // {§notifications-loop-terminated} What a retired loop held settles as its worker's drain unwinds the turn
549
+ // and its streams close; the terminal follows both. The caller is not held: it may be that turn.
550
+ const drain = drains.get(targetWorkerId);
551
+ const held = [
552
+ ...drain === undefined ? [] : [drain],
553
+ ...(subscriptions.get(targetWorkerId) ?? []).map(({ id }) => this.#streamConcluded(id)),
554
+ ];
555
+ if (held.length === 0)
556
+ await announce();
557
+ else
558
+ void this.#trackSettlement(Promise.allSettled(held).then(announce), `announce(${loopId})`);
561
559
  }
562
560
  }
561
+ // A terminal loop's loop/terminated, then what it left unanswered answered for it.
562
+ async #announce(workspaceId, workerId, loopId, result) {
563
+ const [usage, attributions, turnIds] = await Promise.all([
564
+ this.#loopUsage(loopId),
565
+ this.#loopAttributions(loopId),
566
+ this.#lifecycle.turnIds(loopId),
567
+ ]);
568
+ await this.#publishTermination(workspaceId, { workerId, loopId, result, hitMaxTurns: false, turnIds, usage, attributions });
569
+ await this.#abandonUnanswered(workspaceId, workerId, loopId, result.status);
570
+ }
563
571
  // {§message-abandonment} — after a loop's end is durable and published, whatever it left unanswered is
564
572
  // answered for it. A failure here is logged, never a second failure of the loop that already ended.
565
573
  async #abandonUnanswered(workspaceId, workerId, loopId, status) {
@@ -585,17 +593,37 @@ export default class DrainSupervisor {
585
593
  // {§module-shutdown-order}: the producer emits synchronously, while the
586
594
  // supervisor owns the asynchronous scheduler work and its shutdown truth.
587
595
  notifyWakeWorker(payload) {
588
- this.#trackSettlement(this.#handleWakeWorker(payload), "wake-on-completion");
596
+ const { subscriptionId } = payload;
597
+ const announced = Promise.withResolvers();
598
+ const announcement = { workerId: payload.workerId, delivered: announced.promise };
599
+ this.#streamAnnouncements.set(subscriptionId, announcement);
600
+ const handled = this.#handleWakeWorker(payload, announced.resolve).finally(() => {
601
+ announced.resolve();
602
+ if (this.#streamAnnouncements.get(subscriptionId) === announcement)
603
+ this.#streamAnnouncements.delete(subscriptionId);
604
+ });
605
+ void this.#trackSettlement(handled, "wake-on-completion");
606
+ }
607
+ // {§notifications-loop-terminated} A subscription's conclusion as its observers receive it: its close delivered,
608
+ // then its `stream/concluded` published.
609
+ async #streamConcluded(subscriptionId) {
610
+ await this.#subscriptionConcluded(subscriptionId);
611
+ await this.#streamAnnouncements.get(subscriptionId)?.delivered;
612
+ }
613
+ // {§notifications-loop-terminated} {§exec-lifetime} What the loop's terminal reaps, concluded and announced.
614
+ async #reapLoop(loopId) {
615
+ const reaped = await this.#reapLoopStreams(loopId);
616
+ await Promise.all(reaped.map((subscriptionId) => this.#streamConcluded(subscriptionId)));
589
617
  }
590
618
  async operationSettled(workspaceId, logEntryId) {
591
619
  const recipients = await this.#db.message_reply_recipients.all({ log_entry_id: logEntryId });
592
620
  if (recipients.length === 0)
593
621
  return;
594
- const systemPrompt = await this.#readSystemPrompt();
622
+ const systemPrompt = await this.#readSystemPrompt(); // {§card-read-per-wake}
595
623
  for (const { worker_id: workerId } of recipients) {
596
624
  // {§worker-optimistic-settlement}: the sender must finish its own
597
625
  // program while the recipient coalesces replies and conclusions.
598
- this.#trackSettlement(this.settleCompletionWake(workspaceId, workerId, systemPrompt), "wake-on-reply");
626
+ void this.#trackSettlement(this.settleCompletionWake(workspaceId, workerId, systemPrompt), "wake-on-reply");
599
627
  }
600
628
  }
601
629
  #trackSettlement(task, label) {
@@ -607,19 +635,21 @@ export default class DrainSupervisor {
607
635
  });
608
636
  return task;
609
637
  }
610
- async #handleWakeWorker(payload) {
638
+ async #handleWakeWorker(payload, announced) {
611
639
  const { workspaceId, ...conclusion } = payload;
640
+ const conclude = (wakeAction) => {
641
+ this.#emit(workspaceId, "stream/concluded", { ...conclusion, wakeAction });
642
+ announced();
643
+ };
612
644
  // {§worker-lifecycle-no-resurrection}: scope cancellation, not the
613
645
  // command's result code, prevents a wake. Cancelling one command still
614
646
  // produces an observation owed to its live worker.
615
647
  const scope = this.#workerAborts.get(payload.workerId);
616
648
  if (scope?.signal.aborted === true) {
617
- this.#emit(workspaceId, "stream/concluded", {
618
- ...conclusion, wakeAction: payload.result.status === 499 ? "skipped-aborted" : "skipped-cancelled",
619
- });
649
+ conclude(payload.result.status === 499 ? "skipped-aborted" : "skipped-cancelled");
620
650
  return;
621
651
  }
622
- const systemPrompt = await this.#readSystemPrompt();
652
+ const systemPrompt = await this.#readSystemPrompt(); // {§card-read-per-wake}
623
653
  // {§notifications-stream-concluded}: publish the terminal stream fact
624
654
  // before settlement. Rechecked wait identities, not this event, decide
625
655
  // whether any loop actually resumes.
@@ -629,25 +659,19 @@ export default class DrainSupervisor {
629
659
  // then let the worker-local gate coalesce only the provider
630
660
  // dispatch. Concurrent stream/child callbacks join that one gate.
631
661
  const settlement = this.settleCompletionWake(workspaceId, payload.workerId, systemPrompt);
632
- this.#emit(workspaceId, "stream/concluded", {
633
- ...conclusion, wakeAction: "wake-pending",
634
- });
662
+ conclude("wake-pending");
635
663
  await settlement;
636
664
  return;
637
665
  }
638
666
  // No slept loop. A live loop surfaces the concluded stream ambiently via the
639
667
  // environment-observation injector ({§exec-stream}) on its next turn; the client is told.
640
668
  if (this.#activeDrains.has(payload.workerId)) {
641
- this.#emit(workspaceId, "stream/concluded", {
642
- ...conclusion, wakeAction: "no-op-active-loop",
643
- });
669
+ conclude("no-op-active-loop");
644
670
  return;
645
671
  }
646
672
  // No slept loop, no active drain — nothing to resume (e.g. a completed worker whose
647
673
  // streams were swept). Surface the conclusion without opening a loop.
648
- this.#emit(workspaceId, "stream/concluded", {
649
- ...conclusion, wakeAction: "no-loop",
650
- });
674
+ conclude("no-loop");
651
675
  }
652
676
  #clearLoopTimer(loopId) {
653
677
  const current = this.#loopTimers.get(loopId);
@@ -685,7 +709,7 @@ export default class DrainSupervisor {
685
709
  if (this.#loopTimers.get(loopId)?.timer !== timer)
686
710
  return;
687
711
  this.#loopTimers.delete(loopId);
688
- this.#trackSettlement(this.#wakeTimedLoop(workspaceId, workerId, systemPrompt, loopId, revision), "wake-on-completion");
712
+ void this.#trackSettlement(this.#wakeTimedLoop(workspaceId, workerId, systemPrompt, loopId, revision), "wake-on-completion");
689
713
  }, Math.max(1, Math.min(dueAt - Date.now(), 2_147_483_647)));
690
714
  timer.unref();
691
715
  this.#loopTimers.set(loopId, { workerId, revision, dueAt, timer });
@@ -707,9 +731,9 @@ export default class DrainSupervisor {
707
731
  if (wait === undefined)
708
732
  return;
709
733
  const waitUntil = wait?.wait_poll_at ?? null;
710
- const { claimedAt, packetCount } = await this.#lifecycle.observation(loopId);
734
+ const { loopSeq, claimedAt, packetCount } = await this.#lifecycle.observation(loopId);
711
735
  this.#emit(workspaceId, "notice/event", {
712
- workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status, waitUntil, packetCount, claimedAt },
736
+ workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status, loopSeq, waitUntil, packetCount, claimedAt },
713
737
  });
714
738
  }
715
739
  async #wakeLoop(workspaceId, workerId, loopId, condition = {}) {
@@ -825,10 +849,14 @@ export default class DrainSupervisor {
825
849
  }
826
850
  // {§notifications-loop-terminated} — the terminal carries the stamp a snapshot reads.
827
851
  async #publishTermination(workspaceId, event) {
828
- const { terminatedAt } = await this.#lifecycle.observation(event.loopId);
852
+ const { loopSeq, terminatedAt } = await this.#lifecycle.observation(event.loopId);
853
+ // A durable close releases the live handle before its announcement is delivered.
854
+ await Promise.all([...this.#streamAnnouncements.values()]
855
+ .filter(({ workerId }) => workerId === event.workerId)
856
+ .map(({ delivered }) => delivered));
829
857
  this.#discardLoopNotices(event.loopId);
830
- this.#emit(workspaceId, "loop/terminated", { ...event, terminatedAt });
831
- this.#trackSettlement(this.#notifyParentCompletion(workspaceId, event.workerId), "wake-on-completion");
858
+ this.#emit(workspaceId, "loop/terminated", { ...event, loopSeq, terminatedAt });
859
+ void this.#trackSettlement(this.#notifyParentCompletion(workspaceId, event.workerId), "wake-on-completion");
832
860
  }
833
861
  // {§worker-lifecycle-child-wake}: completion belongs to a task, not drain teardown.
834
862
  async #notifyParentCompletion(workspaceId, workerId) {