@plurnk/plurnk-service 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.defaults +3 -0
- package/INSTALL.md +7 -2
- package/README.md +3 -1
- package/SPEC.md +1996 -1370
- package/dist/build-info.d.ts.map +1 -1
- package/dist/build-info.js +25 -3
- package/dist/build-info.js.map +1 -1
- package/dist/build-info.json +1 -1
- package/dist/completions/_plurnk-service +2 -2
- package/dist/completions/plurnk-service.bash +2 -2
- package/dist/completions/plurnk-service.fish +2 -0
- package/dist/content/body-preview.js.map +1 -1
- package/dist/content/byte-view.d.ts.map +1 -1
- package/dist/content/byte-view.js +3 -2
- package/dist/content/byte-view.js.map +1 -1
- package/dist/content/edit-collision.d.ts +2 -1
- package/dist/content/edit-collision.d.ts.map +1 -1
- package/dist/content/edit-collision.js.map +1 -1
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +28 -23
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/edited-span.js +1 -1
- package/dist/content/edited-span.js.map +1 -1
- package/dist/content/line-anchors.d.ts +3 -2
- package/dist/content/line-anchors.d.ts.map +1 -1
- package/dist/content/line-anchors.js +33 -19
- package/dist/content/line-anchors.js.map +1 -1
- package/dist/content/line-selection.js.map +1 -1
- package/dist/content/pattern-edits.js +1 -1
- package/dist/content/pattern-edits.js.map +1 -1
- package/dist/content/read-projector.d.ts +1 -1
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +10 -8
- package/dist/content/read-projector.js.map +1 -1
- package/dist/content/read-resolve.d.ts.map +1 -1
- package/dist/content/read-resolve.js +6 -5
- package/dist/content/read-resolve.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts +4 -4
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +20 -18
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/AmbientMembers.d.ts +18 -0
- package/dist/core/AmbientMembers.d.ts.map +1 -0
- package/dist/core/AmbientMembers.js +84 -0
- package/dist/core/AmbientMembers.js.map +1 -0
- package/dist/core/BareBatchRunner.d.ts +1 -1
- package/dist/core/BareBatchRunner.d.ts.map +1 -1
- package/dist/core/BareBatchRunner.js +2 -2
- package/dist/core/BareBatchRunner.js.map +1 -1
- package/dist/core/CapabilityPolicies.js.map +1 -1
- package/dist/core/CapabilityResolver.d.ts +1 -2
- package/dist/core/CapabilityResolver.d.ts.map +1 -1
- package/dist/core/CapabilityResolver.js +11 -7
- package/dist/core/CapabilityResolver.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +2 -2
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +27 -0
- package/dist/core/ClientInteractions.js.map +1 -1
- package/dist/core/ContextFit.js +1 -1
- package/dist/core/ContextFit.js.map +1 -1
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +6 -4
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/DataStatementRunner.d.ts.map +1 -1
- package/dist/core/DataStatementRunner.js +24 -15
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +29 -5
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +242 -132
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/EditMutations.d.ts.map +1 -1
- package/dist/core/EditMutations.js +12 -6
- package/dist/core/EditMutations.js.map +1 -1
- package/dist/core/EditSequence.js.map +1 -1
- package/dist/core/Engine.d.ts +12 -6
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +29 -7
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/ExecutionOutputs.js.map +1 -1
- package/dist/core/ExecutorRegistry.d.ts +1 -2
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +2 -2
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/FileAddress.d.ts +1 -0
- package/dist/core/FileAddress.d.ts.map +1 -1
- package/dist/core/FileAddress.js +8 -1
- package/dist/core/FileAddress.js.map +1 -1
- package/dist/core/FilePublication.d.ts.map +1 -1
- package/dist/core/FilePublication.js +6 -3
- package/dist/core/FilePublication.js.map +1 -1
- package/dist/core/FileSnapshot.d.ts.map +1 -1
- package/dist/core/FileSnapshot.js +8 -5
- package/dist/core/FileSnapshot.js.map +1 -1
- package/dist/core/KillHandler.d.ts.map +1 -1
- package/dist/core/KillHandler.js +7 -0
- package/dist/core/KillHandler.js.map +1 -1
- package/dist/core/LiveSubscription.d.ts +1 -0
- package/dist/core/LiveSubscription.d.ts.map +1 -1
- package/dist/core/LiveSubscription.js +3 -2
- package/dist/core/LiveSubscription.js.map +1 -1
- package/dist/core/LiveSubscriptions.d.ts +4 -1
- package/dist/core/LiveSubscriptions.d.ts.map +1 -1
- package/dist/core/LiveSubscriptions.js +42 -1
- package/dist/core/LiveSubscriptions.js.map +1 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +22 -19
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogVisibility.d.ts +1 -1
- package/dist/core/LogVisibility.d.ts.map +1 -1
- package/dist/core/LogVisibility.js +4 -6
- package/dist/core/LogVisibility.js.map +1 -1
- package/dist/core/LogWriter.d.ts.map +1 -1
- package/dist/core/LogWriter.js +6 -9
- package/dist/core/LogWriter.js.map +1 -1
- package/dist/core/LoopDriver.d.ts +4 -4
- package/dist/core/LoopDriver.d.ts.map +1 -1
- package/dist/core/LoopDriver.js +4 -5
- package/dist/core/LoopDriver.js.map +1 -1
- package/dist/core/LoopLifecycle.d.ts +2 -0
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/LoopLifecycle.js +13 -3
- package/dist/core/LoopLifecycle.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +4 -4
- package/dist/core/LoopOutcome.d.ts.map +1 -1
- package/dist/core/LoopOutcome.js +2 -2
- package/dist/core/LoopOutcome.js.map +1 -1
- package/dist/core/MembershipMaterialization.d.ts.map +1 -1
- package/dist/core/MembershipMaterialization.js +29 -29
- package/dist/core/MembershipMaterialization.js.map +1 -1
- package/dist/core/MessageResources.d.ts +5 -1
- package/dist/core/MessageResources.d.ts.map +1 -1
- package/dist/core/MessageResources.js +12 -0
- package/dist/core/MessageResources.js.map +1 -1
- package/dist/core/MessageResources.sql +8 -0
- package/dist/core/MutationEffects.d.ts +1 -0
- package/dist/core/MutationEffects.d.ts.map +1 -1
- package/dist/core/MutationEffects.js +14 -10
- package/dist/core/MutationEffects.js.map +1 -1
- package/dist/core/OperationHistory.d.ts.map +1 -1
- package/dist/core/OperationHistory.js +4 -3
- package/dist/core/OperationHistory.js.map +1 -1
- package/dist/core/OperatorConfig.d.ts.map +1 -1
- package/dist/core/OperatorConfig.js +3 -2
- package/dist/core/OperatorConfig.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts +1 -1
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +7 -5
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.d.ts +2 -5
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +2 -3
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/ProposalLifecycle.d.ts +12 -11
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +92 -84
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ReasoningEvent.d.ts +2 -0
- package/dist/core/ReasoningEvent.d.ts.map +1 -1
- package/dist/core/ReplyMetadata.d.ts.map +1 -1
- package/dist/core/ReplyMetadata.js +4 -3
- package/dist/core/ReplyMetadata.js.map +1 -1
- package/dist/core/ResourceBindings.d.ts +2 -2
- package/dist/core/ResourceBindings.d.ts.map +1 -1
- package/dist/core/ResourceBindings.js.map +1 -1
- package/dist/core/ResourceMutations.d.ts +1 -1
- package/dist/core/ResourceMutations.d.ts.map +1 -1
- package/dist/core/ResourceMutations.js +1 -2
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/ResourceSelector.js +2 -2
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/ResourceTransfers.d.ts.map +1 -1
- package/dist/core/ResourceTransfers.js +162 -120
- package/dist/core/ResourceTransfers.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +6 -5
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +32 -26
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/ServiceHelp.d.ts.map +1 -1
- package/dist/core/ServiceHelp.js +2 -1
- package/dist/core/ServiceHelp.js.map +1 -1
- package/dist/core/StoredPacket.d.ts.map +1 -1
- package/dist/core/StoredPacket.js +26 -19
- package/dist/core/StoredPacket.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +1 -2
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +4 -4
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TerminalResult.d.ts +0 -2
- package/dist/core/TerminalResult.d.ts.map +1 -1
- package/dist/core/TerminalResult.js +0 -18
- package/dist/core/TerminalResult.js.map +1 -1
- package/dist/core/ToolInputSchema.d.ts.map +1 -1
- package/dist/core/ToolInputSchema.js +2 -2
- package/dist/core/ToolInputSchema.js.map +1 -1
- package/dist/core/ToolResources.d.ts +1 -0
- package/dist/core/ToolResources.d.ts.map +1 -1
- package/dist/core/ToolResources.js +7 -6
- package/dist/core/ToolResources.js.map +1 -1
- package/dist/core/TurnDispositionHandler.d.ts +1 -1
- package/dist/core/TurnDispositionHandler.d.ts.map +1 -1
- package/dist/core/TurnDispositionHandler.js +0 -1
- package/dist/core/TurnDispositionHandler.js.map +1 -1
- package/dist/core/TurnMaterialization.d.ts.map +1 -1
- package/dist/core/TurnMaterialization.js +3 -2
- package/dist/core/TurnMaterialization.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +13 -12
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +197 -173
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/TurnRunner.sql +3 -3
- package/dist/core/WorkerOwners.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbMessageCaps.d.ts +3 -3
- package/dist/core/caps/DbMessageCaps.d.ts.map +1 -1
- package/dist/core/caps/DbProjectionCaps.js +1 -1
- package/dist/core/caps/DbProjectionCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +1 -0
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +1 -0
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +10 -1
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/env-catalog.js.map +1 -1
- package/dist/core/env-defaults.d.ts.map +1 -1
- package/dist/core/env-defaults.js +5 -3
- package/dist/core/env-defaults.js.map +1 -1
- package/dist/core/file-creation-policy.d.ts.map +1 -1
- package/dist/core/file-creation-policy.js +2 -1
- package/dist/core/file-creation-policy.js.map +1 -1
- package/dist/core/file-materialization.d.ts.map +1 -1
- package/dist/core/file-materialization.js +9 -11
- package/dist/core/file-materialization.js.map +1 -1
- package/dist/core/git-env.d.ts +1 -0
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +34 -3
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-membership.d.ts +8 -0
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +107 -21
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +1 -0
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/notifications.d.ts +5 -0
- package/dist/core/notifications.d.ts.map +1 -1
- package/dist/core/packet-wire.d.ts +26 -21
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +150 -112
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +1 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +6 -5
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/results.d.ts +9 -4
- package/dist/core/results.d.ts.map +1 -1
- package/dist/core/results.js +14 -1
- package/dist/core/results.js.map +1 -1
- package/dist/core/runtime-floor.d.ts +8 -0
- package/dist/core/runtime-floor.d.ts.map +1 -0
- package/dist/core/runtime-floor.js +40 -0
- package/dist/core/runtime-floor.js.map +1 -0
- package/dist/core/runtime-guard.d.ts +2 -0
- package/dist/core/runtime-guard.d.ts.map +1 -0
- package/dist/core/runtime-guard.js +5 -0
- package/dist/core/runtime-guard.js.map +1 -0
- package/dist/core/scheme-types.d.ts +20 -0
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/sqlite-synchronous.d.ts +1 -1
- package/dist/core/sqlite-synchronous.d.ts.map +1 -1
- package/dist/core/sqlite-synchronous.js.map +1 -1
- package/dist/core/statement-primary.d.ts +2 -2
- package/dist/core/statement-primary.d.ts.map +1 -1
- package/dist/core/statement-primary.js.map +1 -1
- package/dist/core/workspace-settings.js +1 -1
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/evidence/EvidenceReader.d.ts.map +1 -1
- package/dist/evidence/EvidenceReader.js +20 -6
- package/dist/evidence/EvidenceReader.js.map +1 -1
- package/dist/launch/Launch.d.ts +1 -0
- package/dist/launch/Launch.d.ts.map +1 -1
- package/dist/launch/Launch.js +28 -12
- package/dist/launch/Launch.js.map +1 -1
- package/dist/man/plurnk-service.1 +12 -2
- package/dist/schemes/EffectPolicy.js +1 -1
- package/dist/schemes/EffectPolicy.js.map +1 -1
- package/dist/schemes/Env.d.ts +1 -1
- package/dist/schemes/Env.d.ts.map +1 -1
- package/dist/schemes/Env.js +7 -1
- package/dist/schemes/Env.js.map +1 -1
- package/dist/schemes/Exec.d.ts +10 -12
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +337 -437
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +4 -6
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +17 -17
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/ExecScheduler.d.ts.map +1 -1
- package/dist/schemes/ExecScheduler.js +1 -2
- package/dist/schemes/ExecScheduler.js.map +1 -1
- package/dist/schemes/ExecutionInput.js +3 -3
- package/dist/schemes/ExecutionInput.js.map +1 -1
- package/dist/schemes/File.d.ts +3 -18
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +148 -84
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +193 -161
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/QuestionTool.d.ts.map +1 -1
- package/dist/schemes/QuestionTool.js +6 -8
- package/dist/schemes/QuestionTool.js.map +1 -1
- package/dist/schemes/ResourceTreeScheme.d.ts.map +1 -1
- package/dist/schemes/ResourceTreeScheme.js +2 -2
- package/dist/schemes/ResourceTreeScheme.js.map +1 -1
- package/dist/schemes/TurnSource.js +3 -3
- package/dist/schemes/TurnSource.js.map +1 -1
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +4 -2
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-find.d.ts +2 -1
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +241 -130
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-manifest.js +1 -1
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/schemes/exec-entry-sink.d.ts +30 -0
- package/dist/schemes/exec-entry-sink.d.ts.map +1 -0
- package/dist/schemes/exec-entry-sink.js +238 -0
- package/dist/schemes/exec-entry-sink.js.map +1 -0
- package/dist/schemes/exec-lifetime.d.ts.map +1 -1
- package/dist/schemes/exec-lifetime.js +5 -2
- package/dist/schemes/exec-lifetime.js.map +1 -1
- package/dist/server/ClientOperations.d.ts +45 -0
- package/dist/server/ClientOperations.d.ts.map +1 -0
- package/dist/server/ClientOperations.js +192 -0
- package/dist/server/ClientOperations.js.map +1 -0
- package/dist/server/ClientReads.d.ts +13 -5
- package/dist/server/ClientReads.d.ts.map +1 -1
- package/dist/server/ClientReads.js +108 -21
- package/dist/server/ClientReads.js.map +1 -1
- package/dist/server/Credentials.js +2 -2
- package/dist/server/Credentials.js.map +1 -1
- package/dist/server/Daemon.d.ts +40 -28
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +263 -839
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonLock.d.ts +2 -0
- package/dist/server/DaemonLock.d.ts.map +1 -1
- package/dist/server/DaemonLock.js +10 -1
- package/dist/server/DaemonLock.js.map +1 -1
- package/dist/server/DaemonModules.d.ts +39 -0
- package/dist/server/DaemonModules.d.ts.map +1 -0
- package/dist/server/DaemonModules.js +219 -0
- package/dist/server/DaemonModules.js.map +1 -0
- package/dist/server/DrainSupervisor.d.ts +4 -2
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +318 -290
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts +10 -7
- package/dist/server/EnvFunctionality.d.ts.map +1 -1
- package/dist/server/EnvFunctionality.js +7 -5
- package/dist/server/EnvFunctionality.js.map +1 -1
- package/dist/server/Functionality.d.ts +4 -5
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js +24 -15
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/HttpListener.d.ts.map +1 -1
- package/dist/server/HttpListener.js +28 -8
- package/dist/server/HttpListener.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +19 -7
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js +37 -17
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/MessageDelivery.d.ts +43 -0
- package/dist/server/MessageDelivery.d.ts.map +1 -0
- package/dist/server/MessageDelivery.js +186 -0
- package/dist/server/MessageDelivery.js.map +1 -0
- package/dist/server/ModuleHost.d.ts +3 -3
- package/dist/server/ModuleHost.d.ts.map +1 -1
- package/dist/server/Places.d.ts +19 -0
- package/dist/server/Places.d.ts.map +1 -0
- package/dist/server/Places.js +76 -0
- package/dist/server/Places.js.map +1 -0
- package/dist/server/PluginSources.d.ts.map +1 -1
- package/dist/server/PluginSources.js +2 -1
- package/dist/server/PluginSources.js.map +1 -1
- package/dist/server/PlurnkSkill.d.ts.map +1 -1
- package/dist/server/PlurnkSkill.js +5 -4
- package/dist/server/PlurnkSkill.js.map +1 -1
- package/dist/server/Rollback.d.ts.map +1 -1
- package/dist/server/Rollback.js +2 -1
- package/dist/server/Rollback.js.map +1 -1
- package/dist/server/WorkerModels.d.ts +83 -0
- package/dist/server/WorkerModels.d.ts.map +1 -0
- package/dist/server/WorkerModels.js +500 -0
- package/dist/server/WorkerModels.js.map +1 -0
- package/dist/server/WorkerOverview.d.ts +22 -0
- package/dist/server/WorkerOverview.d.ts.map +1 -0
- package/dist/server/WorkerOverview.js +42 -0
- package/dist/server/WorkerOverview.js.map +1 -0
- package/dist/server/WorkspacePlugins.d.ts +1 -1
- package/dist/server/WorkspacePlugins.d.ts.map +1 -1
- package/dist/server/WorkspacePlugins.js +0 -1
- package/dist/server/WorkspacePlugins.js.map +1 -1
- package/dist/server/WorkspaceResidency.d.ts +3 -3
- package/dist/server/WorkspaceResidency.d.ts.map +1 -1
- package/dist/server/WorkspaceResidency.js.map +1 -1
- package/dist/server/client-input.d.ts +2 -2
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +19 -12
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/envelope.d.ts +4 -1
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +7 -3
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +10 -6
- package/dist/server/logEntry.d.ts +38 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +9 -9
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +4 -2
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +11 -2
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/module-discovery.d.ts +2 -3
- package/dist/server/module-discovery.d.ts.map +1 -1
- package/dist/server/module-discovery.js +6 -6
- package/dist/server/module-discovery.js.map +1 -1
- package/dist/server/places.sql +83 -0
- package/dist/server/seam-loop.sql +8 -0
- package/dist/server/worker-overview.sql +50 -0
- package/dist/service.d.ts +1 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +35 -9
- package/dist/service.js.map +1 -1
- package/docs/members.md +31 -33
- package/migrations/033_hidden_places.sql +10 -0
- package/package.json +48 -57
- package/dist/observe/init.d.ts +0 -8
- package/dist/observe/init.d.ts.map +0 -1
- package/dist/observe/init.js +0 -163
- package/dist/observe/init.js.map +0 -1
- package/dist/server/WorkerModelResolver.d.ts +0 -20
- package/dist/server/WorkerModelResolver.d.ts.map +0 -1
- package/dist/server/WorkerModelResolver.js +0 -200
- package/dist/server/WorkerModelResolver.js.map +0 -1
package/dist/server/Daemon.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 {
|
|
62
|
-
import
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
#
|
|
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,
|
|
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
|
-
|
|
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
|
|
@@ -310,6 +219,19 @@ export default class Daemon {
|
|
|
310
219
|
// worker:// KILL (terminate) — cancel the addressed worker subtree and
|
|
311
220
|
// tear down its held streams before the operation completes.
|
|
312
221
|
cancelWorker: async (workerId, reason) => this.#drains.cancelWorkerTree(workerId, reason),
|
|
222
|
+
// {§membership-auto-add} — a READ or FIND admits what it reached for as the Worker's own
|
|
223
|
+
// definition; the members scope's refusal admits nothing and is not a failure of the read.
|
|
224
|
+
admitMembers: async (identity, files) => {
|
|
225
|
+
try {
|
|
226
|
+
const result = await this.#functionality.invoke("members", "add", { files }, identity, "operation");
|
|
227
|
+
return result.status < 400 ? files : [];
|
|
228
|
+
}
|
|
229
|
+
catch (cause) {
|
|
230
|
+
if (cause instanceof OperationFailureError && cause.result.status < 500)
|
|
231
|
+
return [];
|
|
232
|
+
throw cause;
|
|
233
|
+
}
|
|
234
|
+
},
|
|
313
235
|
noticeNotify: (workspaceId, payload) => this.notifyNotice(workspaceId, payload),
|
|
314
236
|
loopPacketNotify: (workspaceId, payload) => {
|
|
315
237
|
this.#broadcast({ workspaceId }, "loop/packet", payload);
|
|
@@ -321,54 +243,16 @@ export default class Daemon {
|
|
|
321
243
|
completeAbandonedMessages: (workspaceId, workerId, loopId, status) => MessageAbandonment.complete(this.#engine, this.#db, workspaceId, workerId, loopId, status),
|
|
322
244
|
lifecycle: this.#lifecycle,
|
|
323
245
|
injectPrompt: (loopId, prompt, openPaths, source, evidence, messageAddress, opener) => this.#engine.injectIntoLoop(loopId, prompt, openPaths, source, evidence, messageAddress, opener),
|
|
324
|
-
assertInjectionCompatibility:
|
|
325
|
-
|
|
326
|
-
// explicit selection is checked against it (a deliberate switch).
|
|
327
|
-
if (providerSpecExplicit !== false) {
|
|
328
|
-
await this.#assertLoopProvider(loopId, providerSpec);
|
|
329
|
-
}
|
|
330
|
-
await this.#assertLoopEffort(loopId, effort);
|
|
331
|
-
if (childProviderSpec !== undefined) {
|
|
332
|
-
await this.#assertLoopChildProvider(loopId, childProviderSpec);
|
|
333
|
-
}
|
|
334
|
-
await this.#assertLoopMaxTurns(loopId, turnCeiling?.source === "explicit" ? turnCeiling.effective : undefined);
|
|
335
|
-
},
|
|
336
|
-
runLoop: async ({ workspaceId, workerId, loopId, maxTurns, prompt, systemPrompt, signal, onSettled }) => {
|
|
337
|
-
// The drain callback is the final provider/model boundary. Every
|
|
338
|
-
// admission path, including boot recovery, terminates here.
|
|
339
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
340
|
-
const releaseCapabilities = await this.#residency.acquire(workspaceId);
|
|
341
|
-
try {
|
|
342
|
-
// Worker settings can change while Functionality remains
|
|
343
|
-
// resident. Reconcile the worker-private discovery surface
|
|
344
|
-
// at the same pre-inference boundary used for activation.
|
|
345
|
-
await this.#residency.reconcile(workspaceId);
|
|
346
|
-
const { provider, childProvider } = await this.#providersForLoop(loopId);
|
|
347
|
-
return await this.#engine.runLoop({
|
|
348
|
-
provider,
|
|
349
|
-
childProvider,
|
|
350
|
-
workspaceId,
|
|
351
|
-
workerId,
|
|
352
|
-
loopId,
|
|
353
|
-
maxTurns,
|
|
354
|
-
messages: [
|
|
355
|
-
{ role: "system", content: systemPrompt },
|
|
356
|
-
{ role: "user", content: prompt },
|
|
357
|
-
],
|
|
358
|
-
signal,
|
|
359
|
-
onSettled
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
finally {
|
|
363
|
-
releaseCapabilities();
|
|
364
|
-
}
|
|
365
|
-
},
|
|
246
|
+
assertInjectionCompatibility: (args) => this.#workerModels.assertInjectionCompatibility(args),
|
|
247
|
+
runLoop: (args) => this.#runDrainLoop(args),
|
|
366
248
|
loopUsage: (loopId) => this.#engine.loopUsage(loopId),
|
|
367
249
|
loopAttributions: (loopId) => this.#engine.loopAttributions(loopId),
|
|
368
250
|
discardLoopNotices: (loopId) => this.#engine.discardLoopNotices(loopId),
|
|
369
251
|
cancelSubscription: (subscriptionId) => this.#engine.cancelSubscription(subscriptionId),
|
|
252
|
+
subscriptionConcluded: (subscriptionId) => this.#engine.subscriptionConcluded(subscriptionId),
|
|
253
|
+
reapLoopStreams: (loopId) => this.#engine.reapLoopStreams(loopId),
|
|
370
254
|
hasActiveStreams: (workerId) => this.#workerHasActiveStreams(workerId),
|
|
371
|
-
readSystemPrompt: () => readFile(Paths.instructionsSystem, "utf8"),
|
|
255
|
+
readSystemPrompt: () => readFile(Paths.instructionsSystem, "utf8"), // {§card-read-per-wake}
|
|
372
256
|
emitLogEntry: async (workspaceId, logEntryId) => {
|
|
373
257
|
const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
|
|
374
258
|
this.#broadcast({ workspaceId }, "log/entry", { entry });
|
|
@@ -386,8 +270,53 @@ export default class Daemon {
|
|
|
386
270
|
const { workspaceId, ...interaction } = event;
|
|
387
271
|
this.#broadcast({ workspaceId }, "loop/interaction", interaction);
|
|
388
272
|
});
|
|
389
|
-
this.#workerModels = new
|
|
273
|
+
this.#workerModels = new WorkerModels({ db: this.#db, provider: this.#provider, policyChanged: (workerId) => this.#announceWorkerPolicy(workerId) });
|
|
274
|
+
this.#delivery = new MessageDelivery({
|
|
275
|
+
db, workerModels: this.#workerModels, functionality: this.#functionality, mimetypes: this.#mimetypes,
|
|
276
|
+
inject: (args) => this.inject(args),
|
|
277
|
+
});
|
|
390
278
|
this.#reads = new ClientReads({ db: this.#db, engine: this.#engine, workspaceGate: this.#workspaceGate, residency: this.#residency });
|
|
279
|
+
this.#clientOperations = new ClientOperations({
|
|
280
|
+
db, engine: this.#engine, residency: this.#residency, workspaceGate: this.#workspaceGate,
|
|
281
|
+
assertModelWorker: (workspaceId, workerId) => this.#workerModels.assertModelWorker(workspaceId, workerId),
|
|
282
|
+
assertWorkerOwned: (workspaceId, workerId) => this.#assertWorkerOwned(workspaceId, workerId),
|
|
283
|
+
emitLogEntry: async (workspaceId, logEntryId) => {
|
|
284
|
+
const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
|
|
285
|
+
this.#broadcast({ workspaceId }, "log/entry", { entry });
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
// The drain callback is the final provider/model boundary. Every admission path, including boot recovery,
|
|
290
|
+
// terminates here.
|
|
291
|
+
async #runDrainLoop({ workspaceId, workerId, loopId, maxTurns, prompt, systemPrompt, signal, onSettled }) {
|
|
292
|
+
// The drain callback is the final provider/model boundary. Every
|
|
293
|
+
// admission path, including boot recovery, terminates here.
|
|
294
|
+
await this.#workerModels.assertModelWorker(workspaceId, workerId);
|
|
295
|
+
const releaseCapabilities = await this.#residency.acquire(workspaceId);
|
|
296
|
+
try {
|
|
297
|
+
// Worker settings can change while Functionality remains
|
|
298
|
+
// resident. Reconcile the worker-private discovery surface
|
|
299
|
+
// at the same pre-inference boundary used for activation.
|
|
300
|
+
await this.#residency.reconcile(workspaceId);
|
|
301
|
+
const { provider: loopProvider, childProvider } = await this.#workerModels.providersForLoop(loopId);
|
|
302
|
+
return await this.#engine.runLoop({
|
|
303
|
+
provider: loopProvider,
|
|
304
|
+
childProvider,
|
|
305
|
+
workspaceId,
|
|
306
|
+
workerId,
|
|
307
|
+
loopId,
|
|
308
|
+
maxTurns,
|
|
309
|
+
messages: [
|
|
310
|
+
{ role: "system", content: systemPrompt },
|
|
311
|
+
{ role: "user", content: prompt },
|
|
312
|
+
],
|
|
313
|
+
signal,
|
|
314
|
+
onSettled
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
releaseCapabilities();
|
|
319
|
+
}
|
|
391
320
|
}
|
|
392
321
|
// {§methods-event-subscribe}. A transport module subscribes to the daemon's in-process
|
|
393
322
|
// event source: it receives every workspace-scoped engine event as `(workspaceId, method, params)`
|
|
@@ -442,179 +371,24 @@ export default class Daemon {
|
|
|
442
371
|
ClientInput.assertId("resolveClientInteraction", "workspaceId", respondent.workspaceId);
|
|
443
372
|
await this.#engine.resolveClientInteraction(checkedInteractionId, checkedResolution, respondent, message);
|
|
444
373
|
}
|
|
445
|
-
async #prepareMessage(workspaceId, workerId, body, attachments = [], envelope) {
|
|
446
|
-
const published = await MessageResources.publish(body, attachments, {
|
|
447
|
-
db: this.#db, workspaceId, workerId, loopId: 0, turnId: 0, writer: "_plurnk", signal: undefined,
|
|
448
|
-
weigh: contentWeight, mimetypes: this.#mimetypes,
|
|
449
|
-
});
|
|
450
|
-
return { body: published.body, evidence: {
|
|
451
|
-
...(envelope === undefined ? {} : { envelope: structuredClone(envelope) }),
|
|
452
|
-
...(published.attachments.length === 0 ? {} : { attachments: published.attachments }),
|
|
453
|
-
} };
|
|
454
|
-
}
|
|
455
374
|
async readMessages(args) {
|
|
456
|
-
await this.#assertModelWorker(args.workspaceId, args.workerId);
|
|
375
|
+
await this.#workerModels.assertModelWorker(args.workspaceId, args.workerId);
|
|
457
376
|
return MessageResources.read(this.#db, args);
|
|
458
377
|
}
|
|
378
|
+
// {§methods-worker-inbox}
|
|
379
|
+
async readInbox(args) {
|
|
380
|
+
await this.#workerModels.assertModelWorker(args.workspaceId, args.workerId);
|
|
381
|
+
return MessageResources.inbox(this.#db, args);
|
|
382
|
+
}
|
|
459
383
|
// {§message-idempotency}
|
|
460
384
|
async messageAdmission(args) {
|
|
461
385
|
const workspaceId = ClientInput.assertId("messageAdmission", "workspaceId", args.workspaceId);
|
|
462
386
|
return await MessageResources.admission(this.#db, workspaceId, args.address, args.body);
|
|
463
387
|
}
|
|
464
|
-
// {§methods-loop-run} —
|
|
465
|
-
// the
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
async runLoop(args) {
|
|
469
|
-
const workspaceId = ClientInput.assertId("runLoop", "workspaceId", args.workspaceId);
|
|
470
|
-
const workerId = ClientInput.assertId("runLoop", "workerId", args.workerId);
|
|
471
|
-
const recipientLoopId = args.loopId === undefined ? undefined : ClientInput.assertId("runLoop", "loopId", args.loopId);
|
|
472
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
473
|
-
const attachments = ClientInput.assertMessageResources("runLoop", args.attachments);
|
|
474
|
-
const body = ClientInput.assertPrompt("runLoop", args.prompt, attachments.length > 0);
|
|
475
|
-
const source = ClientInput.assertOptionalSource("runLoop", args.source);
|
|
476
|
-
const messageAddress = ClientInput.assertOptionalSource("runLoop", args.messageAddress);
|
|
477
|
-
const requestedMaxTurns = ClientInput.assertMaxTurns("runLoop", args.maxTurns);
|
|
478
|
-
const openPaths = ClientInput.assertOpenPaths("runLoop", args.openPaths);
|
|
479
|
-
const selector = ClientInput.assertOptionalSelector("runLoop", "selector", args.selector);
|
|
480
|
-
const childSelector = ClientInput.assertOptionalChildSelector("runLoop", args.childSelector);
|
|
481
|
-
// {§worker-model-selection} — the worker owns the model. An explicit selector
|
|
482
|
-
// persists onto the worker; an omitted selector resolves the worker's durable model
|
|
483
|
-
// (seeded once from the daemon default). The loop then snapshots the resolved route.
|
|
484
|
-
// A continuation's omitted selector must still keep the loop's durable provider.
|
|
485
|
-
const selectorExplicit = selector !== undefined;
|
|
486
|
-
const generationPolicy = await this.#workerModels.resolveWorkerModel(workerId, selector);
|
|
487
|
-
if (generationPolicy === null) {
|
|
488
|
-
throw new OperationFailureError(Results.failure("daemon:provider", "not-configured", 501, "No provider is configured for this worker.", {}, {
|
|
489
|
-
stage: "provider-selection",
|
|
490
|
-
recovery: "Select a configured model provider.",
|
|
491
|
-
retryable: false
|
|
492
|
-
}));
|
|
493
|
-
}
|
|
494
|
-
const { providerSpec: selection, effort } = generationPolicy;
|
|
495
|
-
// {§methods-loop-run-child-provider} — the worker's persistent spawn override.
|
|
496
|
-
const childSelection = await this.#workerModels.resolveWorkerSpawnModel(workerId, childSelector);
|
|
497
|
-
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
498
|
-
// {§operator-config-max-turns-ceiling} — the operator ceiling clamps a per-call maxTurns; a
|
|
499
|
-
// seam caller must not bypass operator policy (inject only DEFAULTS from env, never clamps).
|
|
500
|
-
const ceiling = Knob.integer("PLURNK_SERVICE_MAX_TURNS", -1);
|
|
501
|
-
const requested = requestedMaxTurns ?? ceiling;
|
|
502
|
-
const maxTurns = ceiling < 0 ? requested : (requested < 0 ? ceiling : Math.min(requested, ceiling));
|
|
503
|
-
const turnCeiling = {
|
|
504
|
-
effective: maxTurns,
|
|
505
|
-
source: requestedMaxTurns === undefined ? "implicit" : "explicit"
|
|
506
|
-
};
|
|
507
|
-
const { action, loopId, turnSeq } = await ContentUse.read(this.#db, async () => {
|
|
508
|
-
const delivered = await this.#prepareMessage(workspaceId, workerId, body, attachments, args.envelope);
|
|
509
|
-
return this.inject({
|
|
510
|
-
workspaceId,
|
|
511
|
-
workerId,
|
|
512
|
-
prompt: delivered.body,
|
|
513
|
-
...(recipientLoopId === undefined ? {} : { loopId: recipientLoopId }),
|
|
514
|
-
evidence: delivered.evidence,
|
|
515
|
-
...(messageAddress === undefined ? {} : { messageAddress }),
|
|
516
|
-
...(source === undefined ? {} : { source }),
|
|
517
|
-
...(openPaths !== undefined ? { openPaths } : {}),
|
|
518
|
-
turnCeiling,
|
|
519
|
-
providerSpec: selection,
|
|
520
|
-
providerSpecExplicit: selectorExplicit,
|
|
521
|
-
effort,
|
|
522
|
-
childProviderSpec: childSelection,
|
|
523
|
-
systemPrompt
|
|
524
|
-
});
|
|
525
|
-
});
|
|
526
|
-
return { status: 100, action, loopId, ...(turnSeq !== undefined ? { turnSeq } : {}) };
|
|
527
|
-
}
|
|
528
|
-
// {§worker-model-selection} — the loop's durable snapshot is the immutable route ids,
|
|
529
|
-
// resolved through model_routes at the claim boundary; never re-resolved through the
|
|
530
|
-
// alias cascade (a changed alias declaration must not rewrite history).
|
|
531
|
-
async #providerPolicyForLoop(loopId) {
|
|
532
|
-
const row = await this.#db.drain_loop_generation_policy.get({ loop_id: loopId });
|
|
533
|
-
if (row === undefined)
|
|
534
|
-
throw new Error(`loop ${loopId}: provider selection row is missing`);
|
|
535
|
-
const providerSpec = await specForRoute(this.#db, row.model_route_id);
|
|
536
|
-
if (providerSpec === null) {
|
|
537
|
-
throw new Error(`loop ${loopId}: persisted provider selection is missing — refusing boot-default substitution`);
|
|
538
|
-
}
|
|
539
|
-
if (row.effort === null) {
|
|
540
|
-
throw new Error(`loop ${loopId}: persisted effort is missing`);
|
|
541
|
-
}
|
|
542
|
-
return {
|
|
543
|
-
providerSpec,
|
|
544
|
-
childProviderSpec: await specForRoute(this.#db, row.spawn_model_route_id),
|
|
545
|
-
effort: row.effort
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
async #providerSpecForLoop(loopId) {
|
|
549
|
-
return (await this.#providerPolicyForLoop(loopId)).providerSpec;
|
|
550
|
-
}
|
|
551
|
-
async #providersForLoop(loopId) {
|
|
552
|
-
const policy = await this.#providerPolicyForLoop(loopId);
|
|
553
|
-
const provider = await this.#workerModels.providerForPolicy(policy.providerSpec, policy.effort);
|
|
554
|
-
const childProvider = policy.childProviderSpec === null
|
|
555
|
-
? provider
|
|
556
|
-
: await this.#workerModels.providerForPolicy(policy.childProviderSpec, policy.effort);
|
|
557
|
-
return { provider, childProvider };
|
|
558
|
-
}
|
|
559
|
-
async #assertLoopProvider(loopId, requested) {
|
|
560
|
-
const selected = await this.#providerSpecForLoop(loopId);
|
|
561
|
-
if (JSON.stringify(selected) !== JSON.stringify(requested)) {
|
|
562
|
-
throw daemonFailure("daemon:provider", "loop-provider-conflict", 409, `Loop ${loopId} uses ${modelRouteLabel(selected)}, not ${modelRouteLabel(requested)}.`, {
|
|
563
|
-
loopId,
|
|
564
|
-
...(selected.alias === undefined ? {} : { selectedAlias: selected.alias }),
|
|
565
|
-
selectedModel: `${selected.provider}/${selected.model}`,
|
|
566
|
-
...(requested.alias === undefined ? {} : { requestedAlias: requested.alias }),
|
|
567
|
-
requestedModel: `${requested.provider}/${requested.model}`,
|
|
568
|
-
stage: "loop-injection",
|
|
569
|
-
recovery: "Cancel or conclude the loop before selecting another provider.",
|
|
570
|
-
retryable: false
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
async #assertLoopEffort(loopId, requested) {
|
|
575
|
-
const selected = (await this.#providerPolicyForLoop(loopId)).effort;
|
|
576
|
-
if (selected !== requested) {
|
|
577
|
-
throw daemonFailure("daemon:provider", "loop-effort-conflict", 409, `Loop ${loopId} uses effort '${selected}', not '${requested}'.`, {
|
|
578
|
-
loopId,
|
|
579
|
-
selectedEffort: selected,
|
|
580
|
-
requestedEffort: requested,
|
|
581
|
-
stage: "loop-injection",
|
|
582
|
-
recovery: "Cancel or conclude the loop before selecting another effort.",
|
|
583
|
-
retryable: false
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
async #assertLoopChildProvider(loopId, requested) {
|
|
588
|
-
const selected = (await this.#providerPolicyForLoop(loopId)).childProviderSpec;
|
|
589
|
-
if (JSON.stringify(selected) !== JSON.stringify(requested)) {
|
|
590
|
-
throw daemonFailure("daemon:provider", "loop-child-provider-conflict", 409, `Loop ${loopId} already has a different child provider policy.`, {
|
|
591
|
-
loopId,
|
|
592
|
-
selectedChildAlias: selected?.alias ?? null,
|
|
593
|
-
selectedChildModel: selected === null ? null : `${selected.provider}/${selected.model}`,
|
|
594
|
-
requestedChildAlias: requested?.alias ?? null,
|
|
595
|
-
requestedChildModel: requested === null ? null : `${requested.provider}/${requested.model}`,
|
|
596
|
-
stage: "loop-injection",
|
|
597
|
-
recovery: "Cancel or conclude the loop before changing its child provider policy.",
|
|
598
|
-
retryable: false
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
async #assertLoopMaxTurns(loopId, requested) {
|
|
603
|
-
if (requested === undefined)
|
|
604
|
-
return;
|
|
605
|
-
const durable = await this.#db.drain_get_loop_max_turns.get({ loop_id: loopId });
|
|
606
|
-
if (durable === undefined)
|
|
607
|
-
throw new Error(`inject: loop ${loopId} has no durable turn ceiling`);
|
|
608
|
-
if (durable.max_turns !== requested) {
|
|
609
|
-
throw daemonFailure("daemon:loop", "turn-ceiling-conflict", 409, `Loop ${loopId} has turn ceiling ${durable.max_turns}, not ${requested}.`, {
|
|
610
|
-
loopId,
|
|
611
|
-
selectedMaximumTurns: durable.max_turns,
|
|
612
|
-
requestedMaximumTurns: requested,
|
|
613
|
-
stage: "loop-injection",
|
|
614
|
-
recovery: "Cancel or conclude the loop before selecting another turn ceiling.",
|
|
615
|
-
retryable: false
|
|
616
|
-
});
|
|
617
|
-
}
|
|
388
|
+
// {§methods-loop-run} — a message delivered to a worker drives or steers its loop: the delivery's owner resolves
|
|
389
|
+
// the worker's model and the card and injects into the drain; the port's entry point delegates.
|
|
390
|
+
runLoop(args) {
|
|
391
|
+
return this.#delivery.runLoop(args);
|
|
618
392
|
}
|
|
619
393
|
async ensureRuntimeWorker(workspaceId) {
|
|
620
394
|
const checked = ClientInput.assertId("worker.ensure-runtime", "workspaceId", workspaceId);
|
|
@@ -632,130 +406,34 @@ export default class Daemon {
|
|
|
632
406
|
async setWorkspaceCapabilities(args) {
|
|
633
407
|
const workspaceId = ClientInput.assertId("workspace.capabilities.set", "workspaceId", args.workspaceId);
|
|
634
408
|
const settings = JSON.parse(ClientInput.parseSettings({ capabilities: args.policy }));
|
|
635
|
-
|
|
409
|
+
const capabilities = isRecord(settings) && settings.capabilities !== undefined ? Validator.assertCapabilityPolicy(settings.capabilities) : undefined;
|
|
410
|
+
if (capabilities === undefined)
|
|
636
411
|
throw daemonFailure("daemon:input", "capability-policy-required", 400, "policy is required.", { field: "policy", retryable: false });
|
|
637
412
|
return this.#residency.exclusively(workspaceId, "capabilities", "try", async () => {
|
|
638
413
|
const row = await this.#db.workspace_capability_policy_update.get({
|
|
639
|
-
workspace_id: workspaceId, policy: JSON.stringify(
|
|
414
|
+
workspace_id: workspaceId, policy: JSON.stringify(capabilities),
|
|
640
415
|
});
|
|
641
416
|
if (row === undefined)
|
|
642
417
|
throw daemonFailure("daemon:workspace", "workspace-not-found", 404, `Workspace ${workspaceId} does not exist.`, { workspaceId });
|
|
643
418
|
return await this.#engine.capabilityProjection(workspaceId);
|
|
644
419
|
});
|
|
645
420
|
}
|
|
646
|
-
// {§worker-model-selection} —
|
|
647
|
-
//
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const workerId = ClientInput.assertId("worker.model.get", "workerId", args.workerId);
|
|
651
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
652
|
-
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
653
|
-
if (row === undefined)
|
|
654
|
-
throw new Error(`worker ${workerId}: model route row missing`);
|
|
655
|
-
const modelSpec = await specForRoute(this.#db, row.model_route_id);
|
|
656
|
-
const spawnModelSpec = await specForRoute(this.#db, row.spawn_model_route_id);
|
|
657
|
-
return {
|
|
658
|
-
model: modelSpec === null ? null : projectModelRoute(modelSpec, row.effort, row.effort_source),
|
|
659
|
-
spawnModel: spawnModelSpec === null ? null : projectModelRoute(spawnModelSpec, row.effort, row.effort_source)
|
|
660
|
-
};
|
|
421
|
+
// {§worker-model-selection} {§worker-effort} — the worker's durable model, effort and spawn override are
|
|
422
|
+
// WorkerModels' to read and set; the port's entry points delegate.
|
|
423
|
+
readWorkerModel(args) {
|
|
424
|
+
return this.#workerModels.readWorkerModel(args);
|
|
661
425
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
async setWorkerModel(args) {
|
|
665
|
-
const workspaceId = ClientInput.assertId("worker.model.set", "workspaceId", args.workspaceId);
|
|
666
|
-
const workerId = ClientInput.assertId("worker.model.set", "workerId", args.workerId);
|
|
667
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
668
|
-
const selector = ClientInput.assertSelector("worker.model.set", "selector", args.selector);
|
|
669
|
-
let chosen;
|
|
670
|
-
if (args.effort !== undefined) {
|
|
671
|
-
try {
|
|
672
|
-
chosen = Validator.assertEffort(args.effort);
|
|
673
|
-
}
|
|
674
|
-
catch (cause) {
|
|
675
|
-
throw daemonFailure("daemon:input", "effort-invalid", 400, cause instanceof Error ? cause.message : "The effort is invalid.", { field: "effort", retryable: false });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
const policy = await this.#workerModels.resolveWorkerModel(workerId, selector, chosen);
|
|
679
|
-
if (policy === null) {
|
|
680
|
-
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 }));
|
|
681
|
-
}
|
|
682
|
-
return projectModelRoute(policy.providerSpec, policy.effort, policy.effortSource);
|
|
683
|
-
}
|
|
684
|
-
// {§worker-effort} — the worker's effort is durable and
|
|
685
|
-
// provider-relative. A model-less worker remains explicitly uninitialized.
|
|
686
|
-
async readWorkerEffort(args) {
|
|
687
|
-
const workspaceId = ClientInput.assertId("worker.effort.get", "workspaceId", args.workspaceId);
|
|
688
|
-
const workerId = ClientInput.assertId("worker.effort.get", "workerId", args.workerId);
|
|
689
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
690
|
-
await this.#workerModels.resolveWorkerModel(workerId, undefined);
|
|
691
|
-
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
692
|
-
if (row === undefined)
|
|
693
|
-
throw new Error(`worker ${workerId}: generation policy row missing`);
|
|
694
|
-
if (row.model_route_id === null) {
|
|
695
|
-
if (row.effort !== null) {
|
|
696
|
-
throw new Error(`worker ${workerId}: effort exists without a model route`);
|
|
697
|
-
}
|
|
698
|
-
return { effort: null, source: "default", supportedEfforts: [] };
|
|
699
|
-
}
|
|
700
|
-
if (row.effort === null) {
|
|
701
|
-
throw new Error(`worker ${workerId}: durable model has no effort`);
|
|
702
|
-
}
|
|
703
|
-
return {
|
|
704
|
-
effort: row.effort,
|
|
705
|
-
source: row.effort_source,
|
|
706
|
-
supportedEfforts: await this.#workerModels.supportedEfforts(row)
|
|
707
|
-
};
|
|
426
|
+
setWorkerModel(args) {
|
|
427
|
+
return this.#workerModels.setWorkerModel(args);
|
|
708
428
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
catch (cause) {
|
|
719
|
-
throw daemonFailure("daemon:input", "effort-invalid", 400, cause instanceof Error ? cause.message : "The effort is invalid.", { field: "effort", retryable: false });
|
|
720
|
-
}
|
|
721
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
722
|
-
// A model-less worker takes the default first; an existing pair is replaced, not re-validated, so an
|
|
723
|
-
// effort its model no longer supports can be corrected ({§worker-effort}).
|
|
724
|
-
const current = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
725
|
-
if (current?.model_route_id === null)
|
|
726
|
-
await this.#workerModels.resolveWorkerModel(workerId, undefined);
|
|
727
|
-
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
728
|
-
if (row === undefined)
|
|
729
|
-
throw new Error(`worker ${workerId}: generation policy row missing`);
|
|
730
|
-
const spec = await specForRoute(this.#db, row.model_route_id);
|
|
731
|
-
if (spec === null) {
|
|
732
|
-
throw daemonFailure("daemon:provider", "worker-model-unset", 409, `Worker ${workerId} has no model against which to validate an effort.`, {
|
|
733
|
-
workerId,
|
|
734
|
-
stage: "provider-selection",
|
|
735
|
-
recovery: "Select a model before selecting its effort.",
|
|
736
|
-
retryable: false
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
const supportedEfforts = await this.#workerModels.persistGenerationPolicy(workerId, {
|
|
740
|
-
model_route_id: row.model_route_id,
|
|
741
|
-
spawn_model_route_id: row.spawn_model_route_id,
|
|
742
|
-
effort,
|
|
743
|
-
effort_source: "explicit"
|
|
744
|
-
});
|
|
745
|
-
return { effort, source: "explicit", supportedEfforts };
|
|
746
|
-
}
|
|
747
|
-
// {§worker-model-selection} — persist the worker's spawn override; a null
|
|
748
|
-
// selector means inherit (clears the override).
|
|
749
|
-
async setWorkerSpawnModel(args) {
|
|
750
|
-
const workspaceId = ClientInput.assertId("worker.child.set", "workspaceId", args.workspaceId);
|
|
751
|
-
const workerId = ClientInput.assertId("worker.child.set", "workerId", args.workerId);
|
|
752
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
753
|
-
const selector = ClientInput.assertChildSelector("worker.child.set", args.selector);
|
|
754
|
-
const spec = await this.#workerModels.resolveWorkerSpawnModel(workerId, selector);
|
|
755
|
-
if (spec === null)
|
|
756
|
-
return null;
|
|
757
|
-
const row = await this.#db.worker_generation_policy_read.get({ id: workerId });
|
|
758
|
-
return projectModelRoute(spec, row?.effort ?? null, row?.effort_source ?? "default");
|
|
429
|
+
readWorkerEffort(args) {
|
|
430
|
+
return this.#workerModels.readWorkerEffort(args);
|
|
431
|
+
}
|
|
432
|
+
setWorkerEffort(args) {
|
|
433
|
+
return this.#workerModels.setWorkerEffort(args);
|
|
434
|
+
}
|
|
435
|
+
setWorkerSpawnModel(args) {
|
|
436
|
+
return this.#workerModels.setWorkerSpawnModel(args);
|
|
759
437
|
}
|
|
760
438
|
async #assertWorkerOwned(workspaceId, workerId) {
|
|
761
439
|
const owner = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
@@ -766,46 +444,6 @@ export default class Daemon {
|
|
|
766
444
|
throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, { workerId, workspaceId, actualWorkspaceId: owner.workspace_id });
|
|
767
445
|
}
|
|
768
446
|
}
|
|
769
|
-
// {§machine-processes} — provider inference is an exclusive capability of a
|
|
770
|
-
// model worker. The same assertion is used by early client admissions, the final
|
|
771
|
-
// drain callback, and the model, reasoning, and spawn-model controls
|
|
772
|
-
// ({§worker-model-selection}), so recovery and internal callers cannot bypass it.
|
|
773
|
-
async #assertModelWorker(workspaceId, workerId) {
|
|
774
|
-
const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
775
|
-
if (worker === undefined) {
|
|
776
|
-
throw daemonFailure("daemon:worker", "worker-not-found", 404, `Worker ${workerId} does not exist.`, { workerId });
|
|
777
|
-
}
|
|
778
|
-
if (worker.workspace_id !== workspaceId) {
|
|
779
|
-
throw daemonFailure("daemon:worker", "workspace-mismatch", 409, `Worker ${workerId} does not belong to workspace ${workspaceId}.`, {
|
|
780
|
-
workerId,
|
|
781
|
-
workspaceId,
|
|
782
|
-
actualWorkspaceId: worker.workspace_id,
|
|
783
|
-
retryable: false
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
if (worker.origin !== "model") {
|
|
787
|
-
throw daemonFailure("daemon:worker", "model-worker-required", 409, `Worker ${workerId} is not a model worker.`, {
|
|
788
|
-
workerId,
|
|
789
|
-
actualOrigin: worker.origin,
|
|
790
|
-
recovery: "Select or create a model worker.",
|
|
791
|
-
retryable: false
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// {§actor-boundary-client-operations} A message speaks for its loop's actor: a person's operation for that
|
|
796
|
-
// person, as a thread message does; any other loop for its worker.
|
|
797
|
-
async #messageSource(workspaceId, sourceLoopId, sourceWorkerId, targetWorkerId) {
|
|
798
|
-
const person = await this.#db.envelope_client_operation_person.get({ loop_id: sourceLoopId });
|
|
799
|
-
if (person !== undefined)
|
|
800
|
-
return person.person;
|
|
801
|
-
if (sourceWorkerId === targetWorkerId)
|
|
802
|
-
return undefined;
|
|
803
|
-
const source = await this.#db.envelope_get_worker_by_id.get({ id: sourceWorkerId });
|
|
804
|
-
if (source === undefined || source.workspace_id !== workspaceId) {
|
|
805
|
-
throw new Error(`Worker prompt source ${sourceWorkerId} does not belong to workspace ${workspaceId}.`);
|
|
806
|
-
}
|
|
807
|
-
return WorkerControlAddress.render(source.name);
|
|
808
|
-
}
|
|
809
447
|
// {§functionality-model-mutation} — await every queued Functionality
|
|
810
448
|
// publication (a Worker's accepted mutation publishes at its turn boundary).
|
|
811
449
|
async settleFunctionality(workspaceId) {
|
|
@@ -816,154 +454,20 @@ export default class Daemon {
|
|
|
816
454
|
}
|
|
817
455
|
// {§connection-lifecycle} A person's client operation: one administrative loop in the workspace's runtime
|
|
818
456
|
// worker, attributed to its actor, reading `log:///` in the worker that person acts for
|
|
819
|
-
// ({§actor-boundary-client-operations}).
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const actor = ClientInput.assertActor("operation.dispatch-batch", args.actor);
|
|
823
|
-
const contextWorkerId = args.contextWorkerId === undefined
|
|
824
|
-
? undefined
|
|
825
|
-
: ClientInput.assertId("operation.dispatch-batch", "contextWorkerId", args.contextWorkerId);
|
|
826
|
-
if (contextWorkerId !== undefined)
|
|
827
|
-
await this.#assertModelWorker(workspaceId, contextWorkerId);
|
|
828
|
-
const { statements } = args;
|
|
829
|
-
if (statements.length === 0)
|
|
830
|
-
return [];
|
|
831
|
-
return await this.#clientOperation({ workspaceId, actor, contextWorkerId, onLoop: args.onLoop }, async (dispatch) => {
|
|
832
|
-
const results = [];
|
|
833
|
-
for (const statement of statements)
|
|
834
|
-
results.push(await dispatch(statement));
|
|
835
|
-
return results;
|
|
836
|
-
});
|
|
457
|
+
// ({§actor-boundary-client-operations}). The operations' owner runs it; the port's entry points delegate.
|
|
458
|
+
dispatchClientAction(args) {
|
|
459
|
+
return this.#clientOperations.dispatchClientAction(args);
|
|
837
460
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
async #clientOperation(operation, body) {
|
|
841
|
-
const { workspaceId, actor, contextWorkerId } = operation;
|
|
842
|
-
const workerId = await RuntimeWorker.ensure(this.#db, workspaceId);
|
|
843
|
-
const releaseCapabilities = await this.#residency.acquire(workspaceId);
|
|
844
|
-
try {
|
|
845
|
-
const loopId = await Envelope.ensureClientLoop(this.#db, workerId, actor);
|
|
846
|
-
const scope = new AbortController();
|
|
847
|
-
const signal = AbortSignal.any([scope.signal, this.#clientOperations.signal]);
|
|
848
|
-
const settled = Promise.withResolvers();
|
|
849
|
-
this.#clientLoops.set(loopId, { workspaceId, scope, settled: settled.promise });
|
|
850
|
-
const unbind = this.#engine.bindLoopSignal(loopId, signal);
|
|
851
|
-
try {
|
|
852
|
-
operation.onLoop?.({ workerId, loopId });
|
|
853
|
-
const result = await body(async (statement) => {
|
|
854
|
-
signal.throwIfAborted();
|
|
855
|
-
return await this.#dispatchClientStatement({
|
|
856
|
-
workspaceId, workerId, loopId, statement,
|
|
857
|
-
contextWorkerId: contextWorkerId ?? null,
|
|
858
|
-
});
|
|
859
|
-
});
|
|
860
|
-
signal.throwIfAborted();
|
|
861
|
-
await Envelope.closeClientLoop(this.#db, loopId, { status: 200 });
|
|
862
|
-
return result;
|
|
863
|
-
}
|
|
864
|
-
catch (error) {
|
|
865
|
-
// A cancelled operation settles cancelled, however its statement in flight ended.
|
|
866
|
-
const failure = signal.aborted ? new OperationFailureError(clientOperationCancelled(signal.reason)) : error;
|
|
867
|
-
await Envelope.closeClientLoop(this.#db, loopId, clientActionFailure(failure));
|
|
868
|
-
throw failure;
|
|
869
|
-
}
|
|
870
|
-
finally {
|
|
871
|
-
unbind();
|
|
872
|
-
this.#clientLoops.delete(loopId);
|
|
873
|
-
settled.resolve();
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
finally {
|
|
877
|
-
releaseCapabilities();
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
// {§connection-lifecycle} Cancel exactly one client operation: its dispatch while it runs, and every execution its
|
|
881
|
-
// rows started that still runs. The rest of the runtime worker runs on.
|
|
882
|
-
async cancelLoop(args) {
|
|
883
|
-
const workspaceId = ClientInput.assertId("operation.cancel", "workspaceId", args.workspaceId);
|
|
884
|
-
const loopId = ClientInput.assertId("operation.cancel", "loopId", args.loopId);
|
|
885
|
-
const operation = this.#clientLoops.get(loopId);
|
|
886
|
-
const dispatching = operation !== undefined && operation.workspaceId === workspaceId && !operation.scope.signal.aborted;
|
|
887
|
-
if (dispatching)
|
|
888
|
-
operation.scope.abort(args.reason ?? "user_cancelled");
|
|
889
|
-
const executions = await ChannelWrite.findOpenSubscriptionsForClientLoop(this.#db, { workspaceId, loopId });
|
|
890
|
-
const reaped = await Promise.all(executions.map((subscriptionId) => this.#engine.cancelSubscription(subscriptionId)));
|
|
891
|
-
return dispatching || reaped.includes(true);
|
|
892
|
-
}
|
|
893
|
-
async #dispatchClientStatement(args) {
|
|
894
|
-
const { workspaceId, workerId, loopId, statement, contextWorkerId } = args;
|
|
895
|
-
return this.#workspaceGate.withTurn(workspaceId, workerId, async () => {
|
|
896
|
-
await this.#residency.reconcile(workspaceId);
|
|
897
|
-
const { id: turnId } = await Turn.open(this.#db, {
|
|
898
|
-
loopId,
|
|
899
|
-
producer: "client",
|
|
900
|
-
kind: "operation"
|
|
901
|
-
});
|
|
902
|
-
let turnOpen = true;
|
|
903
|
-
try {
|
|
904
|
-
const entryIds = [];
|
|
905
|
-
const result = await this.#engine.dispatch({
|
|
906
|
-
statement, workspaceId, workerId, loopId, turnId, sequence: 1,
|
|
907
|
-
origin: "client", client: { contextWorkerId },
|
|
908
|
-
onDispatch: (logEntryId) => { entryIds.push(logEntryId); }
|
|
909
|
-
});
|
|
910
|
-
await Turn.complete(this.#db, turnId, result.status);
|
|
911
|
-
turnOpen = false;
|
|
912
|
-
for (const logEntryId of entryIds) {
|
|
913
|
-
const entry = await LogEntry.fetchLogEntry(this.#db, logEntryId);
|
|
914
|
-
this.#broadcast({ workspaceId }, "log/entry", { entry });
|
|
915
|
-
}
|
|
916
|
-
return result;
|
|
917
|
-
}
|
|
918
|
-
catch (cause) {
|
|
919
|
-
if (turnOpen) {
|
|
920
|
-
try {
|
|
921
|
-
await Turn.complete(this.#db, turnId, 500);
|
|
922
|
-
}
|
|
923
|
-
catch (completionCause) {
|
|
924
|
-
throw new AggregateError([cause, completionCause], `client operation turn ${turnId} failed and could not be completed`);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
throw cause;
|
|
928
|
-
}
|
|
929
|
-
}, this.#clientLoops.get(loopId)?.scope.signal);
|
|
461
|
+
cancelLoop(args) {
|
|
462
|
+
return this.#clientOperations.cancelLoop(args);
|
|
930
463
|
}
|
|
931
464
|
// {§methods} — the module's render surface beyond the journal. Thin delegations
|
|
932
465
|
// into core's envelope / membership / provider machinery; the module fans the results into its own views.
|
|
933
466
|
listProviders() {
|
|
934
|
-
|
|
935
|
-
let aliases;
|
|
936
|
-
try {
|
|
937
|
-
aliases = parseAliasesFromEnv();
|
|
938
|
-
}
|
|
939
|
-
catch (cause) {
|
|
940
|
-
if (!(cause instanceof ConfigurationError))
|
|
941
|
-
throw cause;
|
|
942
|
-
throw new OperationFailureError(Results.configurationFailure(cause), { cause });
|
|
943
|
-
}
|
|
944
|
-
return {
|
|
945
|
-
aliases: aliases.map((a) => {
|
|
946
|
-
const isActive = active === a.alias;
|
|
947
|
-
// {§agui-provider-routes} — the route a worker seeded from this alias gets: what it would read back.
|
|
948
|
-
let effort;
|
|
949
|
-
try {
|
|
950
|
-
effort = ProviderInstantiate.configuredEffort(a);
|
|
951
|
-
}
|
|
952
|
-
catch (cause) {
|
|
953
|
-
const invalid = new ConfigurationError("PLURNK_PROVIDERS_EFFORT", cause instanceof Error ? cause.message : String(cause), { cause });
|
|
954
|
-
throw new OperationFailureError(Results.configurationFailure(invalid), { cause });
|
|
955
|
-
}
|
|
956
|
-
return {
|
|
957
|
-
...projectModelRoute(a, effort, "default"), alias: a.alias, active: isActive,
|
|
958
|
-
inputCapacity: isActive && this.#provider !== null ? this.#provider.inputCapacity : null
|
|
959
|
-
};
|
|
960
|
-
})
|
|
961
|
-
};
|
|
467
|
+
return this.#workerModels.listProviders();
|
|
962
468
|
}
|
|
963
|
-
// {§model-catalog} The catalog is a bounded, daemon-local projection.
|
|
964
|
-
// It performs no provider I/O and never changes the worker's selection.
|
|
965
469
|
listModels(query) {
|
|
966
|
-
return
|
|
470
|
+
return this.#workerModels.listModels(query);
|
|
967
471
|
}
|
|
968
472
|
// {§client-display-capabilities} Core composes the installed family
|
|
969
473
|
// declarations; interface modules expose this contracts-owned wire without
|
|
@@ -988,8 +492,9 @@ export default class Daemon {
|
|
|
988
492
|
}));
|
|
989
493
|
return Validator.assertClientDisplayCapabilities([...schemes, ...mimetypes]);
|
|
990
494
|
}
|
|
991
|
-
async listWorkspaces() {
|
|
992
|
-
|
|
495
|
+
async listWorkspaces(query = {}) {
|
|
496
|
+
const hidden = query.hidden === undefined ? undefined : ClientInput.assertFlag("workspace.list", "hidden", query.hidden);
|
|
497
|
+
return (await Envelope.listWorkspaces(this.#db, hidden)).map((row) => ({ ...row, project_label: this.#projectLabel(row.project_root) }));
|
|
993
498
|
}
|
|
994
499
|
// {§workspace-label} — the folder as the daemon spells it for display: the spelling a default name takes.
|
|
995
500
|
#projectLabel(projectRoot) {
|
|
@@ -998,21 +503,23 @@ export default class Daemon {
|
|
|
998
503
|
#labelled(envelope) {
|
|
999
504
|
return { ...envelope, projectLabel: this.#projectLabel(envelope.projectRoot) };
|
|
1000
505
|
}
|
|
1001
|
-
listWorkers(workspaceId, query = {}) {
|
|
506
|
+
async listWorkers(workspaceId, query = {}) {
|
|
1002
507
|
const origin = query.origin;
|
|
1003
508
|
if (origin !== undefined && !["model", "client", "_plurnk"].includes(origin)) {
|
|
1004
|
-
throw daemonFailure("daemon:worker", "origin-invalid", 400, `Worker origin '${
|
|
509
|
+
throw daemonFailure("daemon:worker", "origin-invalid", 400, `Worker origin '${origin}' is invalid.`, { origin, retryable: false });
|
|
1005
510
|
}
|
|
1006
511
|
const parentWorkerId = query.parentWorkerId === undefined
|
|
1007
512
|
? undefined
|
|
1008
513
|
: query.parentWorkerId === null
|
|
1009
514
|
? null
|
|
1010
515
|
: ClientInput.assertId("workspace.workers", "parentWorkerId", query.parentWorkerId);
|
|
516
|
+
const hidden = query.hidden === undefined ? undefined : ClientInput.assertFlag("workspace.workers", "hidden", query.hidden);
|
|
1011
517
|
// {§methods-worker-list} — an omitted parent filter must stay absent:
|
|
1012
518
|
// the envelope keys the filter on the property's presence, not its value.
|
|
1013
|
-
return Envelope.listWorkersForWorkspace(this.#db, ClientInput.assertId("workspace.workers", "workspaceId", workspaceId), {
|
|
519
|
+
return await Envelope.listWorkersForWorkspace(this.#db, ClientInput.assertId("workspace.workers", "workspaceId", workspaceId), {
|
|
1014
520
|
...(origin === undefined ? {} : { origin }),
|
|
1015
|
-
...(parentWorkerId === undefined ? {} : { parentWorkerId })
|
|
521
|
+
...(parentWorkerId === undefined ? {} : { parentWorkerId }),
|
|
522
|
+
...(hidden === undefined ? {} : { hidden })
|
|
1016
523
|
});
|
|
1017
524
|
}
|
|
1018
525
|
async readWorker(args) {
|
|
@@ -1044,6 +551,16 @@ export default class Daemon {
|
|
|
1044
551
|
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
1045
552
|
return this.#engine.workerAccounting(workerId);
|
|
1046
553
|
}
|
|
554
|
+
// {§methods-worker-gauge}
|
|
555
|
+
async workerGauge(args) {
|
|
556
|
+
const workspaceId = ClientInput.assertId("worker.gauge", "workspaceId", args.workspaceId);
|
|
557
|
+
const workerId = ClientInput.assertId("worker.gauge", "workerId", args.workerId);
|
|
558
|
+
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
559
|
+
const latest = await this.#db.application_latest_packet_loop.get({ worker_id: workerId });
|
|
560
|
+
if (latest === undefined)
|
|
561
|
+
return { curationWeight: null, curationBudget: null, contextTokens: null, contextCapacity: null };
|
|
562
|
+
return Engine.clientGauge(await this.#engine.loopGauge(latest.id));
|
|
563
|
+
}
|
|
1047
564
|
async listWorkerLoops(args) {
|
|
1048
565
|
const workspaceId = ClientInput.assertId("worker.loops", "workspaceId", args.workspaceId);
|
|
1049
566
|
const workerId = ClientInput.assertId("worker.loops", "workerId", args.workerId);
|
|
@@ -1062,9 +579,25 @@ export default class Daemon {
|
|
|
1062
579
|
waitUntil: row.waitUntil,
|
|
1063
580
|
invocation: row.invocation === null || row.invocationWorkerId === null ? null : { workerId: row.invocationWorkerId, address: row.invocation },
|
|
1064
581
|
// {§validation-topology}: a stored result is chapter 3's (`loops_terminal_result_contract`); it is parsed, not re-asserted.
|
|
1065
|
-
terminalResult: row.terminalResult === null ? null :
|
|
582
|
+
terminalResult: row.terminalResult === null ? null : Results.fromRow(row.terminalResult)
|
|
1066
583
|
}));
|
|
1067
584
|
}
|
|
585
|
+
// {§methods-worker-overview} The places of the daemon, or of one workspace, with what waits there.
|
|
586
|
+
async readWorkerOverview(args) {
|
|
587
|
+
const workspaceId = args.workspaceId === undefined ? null : ClientInput.assertId("worker.overview", "workspaceId", args.workspaceId);
|
|
588
|
+
const { limit, excerptChars } = args;
|
|
589
|
+
if (!isSafeInteger(limit) || limit < 1 || !isSafeInteger(excerptChars) || excerptChars < 1) {
|
|
590
|
+
throw new TypeError("readWorkerOverview takes a positive limit and excerpt length.");
|
|
591
|
+
}
|
|
592
|
+
if (workspaceId !== null && await this.#db.envelope_get_workspace.get({ id: workspaceId }) === undefined) {
|
|
593
|
+
throw daemonFailure("daemon:workspace", "workspace-not-found", 404, `Workspace ${workspaceId} does not exist.`, { workspaceId });
|
|
594
|
+
}
|
|
595
|
+
return await WorkerOverview.read(this.#db, {
|
|
596
|
+
workspaceId, limit, excerptChars, projectLabel: (projectRoot) => this.#projectLabel(projectRoot),
|
|
597
|
+
// {§agui-status-descendants} The gates the status tree counts: the pending proposals and client interactions.
|
|
598
|
+
pending: async (id) => [...await this.#engine.pendingProposals(id), ...await this.#engine.pendingClientInteractions(id)],
|
|
599
|
+
});
|
|
600
|
+
}
|
|
1068
601
|
// {§methods-workspace-prompts}: root-worker loop seeds, newest-first.
|
|
1069
602
|
// {§share} — one workspace's share. The daemon names its own database; the caller names an absolute folder.
|
|
1070
603
|
async shareWorkspace({ workspaceId, folder }) {
|
|
@@ -1111,7 +644,12 @@ export default class Daemon {
|
|
|
1111
644
|
if (derived === undefined)
|
|
1112
645
|
return undefined;
|
|
1113
646
|
const existing = await this.#db.envelope_get_workspace_by_name.get({ name: derived });
|
|
1114
|
-
|
|
647
|
+
if (existing === undefined)
|
|
648
|
+
return undefined;
|
|
649
|
+
// {§place-hiding} Creating by the folder selects a hidden workspace and shows it.
|
|
650
|
+
if (existing.hidden_at !== null)
|
|
651
|
+
await this.setWorkspaceHidden({ workspaceId: existing.id, hidden: false });
|
|
652
|
+
return this.#labelled(await Envelope.attachToWorkspace(this.#db, existing.id));
|
|
1115
653
|
};
|
|
1116
654
|
return observed(// {§observability-boundary}
|
|
1117
655
|
"workspace.create", {}, async (span) => {
|
|
@@ -1172,29 +710,46 @@ export default class Daemon {
|
|
|
1172
710
|
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
1173
711
|
return (await this.#db.worker_reference_list.all({ namespace, worker_id: workerId })).map(({ reference }) => reference);
|
|
1174
712
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
async rollback(args) {
|
|
1178
|
-
const workspaceId = ClientInput.assertId("workspace.rollback", "workspaceId", args.workspaceId);
|
|
1179
|
-
const workerId = ClientInput.assertId("workspace.rollback", "workerId", args.workerId);
|
|
1180
|
-
const actor = ClientInput.assertActor("workspace.rollback", args.actor);
|
|
1181
|
-
if (typeof args.to !== "string")
|
|
1182
|
-
throw daemonFailure("daemon:rollback", "coordinate-invalid", 400, "The rollback coordinate must be a string.", {});
|
|
1183
|
-
await this.#assertWorkerOwned(workspaceId, workerId);
|
|
1184
|
-
const report = await this.#clientOperation({ workspaceId, actor, contextWorkerId: workerId }, (dispatch) => Rollback.run({
|
|
1185
|
-
db: this.#db,
|
|
1186
|
-
projectRoot: async (id) => (await this.#db.envelope_get_workspace.get({ id }))?.project_root ?? null,
|
|
1187
|
-
executorTags: (id) => this.#engine.executorTags(id),
|
|
1188
|
-
apply: async (statement) => await dispatch(statement),
|
|
1189
|
-
}, { workspaceId, workerId, to: args.to }));
|
|
1190
|
-
return Validator.assertRollbackReport(report);
|
|
713
|
+
rollback(args) {
|
|
714
|
+
return this.#clientOperations.rollback(args);
|
|
1191
715
|
}
|
|
1192
716
|
async renameWorkspace(workspaceId, name) {
|
|
1193
717
|
const checkedWorkspaceId = ClientInput.assertId("workspace.rename", "workspaceId", workspaceId);
|
|
1194
718
|
const checkedName = ClientInput.assertOptionalWorkspaceName("workspace.rename", "name", name);
|
|
1195
719
|
if (checkedName === undefined)
|
|
1196
720
|
throw new Error("ClientInput.assertOptionalWorkspaceName accepted a required name as undefined");
|
|
1197
|
-
|
|
721
|
+
const renamed = { id: checkedWorkspaceId, name: await Envelope.updateWorkspaceName(this.#db, checkedWorkspaceId, checkedName) };
|
|
722
|
+
// {§notifications-workspace-renamed}
|
|
723
|
+
this.#broadcast({ workspaceId: checkedWorkspaceId }, "workspace/renamed", renamed);
|
|
724
|
+
return renamed;
|
|
725
|
+
}
|
|
726
|
+
// {§methods-workspace-hide} {§place-hiding}
|
|
727
|
+
async setWorkspaceHidden(args) {
|
|
728
|
+
const workspaceId = ClientInput.assertId("workspace.hidden", "workspaceId", args.workspaceId);
|
|
729
|
+
const hidden = ClientInput.assertFlag("workspace.hidden", "hidden", args.hidden);
|
|
730
|
+
const { hiding, changed } = await Places.setWorkspaceHidden(this.#db, (id) => this.#placeGates(id), workspaceId, hidden);
|
|
731
|
+
// {§notifications-workspace-hidden}
|
|
732
|
+
if (changed)
|
|
733
|
+
this.#broadcast({ workspaceId }, "workspace/hidden", hiding);
|
|
734
|
+
return hiding;
|
|
735
|
+
}
|
|
736
|
+
// {§methods-worker-hide} {§place-hiding}
|
|
737
|
+
async setWorkerHidden(args) {
|
|
738
|
+
const workspaceId = ClientInput.assertId("worker.hidden", "workspaceId", args.workspaceId);
|
|
739
|
+
const workerId = ClientInput.assertId("worker.hidden", "workerId", args.workerId);
|
|
740
|
+
const hidden = ClientInput.assertFlag("worker.hidden", "hidden", args.hidden);
|
|
741
|
+
const hiding = await Places.setWorkerHidden(this.#db, (id) => this.#placeGates(id), workspaceId, workerId, hidden);
|
|
742
|
+
// {§notifications-worker-hidden}
|
|
743
|
+
if (hiding.workerIds.length > 0)
|
|
744
|
+
this.#broadcast({ workspaceId }, "worker/hidden", hiding);
|
|
745
|
+
return hiding;
|
|
746
|
+
}
|
|
747
|
+
// The gates a hide is refused over, as the overview counts them.
|
|
748
|
+
async #placeGates(workspaceId) {
|
|
749
|
+
return {
|
|
750
|
+
proposals: await this.#engine.pendingProposals(workspaceId),
|
|
751
|
+
questions: await this.#engine.pendingClientInteractions(workspaceId),
|
|
752
|
+
};
|
|
1198
753
|
}
|
|
1199
754
|
// {§methods-worker-create}: a fresh worker is model-origin, with an empty private log.
|
|
1200
755
|
// {§machine-processes} Workers have independent histories within a shared workspace.
|
|
@@ -1216,15 +771,22 @@ export default class Daemon {
|
|
|
1216
771
|
else if (args.owner !== undefined) {
|
|
1217
772
|
await WorkerOwners.registered(this.#db, workspaceId, args.owner);
|
|
1218
773
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
774
|
+
let worker;
|
|
775
|
+
try {
|
|
776
|
+
worker = await Envelope.createModelWorker(this.#db, workspaceId, name, parentWorkerId, args.owner);
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
if (name === undefined || !(error instanceof WorkerNameConflictError))
|
|
780
|
+
throw error;
|
|
781
|
+
throw await this.#workerNameConflict(workspaceId, name);
|
|
1224
782
|
}
|
|
1225
|
-
const worker = await Envelope.createModelWorker(this.#db, workspaceId, name, parentWorkerId, args.owner);
|
|
1226
783
|
return { workerId: worker.id, workerName: worker.name };
|
|
1227
784
|
}
|
|
785
|
+
// {§methods-worker-create} The claim decides a name, so a request that loses it to another, at any moment, is refused alike.
|
|
786
|
+
async #workerNameConflict(workspaceId, name) {
|
|
787
|
+
const taken = await this.#db.envelope_get_worker_by_name.get({ workspace_id: workspaceId, name });
|
|
788
|
+
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 });
|
|
789
|
+
}
|
|
1228
790
|
// {§worker-scheme-fork} — branch a worker's log while sharing its workspace.
|
|
1229
791
|
// Core owns the workspace check and immutable worker-name admission.
|
|
1230
792
|
async forkWorker(args) {
|
|
@@ -1232,14 +794,16 @@ export default class Daemon {
|
|
|
1232
794
|
const workerId = ClientInput.assertId("run.fork", "workerId", args.workerId);
|
|
1233
795
|
const name = ClientInput.assertOptionalWorkerName("run.fork", "name", args.name);
|
|
1234
796
|
// {§machine-processes-worker-origin} A branch is a model's: only a model worker's log forks.
|
|
1235
|
-
await this.#assertModelWorker(workspaceId, workerId);
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
797
|
+
await this.#workerModels.assertModelWorker(workspaceId, workerId);
|
|
798
|
+
let branchWorkerId;
|
|
799
|
+
try {
|
|
800
|
+
branchWorkerId = await Fork.fork(this.#db, workerId, name);
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
if (name === undefined || !(error instanceof WorkerNameConflictError))
|
|
804
|
+
throw error;
|
|
805
|
+
throw await this.#workerNameConflict(workspaceId, name);
|
|
1241
806
|
}
|
|
1242
|
-
const branchWorkerId = await Fork.fork(this.#db, workerId, name);
|
|
1243
807
|
const branch = await this.#db.envelope_get_worker_by_id.get({ id: branchWorkerId });
|
|
1244
808
|
return { workerId: branchWorkerId, workerName: branch?.name ?? null, parentWorkerId: workerId };
|
|
1245
809
|
}
|
|
@@ -1282,102 +846,22 @@ export default class Daemon {
|
|
|
1282
846
|
scheme
|
|
1283
847
|
};
|
|
1284
848
|
}
|
|
1285
|
-
// {§http-host} —
|
|
1286
|
-
//
|
|
849
|
+
// {§http-host} {§module-http-mounts} — a module mounts on the daemon's one listener exactly what it claimed;
|
|
850
|
+
// the modules' owner keeps the claims. {§module-action-registration}: a module's actions, registered, listed and invoked.
|
|
1287
851
|
registerHttpRoute(prefix, handler) {
|
|
1288
|
-
|
|
1289
|
-
throw new Error(`registerHttpRoute: this daemon has no HTTP listener to mount '${prefix}' on`);
|
|
1290
|
-
// {§module-http-mounts} — a module mounts exactly what it claimed.
|
|
1291
|
-
if (!this.#mountClaims.has(prefix))
|
|
1292
|
-
throw new Error(`registerHttpRoute: no module declared '${prefix}' among its mounts`);
|
|
1293
|
-
this.#http.registerHttpRoute(prefix, handler);
|
|
1294
|
-
this.#mounted.add(prefix);
|
|
852
|
+
this.#modules.registerHttpRoute(prefix, handler);
|
|
1295
853
|
}
|
|
1296
854
|
httpAddress() {
|
|
1297
|
-
|
|
1298
|
-
throw new Error("httpAddress: this daemon has no HTTP listener");
|
|
1299
|
-
return this.#http.httpAddress();
|
|
855
|
+
return this.#modules.httpAddress();
|
|
1300
856
|
}
|
|
1301
857
|
registerModuleAction(registration) {
|
|
1302
|
-
|
|
1303
|
-
if (name.length === 0)
|
|
1304
|
-
throw new Error("registerModuleAction: action name must not be empty");
|
|
1305
|
-
if (scope !== "daemon" && scope !== "workspace" && scope !== "worker") {
|
|
1306
|
-
throw new Error(`module action '${name}' has invalid scope '${String(scope)}'`);
|
|
1307
|
-
}
|
|
1308
|
-
if (inputSchema === null || typeof inputSchema !== "object" || Array.isArray(inputSchema)) {
|
|
1309
|
-
throw new Error(`module action '${name}' has no input schema`);
|
|
1310
|
-
}
|
|
1311
|
-
if (outputSchema === null || typeof outputSchema !== "object" || Array.isArray(outputSchema)) {
|
|
1312
|
-
throw new Error(`module action '${name}' has no output schema`);
|
|
1313
|
-
}
|
|
1314
|
-
for (const [role, schema] of [["input", inputSchema], ["output", outputSchema]]) {
|
|
1315
|
-
try {
|
|
1316
|
-
Validator.validateJsonSchemaInstance(schema, {});
|
|
1317
|
-
}
|
|
1318
|
-
catch (cause) {
|
|
1319
|
-
throw new Error(`module action '${name}' has an invalid ${role} schema`, { cause });
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
if (typeof handler !== "function")
|
|
1323
|
-
throw new Error(`module action '${name}' has no handler`);
|
|
1324
|
-
if (registration.residency !== "required" && registration.residency !== "none") {
|
|
1325
|
-
throw new Error(`module action '${name}' must declare its residency requirement`);
|
|
1326
|
-
}
|
|
1327
|
-
if (scope === "daemon" && registration.residency !== "none") {
|
|
1328
|
-
throw new Error(`daemon module action '${name}' cannot require workspace residency`);
|
|
1329
|
-
}
|
|
1330
|
-
if (this.#moduleActions.has(name))
|
|
1331
|
-
throw new Error(`module action '${name}' is already registered`);
|
|
1332
|
-
this.#moduleActions.set(name, registration);
|
|
858
|
+
this.#modules.registerAction(registration);
|
|
1333
859
|
}
|
|
1334
860
|
listModuleActions() {
|
|
1335
|
-
return
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
inputSchema,
|
|
1340
|
-
outputSchema
|
|
1341
|
-
}))
|
|
1342
|
-
.toSorted((left, right) => left.name.localeCompare(right.name));
|
|
1343
|
-
}
|
|
1344
|
-
async invokeModuleAction(name, params, context) {
|
|
1345
|
-
const registration = this.#moduleActions.get(name);
|
|
1346
|
-
if (registration === undefined)
|
|
1347
|
-
throw new Error(`module action '${name}' is not registered`);
|
|
1348
|
-
if (registration.scope !== context.scope) {
|
|
1349
|
-
throw new Error(`module action '${name}' requires ${registration.scope} context, not ${context.scope}`);
|
|
1350
|
-
}
|
|
1351
|
-
let releaseCapabilities;
|
|
1352
|
-
if (context.scope === "worker") {
|
|
1353
|
-
const workspaceId = ClientInput.assertId(`module action '${name}'`, "workspaceId", context.workspaceId);
|
|
1354
|
-
const workerId = ClientInput.assertId(`module action '${name}'`, "workerId", context.workerId);
|
|
1355
|
-
const worker = await this.#db.envelope_get_worker_by_id.get({ id: workerId });
|
|
1356
|
-
if (worker === undefined || worker.workspace_id !== workspaceId) {
|
|
1357
|
-
throw daemonFailure("daemon:module-action", "worker-not-found", 404, `Worker ${workerId} does not exist in workspace ${workspaceId}.`, { workspaceId, workerId, retryable: false });
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
if (registration.residency === "required" && context.scope !== "daemon") {
|
|
1361
|
-
releaseCapabilities = await this.#residency.acquire(context.workspaceId);
|
|
1362
|
-
}
|
|
1363
|
-
else if (context.scope === "workspace") {
|
|
1364
|
-
await this.#residency.identity(context.workspaceId);
|
|
1365
|
-
}
|
|
1366
|
-
try {
|
|
1367
|
-
if (registration.residency === "required" && context.scope === "worker") {
|
|
1368
|
-
const release = await this.#workspaceGate.acquireTurn(context.workspaceId, context.workerId);
|
|
1369
|
-
try {
|
|
1370
|
-
await this.#residency.reconcile(context.workspaceId);
|
|
1371
|
-
}
|
|
1372
|
-
finally {
|
|
1373
|
-
release();
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
return await registration.handler(params, context);
|
|
1377
|
-
}
|
|
1378
|
-
finally {
|
|
1379
|
-
releaseCapabilities?.();
|
|
1380
|
-
}
|
|
861
|
+
return this.#modules.listActions();
|
|
862
|
+
}
|
|
863
|
+
invokeModuleAction(name, params, context) {
|
|
864
|
+
return this.#modules.invokeAction(name, params, context);
|
|
1381
865
|
}
|
|
1382
866
|
registerWorkspaceCapabilityProvider(namespaceOwner, provider) {
|
|
1383
867
|
this.#residency.registerProvider(namespaceOwner, provider);
|
|
@@ -1434,7 +918,7 @@ export default class Daemon {
|
|
|
1434
918
|
registerModule(module, owner) {
|
|
1435
919
|
if (this.#started)
|
|
1436
920
|
throw new Error("registerModule: modules must be registered before daemon start");
|
|
1437
|
-
this.#modules.
|
|
921
|
+
this.#modules.register(module, owner);
|
|
1438
922
|
}
|
|
1439
923
|
async start() {
|
|
1440
924
|
if (this.#started)
|
|
@@ -1445,7 +929,7 @@ export default class Daemon {
|
|
|
1445
929
|
await this.#configuration.capture("wait", () => TurnDispositionHandler.configuredWaitSeconds());
|
|
1446
930
|
await this.#configuration.capture("execution", () => Exec.validateConfiguration());
|
|
1447
931
|
this.#retention = await this.#configuration.capture("retention", () => new Retention(this.#db, retentionPolicy()));
|
|
1448
|
-
this.#clientOperations
|
|
932
|
+
this.#clientOperations.start();
|
|
1449
933
|
this.#started = true;
|
|
1450
934
|
// {§db-space-reclamation} — the file is brought to the policy's auto-vacuum mode before any work.
|
|
1451
935
|
if (this.#retention !== null) {
|
|
@@ -1472,7 +956,7 @@ export default class Daemon {
|
|
|
1472
956
|
this.#engine.setFunctionalityDocuments((workspaceId) => this.#functionality.documents(workspaceId));
|
|
1473
957
|
// {§question-tool} — the native request-user-input runtime, process-wide;
|
|
1474
958
|
// workspace admission gates its doc visibility and dispatch ({§workspace-capability-policy}).
|
|
1475
|
-
|
|
959
|
+
this.#engine.registerRuntimes([{
|
|
1476
960
|
tag: "question",
|
|
1477
961
|
entry: {
|
|
1478
962
|
executor: new QuestionTool({ runtime: "question", glyph: "❓" }),
|
|
@@ -1492,44 +976,9 @@ export default class Daemon {
|
|
|
1492
976
|
// (agnostic, by plurnk.kind:"scheme"). They light up http://, etc. with
|
|
1493
977
|
// no further engine change — #run wraps their context in SchemeCtxImpl ({§extension-discovery}).
|
|
1494
978
|
await this.#schemes.discoverExternal(this.#discoveryCwd);
|
|
1495
|
-
// {§module-discovery} —
|
|
1496
|
-
//
|
|
1497
|
-
|
|
1498
|
-
const discoveredModules = await discoverDaemonModules({
|
|
1499
|
-
cwd: this.#discoveryCwd, hostPaths: this.#hostPaths, registered: new Set(this.#modules.map(({ owner }) => owner)),
|
|
1500
|
-
});
|
|
1501
|
-
for (const { owner, cause } of discoveredModules.configurationErrors)
|
|
1502
|
-
this.#configuration.record(owner, cause);
|
|
1503
|
-
this.#configuration.pluginReports(discoveredModules.reports);
|
|
1504
|
-
for (const packageName of discoveredModules.skipped) {
|
|
1505
|
-
console.warn(`module discovery: '${packageName}' is discovered but untrusted (PLURNK_EXTENSIONS_TRUSTED_ONLY); not registered`);
|
|
1506
|
-
}
|
|
1507
|
-
for (const { module, owner } of discoveredModules.modules) {
|
|
1508
|
-
this.#modules.push({ module, owner });
|
|
1509
|
-
}
|
|
1510
|
-
// {§module-http-mounts} — a daemon without a listener has nothing to serve a mount on: it
|
|
1511
|
-
// leaves out every module that declares one, and says so.
|
|
1512
|
-
if (this.#http === null) {
|
|
1513
|
-
this.#modules = this.#modules.filter(({ module, owner }) => {
|
|
1514
|
-
const mounts = module.mounts ?? [];
|
|
1515
|
-
if (mounts.length === 0)
|
|
1516
|
-
return true;
|
|
1517
|
-
this.#configuration.leftOut(`module:${owner}`, `module '${owner}' mounts ${mounts.map((prefix) => `'${prefix}'`).join(", ")} and this daemon has no HTTP listener: left out`);
|
|
1518
|
-
return false;
|
|
1519
|
-
});
|
|
1520
|
-
}
|
|
1521
|
-
// {§module-contained-configuration} — what a module contained is its own configuration diagnostic.
|
|
1522
|
-
for (const { module, owner } of this.#modules) {
|
|
1523
|
-
for (const { key, message } of module.contained ?? [])
|
|
1524
|
-
this.#configuration.record(`module:${owner}`, new ConfigurationError(key, message));
|
|
1525
|
-
}
|
|
1526
|
-
this.#claimMounts();
|
|
1527
|
-
const setupSeam = this;
|
|
1528
|
-
for (const { module } of this.#modules) {
|
|
1529
|
-
if (module.stop !== undefined || module.close !== undefined)
|
|
1530
|
-
this.#moduleLifetimes.push(module);
|
|
1531
|
-
await module.setup?.(setupSeam);
|
|
1532
|
-
}
|
|
979
|
+
// {§module-discovery} — discovery composes beside the service's explicit modules, then every module sets
|
|
980
|
+
// up against this daemon as the seam ({§module-lifecycle}).
|
|
981
|
+
await this.#modules.setup(this, { cwd: this.#discoveryCwd, hostPaths: this.#hostPaths });
|
|
1533
982
|
// {§credential-authority} — the posture is never an inference: a listening daemon that
|
|
1534
983
|
// authenticates needs a module that issues, and it writes a minted secret where same-user
|
|
1535
984
|
// clients read it before any mount is admitted; a configured or absent secret leaves no file.
|
|
@@ -1548,39 +997,8 @@ export default class Daemon {
|
|
|
1548
997
|
// {§module-lifecycle} — the daemon opened its one transport before admission ({§http-host});
|
|
1549
998
|
// modules mount their routes on it here, only after capability publication and durable
|
|
1550
999
|
// lifecycle recovery are complete. None opens a listener of its own.
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
if (started !== undefined && !this.#moduleLifetimes.includes(started)) {
|
|
1554
|
-
this.#moduleLifetimes.push(started);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
this.#admitMounts();
|
|
1558
|
-
}
|
|
1559
|
-
// {§module-http-mounts} — every prefix is claimed before any module sets up: one owner per
|
|
1560
|
-
// prefix, including the optional root mount.
|
|
1561
|
-
#claimMounts() {
|
|
1562
|
-
for (const { module, owner } of this.#modules) {
|
|
1563
|
-
for (const prefix of module.mounts ?? []) {
|
|
1564
|
-
if (!HttpListener.isPrefix(prefix)) {
|
|
1565
|
-
throw new Error(`module '${owner}' declares HTTP mount '${prefix}', which is not an absolute pathname prefix`);
|
|
1566
|
-
}
|
|
1567
|
-
const holder = this.#mountClaims.get(prefix);
|
|
1568
|
-
if (holder !== undefined)
|
|
1569
|
-
throw new Error(`HTTP mount '${prefix}' is claimed by both '${holder}' and '${owner}'`);
|
|
1570
|
-
this.#mountClaims.set(prefix, owner);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
// {§module-http-mounts} — the listener is admitted once every module has started and every
|
|
1575
|
-
// claim is mounted, so readiness never depends on registration order.
|
|
1576
|
-
#admitMounts() {
|
|
1577
|
-
if (this.#http === null)
|
|
1578
|
-
return;
|
|
1579
|
-
for (const [prefix, owner] of this.#mountClaims) {
|
|
1580
|
-
if (!this.#mounted.has(prefix))
|
|
1581
|
-
throw new Error(`module '${owner}' claimed HTTP mount '${prefix}' but did not mount it at start`);
|
|
1582
|
-
}
|
|
1583
|
-
this.#http.admit();
|
|
1000
|
+
await this.#modules.start(this);
|
|
1001
|
+
this.#modules.admitMounts();
|
|
1584
1002
|
}
|
|
1585
1003
|
async #recoverLifecycle() {
|
|
1586
1004
|
await this.#db.recovery_settle_process_owners({});
|
|
@@ -1592,7 +1010,7 @@ export default class Daemon {
|
|
|
1592
1010
|
for (const source of orphanSources) {
|
|
1593
1011
|
await this.#drains.reconcileOrphanedMessages(source.worker_id, source.loop_id);
|
|
1594
1012
|
}
|
|
1595
|
-
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8");
|
|
1013
|
+
const systemPrompt = await readFile(Paths.instructionsSystem, "utf8"); // {§card-read-per-wake}
|
|
1596
1014
|
const queued = await this.#db.recovery_queued_workers.all({});
|
|
1597
1015
|
const invalidQueued = queued.find(({ origin }) => origin !== "model");
|
|
1598
1016
|
if (invalidQueued !== undefined) {
|
|
@@ -1631,21 +1049,14 @@ export default class Daemon {
|
|
|
1631
1049
|
this.#engine.cancelAllProposals("daemon_stopping");
|
|
1632
1050
|
this.#engine.cancelDerivations(derivationAbort);
|
|
1633
1051
|
this.#drains.beginStop("daemon_stopping");
|
|
1634
|
-
this.#clientOperations.
|
|
1052
|
+
this.#clientOperations.beginStop("daemon_stopping");
|
|
1635
1053
|
this.#residency.beginStop();
|
|
1636
1054
|
const shutdown = deadline ?? new StopDeadline();
|
|
1637
1055
|
// Stop accepting external work immediately, but do not await listener
|
|
1638
1056
|
// closure before cancelling active workers: an SSE connection may itself be
|
|
1639
1057
|
// waiting for the worker cancellation that follows.
|
|
1640
|
-
const
|
|
1641
|
-
|
|
1642
|
-
const modulePhase = async (phase) => {
|
|
1643
|
-
const results = await Promise.allSettled(modules.map((module) => Promise.resolve().then(() => module[phase]?.())));
|
|
1644
|
-
const errors = results.filter((result) => result.status === "rejected").map(({ reason }) => reason);
|
|
1645
|
-
if (errors.length > 0)
|
|
1646
|
-
throw new AggregateError(errors, `module ${phase} failed`);
|
|
1647
|
-
};
|
|
1648
|
-
const moduleStop = shutdown.settle("modules stop", () => modulePhase("stop"));
|
|
1058
|
+
const modulePhases = this.#modules.beginStop();
|
|
1059
|
+
const moduleStop = shutdown.settle("modules stop", () => modulePhases.stop());
|
|
1649
1060
|
// Drain order: (1) tell the supervisor to abort worker scopes so
|
|
1650
1061
|
// strike paths don't keep going, (2) await its active drains
|
|
1651
1062
|
// to completion, (3) drain streaming schemes' background work
|
|
@@ -1664,9 +1075,7 @@ export default class Daemon {
|
|
|
1664
1075
|
// process may exit with the WAL in place (SQLite recovers) rather than
|
|
1665
1076
|
// leak as a live-but-wedged tree.
|
|
1666
1077
|
const drainResult = await shutdown.settle("drains idle", () => this.#drains.idle());
|
|
1667
|
-
const clientResult = await shutdown.settle("client operations idle",
|
|
1668
|
-
await Promise.all([...this.#clientLoops.values()].map(({ settled }) => settled));
|
|
1669
|
-
});
|
|
1078
|
+
const clientResult = await shutdown.settle("client operations idle", () => this.#clientOperations.idle());
|
|
1670
1079
|
const residencyResult = await shutdown.settle("workspace Functionality transitions", () => this.#residency.settle());
|
|
1671
1080
|
// A boundary publication queued behind the last turn settles before
|
|
1672
1081
|
// modules close and before the DB goes away ({§functionality-publication}).
|
|
@@ -1682,7 +1091,7 @@ export default class Daemon {
|
|
|
1682
1091
|
// conclusion notifications. Join the supervisor-owned async tails only
|
|
1683
1092
|
// after those producers settle, before the caller may close SQLite.
|
|
1684
1093
|
const wakeResult = await shutdown.settle("drains idle (wake)", () => this.#drains.idle());
|
|
1685
|
-
const moduleCloseResult = await shutdown.settle("modules close", () =>
|
|
1094
|
+
const moduleCloseResult = await shutdown.settle("modules close", () => modulePhases.close());
|
|
1686
1095
|
// {§db-maintenance-optimize} — the last database step before the caller closes SQLite:
|
|
1687
1096
|
// planner statistics refreshed on the writer, bounded by SQLite's own analysis limit.
|
|
1688
1097
|
// {§retention-policy} — the operator's retention runs once more before the statistics.
|
|
@@ -1714,10 +1123,11 @@ export default class Daemon {
|
|
|
1714
1123
|
// (SSE, WS) add themselves here as they land.
|
|
1715
1124
|
async #drainStreamingSchemes() {
|
|
1716
1125
|
const exec = this.#schemes.get("exec");
|
|
1126
|
+
if (!(exec instanceof Exec))
|
|
1127
|
+
return;
|
|
1717
1128
|
// {§exec-lifetime} — a detached spawn ends with the daemon: nothing else ever aborts it.
|
|
1718
|
-
exec
|
|
1719
|
-
|
|
1720
|
-
await exec.idle();
|
|
1129
|
+
exec.abortDetached();
|
|
1130
|
+
await exec.idle();
|
|
1721
1131
|
}
|
|
1722
1132
|
/**
|
|
1723
1133
|
* Emit a stream/event notification scoped to the workspace containing the
|
|
@@ -1757,7 +1167,7 @@ export default class Daemon {
|
|
|
1757
1167
|
// scheme registry rather than exposing that registry across the boundary.
|
|
1758
1168
|
#workerHasActiveStreams(workerId) {
|
|
1759
1169
|
const exec = this.#schemes.get("exec");
|
|
1760
|
-
return exec
|
|
1170
|
+
return exec instanceof Exec && exec.hasActiveSpawns(workerId);
|
|
1761
1171
|
}
|
|
1762
1172
|
// {§methods-event-subscribe} — subscriber failures are transport-local:
|
|
1763
1173
|
// log them at this boundary and never re-enter engine control flow.
|
|
@@ -1771,6 +1181,14 @@ export default class Daemon {
|
|
|
1771
1181
|
}
|
|
1772
1182
|
}
|
|
1773
1183
|
}
|
|
1184
|
+
// {§notifications-worker-policy} A worker's generation policy, as worker.model.get reads it, to its workspace.
|
|
1185
|
+
async #announceWorkerPolicy(workerId) {
|
|
1186
|
+
const workspaceId = (await this.#db.drain_get_worker_workspace.get({ worker_id: workerId }))?.workspace_id;
|
|
1187
|
+
if (workspaceId === undefined)
|
|
1188
|
+
return;
|
|
1189
|
+
const { model, spawnModel } = await this.#workerModels.readWorkerModel({ workspaceId, workerId });
|
|
1190
|
+
this.#broadcast({ workspaceId }, "worker/policy", { workerId, model, spawnModel });
|
|
1191
|
+
}
|
|
1774
1192
|
#broadcast(target, method, params) {
|
|
1775
1193
|
observedSync(// {§observability-boundary}
|
|
1776
1194
|
"stream.broadcast", { method, ...(target === "all" ? {} : { "workspace.id": target.workspaceId }) }, () => {
|
|
@@ -1792,5 +1210,11 @@ export default class Daemon {
|
|
|
1792
1210
|
async readLog(...args) {
|
|
1793
1211
|
return this.#reads.readLog(...args);
|
|
1794
1212
|
}
|
|
1213
|
+
async readEntryChannel(...args) {
|
|
1214
|
+
return this.#reads.readEntryChannel(...args);
|
|
1215
|
+
}
|
|
1216
|
+
async readStreams(...args) {
|
|
1217
|
+
return this.#reads.readStreams(...args);
|
|
1218
|
+
}
|
|
1795
1219
|
}
|
|
1796
1220
|
//# sourceMappingURL=Daemon.js.map
|