@plurnk/plurnk-service 4.2.1 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/INSTALL.md +7 -2
  2. package/README.md +3 -1
  3. package/SPEC.md +1988 -1408
  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 +15 -7
  76. package/dist/core/Engine.d.ts.map +1 -1
  77. package/dist/core/Engine.js +32 -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 +26 -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 +3 -7
  143. package/dist/core/PacketBuilder.d.ts.map +1 -1
  144. package/dist/core/PacketBuilder.js +40 -31
  145. package/dist/core/PacketBuilder.js.map +1 -1
  146. package/dist/core/PacketBuilder.sql +11 -0
  147. package/dist/core/ProblemLog.d.ts +2 -5
  148. package/dist/core/ProblemLog.d.ts.map +1 -1
  149. package/dist/core/ProblemLog.js +2 -3
  150. package/dist/core/ProblemLog.js.map +1 -1
  151. package/dist/core/ProposalLifecycle.d.ts +12 -11
  152. package/dist/core/ProposalLifecycle.d.ts.map +1 -1
  153. package/dist/core/ProposalLifecycle.js +92 -84
  154. package/dist/core/ProposalLifecycle.js.map +1 -1
  155. package/dist/core/ReasoningEvent.d.ts +2 -0
  156. package/dist/core/ReasoningEvent.d.ts.map +1 -1
  157. package/dist/core/ReplyMetadata.d.ts.map +1 -1
  158. package/dist/core/ReplyMetadata.js +4 -3
  159. package/dist/core/ReplyMetadata.js.map +1 -1
  160. package/dist/core/ResourceBindings.d.ts +2 -2
  161. package/dist/core/ResourceBindings.d.ts.map +1 -1
  162. package/dist/core/ResourceBindings.js.map +1 -1
  163. package/dist/core/ResourceMutations.d.ts +1 -1
  164. package/dist/core/ResourceMutations.d.ts.map +1 -1
  165. package/dist/core/ResourceMutations.js +1 -2
  166. package/dist/core/ResourceMutations.js.map +1 -1
  167. package/dist/core/ResourceSelector.js +2 -2
  168. package/dist/core/ResourceSelector.js.map +1 -1
  169. package/dist/core/ResourceTransfers.d.ts.map +1 -1
  170. package/dist/core/ResourceTransfers.js +162 -120
  171. package/dist/core/ResourceTransfers.js.map +1 -1
  172. package/dist/core/SchemeRegistry.d.ts +6 -5
  173. package/dist/core/SchemeRegistry.d.ts.map +1 -1
  174. package/dist/core/SchemeRegistry.js +32 -26
  175. package/dist/core/SchemeRegistry.js.map +1 -1
  176. package/dist/core/ServiceHelp.d.ts.map +1 -1
  177. package/dist/core/ServiceHelp.js +2 -1
  178. package/dist/core/ServiceHelp.js.map +1 -1
  179. package/dist/core/StoredPacket.d.ts.map +1 -1
  180. package/dist/core/StoredPacket.js +26 -19
  181. package/dist/core/StoredPacket.js.map +1 -1
  182. package/dist/core/StrikeRail.d.ts +1 -2
  183. package/dist/core/StrikeRail.d.ts.map +1 -1
  184. package/dist/core/StrikeRail.js +4 -4
  185. package/dist/core/StrikeRail.js.map +1 -1
  186. package/dist/core/TerminalResult.d.ts +0 -2
  187. package/dist/core/TerminalResult.d.ts.map +1 -1
  188. package/dist/core/TerminalResult.js +0 -18
  189. package/dist/core/TerminalResult.js.map +1 -1
  190. package/dist/core/TokenCalibration.d.ts +4 -2
  191. package/dist/core/TokenCalibration.d.ts.map +1 -1
  192. package/dist/core/TokenCalibration.js +30 -13
  193. package/dist/core/TokenCalibration.js.map +1 -1
  194. package/dist/core/TokenCalibration.sql +7 -17
  195. package/dist/core/ToolResources.d.ts +2 -1
  196. package/dist/core/ToolResources.d.ts.map +1 -1
  197. package/dist/core/ToolResources.js +10 -34
  198. package/dist/core/ToolResources.js.map +1 -1
  199. package/dist/core/TurnDispositionHandler.d.ts +1 -1
  200. package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
  201. package/dist/core/TurnDispositionHandler.js +7 -3
  202. package/dist/core/TurnDispositionHandler.js.map +1 -1
  203. package/dist/core/TurnMaterialization.d.ts.map +1 -1
  204. package/dist/core/TurnMaterialization.js +3 -2
  205. package/dist/core/TurnMaterialization.js.map +1 -1
  206. package/dist/core/TurnRunner.d.ts +13 -12
  207. package/dist/core/TurnRunner.d.ts.map +1 -1
  208. package/dist/core/TurnRunner.js +198 -230
  209. package/dist/core/TurnRunner.js.map +1 -1
  210. package/dist/core/TurnRunner.sql +3 -3
  211. package/dist/core/WorkerOwners.js.map +1 -1
  212. package/dist/core/caps/DbEntryCaps.js.map +1 -1
  213. package/dist/core/caps/DbMessageCaps.d.ts +3 -3
  214. package/dist/core/caps/DbMessageCaps.d.ts.map +1 -1
  215. package/dist/core/caps/DbProjectionCaps.js +1 -1
  216. package/dist/core/caps/DbProjectionCaps.js.map +1 -1
  217. package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
  218. package/dist/core/caps/DbSubscriptionCaps.js +1 -0
  219. package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
  220. package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
  221. package/dist/core/caps/SchemeCtxImpl.js +4 -1
  222. package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
  223. package/dist/core/env-catalog.js.map +1 -1
  224. package/dist/core/env-defaults.d.ts.map +1 -1
  225. package/dist/core/env-defaults.js +5 -3
  226. package/dist/core/env-defaults.js.map +1 -1
  227. package/dist/core/file-creation-policy.d.ts.map +1 -1
  228. package/dist/core/file-creation-policy.js +2 -1
  229. package/dist/core/file-creation-policy.js.map +1 -1
  230. package/dist/core/file-materialization.d.ts.map +1 -1
  231. package/dist/core/file-materialization.js +9 -11
  232. package/dist/core/file-materialization.js.map +1 -1
  233. package/dist/core/git-env.d.ts +1 -0
  234. package/dist/core/git-env.d.ts.map +1 -1
  235. package/dist/core/git-env.js +34 -3
  236. package/dist/core/git-env.js.map +1 -1
  237. package/dist/core/git-membership.d.ts +5 -0
  238. package/dist/core/git-membership.d.ts.map +1 -1
  239. package/dist/core/git-membership.js +55 -21
  240. package/dist/core/git-membership.js.map +1 -1
  241. package/dist/core/git-state.d.ts.map +1 -1
  242. package/dist/core/git-state.js +1 -0
  243. package/dist/core/git-state.js.map +1 -1
  244. package/dist/core/packet-wire.d.ts +26 -27
  245. package/dist/core/packet-wire.d.ts.map +1 -1
  246. package/dist/core/packet-wire.js +154 -132
  247. package/dist/core/packet-wire.js.map +1 -1
  248. package/dist/core/results.d.ts +9 -4
  249. package/dist/core/results.d.ts.map +1 -1
  250. package/dist/core/results.js +14 -1
  251. package/dist/core/results.js.map +1 -1
  252. package/dist/core/runtime-floor.d.ts +8 -0
  253. package/dist/core/runtime-floor.d.ts.map +1 -0
  254. package/dist/core/runtime-floor.js +40 -0
  255. package/dist/core/runtime-floor.js.map +1 -0
  256. package/dist/core/runtime-guard.d.ts +2 -0
  257. package/dist/core/runtime-guard.d.ts.map +1 -0
  258. package/dist/core/runtime-guard.js +5 -0
  259. package/dist/core/runtime-guard.js.map +1 -0
  260. package/dist/core/sqlite-synchronous.d.ts +1 -1
  261. package/dist/core/sqlite-synchronous.d.ts.map +1 -1
  262. package/dist/core/sqlite-synchronous.js.map +1 -1
  263. package/dist/core/statement-primary.d.ts +2 -2
  264. package/dist/core/statement-primary.d.ts.map +1 -1
  265. package/dist/core/statement-primary.js.map +1 -1
  266. package/dist/core/workspace-settings.js +1 -1
  267. package/dist/core/workspace-settings.js.map +1 -1
  268. package/dist/evidence/EvidenceReader.d.ts.map +1 -1
  269. package/dist/evidence/EvidenceReader.js +20 -6
  270. package/dist/evidence/EvidenceReader.js.map +1 -1
  271. package/dist/launch/Launch.d.ts +1 -0
  272. package/dist/launch/Launch.d.ts.map +1 -1
  273. package/dist/launch/Launch.js +28 -12
  274. package/dist/launch/Launch.js.map +1 -1
  275. package/dist/man/plurnk-service.1 +6 -2
  276. package/dist/schemes/EffectPolicy.js +1 -1
  277. package/dist/schemes/EffectPolicy.js.map +1 -1
  278. package/dist/schemes/Env.d.ts +1 -1
  279. package/dist/schemes/Env.d.ts.map +1 -1
  280. package/dist/schemes/Env.js +7 -1
  281. package/dist/schemes/Env.js.map +1 -1
  282. package/dist/schemes/Exec.d.ts +10 -12
  283. package/dist/schemes/Exec.d.ts.map +1 -1
  284. package/dist/schemes/Exec.js +337 -437
  285. package/dist/schemes/Exec.js.map +1 -1
  286. package/dist/schemes/ExecOutputScheme.d.ts +4 -6
  287. package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
  288. package/dist/schemes/ExecOutputScheme.js +17 -17
  289. package/dist/schemes/ExecOutputScheme.js.map +1 -1
  290. package/dist/schemes/ExecScheduler.d.ts.map +1 -1
  291. package/dist/schemes/ExecScheduler.js +1 -2
  292. package/dist/schemes/ExecScheduler.js.map +1 -1
  293. package/dist/schemes/ExecutionInput.js +3 -3
  294. package/dist/schemes/ExecutionInput.js.map +1 -1
  295. package/dist/schemes/File.d.ts +3 -18
  296. package/dist/schemes/File.d.ts.map +1 -1
  297. package/dist/schemes/File.js +136 -79
  298. package/dist/schemes/File.js.map +1 -1
  299. package/dist/schemes/Log.d.ts.map +1 -1
  300. package/dist/schemes/Log.js +193 -161
  301. package/dist/schemes/Log.js.map +1 -1
  302. package/dist/schemes/QuestionTool.d.ts.map +1 -1
  303. package/dist/schemes/QuestionTool.js +6 -8
  304. package/dist/schemes/QuestionTool.js.map +1 -1
  305. package/dist/schemes/ResourceTreeScheme.d.ts.map +1 -1
  306. package/dist/schemes/ResourceTreeScheme.js +2 -2
  307. package/dist/schemes/ResourceTreeScheme.js.map +1 -1
  308. package/dist/schemes/TurnSource.js +3 -3
  309. package/dist/schemes/TurnSource.js.map +1 -1
  310. package/dist/schemes/Worker.js.map +1 -1
  311. package/dist/schemes/_entry-crud.d.ts.map +1 -1
  312. package/dist/schemes/_entry-crud.js +4 -2
  313. package/dist/schemes/_entry-crud.js.map +1 -1
  314. package/dist/schemes/_entry-find.d.ts.map +1 -1
  315. package/dist/schemes/_entry-find.js +187 -139
  316. package/dist/schemes/_entry-find.js.map +1 -1
  317. package/dist/schemes/_entry-manifest.js +1 -1
  318. package/dist/schemes/_entry-manifest.js.map +1 -1
  319. package/dist/schemes/_entry-ops.js.map +1 -1
  320. package/dist/schemes/_search-index.js.map +1 -1
  321. package/dist/schemes/exec-entry-sink.d.ts +30 -0
  322. package/dist/schemes/exec-entry-sink.d.ts.map +1 -0
  323. package/dist/schemes/exec-entry-sink.js +238 -0
  324. package/dist/schemes/exec-entry-sink.js.map +1 -0
  325. package/dist/schemes/exec-lifetime.d.ts.map +1 -1
  326. package/dist/schemes/exec-lifetime.js +5 -2
  327. package/dist/schemes/exec-lifetime.js.map +1 -1
  328. package/dist/server/ClientOperations.d.ts +45 -0
  329. package/dist/server/ClientOperations.d.ts.map +1 -0
  330. package/dist/server/ClientOperations.js +192 -0
  331. package/dist/server/ClientOperations.js.map +1 -0
  332. package/dist/server/ClientReads.d.ts +13 -5
  333. package/dist/server/ClientReads.d.ts.map +1 -1
  334. package/dist/server/ClientReads.js +108 -21
  335. package/dist/server/ClientReads.js.map +1 -1
  336. package/dist/server/Credentials.js +2 -2
  337. package/dist/server/Credentials.js.map +1 -1
  338. package/dist/server/Daemon.d.ts +40 -28
  339. package/dist/server/Daemon.d.ts.map +1 -1
  340. package/dist/server/Daemon.js +255 -839
  341. package/dist/server/Daemon.js.map +1 -1
  342. package/dist/server/DaemonLock.d.ts +2 -0
  343. package/dist/server/DaemonLock.d.ts.map +1 -1
  344. package/dist/server/DaemonLock.js +10 -1
  345. package/dist/server/DaemonLock.js.map +1 -1
  346. package/dist/server/DaemonModules.d.ts +39 -0
  347. package/dist/server/DaemonModules.d.ts.map +1 -0
  348. package/dist/server/DaemonModules.js +219 -0
  349. package/dist/server/DaemonModules.js.map +1 -0
  350. package/dist/server/DrainSupervisor.d.ts +4 -2
  351. package/dist/server/DrainSupervisor.d.ts.map +1 -1
  352. package/dist/server/DrainSupervisor.js +318 -290
  353. package/dist/server/DrainSupervisor.js.map +1 -1
  354. package/dist/server/EnvFunctionality.d.ts +10 -7
  355. package/dist/server/EnvFunctionality.d.ts.map +1 -1
  356. package/dist/server/EnvFunctionality.js +6 -5
  357. package/dist/server/EnvFunctionality.js.map +1 -1
  358. package/dist/server/Functionality.d.ts +8 -6
  359. package/dist/server/Functionality.d.ts.map +1 -1
  360. package/dist/server/Functionality.js +110 -32
  361. package/dist/server/Functionality.js.map +1 -1
  362. package/dist/server/FunctionalityManager.d.ts.map +1 -1
  363. package/dist/server/FunctionalityManager.js +16 -2
  364. package/dist/server/FunctionalityManager.js.map +1 -1
  365. package/dist/server/HttpListener.d.ts.map +1 -1
  366. package/dist/server/HttpListener.js +28 -8
  367. package/dist/server/HttpListener.js.map +1 -1
  368. package/dist/server/MembersFunctionality.d.ts +19 -7
  369. package/dist/server/MembersFunctionality.d.ts.map +1 -1
  370. package/dist/server/MembersFunctionality.js +30 -16
  371. package/dist/server/MembersFunctionality.js.map +1 -1
  372. package/dist/server/MessageDelivery.d.ts +43 -0
  373. package/dist/server/MessageDelivery.d.ts.map +1 -0
  374. package/dist/server/MessageDelivery.js +186 -0
  375. package/dist/server/MessageDelivery.js.map +1 -0
  376. package/dist/server/ModuleHost.d.ts +3 -3
  377. package/dist/server/ModuleHost.d.ts.map +1 -1
  378. package/dist/server/Places.d.ts +19 -0
  379. package/dist/server/Places.d.ts.map +1 -0
  380. package/dist/server/Places.js +76 -0
  381. package/dist/server/Places.js.map +1 -0
  382. package/dist/server/PluginSources.d.ts.map +1 -1
  383. package/dist/server/PluginSources.js +2 -1
  384. package/dist/server/PluginSources.js.map +1 -1
  385. package/dist/server/PlurnkSkill.d.ts.map +1 -1
  386. package/dist/server/PlurnkSkill.js +5 -4
  387. package/dist/server/PlurnkSkill.js.map +1 -1
  388. package/dist/server/Rollback.d.ts.map +1 -1
  389. package/dist/server/Rollback.js +2 -1
  390. package/dist/server/Rollback.js.map +1 -1
  391. package/dist/server/WorkerModels.d.ts +87 -0
  392. package/dist/server/WorkerModels.d.ts.map +1 -0
  393. package/dist/server/WorkerModels.js +540 -0
  394. package/dist/server/WorkerModels.js.map +1 -0
  395. package/dist/server/WorkerOverview.d.ts +22 -0
  396. package/dist/server/WorkerOverview.d.ts.map +1 -0
  397. package/dist/server/WorkerOverview.js +42 -0
  398. package/dist/server/WorkerOverview.js.map +1 -0
  399. package/dist/server/WorkspacePlugins.d.ts +1 -1
  400. package/dist/server/WorkspacePlugins.d.ts.map +1 -1
  401. package/dist/server/WorkspacePlugins.js +0 -1
  402. package/dist/server/WorkspacePlugins.js.map +1 -1
  403. package/dist/server/WorkspaceResidency.d.ts +3 -3
  404. package/dist/server/WorkspaceResidency.d.ts.map +1 -1
  405. package/dist/server/WorkspaceResidency.js.map +1 -1
  406. package/dist/server/client-input.d.ts +2 -2
  407. package/dist/server/client-input.d.ts.map +1 -1
  408. package/dist/server/client-input.js +19 -12
  409. package/dist/server/client-input.js.map +1 -1
  410. package/dist/server/envelope.d.ts +4 -1
  411. package/dist/server/envelope.d.ts.map +1 -1
  412. package/dist/server/envelope.js +7 -3
  413. package/dist/server/envelope.js.map +1 -1
  414. package/dist/server/envelope.sql +10 -6
  415. package/dist/server/logEntry.d.ts +38 -1
  416. package/dist/server/logEntry.d.ts.map +1 -1
  417. package/dist/server/logEntry.js +9 -9
  418. package/dist/server/logEntry.js.map +1 -1
  419. package/dist/server/logEntry.sql +4 -2
  420. package/dist/server/loopDocs.d.ts.map +1 -1
  421. package/dist/server/loopDocs.js +11 -2
  422. package/dist/server/loopDocs.js.map +1 -1
  423. package/dist/server/module-discovery.d.ts +2 -3
  424. package/dist/server/module-discovery.d.ts.map +1 -1
  425. package/dist/server/module-discovery.js +6 -6
  426. package/dist/server/module-discovery.js.map +1 -1
  427. package/dist/server/places.sql +83 -0
  428. package/dist/server/seam-loop.sql +8 -0
  429. package/dist/server/worker-overview.sql +57 -0
  430. package/dist/service.d.ts +1 -0
  431. package/dist/service.d.ts.map +1 -1
  432. package/dist/service.js +35 -9
  433. package/dist/service.js.map +1 -1
  434. package/docs/members.md +11 -21
  435. package/migrations/033_hidden_places.sql +10 -0
  436. package/package.json +52 -61
  437. package/dist/core/ToolInputSchema.d.ts +0 -7
  438. package/dist/core/ToolInputSchema.d.ts.map +0 -1
  439. package/dist/core/ToolInputSchema.js +0 -65
  440. package/dist/core/ToolInputSchema.js.map +0 -1
  441. package/dist/observe/init.d.ts +0 -8
  442. package/dist/observe/init.d.ts.map +0 -1
  443. package/dist/observe/init.js +0 -163
  444. package/dist/observe/init.js.map +0 -1
  445. package/dist/server/WorkerModelResolver.d.ts +0 -20
  446. package/dist/server/WorkerModelResolver.d.ts.map +0 -1
  447. package/dist/server/WorkerModelResolver.js +0 -200
  448. package/dist/server/WorkerModelResolver.js.map +0 -1
@@ -4,81 +4,62 @@ import { readFile } from "node:fs/promises";
4
4
  import { resolve, dirname } from "node:path";
5
5
  import { Share } from "@plurnk/plurnk-digest";
6
6
  import EvidenceReader from "@plurnk/plurnk-service/evidence";
7
- import ChannelWrite, {} from "../core/ChannelWrite.js";
8
7
  import Paths from "../Paths.js";
9
- import Engine from "../core/Engine.js";
8
+ import Engine, {} from "../core/Engine.js";
10
9
  import ExecutorRegistry from "../core/ExecutorRegistry.js";
11
10
  import Exec from "../schemes/Exec.js";
12
11
  import SchemeRegistry from "../core/SchemeRegistry.js";
13
12
  import { Mimetypes } from "@plurnk/plurnk-mimetypes";
14
13
  import { RuntimeDeclaration } from "@plurnk/plurnk-execs";
15
- import { projectModelRoute, routeForSpec, specForRoute } from "./model-route.js";
16
- import { discoverDaemonModules } from "./module-discovery.js";
17
14
  import EffectPolicy from "../schemes/EffectPolicy.js";
18
15
  import QuestionTool, { questionRuntimeDecl } from "../schemes/QuestionTool.js";
19
16
  import DrainSupervisor, {} from "./DrainSupervisor.js";
20
17
  import MessageAbandonment from "./MessageAbandonment.js";
21
18
  import Retention, { retentionPolicy } from "./Retention.js";
22
19
  import TurnDispositionHandler from "../core/TurnDispositionHandler.js";
23
- import { Validator } from "@plurnk/plurnk-contracts";
20
+ import { Validator, isRecord, isSafeInteger } from "@plurnk/plurnk-contracts";
24
21
  import LogEntry from "./logEntry.js";
25
- import HttpListener from "./HttpListener.js";
26
22
  import Credentials from "./Credentials.js";
27
23
  import Envelope, { projectWorkerRow } from "./envelope.js";
28
24
  import WorkerReferences from "./WorkerReferences.js";
25
+ import WorkerOverview from "./WorkerOverview.js";
26
+ import Places, {} from "./Places.js";
29
27
  import ClientInput from "./client-input.js";
30
- import Turn from "../core/Turn.js";
31
28
  import WorkspacePlugins from "./WorkspacePlugins.js";
32
29
  import { agentRootScopes, workspacePaths } from "./AgentRoots.js";
33
30
  import ExecEnv from "../schemes/exec-env.js";
34
31
  import PlurnkSkill from "./PlurnkSkill.js";
35
32
  import ResourceTreeScheme from "../schemes/ResourceTreeScheme.js";
36
33
  import MembersFunctionality, { modelScope, serviceMembers } from "./MembersFunctionality.js";
37
- import Rollback from "./Rollback.js";
38
34
  import FileCreationPolicy from "../core/file-creation-policy.js";
39
35
  import EnvFunctionality, { ENV_OWNER } from "./EnvFunctionality.js";
40
36
  import EnvScheme from "../schemes/Env.js";
41
37
  import HostPaths from "../core/HostPaths.js";
42
38
  import WorkspaceStorage from "./WorkspaceStorage.js";
43
39
  import Fork from "../core/fork.js";
44
- import WorkerControlAddress from "../core/WorkerControlAddress.js";
45
40
  import LoopLifecycle from "../core/LoopLifecycle.js";
46
- import { ConfigurationError, Knob } from "@plurnk/plurnk-meta";
41
+ import { Knob } from "@plurnk/plurnk-meta";
47
42
  import ProposalPolicies from "../core/ProposalPolicies.js";
48
43
  import WorkerOwners from "../core/WorkerOwners.js";
49
44
  import RuntimeWorker from "../core/RuntimeWorker.js";
50
45
  import { contentWeight } from "../core/content-weight.js";
51
46
  import MessageResources from "../core/MessageResources.js";
52
- import ContentUse from "../core/ContentUse.js";
53
- import { parseAliasesFromEnv, resolveActiveRoute } from "@plurnk/plurnk-providers";
47
+ import { resolveActiveRoute } from "@plurnk/plurnk-providers";
54
48
  import ProviderInstantiate from "../core/ProviderInstantiate.js";
55
49
  import Results, { OperationFailureError } from "../core/results.js";
56
50
  import WorkspaceGate from "../core/WorkspaceGate.js";
57
51
  import StopDeadline from "../core/StopDeadline.js";
52
+ import { WorkerNameConflictError } from "../core/WorkerName.js";
58
53
  import WorkspaceResidency from "./WorkspaceResidency.js";
59
54
  import Functionality from "./Functionality.js";
60
55
  import { observed, observedSync } from "../observe/spans.js";
61
- import { listModelCatalog } from "./model-catalog.js";
62
- import { daemonFailure, modelRouteLabel } from "./daemon-results.js";
63
- import WorkerModelResolver from "./WorkerModelResolver.js";
56
+ import { daemonFailure } from "./daemon-results.js";
57
+ import WorkerModels, {} from "./WorkerModels.js";
64
58
  import ClientReads from "./ClientReads.js";
59
+ import MessageDelivery from "./MessageDelivery.js";
60
+ import DaemonModules from "./DaemonModules.js";
61
+ import ClientOperations from "./ClientOperations.js";
65
62
  import ConfigurationDiagnostics from "./ConfigurationDiagnostics.js";
66
- // {§connection-lifecycle} A client operation cancelled mid-way settles its loop with what happened.
67
- const clientOperationCancelled = (reason) => {
68
- const said = typeof reason === "string" ? reason : "cancelled";
69
- return Results.failure("daemon:client", "operation-cancelled", 499, `The client operation was cancelled: ${said}.`, {}, {
70
- reason: said, stage: "client-action", retryable: false,
71
- });
72
- };
73
- const clientActionFailure = (error) => {
74
- if (error instanceof OperationFailureError)
75
- return error.result;
76
- console.error("Client action failed outside its operation result contract:", error);
77
- return Results.failure("daemon:client", "action-threw", 500, "The client action failed outside its operation result contract.", {}, {
78
- stage: "client-action",
79
- retryable: false
80
- });
81
- };
82
63
  export default class Daemon {
83
64
  static validateConfiguration() {
84
65
  Credentials.posture();
@@ -119,17 +100,12 @@ export default class Daemon {
119
100
  #secretMinted;
120
101
  #discoveryCwd;
121
102
  #started = false; // {§module-lifecycle}: one discovery/module boot; no listener
122
- #modules = [];
123
- // {§module-http-mounts} — each claimed prefix's owner, and the claims mounted at start.
124
- #mountClaims = new Map();
125
- #mounted = new Set();
126
- #moduleLifetimes = [];
127
- #moduleActions = new Map();
128
- // {§connection-lifecycle} Each running client operation's loop, its own cancellation scope and its settlement.
129
- #clientLoops = new Map();
130
- // {§worker-lifecycle-total-reap} Every client operation's scope descends from this one, which shutdown aborts:
131
- // an operation still dispatching, and every execution an operation started, ends with the daemon.
132
- #clientOperations = new AbortController();
103
+ // {§module-discovery} — the daemon's modules, composed and discovered, with their mounts, phases and actions.
104
+ #modules;
105
+ // {§connection-lifecycle} — the client operations in flight: their loops, scopes and settlement.
106
+ #clientOperations;
107
+ // {§methods-loop-run} {§machine-processes} — a message's delivery to a worker: the loop it starts or continues.
108
+ #delivery;
133
109
  #residency;
134
110
  #functionality;
135
111
  // {§retention-policy} — the operator's retention, run on a cadence and at shutdown.
@@ -189,12 +165,17 @@ export default class Daemon {
189
165
  workspaceGate: this.#workspaceGate,
190
166
  normalizeRuntime: (registration) => this.#normalizeRuntime(registration)
191
167
  });
168
+ // {§module-discovery} — the modules' owner exists before Core registers its own families below: their
169
+ // actions register through the same seam a module's do.
170
+ this.#modules = new DaemonModules({
171
+ db, http: this.#http, configuration: this.#configuration, residency: this.#residency, workspaceGate: this.#workspaceGate,
172
+ });
192
173
  // {§functionality-coordinator} — the one owner above every family
193
174
  // adapter; Core hosts it, modules register adapters through the seam.
194
175
  this.#functionality = new Functionality({
195
176
  signal: this.#residency.signal,
196
177
  registerModuleAction: (registration) => this.registerModuleAction(registration),
197
- registerWorkspaceCapabilityProvider: (owner, provider) => this.registerWorkspaceCapabilityProvider(owner, provider),
178
+ registerWorkspaceCapabilityProvider: (owner, capabilities) => this.registerWorkspaceCapabilityProvider(owner, capabilities),
198
179
  readWorkspaceModuleState: (workspaceId, owner) => this.readWorkspaceModuleState(workspaceId, owner),
199
180
  replaceWorkspaceCapabilities: (replacement, options) => this.replaceWorkspaceCapabilities(replacement, options),
200
181
  readWorkerModuleState: (workerId, owner) => this.#residency.readWorkerModuleState(workerId, owner),
@@ -226,79 +207,7 @@ export default class Daemon {
226
207
  reasoningEventNotify: (workspaceId, event) => this.notifyReasoningEvent(workspaceId, event),
227
208
  outsideEventNotify: (workspaceId, event) => this.notifyOutsideEvent(workspaceId, event),
228
209
  wakeWorkerNotify: (payload) => this.#drains.notifyWakeWorker(payload),
229
- // worker:// loop-start primitive — spawn/fork/irc deliver through
230
- // Daemon.inject (active sister → fold; idle → enqueue + drain). The
231
- // daemon owns provider + the law-file system prompt; the worker scheme
232
- // handler carries neither. Fire-and-forget: the returned drain runs
233
- // independently (the sister is its own worker). {§machine-processes}
234
- injectWorker: async ({ workspaceId, workerId, sourceLoopId, prompt, spawn, environment, attachments, opener }) => {
235
- await this.#assertModelWorker(workspaceId, workerId);
236
- const sender = await this.#db.drain_message_source.get({ loop_id: sourceLoopId });
237
- if (sender === undefined || sender.workspace_id !== workspaceId) {
238
- throw new Error(`injectWorker: source loop ${sourceLoopId} does not belong to workspace ${workspaceId}`);
239
- }
240
- const source = await this.#messageSource(workspaceId, sourceLoopId, sender.worker_id, workerId);
241
- const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
242
- let providerSpec;
243
- let childProviderSpec;
244
- let effort;
245
- if (spawn !== true) {
246
- // {§worker-model-selection} — the voice door addresses an
247
- // existing worker. Its own durable generation policy is the
248
- // receiver's identity; the daemon default and sender do not
249
- // overwrite it merely because a new loop must be minted.
250
- const targetPolicy = await this.#workerModels.resolveWorkerModel(workerId, undefined);
251
- if (targetPolicy === null) {
252
- throw new Error(`injectWorker: worker ${workerId} has no resolvable model route`);
253
- }
254
- providerSpec = targetPolicy.providerSpec;
255
- effort = targetPolicy.effort;
256
- childProviderSpec = await this.#workerModels.resolveWorkerSpawnModel(workerId, undefined);
257
- }
258
- else {
259
- const parentPolicy = await this.#providerPolicyForLoop(sourceLoopId);
260
- providerSpec = parentPolicy.childProviderSpec ?? parentPolicy.providerSpec;
261
- effort = parentPolicy.effort;
262
- // {§worker-model-selection} — lineage inheritance by value:
263
- // the child begins with the spawning loop's effective spawn
264
- // model and no separate override of its own.
265
- childProviderSpec = null;
266
- // {§worker-effort-source} — inherited by value, chosen by nobody on this worker.
267
- await this.#workerModels.persistGenerationPolicy(workerId, {
268
- model_route_id: await routeForSpec(this.#db, providerSpec),
269
- spawn_model_route_id: null,
270
- effort: effort,
271
- effort_source: "default"
272
- });
273
- }
274
- // {§env-option} — a created worker's explicit overrides replace copied values and enable them.
275
- // Names were admitted at the operation; a refusal here is a defect and throws.
276
- for (const [alias, value] of Object.entries(environment ?? {})) {
277
- await this.#functionality.invoke("env", "replace", { alias, definition: { value } }, { workspaceId, workerId }, "operation");
278
- await this.#functionality.invoke("env", "enable", { alias }, { workspaceId, workerId }, "operation");
279
- }
280
- const { action, loopId, messageId } = await ContentUse.read(this.#db, async () => {
281
- const delivered = await this.#prepareMessage(workspaceId, workerId, prompt, attachments);
282
- return this.inject({
283
- workspaceId,
284
- workerId,
285
- prompt: delivered.body,
286
- evidence: delivered.evidence,
287
- sourceLoopId,
288
- ...(source === undefined ? {} : { source }),
289
- ...(opener === undefined ? {} : { opener }),
290
- providerSpec,
291
- effort,
292
- childProviderSpec,
293
- systemPrompt
294
- });
295
- });
296
- // {§worker-obligations} — the message's durable address, as Open Work names it.
297
- const message = await this.#db.drain_message_path.get({ id: messageId });
298
- if (message === undefined)
299
- throw new Error(`injectWorker: message ${messageId} has no address`);
300
- return { action, loopId, message: message.path };
301
- },
210
+ injectWorker: (args) => this.#delivery.injectWorker(args),
302
211
  withWorkspaceTurn: (workspaceId, workerId, run, signal) => this.#workspaceGate.withTurn(workspaceId, workerId, run, signal),
303
212
  // {§functionality-hotload} — skills and plugins change out of band.
304
213
  // Republish under the workspace turn gate before packet assembly so
@@ -334,54 +243,16 @@ export default class Daemon {
334
243
  completeAbandonedMessages: (workspaceId, workerId, loopId, status) => MessageAbandonment.complete(this.#engine, this.#db, workspaceId, workerId, loopId, status),
335
244
  lifecycle: this.#lifecycle,
336
245
  injectPrompt: (loopId, prompt, openPaths, source, evidence, messageAddress, opener) => this.#engine.injectIntoLoop(loopId, prompt, openPaths, source, evidence, messageAddress, opener),
337
- assertInjectionCompatibility: async ({ workerId, loopId, providerSpec, providerSpecExplicit, effort, childProviderSpec, turnCeiling }) => {
338
- // An omitted selector keeps the loop's durable provider; only an
339
- // explicit selection is checked against it (a deliberate switch).
340
- if (providerSpecExplicit !== false) {
341
- await this.#assertLoopProvider(loopId, providerSpec);
342
- }
343
- await this.#assertLoopEffort(loopId, effort);
344
- if (childProviderSpec !== undefined) {
345
- await this.#assertLoopChildProvider(loopId, childProviderSpec);
346
- }
347
- await this.#assertLoopMaxTurns(loopId, turnCeiling?.source === "explicit" ? turnCeiling.effective : undefined);
348
- },
349
- runLoop: async ({ workspaceId, workerId, loopId, maxTurns, prompt, systemPrompt, signal, onSettled }) => {
350
- // The drain callback is the final provider/model boundary. Every
351
- // admission path, including boot recovery, terminates here.
352
- await this.#assertModelWorker(workspaceId, workerId);
353
- const releaseCapabilities = await this.#residency.acquire(workspaceId);
354
- try {
355
- // Worker settings can change while Functionality remains
356
- // resident. Reconcile the worker-private discovery surface
357
- // at the same pre-inference boundary used for activation.
358
- await this.#residency.reconcile(workspaceId);
359
- const { provider, childProvider } = await this.#providersForLoop(loopId);
360
- return await this.#engine.runLoop({
361
- provider,
362
- childProvider,
363
- workspaceId,
364
- workerId,
365
- loopId,
366
- maxTurns,
367
- messages: [
368
- { role: "system", content: systemPrompt },
369
- { role: "user", content: prompt },
370
- ],
371
- signal,
372
- onSettled
373
- });
374
- }
375
- finally {
376
- releaseCapabilities();
377
- }
378
- },
246
+ assertInjectionCompatibility: (args) => this.#workerModels.assertInjectionCompatibility(args),
247
+ runLoop: (args) => this.#runDrainLoop(args),
379
248
  loopUsage: (loopId) => this.#engine.loopUsage(loopId),
380
249
  loopAttributions: (loopId) => this.#engine.loopAttributions(loopId),
381
250
  discardLoopNotices: (loopId) => this.#engine.discardLoopNotices(loopId),
382
251
  cancelSubscription: (subscriptionId) => this.#engine.cancelSubscription(subscriptionId),
252
+ subscriptionConcluded: (subscriptionId) => this.#engine.subscriptionConcluded(subscriptionId),
253
+ reapLoopStreams: (loopId) => this.#engine.reapLoopStreams(loopId),
383
254
  hasActiveStreams: (workerId) => this.#workerHasActiveStreams(workerId),
384
- readSystemPrompt: () => readFile(Paths.instructionsSystem, "utf8"),
255
+ readSystemPrompt: () => readFile(Paths.instructionsSystem, "utf8"), // {§card-read-per-wake}
385
256
  emitLogEntry: async (workspaceId, logEntryId) => {
386
257
  const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
387
258
  this.#broadcast({ workspaceId }, "log/entry", { entry });
@@ -399,8 +270,58 @@ export default class Daemon {
399
270
  const { workspaceId, ...interaction } = event;
400
271
  this.#broadcast({ workspaceId }, "loop/interaction", interaction);
401
272
  });
402
- this.#workerModels = new WorkerModelResolver({ db: this.#db });
273
+ this.#workerModels = new WorkerModels({
274
+ db: this.#db,
275
+ provider: this.#provider,
276
+ policyChanged: (workerId) => this.#announceWorkerPolicy(workerId),
277
+ workerCurationOverflow: (workerId, selectedProvider) => this.#engine.workerCurationOverflow(workerId, selectedProvider),
278
+ });
279
+ this.#delivery = new MessageDelivery({
280
+ db, workerModels: this.#workerModels, functionality: this.#functionality, mimetypes: this.#mimetypes,
281
+ inject: (args) => this.inject(args),
282
+ });
403
283
  this.#reads = new ClientReads({ db: this.#db, engine: this.#engine, workspaceGate: this.#workspaceGate, residency: this.#residency });
284
+ this.#clientOperations = new ClientOperations({
285
+ db, engine: this.#engine, residency: this.#residency, workspaceGate: this.#workspaceGate,
286
+ assertModelWorker: (workspaceId, workerId) => this.#workerModels.assertModelWorker(workspaceId, workerId),
287
+ assertWorkerOwned: (workspaceId, workerId) => this.#assertWorkerOwned(workspaceId, workerId),
288
+ emitLogEntry: async (workspaceId, logEntryId) => {
289
+ const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
290
+ this.#broadcast({ workspaceId }, "log/entry", { entry });
291
+ },
292
+ });
293
+ }
294
+ // The drain callback is the final provider/model boundary. Every admission path, including boot recovery,
295
+ // terminates here.
296
+ async #runDrainLoop({ workspaceId, workerId, loopId, maxTurns, prompt, systemPrompt, signal, onSettled }) {
297
+ // The drain callback is the final provider/model boundary. Every
298
+ // admission path, including boot recovery, terminates here.
299
+ await this.#workerModels.assertModelWorker(workspaceId, workerId);
300
+ const releaseCapabilities = await this.#residency.acquire(workspaceId);
301
+ try {
302
+ // Worker settings can change while Functionality remains
303
+ // resident. Reconcile the worker-private discovery surface
304
+ // at the same pre-inference boundary used for activation.
305
+ await this.#residency.reconcile(workspaceId);
306
+ const { provider: loopProvider, childProvider } = await this.#workerModels.providersForLoop(loopId);
307
+ return await this.#engine.runLoop({
308
+ provider: loopProvider,
309
+ childProvider,
310
+ workspaceId,
311
+ workerId,
312
+ loopId,
313
+ maxTurns,
314
+ messages: [
315
+ { role: "system", content: systemPrompt },
316
+ { role: "user", content: prompt },
317
+ ],
318
+ signal,
319
+ onSettled
320
+ });
321
+ }
322
+ finally {
323
+ releaseCapabilities();
324
+ }
404
325
  }
405
326
  // {§methods-event-subscribe}. A transport module subscribes to the daemon's in-process
406
327
  // event source: it receives every workspace-scoped engine event as `(workspaceId, method, params)`
@@ -455,179 +376,24 @@ export default class Daemon {
455
376
  ClientInput.assertId("resolveClientInteraction", "workspaceId", respondent.workspaceId);
456
377
  await this.#engine.resolveClientInteraction(checkedInteractionId, checkedResolution, respondent, message);
457
378
  }
458
- async #prepareMessage(workspaceId, workerId, body, attachments = [], envelope) {
459
- const published = await MessageResources.publish(body, attachments, {
460
- db: this.#db, workspaceId, workerId, loopId: 0, turnId: 0, writer: "_plurnk", signal: undefined,
461
- weigh: contentWeight, mimetypes: this.#mimetypes,
462
- });
463
- return { body: published.body, evidence: {
464
- ...(envelope === undefined ? {} : { envelope: structuredClone(envelope) }),
465
- ...(published.attachments.length === 0 ? {} : { attachments: published.attachments }),
466
- } };
467
- }
468
379
  async readMessages(args) {
469
- await this.#assertModelWorker(args.workspaceId, args.workerId);
380
+ await this.#workerModels.assertModelWorker(args.workspaceId, args.workerId);
470
381
  return MessageResources.read(this.#db, args);
471
382
  }
383
+ // {§methods-worker-inbox}
384
+ async readInbox(args) {
385
+ await this.#workerModels.assertModelWorker(args.workspaceId, args.workerId);
386
+ return MessageResources.inbox(this.#db, args);
387
+ }
472
388
  // {§message-idempotency}
473
389
  async messageAdmission(args) {
474
390
  const workspaceId = ClientInput.assertId("messageAdmission", "workspaceId", args.workspaceId);
475
391
  return await MessageResources.admission(this.#db, workspaceId, args.address, args.body);
476
392
  }
477
- // {§methods-loop-run} — drive/steer a loop. The module supplies only workspace/worker/prompt;
478
- // the provider and the law-file system prompt are core's and stay inside. Returns immediately — the
479
- // loop runs async and its outcome arrives on the event source (loop/terminated). `cancelDrain` (public)
480
- // is the cancel hook. Both funnel through the unified `inject`, which owns the drain lifecycle.
481
- async runLoop(args) {
482
- const workspaceId = ClientInput.assertId("runLoop", "workspaceId", args.workspaceId);
483
- const workerId = ClientInput.assertId("runLoop", "workerId", args.workerId);
484
- const recipientLoopId = args.loopId === undefined ? undefined : ClientInput.assertId("runLoop", "loopId", args.loopId);
485
- await this.#assertModelWorker(workspaceId, workerId);
486
- const attachments = ClientInput.assertMessageResources("runLoop", args.attachments);
487
- const body = ClientInput.assertPrompt("runLoop", args.prompt, attachments.length > 0);
488
- const source = ClientInput.assertOptionalSource("runLoop", args.source);
489
- const messageAddress = ClientInput.assertOptionalSource("runLoop", args.messageAddress);
490
- const requestedMaxTurns = ClientInput.assertMaxTurns("runLoop", args.maxTurns);
491
- const openPaths = ClientInput.assertOpenPaths("runLoop", args.openPaths);
492
- const selector = ClientInput.assertOptionalSelector("runLoop", "selector", args.selector);
493
- const childSelector = ClientInput.assertOptionalChildSelector("runLoop", args.childSelector);
494
- // {§worker-model-selection} — the worker owns the model. An explicit selector
495
- // persists onto the worker; an omitted selector resolves the worker's durable model
496
- // (seeded once from the daemon default). The loop then snapshots the resolved route.
497
- // A continuation's omitted selector must still keep the loop's durable provider.
498
- const selectorExplicit = selector !== undefined;
499
- const generationPolicy = await this.#workerModels.resolveWorkerModel(workerId, selector);
500
- if (generationPolicy === null) {
501
- throw new OperationFailureError(Results.failure("daemon:provider", "not-configured", 501, "No provider is configured for this worker.", {}, {
502
- stage: "provider-selection",
503
- recovery: "Select a configured model provider.",
504
- retryable: false
505
- }));
506
- }
507
- const { providerSpec: selection, effort } = generationPolicy;
508
- // {§methods-loop-run-child-provider} — the worker's persistent spawn override.
509
- const childSelection = await this.#workerModels.resolveWorkerSpawnModel(workerId, childSelector);
510
- const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
511
- // {§operator-config-max-turns-ceiling} — the operator ceiling clamps a per-call maxTurns; a
512
- // seam caller must not bypass operator policy (inject only DEFAULTS from env, never clamps).
513
- const ceiling = Knob.integer("PLURNK_SERVICE_MAX_TURNS", -1);
514
- const requested = requestedMaxTurns ?? ceiling;
515
- const maxTurns = ceiling < 0 ? requested : (requested < 0 ? ceiling : Math.min(requested, ceiling));
516
- const turnCeiling = {
517
- effective: maxTurns,
518
- source: requestedMaxTurns === undefined ? "implicit" : "explicit"
519
- };
520
- const { action, loopId, turnSeq } = await ContentUse.read(this.#db, async () => {
521
- const delivered = await this.#prepareMessage(workspaceId, workerId, body, attachments, args.envelope);
522
- return this.inject({
523
- workspaceId,
524
- workerId,
525
- prompt: delivered.body,
526
- ...(recipientLoopId === undefined ? {} : { loopId: recipientLoopId }),
527
- evidence: delivered.evidence,
528
- ...(messageAddress === undefined ? {} : { messageAddress }),
529
- ...(source === undefined ? {} : { source }),
530
- ...(openPaths !== undefined ? { openPaths } : {}),
531
- turnCeiling,
532
- providerSpec: selection,
533
- providerSpecExplicit: selectorExplicit,
534
- effort,
535
- childProviderSpec: childSelection,
536
- systemPrompt
537
- });
538
- });
539
- return { status: 100, action, loopId, ...(turnSeq !== undefined ? { turnSeq } : {}) };
540
- }
541
- // {§worker-model-selection} — the loop's durable snapshot is the immutable route ids,
542
- // resolved through model_routes at the claim boundary; never re-resolved through the
543
- // alias cascade (a changed alias declaration must not rewrite history).
544
- async #providerPolicyForLoop(loopId) {
545
- const row = await this.#db.drain_loop_generation_policy.get({ loop_id: loopId });
546
- if (row === undefined)
547
- throw new Error(`loop ${loopId}: provider selection row is missing`);
548
- const providerSpec = await specForRoute(this.#db, row.model_route_id);
549
- if (providerSpec === null) {
550
- throw new Error(`loop ${loopId}: persisted provider selection is missing — refusing boot-default substitution`);
551
- }
552
- if (row.effort === null) {
553
- throw new Error(`loop ${loopId}: persisted effort is missing`);
554
- }
555
- return {
556
- providerSpec,
557
- childProviderSpec: await specForRoute(this.#db, row.spawn_model_route_id),
558
- effort: row.effort
559
- };
560
- }
561
- async #providerSpecForLoop(loopId) {
562
- return (await this.#providerPolicyForLoop(loopId)).providerSpec;
563
- }
564
- async #providersForLoop(loopId) {
565
- const policy = await this.#providerPolicyForLoop(loopId);
566
- const provider = await this.#workerModels.providerForPolicy(policy.providerSpec, policy.effort);
567
- const childProvider = policy.childProviderSpec === null
568
- ? provider
569
- : await this.#workerModels.providerForPolicy(policy.childProviderSpec, policy.effort);
570
- return { provider, childProvider };
571
- }
572
- async #assertLoopProvider(loopId, requested) {
573
- const selected = await this.#providerSpecForLoop(loopId);
574
- if (JSON.stringify(selected) !== JSON.stringify(requested)) {
575
- throw daemonFailure("daemon:provider", "loop-provider-conflict", 409, `Loop ${loopId} uses ${modelRouteLabel(selected)}, not ${modelRouteLabel(requested)}.`, {
576
- loopId,
577
- ...(selected.alias === undefined ? {} : { selectedAlias: selected.alias }),
578
- selectedModel: `${selected.provider}/${selected.model}`,
579
- ...(requested.alias === undefined ? {} : { requestedAlias: requested.alias }),
580
- requestedModel: `${requested.provider}/${requested.model}`,
581
- stage: "loop-injection",
582
- recovery: "Cancel or conclude the loop before selecting another provider.",
583
- retryable: false
584
- });
585
- }
586
- }
587
- async #assertLoopEffort(loopId, requested) {
588
- const selected = (await this.#providerPolicyForLoop(loopId)).effort;
589
- if (selected !== requested) {
590
- throw daemonFailure("daemon:provider", "loop-effort-conflict", 409, `Loop ${loopId} uses effort '${selected}', not '${requested}'.`, {
591
- loopId,
592
- selectedEffort: selected,
593
- requestedEffort: requested,
594
- stage: "loop-injection",
595
- recovery: "Cancel or conclude the loop before selecting another effort.",
596
- retryable: false
597
- });
598
- }
599
- }
600
- async #assertLoopChildProvider(loopId, requested) {
601
- const selected = (await this.#providerPolicyForLoop(loopId)).childProviderSpec;
602
- if (JSON.stringify(selected) !== JSON.stringify(requested)) {
603
- throw daemonFailure("daemon:provider", "loop-child-provider-conflict", 409, `Loop ${loopId} already has a different child provider policy.`, {
604
- loopId,
605
- selectedChildAlias: selected?.alias ?? null,
606
- selectedChildModel: selected === null ? null : `${selected.provider}/${selected.model}`,
607
- requestedChildAlias: requested?.alias ?? null,
608
- requestedChildModel: requested === null ? null : `${requested.provider}/${requested.model}`,
609
- stage: "loop-injection",
610
- recovery: "Cancel or conclude the loop before changing its child provider policy.",
611
- retryable: false
612
- });
613
- }
614
- }
615
- async #assertLoopMaxTurns(loopId, requested) {
616
- if (requested === undefined)
617
- return;
618
- const durable = await this.#db.drain_get_loop_max_turns.get({ loop_id: loopId });
619
- if (durable === undefined)
620
- throw new Error(`inject: loop ${loopId} has no durable turn ceiling`);
621
- if (durable.max_turns !== requested) {
622
- throw daemonFailure("daemon:loop", "turn-ceiling-conflict", 409, `Loop ${loopId} has turn ceiling ${durable.max_turns}, not ${requested}.`, {
623
- loopId,
624
- selectedMaximumTurns: durable.max_turns,
625
- requestedMaximumTurns: requested,
626
- stage: "loop-injection",
627
- recovery: "Cancel or conclude the loop before selecting another turn ceiling.",
628
- retryable: false
629
- });
630
- }
393
+ // {§methods-loop-run} — a message delivered to a worker drives or steers its loop: the delivery's owner resolves
394
+ // the worker's model and the card and injects into the drain; the port's entry point delegates.
395
+ runLoop(args) {
396
+ return this.#delivery.runLoop(args);
631
397
  }
632
398
  async ensureRuntimeWorker(workspaceId) {
633
399
  const checked = ClientInput.assertId("worker.ensure-runtime", "workspaceId", workspaceId);
@@ -645,130 +411,34 @@ export default class Daemon {
645
411
  async setWorkspaceCapabilities(args) {
646
412
  const workspaceId = ClientInput.assertId("workspace.capabilities.set", "workspaceId", args.workspaceId);
647
413
  const settings = JSON.parse(ClientInput.parseSettings({ capabilities: args.policy }));
648
- if (settings.capabilities === undefined)
414
+ const capabilities = isRecord(settings) && settings.capabilities !== undefined ? Validator.assertCapabilityPolicy(settings.capabilities) : undefined;
415
+ if (capabilities === undefined)
649
416
  throw daemonFailure("daemon:input", "capability-policy-required", 400, "policy is required.", { field: "policy", retryable: false });
650
417
  return this.#residency.exclusively(workspaceId, "capabilities", "try", async () => {
651
418
  const row = await this.#db.workspace_capability_policy_update.get({
652
- workspace_id: workspaceId, policy: JSON.stringify(settings.capabilities),
419
+ workspace_id: workspaceId, policy: JSON.stringify(capabilities),
653
420
  });
654
421
  if (row === undefined)
655
422
  throw daemonFailure("daemon:workspace", "workspace-not-found", 404, `Workspace ${workspaceId} does not exist.`, { workspaceId });
656
423
  return await this.#engine.capabilityProjection(workspaceId);
657
424
  });
658
425
  }
659
- // {§worker-model-selection} — project a worker's durable model and spawn override
660
- // for a client-interface get/set surface. Ownership is checked at this boundary.
661
- async readWorkerModel(args) {
662
- const workspaceId = ClientInput.assertId("worker.model.get", "workspaceId", args.workspaceId);
663
- const workerId = ClientInput.assertId("worker.model.get", "workerId", args.workerId);
664
- await this.#assertModelWorker(workspaceId, workerId);
665
- const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
666
- if (row === undefined)
667
- throw new Error(`worker ${workerId}: model route row missing`);
668
- const modelSpec = await specForRoute(this.#db, row.model_route_id);
669
- const spawnModelSpec = await specForRoute(this.#db, row.spawn_model_route_id);
670
- return {
671
- model: modelSpec === null ? null : projectModelRoute(modelSpec, row.effort, row.effort_source),
672
- spawnModel: spawnModelSpec === null ? null : projectModelRoute(spawnModelSpec, row.effort, row.effort_source)
673
- };
426
+ // {§worker-model-selection} {§worker-effort} — the worker's durable model, effort and spawn override are
427
+ // WorkerModels' to read and set; the port's entry points delegate.
428
+ readWorkerModel(args) {
429
+ return this.#workerModels.readWorkerModel(args);
674
430
  }
675
- // {§worker-model-selection} — persist an explicit model selection onto the worker
676
- // and return the resolved spec. An unresolvable selector fails loud here.
677
- async setWorkerModel(args) {
678
- const workspaceId = ClientInput.assertId("worker.model.set", "workspaceId", args.workspaceId);
679
- const workerId = ClientInput.assertId("worker.model.set", "workerId", args.workerId);
680
- await this.#assertModelWorker(workspaceId, workerId);
681
- const selector = ClientInput.assertSelector("worker.model.set", "selector", args.selector);
682
- let chosen;
683
- if (args.effort !== undefined) {
684
- try {
685
- chosen = Validator.assertEffort(args.effort);
686
- }
687
- catch (cause) {
688
- throw daemonFailure("daemon:input", "effort-invalid", 400, cause instanceof Error ? cause.message : "The effort is invalid.", { field: "effort", retryable: false });
689
- }
690
- }
691
- const policy = await this.#workerModels.resolveWorkerModel(workerId, selector, chosen);
692
- if (policy === null) {
693
- throw new OperationFailureError(Results.failure("daemon:provider", "not-configured", 501, "No provider is configured for this worker.", {}, { stage: "provider-selection", recovery: "Select a configured model provider.", retryable: false }));
694
- }
695
- return projectModelRoute(policy.providerSpec, policy.effort, policy.effortSource);
696
- }
697
- // {§worker-effort} — the worker's effort is durable and
698
- // provider-relative. A model-less worker remains explicitly uninitialized.
699
- async readWorkerEffort(args) {
700
- const workspaceId = ClientInput.assertId("worker.effort.get", "workspaceId", args.workspaceId);
701
- const workerId = ClientInput.assertId("worker.effort.get", "workerId", args.workerId);
702
- await this.#assertModelWorker(workspaceId, workerId);
703
- await this.#workerModels.resolveWorkerModel(workerId, undefined);
704
- const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
705
- if (row === undefined)
706
- throw new Error(`worker ${workerId}: generation policy row missing`);
707
- if (row.model_route_id === null) {
708
- if (row.effort !== null) {
709
- throw new Error(`worker ${workerId}: effort exists without a model route`);
710
- }
711
- return { effort: null, source: "default", supportedEfforts: [] };
712
- }
713
- if (row.effort === null) {
714
- throw new Error(`worker ${workerId}: durable model has no effort`);
715
- }
716
- return {
717
- effort: row.effort,
718
- source: row.effort_source,
719
- supportedEfforts: await this.#workerModels.supportedEfforts(row)
720
- };
431
+ setWorkerModel(args) {
432
+ return this.#workerModels.setWorkerModel(args);
721
433
  }
722
- // {§worker-effort} — mutate between loops, validate against both
723
- // the worker model and its optional spawn model, then persist atomically.
724
- async setWorkerEffort(args) {
725
- const workspaceId = ClientInput.assertId("worker.effort.set", "workspaceId", args.workspaceId);
726
- const workerId = ClientInput.assertId("worker.effort.set", "workerId", args.workerId);
727
- let effort;
728
- try {
729
- effort = Validator.assertEffort(args.effort);
730
- }
731
- catch (cause) {
732
- throw daemonFailure("daemon:input", "effort-invalid", 400, cause instanceof Error ? cause.message : "The effort is invalid.", { field: "effort", retryable: false });
733
- }
734
- await this.#assertModelWorker(workspaceId, workerId);
735
- // A model-less worker takes the default first; an existing pair is replaced, not re-validated, so an
736
- // effort its model no longer supports can be corrected ({§worker-effort}).
737
- const current = await this.#db.worker_generation_policy_read.get({ id: workerId });
738
- if (current?.model_route_id === null)
739
- await this.#workerModels.resolveWorkerModel(workerId, undefined);
740
- const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
741
- if (row === undefined)
742
- throw new Error(`worker ${workerId}: generation policy row missing`);
743
- const spec = await specForRoute(this.#db, row.model_route_id);
744
- if (spec === null) {
745
- throw daemonFailure("daemon:provider", "worker-model-unset", 409, `Worker ${workerId} has no model against which to validate an effort.`, {
746
- workerId,
747
- stage: "provider-selection",
748
- recovery: "Select a model before selecting its effort.",
749
- retryable: false
750
- });
751
- }
752
- const supportedEfforts = await this.#workerModels.persistGenerationPolicy(workerId, {
753
- model_route_id: row.model_route_id,
754
- spawn_model_route_id: row.spawn_model_route_id,
755
- effort,
756
- effort_source: "explicit"
757
- });
758
- return { effort, source: "explicit", supportedEfforts };
759
- }
760
- // {§worker-model-selection} — persist the worker's spawn override; a null
761
- // selector means inherit (clears the override).
762
- async setWorkerSpawnModel(args) {
763
- const workspaceId = ClientInput.assertId("worker.child.set", "workspaceId", args.workspaceId);
764
- const workerId = ClientInput.assertId("worker.child.set", "workerId", args.workerId);
765
- await this.#assertModelWorker(workspaceId, workerId);
766
- const selector = ClientInput.assertChildSelector("worker.child.set", args.selector);
767
- const spec = await this.#workerModels.resolveWorkerSpawnModel(workerId, selector);
768
- if (spec === null)
769
- return null;
770
- const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
771
- return projectModelRoute(spec, row?.effort ?? null, row?.effort_source ?? "default");
434
+ readWorkerEffort(args) {
435
+ return this.#workerModels.readWorkerEffort(args);
436
+ }
437
+ setWorkerEffort(args) {
438
+ return this.#workerModels.setWorkerEffort(args);
439
+ }
440
+ setWorkerSpawnModel(args) {
441
+ return this.#workerModels.setWorkerSpawnModel(args);
772
442
  }
773
443
  async #assertWorkerOwned(workspaceId, workerId) {
774
444
  const owner = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
@@ -779,46 +449,6 @@ export default class Daemon {
779
449
  throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, { workerId, workspaceId, actualWorkspaceId: owner.workspace_id });
780
450
  }
781
451
  }
782
- // {§machine-processes} — provider inference is an exclusive capability of a
783
- // model worker. The same assertion is used by early client admissions, the final
784
- // drain callback, and the model, reasoning, and spawn-model controls
785
- // ({§worker-model-selection}), so recovery and internal callers cannot bypass it.
786
- async #assertModelWorker(workspaceId, workerId) {
787
- const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
788
- if (worker === undefined) {
789
- throw daemonFailure("daemon:worker", "worker-not-found", 404, `Worker ${workerId} does not exist.`, { workerId });
790
- }
791
- if (worker.workspace_id !== workspaceId) {
792
- throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, {
793
- workerId,
794
- workspaceId,
795
- actualWorkspaceId: worker.workspace_id,
796
- retryable: false
797
- });
798
- }
799
- if (worker.origin !== "model") {
800
- throw daemonFailure("daemon:worker", "model-worker-required", 409, `Worker ${workerId} is not a model worker.`, {
801
- workerId,
802
- actualOrigin: worker.origin,
803
- recovery: "Select or create a model worker.",
804
- retryable: false
805
- });
806
- }
807
- }
808
- // {§actor-boundary-client-operations} A message speaks for its loop's actor: a person's operation for that
809
- // person, as a thread message does; any other loop for its worker.
810
- async #messageSource(workspaceId, sourceLoopId, sourceWorkerId, targetWorkerId) {
811
- const person = await this.#db.envelope_client_operation_person.get({ loop_id: sourceLoopId });
812
- if (person !== undefined)
813
- return person.person;
814
- if (sourceWorkerId === targetWorkerId)
815
- return undefined;
816
- const source = await this.#db.envelope_get_worker_by_id.get({ id: sourceWorkerId });
817
- if (source === undefined || source.workspace_id !== workspaceId) {
818
- throw new Error(`Worker prompt source ${sourceWorkerId} does not belong to workspace ${workspaceId}.`);
819
- }
820
- return WorkerControlAddress.render(source.name);
821
- }
822
452
  // {§functionality-model-mutation} — await every queued Functionality
823
453
  // publication (a Worker's accepted mutation publishes at its turn boundary).
824
454
  async settleFunctionality(workspaceId) {
@@ -829,154 +459,20 @@ export default class Daemon {
829
459
  }
830
460
  // {§connection-lifecycle} A person's client operation: one administrative loop in the workspace's runtime
831
461
  // worker, attributed to its actor, reading `log:///` in the worker that person acts for
832
- // ({§actor-boundary-client-operations}).
833
- async dispatchClientAction(args) {
834
- const workspaceId = ClientInput.assertId("operation.dispatch-batch", "workspaceId", args.workspaceId);
835
- const actor = ClientInput.assertActor("operation.dispatch-batch", args.actor);
836
- const contextWorkerId = args.contextWorkerId === undefined
837
- ? undefined
838
- : ClientInput.assertId("operation.dispatch-batch", "contextWorkerId", args.contextWorkerId);
839
- if (contextWorkerId !== undefined)
840
- await this.#assertModelWorker(workspaceId, contextWorkerId);
841
- const { statements } = args;
842
- if (statements.length === 0)
843
- return [];
844
- return await this.#clientOperation({ workspaceId, actor, contextWorkerId, onLoop: args.onLoop }, async (dispatch) => {
845
- const results = [];
846
- for (const statement of statements)
847
- results.push(await dispatch(statement));
848
- return results;
849
- });
462
+ // ({§actor-boundary-client-operations}). The operations' owner runs it; the port's entry points delegate.
463
+ dispatchClientAction(args) {
464
+ return this.#clientOperations.dispatchClientAction(args);
850
465
  }
851
- // {§connection-lifecycle} One client operation: the runtime worker's administrative loop with its own
852
- // cancellation scope, settled when the operation ends. Nothing else in the runtime worker shares the scope.
853
- async #clientOperation(operation, body) {
854
- const { workspaceId, actor, contextWorkerId } = operation;
855
- const workerId = await RuntimeWorker.ensure(this.#db, workspaceId);
856
- const releaseCapabilities = await this.#residency.acquire(workspaceId);
857
- try {
858
- const loopId = await Envelope.ensureClientLoop(this.#db, workerId, actor);
859
- const scope = new AbortController();
860
- const signal = AbortSignal.any([scope.signal, this.#clientOperations.signal]);
861
- const settled = Promise.withResolvers();
862
- this.#clientLoops.set(loopId, { workspaceId, scope, settled: settled.promise });
863
- const unbind = this.#engine.bindLoopSignal(loopId, signal);
864
- try {
865
- operation.onLoop?.({ workerId, loopId });
866
- const result = await body(async (statement) => {
867
- signal.throwIfAborted();
868
- return await this.#dispatchClientStatement({
869
- workspaceId, workerId, loopId, statement,
870
- contextWorkerId: contextWorkerId ?? null,
871
- });
872
- });
873
- signal.throwIfAborted();
874
- await Envelope.closeClientLoop(this.#db, loopId, { status: 200 });
875
- return result;
876
- }
877
- catch (error) {
878
- // A cancelled operation settles cancelled, however its statement in flight ended.
879
- const failure = signal.aborted ? new OperationFailureError(clientOperationCancelled(signal.reason)) : error;
880
- await Envelope.closeClientLoop(this.#db, loopId, clientActionFailure(failure));
881
- throw failure;
882
- }
883
- finally {
884
- unbind();
885
- this.#clientLoops.delete(loopId);
886
- settled.resolve();
887
- }
888
- }
889
- finally {
890
- releaseCapabilities();
891
- }
892
- }
893
- // {§connection-lifecycle} Cancel exactly one client operation: its dispatch while it runs, and every execution its
894
- // rows started that still runs. The rest of the runtime worker runs on.
895
- async cancelLoop(args) {
896
- const workspaceId = ClientInput.assertId("operation.cancel", "workspaceId", args.workspaceId);
897
- const loopId = ClientInput.assertId("operation.cancel", "loopId", args.loopId);
898
- const operation = this.#clientLoops.get(loopId);
899
- const dispatching = operation !== undefined && operation.workspaceId === workspaceId && !operation.scope.signal.aborted;
900
- if (dispatching)
901
- operation.scope.abort(args.reason ?? "user_cancelled");
902
- const executions = await ChannelWrite.findOpenSubscriptionsForClientLoop(this.#db, { workspaceId, loopId });
903
- const reaped = await Promise.all(executions.map((subscriptionId) => this.#engine.cancelSubscription(subscriptionId)));
904
- return dispatching || reaped.includes(true);
905
- }
906
- async #dispatchClientStatement(args) {
907
- const { workspaceId, workerId, loopId, statement, contextWorkerId } = args;
908
- return this.#workspaceGate.withTurn(workspaceId, workerId, async () => {
909
- await this.#residency.reconcile(workspaceId);
910
- const { id: turnId } = await Turn.open(this.#db, {
911
- loopId,
912
- producer: "client",
913
- kind: "operation"
914
- });
915
- let turnOpen = true;
916
- try {
917
- const entryIds = [];
918
- const result = await this.#engine.dispatch({
919
- statement, workspaceId, workerId, loopId, turnId, sequence: 1,
920
- origin: "client", client: { contextWorkerId },
921
- onDispatch: (logEntryId) => { entryIds.push(logEntryId); }
922
- });
923
- await Turn.complete(this.#db, turnId, result.status);
924
- turnOpen = false;
925
- for (const logEntryId of entryIds) {
926
- const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
927
- this.#broadcast({ workspaceId }, "log/entry", { entry });
928
- }
929
- return result;
930
- }
931
- catch (cause) {
932
- if (turnOpen) {
933
- try {
934
- await Turn.complete(this.#db, turnId, 500);
935
- }
936
- catch (completionCause) {
937
- throw new AggregateError([cause, completionCause], `client operation turn ${turnId} failed and could not be completed`);
938
- }
939
- }
940
- throw cause;
941
- }
942
- }, this.#clientLoops.get(loopId)?.scope.signal);
466
+ cancelLoop(args) {
467
+ return this.#clientOperations.cancelLoop(args);
943
468
  }
944
469
  // {§methods} — the module's render surface beyond the journal. Thin delegations
945
470
  // into core's envelope / membership / provider machinery; the module fans the results into its own views.
946
471
  listProviders() {
947
- const active = process.env.PLURNK_MODEL?.toLowerCase();
948
- let aliases;
949
- try {
950
- aliases = parseAliasesFromEnv();
951
- }
952
- catch (cause) {
953
- if (!(cause instanceof ConfigurationError))
954
- throw cause;
955
- throw new OperationFailureError(Results.configurationFailure(cause), { cause });
956
- }
957
- return {
958
- aliases: aliases.map((a) => {
959
- const isActive = active === a.alias;
960
- // {§agui-provider-routes} — the route a worker seeded from this alias gets: what it would read back.
961
- let effort;
962
- try {
963
- effort = ProviderInstantiate.configuredEffort(a);
964
- }
965
- catch (cause) {
966
- const invalid = new ConfigurationError("PLURNK_PROVIDERS_EFFORT", cause instanceof Error ? cause.message : String(cause), { cause });
967
- throw new OperationFailureError(Results.configurationFailure(invalid), { cause });
968
- }
969
- return {
970
- ...projectModelRoute(a, effort, "default"), alias: a.alias, active: isActive,
971
- inputCapacity: isActive && this.#provider !== null ? this.#provider.inputCapacity : null
972
- };
973
- })
974
- };
472
+ return this.#workerModels.listProviders();
975
473
  }
976
- // {§model-catalog} The catalog is a bounded, daemon-local projection.
977
- // It performs no provider I/O and never changes the worker's selection.
978
474
  listModels(query) {
979
- return listModelCatalog(query);
475
+ return this.#workerModels.listModels(query);
980
476
  }
981
477
  // {§client-display-capabilities} Core composes the installed family
982
478
  // declarations; interface modules expose this contracts-owned wire without
@@ -1001,8 +497,9 @@ export default class Daemon {
1001
497
  }));
1002
498
  return Validator.assertClientDisplayCapabilities([...schemes, ...mimetypes]);
1003
499
  }
1004
- async listWorkspaces() {
1005
- return (await Envelope.listWorkspaces(this.#db)).map((row) => ({ ...row, project_label: this.#projectLabel(row.project_root) }));
500
+ async listWorkspaces(query = {}) {
501
+ const hidden = query.hidden === undefined ? undefined : ClientInput.assertFlag("workspace.list", "hidden", query.hidden);
502
+ return (await Envelope.listWorkspaces(this.#db, hidden)).map((row) => ({ ...row, project_label: this.#projectLabel(row.project_root) }));
1006
503
  }
1007
504
  // {§workspace-label} — the folder as the daemon spells it for display: the spelling a default name takes.
1008
505
  #projectLabel(projectRoot) {
@@ -1011,21 +508,23 @@ export default class Daemon {
1011
508
  #labelled(envelope) {
1012
509
  return { ...envelope, projectLabel: this.#projectLabel(envelope.projectRoot) };
1013
510
  }
1014
- listWorkers(workspaceId, query = {}) {
511
+ async listWorkers(workspaceId, query = {}) {
1015
512
  const origin = query.origin;
1016
513
  if (origin !== undefined && !["model", "client", "_plurnk"].includes(origin)) {
1017
- throw daemonFailure("daemon:worker", "origin-invalid", 400, `Worker origin '${String(origin)}' is invalid.`, { origin, retryable: false });
514
+ throw daemonFailure("daemon:worker", "origin-invalid", 400, `Worker origin '${origin}' is invalid.`, { origin, retryable: false });
1018
515
  }
1019
516
  const parentWorkerId = query.parentWorkerId === undefined
1020
517
  ? undefined
1021
518
  : query.parentWorkerId === null
1022
519
  ? null
1023
520
  : ClientInput.assertId("workspace.workers", "parentWorkerId", query.parentWorkerId);
521
+ const hidden = query.hidden === undefined ? undefined : ClientInput.assertFlag("workspace.workers", "hidden", query.hidden);
1024
522
  // {§methods-worker-list} — an omitted parent filter must stay absent:
1025
523
  // the envelope keys the filter on the property's presence, not its value.
1026
- return Envelope.listWorkersForWorkspace(this.#db, ClientInput.assertId("workspace.workers", "workspaceId", workspaceId), {
524
+ return await Envelope.listWorkersForWorkspace(this.#db, ClientInput.assertId("workspace.workers", "workspaceId", workspaceId), {
1027
525
  ...(origin === undefined ? {} : { origin }),
1028
- ...(parentWorkerId === undefined ? {} : { parentWorkerId })
526
+ ...(parentWorkerId === undefined ? {} : { parentWorkerId }),
527
+ ...(hidden === undefined ? {} : { hidden })
1029
528
  });
1030
529
  }
1031
530
  async readWorker(args) {
@@ -1057,6 +556,16 @@ export default class Daemon {
1057
556
  await this.#assertWorkerOwned(workspaceId, workerId);
1058
557
  return this.#engine.workerAccounting(workerId);
1059
558
  }
559
+ // {§methods-worker-gauge}
560
+ async workerGauge(args) {
561
+ const workspaceId = ClientInput.assertId("worker.gauge", "workspaceId", args.workspaceId);
562
+ const workerId = ClientInput.assertId("worker.gauge", "workerId", args.workerId);
563
+ await this.#assertWorkerOwned(workspaceId, workerId);
564
+ const latest = await this.#db.application_latest_packet_loop.get({ worker_id: workerId });
565
+ if (latest === undefined)
566
+ return { curationWeight: null, curationBudget: null, contextTokens: null, contextCapacity: null };
567
+ return Engine.clientGauge(await this.#engine.loopGauge(latest.id));
568
+ }
1060
569
  async listWorkerLoops(args) {
1061
570
  const workspaceId = ClientInput.assertId("worker.loops", "workspaceId", args.workspaceId);
1062
571
  const workerId = ClientInput.assertId("worker.loops", "workerId", args.workerId);
@@ -1075,9 +584,25 @@ export default class Daemon {
1075
584
  waitUntil: row.waitUntil,
1076
585
  invocation: row.invocation === null || row.invocationWorkerId === null ? null : { workerId: row.invocationWorkerId, address: row.invocation },
1077
586
  // {§validation-topology}: a stored result is chapter 3's (`loops_terminal_result_contract`); it is parsed, not re-asserted.
1078
- terminalResult: row.terminalResult === null ? null : JSON.parse(row.terminalResult)
587
+ terminalResult: row.terminalResult === null ? null : Results.fromRow(row.terminalResult)
1079
588
  }));
1080
589
  }
590
+ // {§methods-worker-overview} The places of the daemon, or of one workspace, with what waits there.
591
+ async readWorkerOverview(args) {
592
+ const workspaceId = args.workspaceId === undefined ? null : ClientInput.assertId("worker.overview", "workspaceId", args.workspaceId);
593
+ const { limit, excerptChars } = args;
594
+ if (!isSafeInteger(limit) || limit < 1 || !isSafeInteger(excerptChars) || excerptChars < 1) {
595
+ throw new TypeError("readWorkerOverview takes a positive limit and excerpt length.");
596
+ }
597
+ if (workspaceId !== null && await this.#db.envelope_get_workspace.get({ id: workspaceId }) === undefined) {
598
+ throw daemonFailure("daemon:workspace", "workspace-not-found", 404, `Workspace ${workspaceId} does not exist.`, { workspaceId });
599
+ }
600
+ return await WorkerOverview.read(this.#db, {
601
+ workspaceId, limit, excerptChars, projectLabel: (projectRoot) => this.#projectLabel(projectRoot),
602
+ // {§agui-status-descendants} The gates the status tree counts: the pending proposals and client interactions.
603
+ pending: async (id) => [...await this.#engine.pendingProposals(id), ...await this.#engine.pendingClientInteractions(id)],
604
+ });
605
+ }
1081
606
  // {§methods-workspace-prompts}: root-worker loop seeds, newest-first.
1082
607
  // {§share} — one workspace's share. The daemon names its own database; the caller names an absolute folder.
1083
608
  async shareWorkspace({ workspaceId, folder }) {
@@ -1124,7 +649,12 @@ export default class Daemon {
1124
649
  if (derived === undefined)
1125
650
  return undefined;
1126
651
  const existing = await this.#db.envelope_get_workspace_by_name.get({ name: derived });
1127
- return existing === undefined ? undefined : this.#labelled(await Envelope.attachToWorkspace(this.#db, existing.id));
652
+ if (existing === undefined)
653
+ return undefined;
654
+ // {§place-hiding} Creating by the folder selects a hidden workspace and shows it.
655
+ if (existing.hidden_at !== null)
656
+ await this.setWorkspaceHidden({ workspaceId: existing.id, hidden: false });
657
+ return this.#labelled(await Envelope.attachToWorkspace(this.#db, existing.id));
1128
658
  };
1129
659
  return observed(// {§observability-boundary}
1130
660
  "workspace.create", {}, async (span) => {
@@ -1185,29 +715,46 @@ export default class Daemon {
1185
715
  await this.#assertWorkerOwned(workspaceId, workerId);
1186
716
  return (await this.#db.worker_reference_list.all({ namespace, worker_id: workerId })).map(({ reference }) => reference);
1187
717
  }
1188
- // {§rollback} {§methods-workspace-rollback} — the compensations are one client operation of the acting person
1189
- // ({§connection-lifecycle}): attributed to that person and, being that person's decision, never routed for review.
1190
- async rollback(args) {
1191
- const workspaceId = ClientInput.assertId("workspace.rollback", "workspaceId", args.workspaceId);
1192
- const workerId = ClientInput.assertId("workspace.rollback", "workerId", args.workerId);
1193
- const actor = ClientInput.assertActor("workspace.rollback", args.actor);
1194
- if (typeof args.to !== "string")
1195
- throw daemonFailure("daemon:rollback", "coordinate-invalid", 400, "The rollback coordinate must be a string.", {});
1196
- await this.#assertWorkerOwned(workspaceId, workerId);
1197
- const report = await this.#clientOperation({ workspaceId, actor, contextWorkerId: workerId }, (dispatch) => Rollback.run({
1198
- db: this.#db,
1199
- projectRoot: async (id) => (await this.#db.envelope_get_workspace.get({ id }))?.project_root ?? null,
1200
- executorTags: (id) => this.#engine.executorTags(id),
1201
- apply: async (statement) => await dispatch(statement),
1202
- }, { workspaceId, workerId, to: args.to }));
1203
- return Validator.assertRollbackReport(report);
718
+ rollback(args) {
719
+ return this.#clientOperations.rollback(args);
1204
720
  }
1205
721
  async renameWorkspace(workspaceId, name) {
1206
722
  const checkedWorkspaceId = ClientInput.assertId("workspace.rename", "workspaceId", workspaceId);
1207
723
  const checkedName = ClientInput.assertOptionalWorkspaceName("workspace.rename", "name", name);
1208
724
  if (checkedName === undefined)
1209
725
  throw new Error("ClientInput.assertOptionalWorkspaceName accepted a required name as undefined");
1210
- return { id: checkedWorkspaceId, name: await Envelope.updateWorkspaceName(this.#db, checkedWorkspaceId, checkedName) };
726
+ const renamed = { id: checkedWorkspaceId, name: await Envelope.updateWorkspaceName(this.#db, checkedWorkspaceId, checkedName) };
727
+ // {§notifications-workspace-renamed}
728
+ this.#broadcast({ workspaceId: checkedWorkspaceId }, "workspace/renamed", renamed);
729
+ return renamed;
730
+ }
731
+ // {§methods-workspace-hide} {§place-hiding}
732
+ async setWorkspaceHidden(args) {
733
+ const workspaceId = ClientInput.assertId("workspace.hidden", "workspaceId", args.workspaceId);
734
+ const hidden = ClientInput.assertFlag("workspace.hidden", "hidden", args.hidden);
735
+ const { hiding, changed } = await Places.setWorkspaceHidden(this.#db, (id) => this.#placeGates(id), workspaceId, hidden);
736
+ // {§notifications-workspace-hidden}
737
+ if (changed)
738
+ this.#broadcast({ workspaceId }, "workspace/hidden", hiding);
739
+ return hiding;
740
+ }
741
+ // {§methods-worker-hide} {§place-hiding}
742
+ async setWorkerHidden(args) {
743
+ const workspaceId = ClientInput.assertId("worker.hidden", "workspaceId", args.workspaceId);
744
+ const workerId = ClientInput.assertId("worker.hidden", "workerId", args.workerId);
745
+ const hidden = ClientInput.assertFlag("worker.hidden", "hidden", args.hidden);
746
+ const hiding = await Places.setWorkerHidden(this.#db, (id) => this.#placeGates(id), workspaceId, workerId, hidden);
747
+ // {§notifications-worker-hidden}
748
+ if (hiding.workerIds.length > 0)
749
+ this.#broadcast({ workspaceId }, "worker/hidden", hiding);
750
+ return hiding;
751
+ }
752
+ // The gates a hide is refused over, as the overview counts them.
753
+ async #placeGates(workspaceId) {
754
+ return {
755
+ proposals: await this.#engine.pendingProposals(workspaceId),
756
+ questions: await this.#engine.pendingClientInteractions(workspaceId),
757
+ };
1211
758
  }
1212
759
  // {§methods-worker-create}: a fresh worker is model-origin, with an empty private log.
1213
760
  // {§machine-processes} Workers have independent histories within a shared workspace.
@@ -1229,15 +776,22 @@ export default class Daemon {
1229
776
  else if (args.owner !== undefined) {
1230
777
  await WorkerOwners.registered(this.#db, workspaceId, args.owner);
1231
778
  }
1232
- if (name !== undefined) {
1233
- const taken = await this.#db.envelope_get_worker_by_name.get({ workspace_id: workspaceId, name });
1234
- if (taken !== undefined) {
1235
- throw daemonFailure("daemon:worker", "name-conflict", 409, `Worker name '${name}' is already in use in workspace ${workspaceId} by a ${taken.origin} worker.`, { workspaceId, name, actualOrigin: taken.origin, recovery: "Choose another worker name.", retryable: false });
1236
- }
779
+ let worker;
780
+ try {
781
+ worker = await Envelope.createModelWorker(this.#db, workspaceId, name, parentWorkerId, args.owner);
782
+ }
783
+ catch (error) {
784
+ if (name === undefined || !(error instanceof WorkerNameConflictError))
785
+ throw error;
786
+ throw await this.#workerNameConflict(workspaceId, name);
1237
787
  }
1238
- const worker = await Envelope.createModelWorker(this.#db, workspaceId, name, parentWorkerId, args.owner);
1239
788
  return { workerId: worker.id, workerName: worker.name };
1240
789
  }
790
+ // {§methods-worker-create} The claim decides a name, so a request that loses it to another, at any moment, is refused alike.
791
+ async #workerNameConflict(workspaceId, name) {
792
+ const taken = await this.#db.envelope_get_worker_by_name.get({ workspace_id: workspaceId, name });
793
+ return daemonFailure("daemon:worker", "name-conflict", 409, `Worker name '${name}' is already in use in workspace ${workspaceId}${taken === undefined ? "" : ` by a ${taken.origin} worker`}.`, { workspaceId, name, ...(taken === undefined ? {} : { actualOrigin: taken.origin }), recovery: "Choose another worker name.", retryable: false });
794
+ }
1241
795
  // {§worker-scheme-fork} — branch a worker's log while sharing its workspace.
1242
796
  // Core owns the workspace check and immutable worker-name admission.
1243
797
  async forkWorker(args) {
@@ -1245,14 +799,16 @@ export default class Daemon {
1245
799
  const workerId = ClientInput.assertId("run.fork", "workerId", args.workerId);
1246
800
  const name = ClientInput.assertOptionalWorkerName("run.fork", "name", args.name);
1247
801
  // {§machine-processes-worker-origin} A branch is a model's: only a model worker's log forks.
1248
- await this.#assertModelWorker(workspaceId, workerId);
1249
- if (name !== undefined) {
1250
- const taken = await this.#db.envelope_get_worker_by_name.get({ workspace_id: workspaceId, name });
1251
- if (taken !== undefined) {
1252
- throw daemonFailure("daemon:worker", "name-conflict", 409, `Worker name '${name}' is already in use in workspace ${workspaceId} by a ${taken.origin} worker.`, { workspaceId, name, actualOrigin: taken.origin, recovery: "Choose another worker name.", retryable: false });
1253
- }
802
+ await this.#workerModels.assertModelWorker(workspaceId, workerId);
803
+ let branchWorkerId;
804
+ try {
805
+ branchWorkerId = await Fork.fork(this.#db, workerId, name);
806
+ }
807
+ catch (error) {
808
+ if (name === undefined || !(error instanceof WorkerNameConflictError))
809
+ throw error;
810
+ throw await this.#workerNameConflict(workspaceId, name);
1254
811
  }
1255
- const branchWorkerId = await Fork.fork(this.#db, workerId, name);
1256
812
  const branch = await this.#db.envelope_get_worker_by_id.get({ id: branchWorkerId });
1257
813
  return { workerId: branchWorkerId, workerName: branch?.name ?? null, parentWorkerId: workerId };
1258
814
  }
@@ -1295,102 +851,22 @@ export default class Daemon {
1295
851
  scheme
1296
852
  };
1297
853
  }
1298
- // {§http-host} — exterior adapters mount on the daemon's one listener at start(); the
1299
- // daemon never opens a second transport, and a daemon without a listener says so.
854
+ // {§http-host} {§module-http-mounts} — a module mounts on the daemon's one listener exactly what it claimed;
855
+ // the modules' owner keeps the claims. {§module-action-registration}: a module's actions, registered, listed and invoked.
1300
856
  registerHttpRoute(prefix, handler) {
1301
- if (this.#http === null)
1302
- throw new Error(`registerHttpRoute: this daemon has no HTTP listener to mount '${prefix}' on`);
1303
- // {§module-http-mounts} — a module mounts exactly what it claimed.
1304
- if (!this.#mountClaims.has(prefix))
1305
- throw new Error(`registerHttpRoute: no module declared '${prefix}' among its mounts`);
1306
- this.#http.registerHttpRoute(prefix, handler);
1307
- this.#mounted.add(prefix);
857
+ this.#modules.registerHttpRoute(prefix, handler);
1308
858
  }
1309
859
  httpAddress() {
1310
- if (this.#http === null)
1311
- throw new Error("httpAddress: this daemon has no HTTP listener");
1312
- return this.#http.httpAddress();
860
+ return this.#modules.httpAddress();
1313
861
  }
1314
862
  registerModuleAction(registration) {
1315
- const { name, scope, inputSchema, outputSchema, handler } = registration;
1316
- if (name.length === 0)
1317
- throw new Error("registerModuleAction: action name must not be empty");
1318
- if (scope !== "daemon" && scope !== "workspace" && scope !== "worker") {
1319
- throw new Error(`module action '${name}' has invalid scope '${String(scope)}'`);
1320
- }
1321
- if (inputSchema === null || typeof inputSchema !== "object" || Array.isArray(inputSchema)) {
1322
- throw new Error(`module action '${name}' has no input schema`);
1323
- }
1324
- if (outputSchema === null || typeof outputSchema !== "object" || Array.isArray(outputSchema)) {
1325
- throw new Error(`module action '${name}' has no output schema`);
1326
- }
1327
- for (const [role, schema] of [["input", inputSchema], ["output", outputSchema]]) {
1328
- try {
1329
- Validator.validateJsonSchemaInstance(schema, {});
1330
- }
1331
- catch (cause) {
1332
- throw new Error(`module action '${name}' has an invalid ${role} schema`, { cause });
1333
- }
1334
- }
1335
- if (typeof handler !== "function")
1336
- throw new Error(`module action '${name}' has no handler`);
1337
- if (registration.residency !== "required" && registration.residency !== "none") {
1338
- throw new Error(`module action '${name}' must declare its residency requirement`);
1339
- }
1340
- if (scope === "daemon" && registration.residency !== "none") {
1341
- throw new Error(`daemon module action '${name}' cannot require workspace residency`);
1342
- }
1343
- if (this.#moduleActions.has(name))
1344
- throw new Error(`module action '${name}' is already registered`);
1345
- this.#moduleActions.set(name, registration);
863
+ this.#modules.registerAction(registration);
1346
864
  }
1347
865
  listModuleActions() {
1348
- return [...this.#moduleActions.values()]
1349
- .map(({ name, scope, inputSchema, outputSchema }) => ({
1350
- name,
1351
- scope,
1352
- inputSchema,
1353
- outputSchema
1354
- }))
1355
- .toSorted((left, right) => left.name.localeCompare(right.name));
1356
- }
1357
- async invokeModuleAction(name, params, context) {
1358
- const registration = this.#moduleActions.get(name);
1359
- if (registration === undefined)
1360
- throw new Error(`module action '${name}' is not registered`);
1361
- if (registration.scope !== context.scope) {
1362
- throw new Error(`module action '${name}' requires ${registration.scope} context, not ${context.scope}`);
1363
- }
1364
- let releaseCapabilities;
1365
- if (context.scope === "worker") {
1366
- const workspaceId = ClientInput.assertId(`module action '${name}'`, "workspaceId", context.workspaceId);
1367
- const workerId = ClientInput.assertId(`module action '${name}'`, "workerId", context.workerId);
1368
- const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
1369
- if (worker === undefined || worker.workspace_id !== workspaceId) {
1370
- throw daemonFailure("daemon:module-action", "worker-not-found", 404, `Worker ${workerId} does not exist in workspace ${workspaceId}.`, { workspaceId, workerId, retryable: false });
1371
- }
1372
- }
1373
- if (registration.residency === "required" && context.scope !== "daemon") {
1374
- releaseCapabilities = await this.#residency.acquire(context.workspaceId);
1375
- }
1376
- else if (context.scope === "workspace") {
1377
- await this.#residency.identity(context.workspaceId);
1378
- }
1379
- try {
1380
- if (registration.residency === "required" && context.scope === "worker") {
1381
- const release = await this.#workspaceGate.acquireTurn(context.workspaceId, context.workerId);
1382
- try {
1383
- await this.#residency.reconcile(context.workspaceId);
1384
- }
1385
- finally {
1386
- release();
1387
- }
1388
- }
1389
- return await registration.handler(params, context);
1390
- }
1391
- finally {
1392
- releaseCapabilities?.();
1393
- }
866
+ return this.#modules.listActions();
867
+ }
868
+ invokeModuleAction(name, params, context) {
869
+ return this.#modules.invokeAction(name, params, context);
1394
870
  }
1395
871
  registerWorkspaceCapabilityProvider(namespaceOwner, provider) {
1396
872
  this.#residency.registerProvider(namespaceOwner, provider);
@@ -1447,7 +923,7 @@ export default class Daemon {
1447
923
  registerModule(module, owner) {
1448
924
  if (this.#started)
1449
925
  throw new Error("registerModule: modules must be registered before daemon start");
1450
- this.#modules.push({ module, owner });
926
+ this.#modules.register(module, owner);
1451
927
  }
1452
928
  async start() {
1453
929
  if (this.#started)
@@ -1458,7 +934,7 @@ export default class Daemon {
1458
934
  await this.#configuration.capture("wait", () => TurnDispositionHandler.configuredWaitSeconds());
1459
935
  await this.#configuration.capture("execution", () => Exec.validateConfiguration());
1460
936
  this.#retention = await this.#configuration.capture("retention", () => new Retention(this.#db, retentionPolicy()));
1461
- this.#clientOperations = new AbortController();
937
+ this.#clientOperations.start();
1462
938
  this.#started = true;
1463
939
  // {§db-space-reclamation} — the file is brought to the policy's auto-vacuum mode before any work.
1464
940
  if (this.#retention !== null) {
@@ -1485,7 +961,7 @@ export default class Daemon {
1485
961
  this.#engine.setFunctionalityDocuments((workspaceId) => this.#functionality.documents(workspaceId));
1486
962
  // {§question-tool} — the native request-user-input runtime, process-wide;
1487
963
  // workspace admission gates its doc visibility and dispatch ({§workspace-capability-policy}).
1488
- await this.#engine.registerRuntimes([{
964
+ this.#engine.registerRuntimes([{
1489
965
  tag: "question",
1490
966
  entry: {
1491
967
  executor: new QuestionTool({ runtime: "question", glyph: "❓" }),
@@ -1505,44 +981,9 @@ export default class Daemon {
1505
981
  // (agnostic, by plurnk.kind:"scheme"). They light up http://, etc. with
1506
982
  // no further engine change — #run wraps their context in SchemeCtxImpl ({§extension-discovery}).
1507
983
  await this.#schemes.discoverExternal(this.#discoveryCwd);
1508
- // {§module-discovery} — third-party daemon-module composition: trusted
1509
- // packages declaring `plurnk.kind: "module"` register beside the
1510
- // service's explicit composition before any module setup runs.
1511
- const discoveredModules = await discoverDaemonModules({
1512
- cwd: this.#discoveryCwd, hostPaths: this.#hostPaths, registered: new Set(this.#modules.map(({ owner }) => owner)),
1513
- });
1514
- for (const { owner, cause } of discoveredModules.configurationErrors)
1515
- this.#configuration.record(owner, cause);
1516
- this.#configuration.pluginReports(discoveredModules.reports);
1517
- for (const packageName of discoveredModules.skipped) {
1518
- console.warn(`module discovery: '${packageName}' is discovered but untrusted (PLURNK_EXTENSIONS_TRUSTED_ONLY); not registered`);
1519
- }
1520
- for (const { module, owner } of discoveredModules.modules) {
1521
- this.#modules.push({ module, owner });
1522
- }
1523
- // {§module-http-mounts} — a daemon without a listener has nothing to serve a mount on: it
1524
- // leaves out every module that declares one, and says so.
1525
- if (this.#http === null) {
1526
- this.#modules = this.#modules.filter(({ module, owner }) => {
1527
- const mounts = module.mounts ?? [];
1528
- if (mounts.length === 0)
1529
- return true;
1530
- this.#configuration.leftOut(`module:${owner}`, `module '${owner}' mounts ${mounts.map((prefix) => `'${prefix}'`).join(", ")} and this daemon has no HTTP listener: left out`);
1531
- return false;
1532
- });
1533
- }
1534
- // {§module-contained-configuration} — what a module contained is its own configuration diagnostic.
1535
- for (const { module, owner } of this.#modules) {
1536
- for (const { key, message } of module.contained ?? [])
1537
- this.#configuration.record(`module:${owner}`, new ConfigurationError(key, message));
1538
- }
1539
- this.#claimMounts();
1540
- const setupSeam = this;
1541
- for (const { module } of this.#modules) {
1542
- if (module.stop !== undefined || module.close !== undefined)
1543
- this.#moduleLifetimes.push(module);
1544
- await module.setup?.(setupSeam);
1545
- }
984
+ // {§module-discovery} — discovery composes beside the service's explicit modules, then every module sets
985
+ // up against this daemon as the seam ({§module-lifecycle}).
986
+ await this.#modules.setup(this, { cwd: this.#discoveryCwd, hostPaths: this.#hostPaths });
1546
987
  // {§credential-authority} — the posture is never an inference: a listening daemon that
1547
988
  // authenticates needs a module that issues, and it writes a minted secret where same-user
1548
989
  // clients read it before any mount is admitted; a configured or absent secret leaves no file.
@@ -1561,39 +1002,8 @@ export default class Daemon {
1561
1002
  // {§module-lifecycle} — the daemon opened its one transport before admission ({§http-host});
1562
1003
  // modules mount their routes on it here, only after capability publication and durable
1563
1004
  // lifecycle recovery are complete. None opens a listener of its own.
1564
- for (const { module } of this.#modules) {
1565
- const started = await module.start?.(this);
1566
- if (started !== undefined && !this.#moduleLifetimes.includes(started)) {
1567
- this.#moduleLifetimes.push(started);
1568
- }
1569
- }
1570
- this.#admitMounts();
1571
- }
1572
- // {§module-http-mounts} — every prefix is claimed before any module sets up: one owner per
1573
- // prefix, including the optional root mount.
1574
- #claimMounts() {
1575
- for (const { module, owner } of this.#modules) {
1576
- for (const prefix of module.mounts ?? []) {
1577
- if (!HttpListener.isPrefix(prefix)) {
1578
- throw new Error(`module '${owner}' declares HTTP mount '${prefix}', which is not an absolute pathname prefix`);
1579
- }
1580
- const holder = this.#mountClaims.get(prefix);
1581
- if (holder !== undefined)
1582
- throw new Error(`HTTP mount '${prefix}' is claimed by both '${holder}' and '${owner}'`);
1583
- this.#mountClaims.set(prefix, owner);
1584
- }
1585
- }
1586
- }
1587
- // {§module-http-mounts} — the listener is admitted once every module has started and every
1588
- // claim is mounted, so readiness never depends on registration order.
1589
- #admitMounts() {
1590
- if (this.#http === null)
1591
- return;
1592
- for (const [prefix, owner] of this.#mountClaims) {
1593
- if (!this.#mounted.has(prefix))
1594
- throw new Error(`module '${owner}' claimed HTTP mount '${prefix}' but did not mount it at start`);
1595
- }
1596
- this.#http.admit();
1005
+ await this.#modules.start(this);
1006
+ this.#modules.admitMounts();
1597
1007
  }
1598
1008
  async #recoverLifecycle() {
1599
1009
  await this.#db.recovery_settle_process_owners({});
@@ -1605,7 +1015,7 @@ export default class Daemon {
1605
1015
  for (const source of orphanSources) {
1606
1016
  await this.#drains.reconcileOrphanedMessages(source.worker_id, source.loop_id);
1607
1017
  }
1608
- const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
1018
+ const systemPrompt = await readFile(Paths.instructionsSystem, "utf8"); // {§card-read-per-wake}
1609
1019
  const queued = await this.#db.recovery_queued_workers.all({});
1610
1020
  const invalidQueued = queued.find(({ origin }) => origin !== "model");
1611
1021
  if (invalidQueued !== undefined) {
@@ -1644,21 +1054,14 @@ export default class Daemon {
1644
1054
  this.#engine.cancelAllProposals("daemon_stopping");
1645
1055
  this.#engine.cancelDerivations(derivationAbort);
1646
1056
  this.#drains.beginStop("daemon_stopping");
1647
- this.#clientOperations.abort("daemon_stopping");
1057
+ this.#clientOperations.beginStop("daemon_stopping");
1648
1058
  this.#residency.beginStop();
1649
1059
  const shutdown = deadline ?? new StopDeadline();
1650
1060
  // Stop accepting external work immediately, but do not await listener
1651
1061
  // closure before cancelling active workers: an SSE connection may itself be
1652
1062
  // waiting for the worker cancellation that follows.
1653
- const modules = this.#moduleLifetimes.toReversed();
1654
- this.#moduleLifetimes = [];
1655
- const modulePhase = async (phase) => {
1656
- const results = await Promise.allSettled(modules.map((module) => Promise.resolve().then(() => module[phase]?.())));
1657
- const errors = results.filter((result) => result.status === "rejected").map(({ reason }) => reason);
1658
- if (errors.length > 0)
1659
- throw new AggregateError(errors, `module ${phase} failed`);
1660
- };
1661
- const moduleStop = shutdown.settle("modules stop", () => modulePhase("stop"));
1063
+ const modulePhases = this.#modules.beginStop();
1064
+ const moduleStop = shutdown.settle("modules stop", () => modulePhases.stop());
1662
1065
  // Drain order: (1) tell the supervisor to abort worker scopes so
1663
1066
  // strike paths don't keep going, (2) await its active drains
1664
1067
  // to completion, (3) drain streaming schemes' background work
@@ -1677,9 +1080,7 @@ export default class Daemon {
1677
1080
  // process may exit with the WAL in place (SQLite recovers) rather than
1678
1081
  // leak as a live-but-wedged tree.
1679
1082
  const drainResult = await shutdown.settle("drains idle", () => this.#drains.idle());
1680
- const clientResult = await shutdown.settle("client operations idle", async () => {
1681
- await Promise.all([...this.#clientLoops.values()].map(({ settled }) => settled));
1682
- });
1083
+ const clientResult = await shutdown.settle("client operations idle", () => this.#clientOperations.idle());
1683
1084
  const residencyResult = await shutdown.settle("workspace Functionality transitions", () => this.#residency.settle());
1684
1085
  // A boundary publication queued behind the last turn settles before
1685
1086
  // modules close and before the DB goes away ({§functionality-publication}).
@@ -1695,7 +1096,7 @@ export default class Daemon {
1695
1096
  // conclusion notifications. Join the supervisor-owned async tails only
1696
1097
  // after those producers settle, before the caller may close SQLite.
1697
1098
  const wakeResult = await shutdown.settle("drains idle (wake)", () => this.#drains.idle());
1698
- const moduleCloseResult = await shutdown.settle("modules close", () => modulePhase("close"));
1099
+ const moduleCloseResult = await shutdown.settle("modules close", () => modulePhases.close());
1699
1100
  // {§db-maintenance-optimize} — the last database step before the caller closes SQLite:
1700
1101
  // planner statistics refreshed on the writer, bounded by SQLite's own analysis limit.
1701
1102
  // {§retention-policy} — the operator's retention runs once more before the statistics.
@@ -1727,10 +1128,11 @@ export default class Daemon {
1727
1128
  // (SSE, WS) add themselves here as they land.
1728
1129
  async #drainStreamingSchemes() {
1729
1130
  const exec = this.#schemes.get("exec");
1131
+ if (!(exec instanceof Exec))
1132
+ return;
1730
1133
  // {§exec-lifetime} — a detached spawn ends with the daemon: nothing else ever aborts it.
1731
- exec?.abortDetached?.();
1732
- if (exec?.idle !== undefined)
1733
- await exec.idle();
1134
+ exec.abortDetached();
1135
+ await exec.idle();
1734
1136
  }
1735
1137
  /**
1736
1138
  * Emit a stream/event notification scoped to the workspace containing the
@@ -1770,7 +1172,7 @@ export default class Daemon {
1770
1172
  // scheme registry rather than exposing that registry across the boundary.
1771
1173
  #workerHasActiveStreams(workerId) {
1772
1174
  const exec = this.#schemes.get("exec");
1773
- return exec?.hasActiveSpawns?.(workerId) ?? false;
1175
+ return exec instanceof Exec && exec.hasActiveSpawns(workerId);
1774
1176
  }
1775
1177
  // {§methods-event-subscribe} — subscriber failures are transport-local:
1776
1178
  // log them at this boundary and never re-enter engine control flow.
@@ -1784,6 +1186,14 @@ export default class Daemon {
1784
1186
  }
1785
1187
  }
1786
1188
  }
1189
+ // {§notifications-worker-policy} A worker's generation policy, as worker.model.get reads it, to its workspace.
1190
+ async #announceWorkerPolicy(workerId) {
1191
+ const workspaceId = (await this.#db.drain_get_worker_workspace.get({ worker_id: workerId }))?.workspace_id;
1192
+ if (workspaceId === undefined)
1193
+ return;
1194
+ const { model, spawnModel } = await this.#workerModels.readWorkerModel({ workspaceId, workerId });
1195
+ this.#broadcast({ workspaceId }, "worker/policy", { workerId, model, spawnModel });
1196
+ }
1787
1197
  #broadcast(target, method, params) {
1788
1198
  observedSync(// {§observability-boundary}
1789
1199
  "stream.broadcast", { method, ...(target === "all" ? {} : { "workspace.id": target.workspaceId }) }, () => {
@@ -1805,5 +1215,11 @@ export default class Daemon {
1805
1215
  async readLog(...args) {
1806
1216
  return this.#reads.readLog(...args);
1807
1217
  }
1218
+ async readEntryChannel(...args) {
1219
+ return this.#reads.readEntryChannel(...args);
1220
+ }
1221
+ async readStreams(...args) {
1222
+ return this.#reads.readStreams(...args);
1223
+ }
1808
1224
  }
1809
1225
  //# sourceMappingURL=Daemon.js.map