@plurnk/plurnk-service 4.2.1 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +7 -2
- package/README.md +3 -1
- package/SPEC.md +1977 -1397
- 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 +1 -1
- package/dist/completions/plurnk-service.bash +1 -1
- package/dist/completions/plurnk-service.fish +1 -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 +1 -1
- package/dist/core/AmbientMembers.d.ts.map +1 -1
- package/dist/core/AmbientMembers.js +5 -2
- package/dist/core/AmbientMembers.js.map +1 -1
- 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 +4 -4
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/DataStatementRunner.d.ts.map +1 -1
- package/dist/core/DataStatementRunner.js +15 -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 +210 -130
- 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.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 +174 -164
- 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.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +4 -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 +5 -0
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +55 -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/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/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/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 +6 -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 +136 -79
- 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.map +1 -1
- package/dist/schemes/_entry-find.js +187 -139
- 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 +250 -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 +6 -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 +19 -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 +30 -16
- 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 +11 -21
- 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
|
@@ -3,7 +3,6 @@ import { aggregateProviderAccounting } from "@plurnk/plurnk-providers";
|
|
|
3
3
|
import { routeForSpec } from "./model-route.js";
|
|
4
4
|
import ChannelWrite from "../core/ChannelWrite.js";
|
|
5
5
|
import ErrorDetail from "../core/ErrorDetail.js";
|
|
6
|
-
import LoopLifecycle from "../core/LoopLifecycle.js";
|
|
7
6
|
import MessageResources from "../core/MessageResources.js";
|
|
8
7
|
import { Knob } from "@plurnk/plurnk-meta";
|
|
9
8
|
import Results, { OperationFailureError } from "../core/results.js";
|
|
@@ -23,13 +22,13 @@ export default class DrainSupervisor {
|
|
|
23
22
|
#loopAttributions;
|
|
24
23
|
#discardLoopNotices;
|
|
25
24
|
#cancelSubscription;
|
|
25
|
+
#subscriptionConcluded;
|
|
26
|
+
#reapLoopStreams;
|
|
26
27
|
#hasActiveStreams;
|
|
27
28
|
#readSystemPrompt;
|
|
28
29
|
#emitLogEntry;
|
|
29
30
|
#emit;
|
|
30
31
|
#completeAbandonedMessages;
|
|
31
|
-
// The handle is the drain identity. Start/exit compare by reference so an
|
|
32
|
-
// exiting drain cannot clobber a successor that raced in.
|
|
33
32
|
#activeDrains = new Map();
|
|
34
33
|
#settlementTasks = new Set();
|
|
35
34
|
#settlementFailures = [];
|
|
@@ -40,8 +39,10 @@ export default class DrainSupervisor {
|
|
|
40
39
|
#drainLocks = new Map();
|
|
41
40
|
#admissionLocks = new Map();
|
|
42
41
|
#completionWakeGates = new Map();
|
|
42
|
+
// {§notifications-stream-concluded} A closed subscription's `stream/concluded`, until it is published.
|
|
43
|
+
#streamAnnouncements = new Map();
|
|
43
44
|
#acceptingWork = false;
|
|
44
|
-
constructor({ db, lifecycle, injectPrompt, assertInjectionCompatibility, runLoop, loopUsage, loopAttributions, discardLoopNotices, cancelSubscription, hasActiveStreams, readSystemPrompt, emitLogEntry, emit, completeAbandonedMessages, }) {
|
|
45
|
+
constructor({ db, lifecycle, injectPrompt, assertInjectionCompatibility, runLoop, loopUsage, loopAttributions, discardLoopNotices, cancelSubscription, subscriptionConcluded, reapLoopStreams, hasActiveStreams, readSystemPrompt, emitLogEntry, emit, completeAbandonedMessages, }) {
|
|
45
46
|
this.#completeAbandonedMessages = completeAbandonedMessages ?? (async () => { });
|
|
46
47
|
this.#db = db;
|
|
47
48
|
this.#lifecycle = lifecycle;
|
|
@@ -52,6 +53,8 @@ export default class DrainSupervisor {
|
|
|
52
53
|
this.#loopAttributions = loopAttributions;
|
|
53
54
|
this.#discardLoopNotices = discardLoopNotices;
|
|
54
55
|
this.#cancelSubscription = cancelSubscription;
|
|
56
|
+
this.#subscriptionConcluded = subscriptionConcluded;
|
|
57
|
+
this.#reapLoopStreams = reapLoopStreams;
|
|
55
58
|
this.#hasActiveStreams = hasActiveStreams;
|
|
56
59
|
this.#readSystemPrompt = readSystemPrompt;
|
|
57
60
|
this.#emitLogEntry = emitLogEntry;
|
|
@@ -153,7 +156,7 @@ export default class DrainSupervisor {
|
|
|
153
156
|
return { action: "enqueued_new_loop", ...accepted };
|
|
154
157
|
}));
|
|
155
158
|
const started = await this.ensureDrain({ workspaceId, workerId, systemPrompt: args.systemPrompt });
|
|
156
|
-
return { ...delivery, ...
|
|
159
|
+
return { ...delivery, ...started };
|
|
157
160
|
}
|
|
158
161
|
async #enqueueFreshLoop(args) {
|
|
159
162
|
// {§worker-model-selection} — resolve the complete route before persistence;
|
|
@@ -190,254 +193,247 @@ export default class DrainSupervisor {
|
|
|
190
193
|
// The drain runs under the worker's cancellation scope (shared with the
|
|
191
194
|
// execs its loops spawn), so loop.cancel/shutdown abort it as a unit.
|
|
192
195
|
const controller = this.#workerSignal(workerId);
|
|
193
|
-
const handle = {
|
|
194
|
-
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.#activeDrains.delete(workerId);
|
|
224
|
-
}
|
|
225
|
-
return claimed;
|
|
226
|
-
});
|
|
227
|
-
if (loopRow === undefined)
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
currentLoopId = loopRow.id;
|
|
231
|
-
this.#clearLoopTimer(loopRow.id);
|
|
232
|
-
await this.#emitLoopStatus(workspaceId, workerId, loopRow.id, 102);
|
|
233
|
-
const onSettled = async (logEntryId) => {
|
|
234
|
-
await this.#emitLogEntry(workspaceId, logEntryId).catch((error) => {
|
|
235
|
-
console.error("log/entry broadcast failed:", error instanceof Error ? error.message : String(error));
|
|
236
|
-
});
|
|
237
|
-
};
|
|
238
|
-
const result = await observed("loop.run", { workspaceId, workerId, "loop.id": loopRow.id }, async (span) => {
|
|
239
|
-
const loopResult = await this.#runLoop({
|
|
240
|
-
workspaceId,
|
|
241
|
-
workerId,
|
|
242
|
-
loopId: loopRow.id,
|
|
243
|
-
maxTurns: loopRow.max_turns,
|
|
244
|
-
prompt: loopRow.prompt,
|
|
245
|
-
systemPrompt,
|
|
246
|
-
signal: controller.signal,
|
|
247
|
-
onSettled,
|
|
248
|
-
});
|
|
249
|
-
span.setAttribute("status", loopResult.result.status);
|
|
250
|
-
recordCounter(LOOP_TERMINALS, { status: loopResult.result.status });
|
|
251
|
-
return loopResult;
|
|
252
|
-
});
|
|
253
|
-
if (result.result.status === 202) {
|
|
254
|
-
await this.#withDrainLock(workerId, () => this.#emitLoopStatus(workspaceId, workerId, loopRow.id, 202));
|
|
255
|
-
// The loop parked — suspended, not terminated. Leave it at 202
|
|
256
|
-
// (resumable); no loop/terminated, no orphan-reconcile. A stream conclusion
|
|
257
|
-
// through handleWakeWorker re-queues it; the durable wait deadline also
|
|
258
|
-
// wakes it to inspect ({§worker-wait-timing}). {§worker-lifecycle-wake-liveness}.
|
|
259
|
-
await this.scheduleWakes(workspaceId, workerId, systemPrompt);
|
|
260
|
-
// Serialize the park boundary against message injection.
|
|
261
|
-
// Whichever side arrives first owns a wake edge: injection wakes
|
|
262
|
-
// an already-parked loop, while this check wakes a message written
|
|
263
|
-
// just before runLoop finished parking.
|
|
264
|
-
const messageWaiting = await this.#withDrainLock(workerId, async () => {
|
|
265
|
-
const unpublished = await this.#db.drain_unpublished_messages_for_loop.get({ loop_id: loopRow.id });
|
|
266
|
-
if (unpublished === undefined)
|
|
267
|
-
return false;
|
|
268
|
-
return this.#wakeLoop(workspaceId, workerId, loopRow.id);
|
|
269
|
-
});
|
|
270
|
-
if (messageWaiting) {
|
|
271
|
-
currentLoopId = null;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
// {§loop-wake-identity}: events observed by another loop
|
|
275
|
-
// cannot consume this loop's completion wake.
|
|
276
|
-
if (await this.#withDrainLock(workerId, () => this.#wakeLoop(workspaceId, workerId, loopRow.id, { eventOnly: true }))) {
|
|
277
|
-
currentLoopId = null;
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
// {§loop-wake-identity}: the wait now belongs to its completion
|
|
281
|
-
// obligations and inherited observation, not this drain.
|
|
282
|
-
currentLoopId = null;
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
const [usage, attributions, turnIds] = await Promise.all([
|
|
286
|
-
this.#loopUsage(loopRow.id),
|
|
287
|
-
this.#loopAttributions(loopRow.id),
|
|
288
|
-
this.#lifecycle.turnIds(loopRow.id),
|
|
289
|
-
]);
|
|
290
|
-
await this.#publishTermination(workspaceId, {
|
|
291
|
-
workerId,
|
|
292
|
-
loopId: loopRow.id,
|
|
293
|
-
result: result.result,
|
|
294
|
-
hitMaxTurns: result.hitMaxTurns,
|
|
295
|
-
turnIds,
|
|
296
|
-
usage,
|
|
297
|
-
attributions,
|
|
298
|
-
});
|
|
299
|
-
loopsDrained++;
|
|
300
|
-
const loopResult = {
|
|
301
|
-
loopId: loopRow.id,
|
|
302
|
-
turnIds,
|
|
303
|
-
result: result.result,
|
|
304
|
-
hitMaxTurns: result.hitMaxTurns,
|
|
305
|
-
usage,
|
|
306
|
-
attributions,
|
|
307
|
-
};
|
|
308
|
-
lastResult = loopResult;
|
|
309
|
-
if (!firstSettled) {
|
|
310
|
-
firstSettled = true;
|
|
311
|
-
resolveFirst(loopResult);
|
|
312
|
-
}
|
|
313
|
-
// A message this loop ended before publishing (a wake conclusion or a
|
|
314
|
-
// runLoop-while-active arrival) is promoted to a fresh queued loop so it's
|
|
315
|
-
// never silently dropped ({§message-loop-containment}).
|
|
316
|
-
await this.reconcileOrphanedMessages(workerId, loopRow.id);
|
|
317
|
-
await this.#abandonUnanswered(workspaceId, workerId, loopRow.id, result.result.status);
|
|
196
|
+
const handle = { controller, promise: Promise.resolve() };
|
|
197
|
+
const firstLoop = Promise.withResolvers();
|
|
198
|
+
const first = { settled: false, resolve: firstLoop.resolve, reject: firstLoop.reject };
|
|
199
|
+
const drainPromise = this.#drain({ workspaceId, workerId, systemPrompt, controller, handle, first });
|
|
200
|
+
handle.promise = drainPromise;
|
|
201
|
+
this.#activeDrains.set(workerId, handle);
|
|
202
|
+
// Swallow unhandled rejections (drain aborts with no awaiter); the
|
|
203
|
+
// error already surfaced via firstLoopPromise or was logged inside.
|
|
204
|
+
drainPromise.catch(() => { });
|
|
205
|
+
firstLoop.promise.catch(() => { });
|
|
206
|
+
return { firstLoopPromise: firstLoop.promise, drainPromise };
|
|
207
|
+
}
|
|
208
|
+
// The drain itself: claim, run, then park or conclude, until the queue is empty under the lock. A
|
|
209
|
+
// cancellation or a failure settles the loop in flight before the drain exits, the first loop's
|
|
210
|
+
// settlement is delivered exactly once, and the exit always schedules the worker's wakes.
|
|
211
|
+
async #drain(scope) {
|
|
212
|
+
const { workspaceId, workerId, systemPrompt, controller, handle, first } = scope;
|
|
213
|
+
let loopsDrained = 0;
|
|
214
|
+
let lastResult = null;
|
|
215
|
+
let currentLoopId = null; // the loop being drained — for abort→499 settlement
|
|
216
|
+
try {
|
|
217
|
+
while (true) {
|
|
218
|
+
controller.signal.throwIfAborted();
|
|
219
|
+
const loopRow = await this.#claimLoop(workerId, handle);
|
|
220
|
+
if (loopRow === undefined)
|
|
221
|
+
break;
|
|
222
|
+
currentLoopId = loopRow.id;
|
|
223
|
+
const result = await this.#runClaimedLoop(scope, loopRow);
|
|
224
|
+
if (result.result.status === 202) {
|
|
225
|
+
await this.#parkLoop(scope, loopRow.id);
|
|
318
226
|
currentLoopId = null;
|
|
227
|
+
continue;
|
|
319
228
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
let usage = {
|
|
327
|
-
accounting: aggregateProviderAccounting([]),
|
|
328
|
-
curationWeight: null,
|
|
329
|
-
curationBudget: null,
|
|
330
|
-
contextTokens: null,
|
|
331
|
-
contextCapacity: null,
|
|
332
|
-
meta: {},
|
|
333
|
-
};
|
|
334
|
-
let attributions = [];
|
|
335
|
-
const message = ErrorDetail.preview(controller.signal.reason ?? "user_cancelled")
|
|
336
|
-
|| "no reason was supplied";
|
|
337
|
-
if (currentLoopId !== null) {
|
|
338
|
-
// {§methods-loop-cancel}/{§worker-lifecycle-terminal-result} —
|
|
339
|
-
// persist the exact 499 cancellation result before broadcasting it.
|
|
340
|
-
const cancelled = await this.#lifecycle.finish(currentLoopId, Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
|
|
341
|
-
reason: message,
|
|
342
|
-
stage: "loop",
|
|
343
|
-
retryable: false,
|
|
344
|
-
}), { terminatedBy: "cancel" });
|
|
345
|
-
[usage, attributions] = await Promise.all([
|
|
346
|
-
this.#loopUsage(currentLoopId),
|
|
347
|
-
this.#loopAttributions(currentLoopId),
|
|
348
|
-
]);
|
|
349
|
-
if (cancelled !== null) {
|
|
350
|
-
await this.#publishTermination(workspaceId, {
|
|
351
|
-
workerId,
|
|
352
|
-
loopId: currentLoopId,
|
|
353
|
-
result: cancelled,
|
|
354
|
-
hitMaxTurns: false,
|
|
355
|
-
turnIds: await this.#lifecycle.turnIds(currentLoopId),
|
|
356
|
-
usage,
|
|
357
|
-
attributions,
|
|
358
|
-
});
|
|
359
|
-
await this.#abandonUnanswered(workspaceId, workerId, currentLoopId, cancelled.status);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (!firstSettled) {
|
|
363
|
-
firstSettled = true;
|
|
364
|
-
resolveFirst({
|
|
365
|
-
loopId: currentLoopId ?? 0,
|
|
366
|
-
turnIds: [],
|
|
367
|
-
result: currentLoopId === null
|
|
368
|
-
? Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
|
|
369
|
-
reason: message,
|
|
370
|
-
stage: "loop",
|
|
371
|
-
retryable: false,
|
|
372
|
-
})
|
|
373
|
-
: await this.#lifecycle.result(currentLoopId)
|
|
374
|
-
?? Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
|
|
375
|
-
reason: message,
|
|
376
|
-
stage: "loop",
|
|
377
|
-
retryable: false,
|
|
378
|
-
}),
|
|
379
|
-
hitMaxTurns: false,
|
|
380
|
-
usage,
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
// {§worker-lifecycle-terminal-result} — a non-abort drain
|
|
386
|
-
// failure becomes an exact durable 500 and terminal notification;
|
|
387
|
-
// daemon diagnostics retain the complete caught error.
|
|
388
|
-
console.error(`drain error (workspace ${workspaceId}, worker ${workerId}, loop ${currentLoopId ?? "?"}):`, err);
|
|
389
|
-
if (currentLoopId !== null) {
|
|
390
|
-
const failure = err instanceof OperationFailureError
|
|
391
|
-
? err.result
|
|
392
|
-
: Results.failure("daemon:drain", "loop-threw", 500, "The loop failed outside its operation result contract.", {}, {
|
|
393
|
-
stage: "loop",
|
|
394
|
-
retryable: false,
|
|
395
|
-
});
|
|
396
|
-
const settled = await this.#lifecycle.finish(currentLoopId, failure)
|
|
397
|
-
?? await this.#lifecycle.result(currentLoopId);
|
|
398
|
-
if (settled === null) {
|
|
399
|
-
throw new Error(`drain could not settle loop ${currentLoopId}`, { cause: err });
|
|
400
|
-
}
|
|
401
|
-
const [usage, attributions] = await Promise.all([
|
|
402
|
-
this.#loopUsage(currentLoopId),
|
|
403
|
-
this.#loopAttributions(currentLoopId),
|
|
404
|
-
]);
|
|
405
|
-
await this.#publishTermination(workspaceId, {
|
|
406
|
-
workerId,
|
|
407
|
-
loopId: currentLoopId,
|
|
408
|
-
result: settled,
|
|
409
|
-
hitMaxTurns: false,
|
|
410
|
-
turnIds: await this.#lifecycle.turnIds(currentLoopId),
|
|
411
|
-
usage,
|
|
412
|
-
attributions,
|
|
413
|
-
});
|
|
414
|
-
await this.#abandonUnanswered(workspaceId, workerId, currentLoopId, settled.status);
|
|
415
|
-
}
|
|
416
|
-
if (!firstSettled) {
|
|
417
|
-
firstSettled = true;
|
|
418
|
-
rejectFirst(err);
|
|
419
|
-
}
|
|
229
|
+
const { loopResult, announced } = await this.#concludeLoop(scope, loopRow.id, result);
|
|
230
|
+
loopsDrained++;
|
|
231
|
+
lastResult = loopResult;
|
|
232
|
+
if (!first.settled) {
|
|
233
|
+
first.settled = true;
|
|
234
|
+
first.resolve(loopResult);
|
|
420
235
|
}
|
|
421
|
-
|
|
236
|
+
// A message this loop ended before publishing (a wake conclusion or a
|
|
237
|
+
// runLoop-while-active arrival) is promoted to a fresh queued loop so it's
|
|
238
|
+
// never silently dropped ({§message-loop-containment}).
|
|
239
|
+
await this.reconcileOrphanedMessages(workerId, loopRow.id);
|
|
240
|
+
if (announced)
|
|
241
|
+
await this.#abandonUnanswered(workspaceId, workerId, loopRow.id, result.result.status);
|
|
242
|
+
currentLoopId = null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
if (controller.signal.aborted)
|
|
247
|
+
await this.#settleCancelledDrain(scope, currentLoopId);
|
|
248
|
+
else
|
|
249
|
+
await this.#settleFailedDrain(scope, currentLoopId, err);
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (!first.settled) {
|
|
254
|
+
first.settled = true;
|
|
255
|
+
first.reject(new Error("drain exited without producing a result"));
|
|
256
|
+
}
|
|
257
|
+
if (this.#activeDrains.get(workerId) === handle)
|
|
258
|
+
this.#activeDrains.delete(workerId);
|
|
259
|
+
await this.scheduleWakes(workspaceId, workerId, systemPrompt);
|
|
260
|
+
}
|
|
261
|
+
return { loopsDrained, lastResult };
|
|
262
|
+
}
|
|
263
|
+
// {§worker-lifecycle-no-lost-loop} The next queued loop. An empty queue is re-claimed UNDER the per-worker
|
|
264
|
+
// drain lock (R4 / I1), serialized against ensureDrain so a concurrent inject can't start a 2nd drain
|
|
265
|
+
// in the gap; the registry slot is relinquished only if it is empty there too, and a loop that raced in
|
|
266
|
+
// is returned and run — the drain stays registered, so there's no transient delete for ensureDrain to
|
|
267
|
+
// catch.
|
|
268
|
+
async #claimLoop(workerId, handle) {
|
|
269
|
+
const claim = () => this.#db.drain_claim_next_loop.get({ worker_id: workerId });
|
|
270
|
+
const claimed = await claim();
|
|
271
|
+
if (claimed !== undefined)
|
|
272
|
+
return claimed;
|
|
273
|
+
return this.#withDrainLock(workerId, async () => {
|
|
274
|
+
const reclaimed = await claim();
|
|
275
|
+
if (reclaimed === undefined && this.#activeDrains.get(workerId) === handle) {
|
|
276
|
+
this.#activeDrains.delete(workerId);
|
|
277
|
+
}
|
|
278
|
+
return reclaimed;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// One claimed loop under observation: its timer cleared, 102 announced, every settled row broadcast.
|
|
282
|
+
async #runClaimedLoop(scope, loopRow) {
|
|
283
|
+
const { workspaceId, workerId, systemPrompt, controller } = scope;
|
|
284
|
+
this.#clearLoopTimer(loopRow.id);
|
|
285
|
+
await this.#emitLoopStatus(workspaceId, workerId, loopRow.id, 102);
|
|
286
|
+
const onSettled = async (logEntryId) => {
|
|
287
|
+
await this.#emitLogEntry(workspaceId, logEntryId).catch((error) => {
|
|
288
|
+
console.error("log/entry broadcast failed:", error instanceof Error ? error.message : String(error));
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
return observed("loop.run", { workspaceId, workerId, "loop.id": loopRow.id }, async (span) => {
|
|
292
|
+
const loopResult = await this.#runLoop({
|
|
293
|
+
workspaceId,
|
|
294
|
+
workerId,
|
|
295
|
+
loopId: loopRow.id,
|
|
296
|
+
maxTurns: loopRow.max_turns,
|
|
297
|
+
prompt: loopRow.prompt,
|
|
298
|
+
systemPrompt,
|
|
299
|
+
signal: controller.signal,
|
|
300
|
+
onSettled,
|
|
301
|
+
});
|
|
302
|
+
span.setAttribute("status", loopResult.result.status);
|
|
303
|
+
recordCounter(LOOP_TERMINALS, { status: loopResult.result.status });
|
|
304
|
+
return loopResult;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// A parked loop (202) is suspended, not terminated: it stays resumable, with no loop/terminated and no
|
|
308
|
+
// orphan reconcile. A stream conclusion through handleWakeWorker re-queues it; the durable wait deadline
|
|
309
|
+
// also wakes it to inspect ({§worker-wait-timing}). {§worker-lifecycle-wake-liveness}
|
|
310
|
+
async #parkLoop(scope, loopId) {
|
|
311
|
+
const { workspaceId, workerId, systemPrompt } = scope;
|
|
312
|
+
await this.#withDrainLock(workerId, () => this.#emitLoopStatus(workspaceId, workerId, loopId, 202));
|
|
313
|
+
await this.scheduleWakes(workspaceId, workerId, systemPrompt);
|
|
314
|
+
// Serialize the park boundary against message injection.
|
|
315
|
+
// Whichever side arrives first owns a wake edge: injection wakes
|
|
316
|
+
// an already-parked loop, while this check wakes a message written
|
|
317
|
+
// just before runLoop finished parking.
|
|
318
|
+
const messageWaiting = await this.#withDrainLock(workerId, async () => {
|
|
319
|
+
const unpublished = await this.#db.drain_unpublished_messages_for_loop.get({ loop_id: loopId });
|
|
320
|
+
if (unpublished === undefined)
|
|
321
|
+
return false;
|
|
322
|
+
return this.#wakeLoop(workspaceId, workerId, loopId);
|
|
323
|
+
});
|
|
324
|
+
if (messageWaiting)
|
|
325
|
+
return;
|
|
326
|
+
// {§loop-wake-identity}: events observed by another loop cannot consume this loop's completion
|
|
327
|
+
// wake. Whether or not one wakes it here, the wait now belongs to its completion obligations and
|
|
328
|
+
// inherited observation, not this drain.
|
|
329
|
+
await this.#withDrainLock(workerId, () => this.#wakeLoop(workspaceId, workerId, loopId, { eventOnly: true }));
|
|
330
|
+
}
|
|
331
|
+
// A loop's terminal: its streams reaped, usage, attributions and turns gathered, loop/terminated published, and
|
|
332
|
+
// the result the drain reports. A loop a cancellation retired is reaped and announced by that cancellation instead.
|
|
333
|
+
async #concludeLoop(scope, loopId, result) {
|
|
334
|
+
const { workspaceId, workerId } = scope;
|
|
335
|
+
const cancelled = await this.#lifecycle.cancelled(loopId);
|
|
336
|
+
// {§notifications-loop-terminated} {§exec-lifetime} The streams the loop's terminal reaps conclude before it is announced.
|
|
337
|
+
if (!cancelled)
|
|
338
|
+
await this.#reapLoop(loopId);
|
|
339
|
+
const [usage, attributions, turnIds] = await Promise.all([
|
|
340
|
+
this.#loopUsage(loopId),
|
|
341
|
+
this.#loopAttributions(loopId),
|
|
342
|
+
this.#lifecycle.turnIds(loopId),
|
|
343
|
+
]);
|
|
344
|
+
const loopResult = {
|
|
345
|
+
loopId: loopId,
|
|
346
|
+
turnIds,
|
|
347
|
+
result: result.result,
|
|
348
|
+
hitMaxTurns: result.hitMaxTurns,
|
|
349
|
+
usage,
|
|
350
|
+
attributions,
|
|
351
|
+
};
|
|
352
|
+
if (!cancelled)
|
|
353
|
+
await this.#publishTermination(workspaceId, { workerId, ...loopResult });
|
|
354
|
+
return { loopResult, announced: !cancelled };
|
|
355
|
+
}
|
|
356
|
+
// {§methods-loop-cancel} loop.cancel / shutdown aborted the live drain. A cancellation is the loop's
|
|
357
|
+
// TERMINAL state (499), delivered via loop/terminated (runLoop no longer blocks to return it), and the
|
|
358
|
+
// exact 499 is persisted before it is broadcast ({§worker-lifecycle-terminal-result}).
|
|
359
|
+
async #settleCancelledDrain(scope, currentLoopId) {
|
|
360
|
+
const { workspaceId, workerId, controller, first } = scope;
|
|
361
|
+
const message = ErrorDetail.preview(controller.signal.reason ?? "user_cancelled")
|
|
362
|
+
|| "no reason was supplied";
|
|
363
|
+
const cancelledResult = () => Results.failure("lifecycle:cancel", "loop-cancelled", 499, `The loop was cancelled: ${message}.`, {}, {
|
|
364
|
+
reason: message,
|
|
365
|
+
stage: "loop",
|
|
366
|
+
retryable: false,
|
|
367
|
+
});
|
|
368
|
+
let usage = {
|
|
369
|
+
accounting: aggregateProviderAccounting([]),
|
|
370
|
+
curationWeight: null,
|
|
371
|
+
curationBudget: null,
|
|
372
|
+
contextTokens: null,
|
|
373
|
+
contextCapacity: null,
|
|
374
|
+
meta: {},
|
|
375
|
+
};
|
|
376
|
+
let attributions = [];
|
|
377
|
+
if (currentLoopId !== null) {
|
|
378
|
+
const cancelled = await this.#lifecycle.finish(currentLoopId, cancelledResult(), { terminatedBy: "cancel" });
|
|
379
|
+
[usage, attributions] = await Promise.all([
|
|
380
|
+
this.#loopUsage(currentLoopId),
|
|
381
|
+
this.#loopAttributions(currentLoopId),
|
|
382
|
+
]);
|
|
383
|
+
if (cancelled !== null) {
|
|
384
|
+
await this.#reapLoop(currentLoopId);
|
|
385
|
+
await this.#publishTermination(workspaceId, {
|
|
386
|
+
workerId,
|
|
387
|
+
loopId: currentLoopId,
|
|
388
|
+
result: cancelled,
|
|
389
|
+
hitMaxTurns: false,
|
|
390
|
+
turnIds: await this.#lifecycle.turnIds(currentLoopId),
|
|
391
|
+
usage,
|
|
392
|
+
attributions,
|
|
393
|
+
});
|
|
394
|
+
await this.#abandonUnanswered(workspaceId, workerId, currentLoopId, cancelled.status);
|
|
422
395
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
396
|
+
}
|
|
397
|
+
if (!first.settled) {
|
|
398
|
+
first.settled = true;
|
|
399
|
+
first.resolve({
|
|
400
|
+
loopId: currentLoopId ?? 0,
|
|
401
|
+
turnIds: [],
|
|
402
|
+
result: currentLoopId === null
|
|
403
|
+
? cancelledResult()
|
|
404
|
+
: await this.#lifecycle.result(currentLoopId) ?? cancelledResult(),
|
|
405
|
+
hitMaxTurns: false,
|
|
406
|
+
usage,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// {§worker-lifecycle-terminal-result} A non-abort drain failure becomes an exact durable 500 and terminal
|
|
411
|
+
// notification; daemon diagnostics retain the complete caught error.
|
|
412
|
+
async #settleFailedDrain(scope, currentLoopId, err) {
|
|
413
|
+
const { workspaceId, workerId, first } = scope;
|
|
414
|
+
console.error(`drain error (workspace ${workspaceId}, worker ${workerId}, loop ${currentLoopId ?? "?"}):`, err);
|
|
415
|
+
if (currentLoopId !== null) {
|
|
416
|
+
const failure = err instanceof OperationFailureError
|
|
417
|
+
? err.result
|
|
418
|
+
: Results.failure("daemon:drain", "loop-threw", 500, "The loop failed outside its operation result contract.", {}, {
|
|
419
|
+
stage: "loop",
|
|
420
|
+
retryable: false,
|
|
421
|
+
});
|
|
422
|
+
const finished = await this.#lifecycle.finish(currentLoopId, failure);
|
|
423
|
+
// A loop a cancellation retired is that cancellation's to announce.
|
|
424
|
+
if (finished !== null || !await this.#lifecycle.cancelled(currentLoopId)) {
|
|
425
|
+
const settled = finished ?? await this.#lifecycle.result(currentLoopId);
|
|
426
|
+
if (settled === null) {
|
|
427
|
+
throw new Error(`drain could not settle loop ${currentLoopId}`, { cause: err });
|
|
427
428
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
await this.scheduleWakes(workspaceId, workerId, systemPrompt);
|
|
429
|
+
await this.#reapLoop(currentLoopId);
|
|
430
|
+
await this.#announce(workspaceId, workerId, currentLoopId, settled);
|
|
431
431
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
// error already surfaced via firstLoopPromise or was logged inside.
|
|
438
|
-
drainPromise.catch(() => { });
|
|
439
|
-
firstLoopPromise.catch(() => { });
|
|
440
|
-
return { firstLoopPromise, drainPromise };
|
|
432
|
+
}
|
|
433
|
+
if (!first.settled) {
|
|
434
|
+
first.settled = true;
|
|
435
|
+
first.reject(err);
|
|
436
|
+
}
|
|
441
437
|
}
|
|
442
438
|
// Per-worker drain-transition lock (R4 / {§worker-lifecycle-single-drain}). ensureDrain's
|
|
443
439
|
// start and a drain's teardown relinquish both run under it, serialized, so the two
|
|
@@ -523,10 +519,11 @@ export default class DrainSupervisor {
|
|
|
523
519
|
const owner = await this.#db.drain_get_worker_workspace.get({ worker_id: workerId });
|
|
524
520
|
if (owner === undefined)
|
|
525
521
|
throw new Error(`cancellation worker ${workerId} does not exist`);
|
|
526
|
-
const { cancelled, subscriptions } = await this.#withAdmissionLock(owner.workspace_id, async () => {
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
|
|
522
|
+
const { cancelled, subscriptions, drains } = await this.#withAdmissionLock(owner.workspace_id, async () => {
|
|
523
|
+
const tree = await this.#lifecycle.cancelTree(workerId, reason, includeRoot);
|
|
524
|
+
const open = await Promise.all(tree.workerIds.map(async (id) => [id, await ChannelWrite.findOpenSubscriptionsForWorker(this.#db, id)]));
|
|
525
|
+
const draining = new Map();
|
|
526
|
+
for (const targetWorkerId of tree.workerIds) {
|
|
530
527
|
for (const [loopId, timer] of this.#loopTimers) {
|
|
531
528
|
if (timer.workerId === targetWorkerId)
|
|
532
529
|
this.#clearLoopTimer(loopId);
|
|
@@ -534,32 +531,43 @@ export default class DrainSupervisor {
|
|
|
534
531
|
const scope = this.#workerAborts.get(targetWorkerId);
|
|
535
532
|
if (scope !== undefined && !scope.signal.aborted)
|
|
536
533
|
scope.abort(reason);
|
|
534
|
+
const drain = this.#activeDrains.get(targetWorkerId);
|
|
535
|
+
if (drain !== undefined)
|
|
536
|
+
draining.set(targetWorkerId, drain.promise);
|
|
537
537
|
}
|
|
538
|
-
return { cancelled, subscriptions };
|
|
538
|
+
return { cancelled: tree, subscriptions: new Map(open), drains: draining };
|
|
539
539
|
});
|
|
540
|
-
await Promise.all(subscriptions.map(({ id }) => this.#cancelSubscription(id)));
|
|
540
|
+
await Promise.all([...subscriptions.values()].flat().map(({ id }) => this.#cancelSubscription(id)));
|
|
541
541
|
for (const { loopId, workerId: targetWorkerId, result } of cancelled.loops) {
|
|
542
542
|
const row = await this.#db.drain_get_worker_workspace.get({
|
|
543
543
|
worker_id: targetWorkerId,
|
|
544
544
|
});
|
|
545
545
|
if (row === undefined)
|
|
546
546
|
continue;
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
});
|
|
560
|
-
await this.#abandonUnanswered(row.workspace_id, targetWorkerId, loopId, result.status);
|
|
547
|
+
const announce = () => this.#announce(row.workspace_id, targetWorkerId, loopId, result);
|
|
548
|
+
// {§notifications-loop-terminated} What a retired loop held settles as its worker's drain unwinds the turn
|
|
549
|
+
// and its streams close; the terminal follows both. The caller is not held: it may be that turn.
|
|
550
|
+
const drain = drains.get(targetWorkerId);
|
|
551
|
+
const held = [
|
|
552
|
+
...drain === undefined ? [] : [drain],
|
|
553
|
+
...(subscriptions.get(targetWorkerId) ?? []).map(({ id }) => this.#streamConcluded(id)),
|
|
554
|
+
];
|
|
555
|
+
if (held.length === 0)
|
|
556
|
+
await announce();
|
|
557
|
+
else
|
|
558
|
+
void this.#trackSettlement(Promise.allSettled(held).then(announce), `announce(${loopId})`);
|
|
561
559
|
}
|
|
562
560
|
}
|
|
561
|
+
// A terminal loop's loop/terminated, then what it left unanswered answered for it.
|
|
562
|
+
async #announce(workspaceId, workerId, loopId, result) {
|
|
563
|
+
const [usage, attributions, turnIds] = await Promise.all([
|
|
564
|
+
this.#loopUsage(loopId),
|
|
565
|
+
this.#loopAttributions(loopId),
|
|
566
|
+
this.#lifecycle.turnIds(loopId),
|
|
567
|
+
]);
|
|
568
|
+
await this.#publishTermination(workspaceId, { workerId, loopId, result, hitMaxTurns: false, turnIds, usage, attributions });
|
|
569
|
+
await this.#abandonUnanswered(workspaceId, workerId, loopId, result.status);
|
|
570
|
+
}
|
|
563
571
|
// {§message-abandonment} — after a loop's end is durable and published, whatever it left unanswered is
|
|
564
572
|
// answered for it. A failure here is logged, never a second failure of the loop that already ended.
|
|
565
573
|
async #abandonUnanswered(workspaceId, workerId, loopId, status) {
|
|
@@ -585,17 +593,37 @@ export default class DrainSupervisor {
|
|
|
585
593
|
// {§module-shutdown-order}: the producer emits synchronously, while the
|
|
586
594
|
// supervisor owns the asynchronous scheduler work and its shutdown truth.
|
|
587
595
|
notifyWakeWorker(payload) {
|
|
588
|
-
|
|
596
|
+
const { subscriptionId } = payload;
|
|
597
|
+
const announced = Promise.withResolvers();
|
|
598
|
+
const announcement = { workerId: payload.workerId, delivered: announced.promise };
|
|
599
|
+
this.#streamAnnouncements.set(subscriptionId, announcement);
|
|
600
|
+
const handled = this.#handleWakeWorker(payload, announced.resolve).finally(() => {
|
|
601
|
+
announced.resolve();
|
|
602
|
+
if (this.#streamAnnouncements.get(subscriptionId) === announcement)
|
|
603
|
+
this.#streamAnnouncements.delete(subscriptionId);
|
|
604
|
+
});
|
|
605
|
+
void this.#trackSettlement(handled, "wake-on-completion");
|
|
606
|
+
}
|
|
607
|
+
// {§notifications-loop-terminated} A subscription's conclusion as its observers receive it: its close delivered,
|
|
608
|
+
// then its `stream/concluded` published.
|
|
609
|
+
async #streamConcluded(subscriptionId) {
|
|
610
|
+
await this.#subscriptionConcluded(subscriptionId);
|
|
611
|
+
await this.#streamAnnouncements.get(subscriptionId)?.delivered;
|
|
612
|
+
}
|
|
613
|
+
// {§notifications-loop-terminated} {§exec-lifetime} What the loop's terminal reaps, concluded and announced.
|
|
614
|
+
async #reapLoop(loopId) {
|
|
615
|
+
const reaped = await this.#reapLoopStreams(loopId);
|
|
616
|
+
await Promise.all(reaped.map((subscriptionId) => this.#streamConcluded(subscriptionId)));
|
|
589
617
|
}
|
|
590
618
|
async operationSettled(workspaceId, logEntryId) {
|
|
591
619
|
const recipients = await this.#db.message_reply_recipients.all({ log_entry_id: logEntryId });
|
|
592
620
|
if (recipients.length === 0)
|
|
593
621
|
return;
|
|
594
|
-
const systemPrompt = await this.#readSystemPrompt();
|
|
622
|
+
const systemPrompt = await this.#readSystemPrompt(); // {§card-read-per-wake}
|
|
595
623
|
for (const { worker_id: workerId } of recipients) {
|
|
596
624
|
// {§worker-optimistic-settlement}: the sender must finish its own
|
|
597
625
|
// program while the recipient coalesces replies and conclusions.
|
|
598
|
-
this.#trackSettlement(this.settleCompletionWake(workspaceId, workerId, systemPrompt), "wake-on-reply");
|
|
626
|
+
void this.#trackSettlement(this.settleCompletionWake(workspaceId, workerId, systemPrompt), "wake-on-reply");
|
|
599
627
|
}
|
|
600
628
|
}
|
|
601
629
|
#trackSettlement(task, label) {
|
|
@@ -607,19 +635,21 @@ export default class DrainSupervisor {
|
|
|
607
635
|
});
|
|
608
636
|
return task;
|
|
609
637
|
}
|
|
610
|
-
async #handleWakeWorker(payload) {
|
|
638
|
+
async #handleWakeWorker(payload, announced) {
|
|
611
639
|
const { workspaceId, ...conclusion } = payload;
|
|
640
|
+
const conclude = (wakeAction) => {
|
|
641
|
+
this.#emit(workspaceId, "stream/concluded", { ...conclusion, wakeAction });
|
|
642
|
+
announced();
|
|
643
|
+
};
|
|
612
644
|
// {§worker-lifecycle-no-resurrection}: scope cancellation, not the
|
|
613
645
|
// command's result code, prevents a wake. Cancelling one command still
|
|
614
646
|
// produces an observation owed to its live worker.
|
|
615
647
|
const scope = this.#workerAborts.get(payload.workerId);
|
|
616
648
|
if (scope?.signal.aborted === true) {
|
|
617
|
-
|
|
618
|
-
...conclusion, wakeAction: payload.result.status === 499 ? "skipped-aborted" : "skipped-cancelled",
|
|
619
|
-
});
|
|
649
|
+
conclude(payload.result.status === 499 ? "skipped-aborted" : "skipped-cancelled");
|
|
620
650
|
return;
|
|
621
651
|
}
|
|
622
|
-
const systemPrompt = await this.#readSystemPrompt();
|
|
652
|
+
const systemPrompt = await this.#readSystemPrompt(); // {§card-read-per-wake}
|
|
623
653
|
// {§notifications-stream-concluded}: publish the terminal stream fact
|
|
624
654
|
// before settlement. Rechecked wait identities, not this event, decide
|
|
625
655
|
// whether any loop actually resumes.
|
|
@@ -629,25 +659,19 @@ export default class DrainSupervisor {
|
|
|
629
659
|
// then let the worker-local gate coalesce only the provider
|
|
630
660
|
// dispatch. Concurrent stream/child callbacks join that one gate.
|
|
631
661
|
const settlement = this.settleCompletionWake(workspaceId, payload.workerId, systemPrompt);
|
|
632
|
-
|
|
633
|
-
...conclusion, wakeAction: "wake-pending",
|
|
634
|
-
});
|
|
662
|
+
conclude("wake-pending");
|
|
635
663
|
await settlement;
|
|
636
664
|
return;
|
|
637
665
|
}
|
|
638
666
|
// No slept loop. A live loop surfaces the concluded stream ambiently via the
|
|
639
667
|
// environment-observation injector ({§exec-stream}) on its next turn; the client is told.
|
|
640
668
|
if (this.#activeDrains.has(payload.workerId)) {
|
|
641
|
-
|
|
642
|
-
...conclusion, wakeAction: "no-op-active-loop",
|
|
643
|
-
});
|
|
669
|
+
conclude("no-op-active-loop");
|
|
644
670
|
return;
|
|
645
671
|
}
|
|
646
672
|
// No slept loop, no active drain — nothing to resume (e.g. a completed worker whose
|
|
647
673
|
// streams were swept). Surface the conclusion without opening a loop.
|
|
648
|
-
|
|
649
|
-
...conclusion, wakeAction: "no-loop",
|
|
650
|
-
});
|
|
674
|
+
conclude("no-loop");
|
|
651
675
|
}
|
|
652
676
|
#clearLoopTimer(loopId) {
|
|
653
677
|
const current = this.#loopTimers.get(loopId);
|
|
@@ -685,7 +709,7 @@ export default class DrainSupervisor {
|
|
|
685
709
|
if (this.#loopTimers.get(loopId)?.timer !== timer)
|
|
686
710
|
return;
|
|
687
711
|
this.#loopTimers.delete(loopId);
|
|
688
|
-
this.#trackSettlement(this.#wakeTimedLoop(workspaceId, workerId, systemPrompt, loopId, revision), "wake-on-completion");
|
|
712
|
+
void this.#trackSettlement(this.#wakeTimedLoop(workspaceId, workerId, systemPrompt, loopId, revision), "wake-on-completion");
|
|
689
713
|
}, Math.max(1, Math.min(dueAt - Date.now(), 2_147_483_647)));
|
|
690
714
|
timer.unref();
|
|
691
715
|
this.#loopTimers.set(loopId, { workerId, revision, dueAt, timer });
|
|
@@ -707,9 +731,9 @@ export default class DrainSupervisor {
|
|
|
707
731
|
if (wait === undefined)
|
|
708
732
|
return;
|
|
709
733
|
const waitUntil = wait?.wait_poll_at ?? null;
|
|
710
|
-
const { claimedAt, packetCount } = await this.#lifecycle.observation(loopId);
|
|
734
|
+
const { loopSeq, claimedAt, packetCount } = await this.#lifecycle.observation(loopId);
|
|
711
735
|
this.#emit(workspaceId, "notice/event", {
|
|
712
|
-
workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status, waitUntil, packetCount, claimedAt },
|
|
736
|
+
workerId, loopId, notice: { source: "engine:lifecycle", kind: "loop_status", level: "info", status, loopSeq, waitUntil, packetCount, claimedAt },
|
|
713
737
|
});
|
|
714
738
|
}
|
|
715
739
|
async #wakeLoop(workspaceId, workerId, loopId, condition = {}) {
|
|
@@ -825,10 +849,14 @@ export default class DrainSupervisor {
|
|
|
825
849
|
}
|
|
826
850
|
// {§notifications-loop-terminated} — the terminal carries the stamp a snapshot reads.
|
|
827
851
|
async #publishTermination(workspaceId, event) {
|
|
828
|
-
const { terminatedAt } = await this.#lifecycle.observation(event.loopId);
|
|
852
|
+
const { loopSeq, terminatedAt } = await this.#lifecycle.observation(event.loopId);
|
|
853
|
+
// A durable close releases the live handle before its announcement is delivered.
|
|
854
|
+
await Promise.all([...this.#streamAnnouncements.values()]
|
|
855
|
+
.filter(({ workerId }) => workerId === event.workerId)
|
|
856
|
+
.map(({ delivered }) => delivered));
|
|
829
857
|
this.#discardLoopNotices(event.loopId);
|
|
830
|
-
this.#emit(workspaceId, "loop/terminated", { ...event, terminatedAt });
|
|
831
|
-
this.#trackSettlement(this.#notifyParentCompletion(workspaceId, event.workerId), "wake-on-completion");
|
|
858
|
+
this.#emit(workspaceId, "loop/terminated", { ...event, loopSeq, terminatedAt });
|
|
859
|
+
void this.#trackSettlement(this.#notifyParentCompletion(workspaceId, event.workerId), "wake-on-completion");
|
|
832
860
|
}
|
|
833
861
|
// {§worker-lifecycle-child-wake}: completion belongs to a task, not drain teardown.
|
|
834
862
|
async #notifyParentCompletion(workspaceId, workerId) {
|