@plurnk/plurnk-service 1.16.5 → 1.18.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 +140 -246
- package/INSTALL.md +84 -138
- package/README.md +10 -6
- package/SPEC.md +2468 -1694
- package/dist/Paths.d.ts +2 -0
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +2 -0
- package/dist/Paths.js.map +1 -1
- package/dist/build-info.d.ts.map +1 -1
- package/dist/build-info.js +7 -1
- package/dist/build-info.js.map +1 -1
- package/dist/build-info.json +1 -1
- package/dist/content/body-preview.d.ts +8 -0
- package/dist/content/body-preview.d.ts.map +1 -0
- package/dist/content/body-preview.js +39 -0
- package/dist/content/body-preview.js.map +1 -0
- package/dist/content/byte-view.d.ts +1 -4
- package/dist/content/byte-view.d.ts.map +1 -1
- package/dist/content/byte-view.js.map +1 -1
- package/dist/content/edit-collision.d.ts.map +1 -1
- package/dist/content/edit-collision.js +3 -4
- package/dist/content/edit-collision.js.map +1 -1
- package/dist/content/line-anchors.d.ts +2 -2
- package/dist/content/line-anchors.d.ts.map +1 -1
- package/dist/content/line-anchors.js +4 -3
- package/dist/content/line-anchors.js.map +1 -1
- package/dist/content/line-selection.d.ts +9 -0
- package/dist/content/line-selection.d.ts.map +1 -0
- package/dist/content/line-selection.js +29 -0
- package/dist/content/line-selection.js.map +1 -0
- package/dist/content/matcher.d.ts +0 -1
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +2 -3
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/pattern-edits.d.ts +39 -0
- package/dist/content/pattern-edits.d.ts.map +1 -0
- package/dist/content/pattern-edits.js +121 -0
- package/dist/content/pattern-edits.js.map +1 -0
- package/dist/content/read-projector.d.ts +18 -9
- package/dist/content/read-projector.d.ts.map +1 -1
- package/dist/content/read-projector.js +132 -35
- package/dist/content/read-projector.js.map +1 -1
- package/dist/content/read-resolve.d.ts +4 -1
- package/dist/content/read-resolve.d.ts.map +1 -1
- package/dist/content/read-resolve.js +42 -7
- package/dist/content/read-resolve.js.map +1 -1
- package/dist/core/AdmittedTurnExecutor.d.ts +2 -5
- package/dist/core/AdmittedTurnExecutor.d.ts.map +1 -1
- package/dist/core/AdmittedTurnExecutor.js +96 -88
- package/dist/core/AdmittedTurnExecutor.js.map +1 -1
- package/dist/core/BareBatchRunner.d.ts +6 -1
- package/dist/core/BareBatchRunner.d.ts.map +1 -1
- package/dist/core/BareBatchRunner.js +20 -5
- package/dist/core/BareBatchRunner.js.map +1 -1
- package/dist/core/BudgetReadout.d.ts +2 -3
- package/dist/core/BudgetReadout.d.ts.map +1 -1
- package/dist/core/BudgetReadout.js +23 -32
- package/dist/core/BudgetReadout.js.map +1 -1
- package/dist/core/CapabilityPolicies.d.ts +3 -8
- package/dist/core/CapabilityPolicies.d.ts.map +1 -1
- package/dist/core/CapabilityPolicies.js +2 -28
- package/dist/core/CapabilityPolicies.js.map +1 -1
- package/dist/core/CapabilityResolver.d.ts +10 -10
- package/dist/core/CapabilityResolver.d.ts.map +1 -1
- package/dist/core/CapabilityResolver.js +122 -93
- package/dist/core/CapabilityResolver.js.map +1 -1
- package/dist/core/ChannelWrite.d.ts +15 -11
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +12 -13
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +99 -9
- package/dist/core/ClientInteractions.d.ts.map +1 -1
- package/dist/core/ClientInteractions.js +7 -0
- package/dist/core/ClientInteractions.js.map +1 -1
- package/dist/core/CoreSchemeServices.d.ts +13 -10
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +13 -5
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/DataStatementRunner.d.ts +2 -2
- package/dist/core/DataStatementRunner.d.ts.map +1 -1
- package/dist/core/DataStatementRunner.js +208 -161
- package/dist/core/DataStatementRunner.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +24 -26
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +381 -232
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/Dispatcher.sql +66 -0
- package/dist/core/DurableStatement.d.ts.map +1 -1
- package/dist/core/DurableStatement.js +2 -1
- package/dist/core/DurableStatement.js.map +1 -1
- package/dist/core/EditMutations.d.ts +14 -18
- package/dist/core/EditMutations.d.ts.map +1 -1
- package/dist/core/EditMutations.js +211 -280
- package/dist/core/EditMutations.js.map +1 -1
- package/dist/core/EditMutations.sql +15 -0
- package/dist/core/EditSequence.d.ts +13 -0
- package/dist/core/EditSequence.d.ts.map +1 -0
- package/dist/core/EditSequence.js +73 -0
- package/dist/core/EditSequence.js.map +1 -0
- package/dist/core/Engine.d.ts +17 -23
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +74 -164
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +0 -724
- package/dist/core/EntryAddressBinding.d.ts +7 -17
- package/dist/core/EntryAddressBinding.d.ts.map +1 -1
- package/dist/core/EntryAddressBinding.js +12 -72
- package/dist/core/EntryAddressBinding.js.map +1 -1
- package/dist/core/ExecutionOutputs.d.ts +9 -0
- package/dist/core/ExecutionOutputs.d.ts.map +1 -0
- package/dist/core/ExecutionOutputs.js +36 -0
- package/dist/core/ExecutionOutputs.js.map +1 -0
- package/dist/core/ExecutionOutputs.sql +21 -0
- package/dist/core/ExecutorRegistry.d.ts +11 -5
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +37 -32
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/HostPaths.d.ts +1 -0
- package/dist/core/HostPaths.d.ts.map +1 -1
- package/dist/core/HostPaths.js +4 -0
- package/dist/core/HostPaths.js.map +1 -1
- package/dist/core/InferenceCall.d.ts.map +1 -1
- package/dist/core/InferenceCall.js +1 -2
- package/dist/core/InferenceCall.js.map +1 -1
- package/dist/core/InferenceCall.sql +29 -0
- package/dist/core/KillHandler.d.ts +1 -0
- package/dist/core/KillHandler.d.ts.map +1 -1
- package/dist/core/KillHandler.js +24 -21
- package/dist/core/KillHandler.js.map +1 -1
- package/dist/core/LogBody.d.ts +8 -1
- package/dist/core/LogBody.d.ts.map +1 -1
- package/dist/core/LogBody.js +44 -12
- package/dist/core/LogBody.js.map +1 -1
- package/dist/core/LogEntryProjection.d.ts +1 -0
- package/dist/core/LogEntryProjection.d.ts.map +1 -1
- package/dist/core/LogEntryProjection.js +12 -10
- package/dist/core/LogEntryProjection.js.map +1 -1
- package/dist/core/LogVisibility.d.ts +1 -0
- package/dist/core/LogVisibility.d.ts.map +1 -1
- package/dist/core/LogVisibility.js +3 -0
- package/dist/core/LogVisibility.js.map +1 -1
- package/dist/core/LogWriter.d.ts +2 -3
- package/dist/core/LogWriter.d.ts.map +1 -1
- package/dist/core/LogWriter.js +31 -36
- package/dist/core/LogWriter.js.map +1 -1
- package/dist/core/LogWriter.sql +23 -0
- package/dist/core/LoopDriver.d.ts +11 -8
- package/dist/core/LoopDriver.d.ts.map +1 -1
- package/dist/core/LoopDriver.js +161 -189
- package/dist/core/LoopDriver.js.map +1 -1
- package/dist/core/LoopDriver.sql +11 -0
- package/dist/core/LoopLifecycle.d.ts +32 -2
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/LoopLifecycle.js +88 -4
- package/dist/core/LoopLifecycle.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +145 -28
- package/dist/core/LoopPolicyReader.sql +6 -0
- package/dist/core/MembershipMaterialization.d.ts.map +1 -1
- package/dist/core/MembershipMaterialization.js +6 -28
- package/dist/core/MembershipMaterialization.js.map +1 -1
- package/dist/core/ModelCall.d.ts.map +1 -1
- package/dist/core/ModelCall.js +2 -6
- package/dist/core/ModelCall.js.map +1 -1
- package/dist/core/ModelCall.sql +41 -0
- package/dist/core/MutationEffects.d.ts +5 -3
- package/dist/core/MutationEffects.d.ts.map +1 -1
- package/dist/core/MutationEffects.js +10 -28
- package/dist/core/MutationEffects.js.map +1 -1
- package/dist/core/NativeContent.d.ts +6 -0
- package/dist/core/NativeContent.d.ts.map +1 -0
- package/dist/core/NativeContent.js +16 -0
- package/dist/core/NativeContent.js.map +1 -0
- package/dist/core/NativeContent.sql +6 -0
- package/dist/core/NoticeChannel.js +1 -1
- package/dist/core/NoticeChannel.js.map +1 -1
- package/dist/core/OperatorConfig.js +5 -5
- package/dist/core/OperatorConfig.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts +8 -5
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +117 -81
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/PacketBuilder.sql +146 -0
- package/dist/core/PatternSelection.d.ts +28 -0
- package/dist/core/PatternSelection.d.ts.map +1 -0
- package/dist/core/PatternSelection.js +42 -0
- package/dist/core/PatternSelection.js.map +1 -0
- package/dist/core/ProblemLog.d.ts.map +1 -1
- package/dist/core/ProblemLog.js +0 -1
- package/dist/core/ProblemLog.js.map +1 -1
- package/dist/core/PromptFrames.d.ts +12 -0
- package/dist/core/PromptFrames.d.ts.map +1 -0
- package/dist/core/PromptFrames.js +28 -0
- package/dist/core/PromptFrames.js.map +1 -0
- package/dist/core/ProposalLifecycle.d.ts +0 -1
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +26 -23
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProposalLifecycle.sql +33 -0
- package/dist/core/ProviderInstantiate.d.ts +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +7 -14
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/ReasoningView.d.ts +9 -0
- package/dist/core/ReasoningView.d.ts.map +1 -0
- package/dist/core/ReasoningView.js +47 -0
- package/dist/core/ReasoningView.js.map +1 -0
- package/dist/core/ResourceBindings.d.ts +16 -0
- package/dist/core/ResourceBindings.d.ts.map +1 -0
- package/dist/core/ResourceBindings.js +45 -0
- package/dist/core/ResourceBindings.js.map +1 -0
- package/dist/core/ResourceMutations.d.ts +5 -8
- package/dist/core/ResourceMutations.d.ts.map +1 -1
- package/dist/core/ResourceMutations.js +6 -7
- package/dist/core/ResourceMutations.js.map +1 -1
- package/dist/core/ResourceSelector.d.ts +2 -2
- package/dist/core/ResourceSelector.d.ts.map +1 -1
- package/dist/core/ResourceSelector.js +122 -62
- package/dist/core/ResourceSelector.js.map +1 -1
- package/dist/core/ResourceTransfers.d.ts +4 -2
- package/dist/core/ResourceTransfers.d.ts.map +1 -1
- package/dist/core/ResourceTransfers.js +95 -26
- package/dist/core/ResourceTransfers.js.map +1 -1
- package/dist/core/RuntimeWorker.d.ts +5 -0
- package/dist/core/RuntimeWorker.d.ts.map +1 -0
- package/dist/core/RuntimeWorker.js +15 -0
- package/dist/core/RuntimeWorker.js.map +1 -0
- package/dist/core/RuntimeWorker.sql +11 -0
- package/dist/core/SchemeRegistry.d.ts +12 -16
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +59 -85
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/StoredPacket.d.ts +6 -1
- package/dist/core/StoredPacket.d.ts.map +1 -1
- package/dist/core/StoredPacket.js +30 -10
- package/dist/core/StoredPacket.js.map +1 -1
- package/dist/core/StrikeRail.d.ts +11 -8
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +63 -114
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/StrikeRail.sql +12 -0
- package/dist/core/TerminalResult.d.ts +1 -1
- package/dist/core/TerminalResult.d.ts.map +1 -1
- package/dist/core/TerminalResult.js +2 -2
- package/dist/core/TerminalResult.js.map +1 -1
- package/dist/core/TokenCalibration.d.ts +1 -6
- package/dist/core/TokenCalibration.d.ts.map +1 -1
- package/dist/core/TokenCalibration.js +13 -9
- package/dist/core/TokenCalibration.js.map +1 -1
- package/dist/core/TokenCalibration.sql +15 -0
- package/dist/core/ToolInputSchema.d.ts +6 -0
- package/dist/core/ToolInputSchema.d.ts.map +1 -0
- package/dist/core/ToolInputSchema.js +43 -0
- package/dist/core/ToolInputSchema.js.map +1 -0
- package/dist/core/ToolResources.d.ts.map +1 -1
- package/dist/core/ToolResources.js +77 -51
- package/dist/core/ToolResources.js.map +1 -1
- package/dist/core/Turn.d.ts +5 -2
- package/dist/core/Turn.d.ts.map +1 -1
- package/dist/core/Turn.js +13 -11
- package/dist/core/Turn.js.map +1 -1
- package/dist/core/Turn.sql +25 -39
- package/dist/core/{SendBroadcastHandler.d.ts → TurnDispositionHandler.d.ts} +24 -18
- package/dist/core/TurnDispositionHandler.d.ts.map +1 -0
- package/dist/core/TurnDispositionHandler.js +206 -0
- package/dist/core/TurnDispositionHandler.js.map +1 -0
- package/dist/core/TurnMaterialization.d.ts +2 -3
- package/dist/core/TurnMaterialization.d.ts.map +1 -1
- package/dist/core/TurnMaterialization.js +47 -24
- package/dist/core/TurnMaterialization.js.map +1 -1
- package/dist/core/TurnMaterialization.sql +44 -0
- package/dist/core/TurnOps.d.ts.map +1 -1
- package/dist/core/TurnOps.js +8 -64
- package/dist/core/TurnOps.js.map +1 -1
- package/dist/core/TurnRunner.d.ts +8 -15
- package/dist/core/TurnRunner.d.ts.map +1 -1
- package/dist/core/TurnRunner.js +1175 -1317
- package/dist/core/TurnRunner.js.map +1 -1
- package/dist/core/TurnRunner.sql +74 -0
- package/dist/core/TurnSources.sql +39 -0
- package/dist/core/WorkerControlAddress.d.ts +1 -0
- package/dist/core/WorkerControlAddress.d.ts.map +1 -1
- package/dist/core/WorkerControlAddress.js +5 -0
- package/dist/core/WorkerControlAddress.js.map +1 -1
- package/dist/core/WorkerControlHandler.d.ts +1 -3
- package/dist/core/WorkerControlHandler.d.ts.map +1 -1
- package/dist/core/WorkerControlHandler.js +64 -48
- package/dist/core/WorkerControlHandler.js.map +1 -1
- package/dist/core/WorkerName.d.ts +11 -12
- package/dist/core/WorkerName.d.ts.map +1 -1
- package/dist/core/WorkerName.js +40 -47
- package/dist/core/WorkerName.js.map +1 -1
- package/dist/core/WorkerName.sql +7 -10
- package/dist/core/WorkspaceGate.d.ts +1 -1
- package/dist/core/WorkspaceGate.d.ts.map +1 -1
- package/dist/core/WorkspaceGate.js +33 -10
- package/dist/core/WorkspaceGate.js.map +1 -1
- package/dist/core/ambient.sql +236 -0
- package/dist/core/attachments.d.ts +2 -1
- package/dist/core/attachments.d.ts.map +1 -1
- package/dist/core/attachments.js +5 -4
- package/dist/core/attachments.js.map +1 -1
- package/dist/core/caps/CapsResolve.d.ts +2 -3
- package/dist/core/caps/CapsResolve.d.ts.map +1 -1
- package/dist/core/caps/CapsResolve.js +5 -5
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts.map +1 -1
- package/dist/core/caps/DbChannelCaps.js +4 -6
- package/dist/core/caps/DbChannelCaps.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts +2 -1
- package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
- package/dist/core/caps/DbEntryCaps.js +11 -17
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +3 -5
- package/dist/core/caps/DbNotifyCaps.js.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 +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +5 -7
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +0 -2
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +4 -20
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/env-catalog.d.ts +28 -0
- package/dist/core/env-catalog.d.ts.map +1 -0
- package/dist/core/env-catalog.js +99 -0
- package/dist/core/env-catalog.js.map +1 -0
- package/dist/core/env-defaults.d.ts.map +1 -1
- package/dist/core/env-defaults.js +4 -2
- package/dist/core/env-defaults.js.map +1 -1
- package/dist/core/file-materialization.d.ts +4 -1
- package/dist/core/file-materialization.d.ts.map +1 -1
- package/dist/core/file-materialization.js +15 -1
- package/dist/core/file-materialization.js.map +1 -1
- package/dist/core/fork.d.ts +1 -6
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +9 -153
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +162 -185
- 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 +39 -2
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +64 -48
- 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 +26 -20
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/glob_match.d.ts +4 -0
- package/dist/core/glob_match.d.ts.map +1 -0
- package/dist/core/glob_match.js +7 -0
- package/dist/core/glob_match.js.map +1 -0
- package/dist/core/maintenance.sql +6 -0
- package/dist/core/mutation-types.d.ts +7 -28
- package/dist/core/mutation-types.d.ts.map +1 -1
- package/dist/core/operation-target-groups.d.ts.map +1 -1
- package/dist/core/operation-target-groups.js +2 -1
- package/dist/core/operation-target-groups.js.map +1 -1
- package/dist/core/packet-inject.d.ts.map +1 -1
- package/dist/core/packet-inject.js +2 -3
- package/dist/core/packet-inject.js.map +1 -1
- package/dist/core/packet-wire.d.ts +8 -6
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +381 -388
- 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 +5 -12
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/scheme-types.d.ts +3 -3
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/sha256.d.ts +4 -0
- package/dist/core/sha256.d.ts.map +1 -0
- package/dist/core/sha256.js +6 -0
- package/dist/core/sha256.js.map +1 -0
- package/dist/core/teaching.js +1 -1
- package/dist/core/teaching.js.map +1 -1
- package/dist/core/turn-scheduler.d.ts.map +1 -1
- package/dist/core/turn-scheduler.js +5 -18
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/core/turn-signals.d.ts +0 -6
- package/dist/core/turn-signals.d.ts.map +1 -1
- package/dist/core/turn-signals.js +1 -7
- package/dist/core/turn-signals.js.map +1 -1
- package/dist/core/worker-ops.sql +15 -22
- package/dist/core/workspace-settings.d.ts.map +1 -1
- package/dist/core/workspace-settings.js +18 -2
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/core/workspace-settings.sql +6 -0
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +30 -91
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/DigestRender.d.ts +1 -0
- package/dist/digest/DigestRender.d.ts.map +1 -1
- package/dist/digest/DigestRender.js +58 -62
- package/dist/digest/DigestRender.js.map +1 -1
- package/dist/digest/digest-rows.d.ts +16 -32
- package/dist/digest/digest-rows.d.ts.map +1 -1
- package/dist/digest/digest.sql +54 -20
- package/dist/schemes/Exec.d.ts +6 -12
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +318 -215
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +8 -6
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +33 -61
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/ExecScheduler.js +2 -2
- package/dist/schemes/ExecScheduler.js.map +1 -1
- package/dist/schemes/ExecutionInput.d.ts +12 -0
- package/dist/schemes/ExecutionInput.d.ts.map +1 -0
- package/dist/schemes/ExecutionInput.js +94 -0
- package/dist/schemes/ExecutionInput.js.map +1 -0
- package/dist/schemes/File.d.ts +5 -2
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +62 -91
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +84 -79
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +183 -15
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +3 -12
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/QuestionTool.d.ts +5 -0
- package/dist/schemes/QuestionTool.d.ts.map +1 -1
- package/dist/schemes/QuestionTool.js +7 -15
- package/dist/schemes/QuestionTool.js.map +1 -1
- package/dist/schemes/Skill.d.ts +12 -7
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +149 -21
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/TurnSource.d.ts +12 -0
- package/dist/schemes/TurnSource.d.ts.map +1 -0
- package/dist/schemes/TurnSource.js +117 -0
- package/dist/schemes/TurnSource.js.map +1 -0
- package/dist/schemes/Worker.d.ts +3 -3
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +61 -208
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +12 -15
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +63 -63
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +159 -45
- package/dist/schemes/_entry-find.d.ts +1 -3
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +111 -112
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +22 -14
- package/dist/schemes/_entry-fts.d.ts +14 -0
- package/dist/schemes/_entry-fts.d.ts.map +1 -0
- package/dist/schemes/_entry-fts.js +82 -0
- package/dist/schemes/_entry-fts.js.map +1 -0
- package/dist/schemes/_entry-fts.sql +31 -0
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +6 -14
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-graph.sql +29 -44
- package/dist/schemes/_entry-manifest.d.ts +2 -1
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +20 -26
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-manifest.sql +26 -0
- package/dist/schemes/_entry-ops.d.ts +3 -5
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +30 -43
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +5 -6
- package/dist/schemes/_entry-readable.d.ts +8 -0
- package/dist/schemes/_entry-readable.d.ts.map +1 -0
- package/dist/schemes/_entry-readable.js +38 -0
- package/dist/schemes/_entry-readable.js.map +1 -0
- package/dist/schemes/_entry-send.d.ts +1 -1
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +3 -3
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/_search-index.d.ts +5 -3
- package/dist/schemes/_search-index.d.ts.map +1 -1
- package/dist/schemes/_search-index.js +169 -160
- package/dist/schemes/_search-index.js.map +1 -1
- package/dist/schemes/_search-index.sql +47 -0
- package/dist/schemes/exec-abort.js +1 -1
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/schemes/exec-env.d.ts +3 -0
- package/dist/schemes/exec-env.d.ts.map +1 -1
- package/dist/schemes/exec-env.js +66 -15
- package/dist/schemes/exec-env.js.map +1 -1
- package/dist/schemes/exec-runtime.d.ts +1 -1
- package/dist/schemes/exec-runtime.d.ts.map +1 -1
- package/dist/schemes/exec-runtime.js +1 -1
- package/dist/schemes/exec-runtime.js.map +1 -1
- package/dist/server/ClientReads.d.ts +2 -2
- package/dist/server/ClientReads.d.ts.map +1 -1
- package/dist/server/ClientReads.js +7 -9
- package/dist/server/ClientReads.js.map +1 -1
- package/dist/server/Daemon.d.ts +15 -21
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +165 -168
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +29 -22
- package/dist/server/DaemonModule.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.d.ts +8 -21
- package/dist/server/DrainSupervisor.d.ts.map +1 -1
- package/dist/server/DrainSupervisor.js +278 -306
- package/dist/server/DrainSupervisor.js.map +1 -1
- package/dist/server/EnvFunctionality.d.ts +43 -0
- package/dist/server/EnvFunctionality.d.ts.map +1 -0
- package/dist/server/EnvFunctionality.js +194 -0
- package/dist/server/EnvFunctionality.js.map +1 -0
- package/dist/server/Functionality.d.ts +12 -11
- package/dist/server/Functionality.d.ts.map +1 -1
- package/dist/server/Functionality.js +279 -142
- package/dist/server/Functionality.js.map +1 -1
- package/dist/server/FunctionalityManager.d.ts +5 -5
- package/dist/server/FunctionalityManager.d.ts.map +1 -1
- package/dist/server/FunctionalityManager.js +29 -57
- package/dist/server/FunctionalityManager.js.map +1 -1
- package/dist/server/MembersFunctionality.d.ts +5 -4
- package/dist/server/MembersFunctionality.d.ts.map +1 -1
- package/dist/server/MembersFunctionality.js +24 -38
- package/dist/server/MembersFunctionality.js.map +1 -1
- package/dist/server/PlurnkSkill.d.ts +11 -0
- package/dist/server/PlurnkSkill.d.ts.map +1 -0
- package/dist/server/PlurnkSkill.js +41 -0
- package/dist/server/PlurnkSkill.js.map +1 -0
- package/dist/server/Retention.d.ts +25 -0
- package/dist/server/Retention.d.ts.map +1 -0
- package/dist/server/Retention.js +57 -0
- package/dist/server/Retention.js.map +1 -0
- package/dist/server/Retention.sql +59 -0
- package/dist/server/ServiceModules.d.ts +5 -0
- package/dist/server/ServiceModules.d.ts.map +1 -0
- package/dist/server/ServiceModules.js +10 -0
- package/dist/server/ServiceModules.js.map +1 -0
- package/dist/server/SkillsFunctionality.d.ts +14 -19
- package/dist/server/SkillsFunctionality.d.ts.map +1 -1
- package/dist/server/SkillsFunctionality.js +102 -106
- package/dist/server/SkillsFunctionality.js.map +1 -1
- package/dist/server/WorkerModelResolver.d.ts +4 -0
- package/dist/server/WorkerModelResolver.d.ts.map +1 -1
- package/dist/server/WorkerModelResolver.js +72 -39
- package/dist/server/WorkerModelResolver.js.map +1 -1
- package/dist/server/WorkspaceCapabilities.d.ts +21 -0
- package/dist/server/WorkspaceCapabilities.d.ts.map +1 -0
- package/dist/server/{WorkerCapabilities.js → WorkspaceCapabilities.js} +40 -40
- package/dist/server/WorkspaceCapabilities.js.map +1 -0
- package/dist/server/WorkspaceResidency.d.ts +38 -0
- package/dist/server/WorkspaceResidency.d.ts.map +1 -0
- package/dist/server/WorkspaceResidency.js +226 -0
- package/dist/server/WorkspaceResidency.js.map +1 -0
- package/dist/server/client-input.d.ts +1 -4
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +5 -46
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +4 -22
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +66 -43
- package/dist/server/envelope.d.ts +8 -1
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +9 -19
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +49 -13
- package/dist/server/lifecycle-recovery.sql +18 -20
- package/dist/server/logEntry.d.ts +1 -1
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +4 -1
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +25 -1
- package/dist/server/loopDocs.d.ts +2 -2
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +38 -31
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/loopDocs.sql +1 -3
- package/dist/server/model-catalog.d.ts.map +1 -1
- package/dist/server/model-catalog.js +2 -1
- package/dist/server/model-catalog.js.map +1 -1
- package/dist/server/model-route.d.ts +1 -1
- package/dist/server/model-route.d.ts.map +1 -1
- package/dist/server/model-route.js +4 -2
- package/dist/server/model-route.js.map +1 -1
- package/dist/server/seam-entry-read.sql +3 -4
- package/dist/server/seam-loop.sql +2 -9
- package/dist/server/workspace-capabilities.sql +64 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +21 -18
- package/dist/service.js.map +1 -1
- package/docs/env.md +55 -0
- package/docs/members.md +83 -0
- package/docs/skills.md +50 -0
- package/migrations/001_workspaces.sql +53 -0
- package/migrations/002_workers.sql +198 -0
- package/migrations/003_loops.sql +326 -0
- package/migrations/004_inference.sql +401 -0
- package/migrations/005_entries.sql +215 -0
- package/migrations/006_log.sql +581 -0
- package/migrations/007_subscriptions.sql +175 -0
- package/migrations/008_interactions.sql +29 -0
- package/package.json +44 -41
- package/digest-sql/channel-dispositions/dispositions.sql +0 -15
- package/digest-sql/channel-state/state.sql +0 -6
- package/digest-sql/curation/curation.sql +0 -6
- package/digest-sql/derivations/derivations.sql +0 -4
- package/digest-sql/embeddings/embeddings.sql +0 -4
- package/digest-sql/entry-dispositions/dispositions.sql +0 -15
- package/digest-sql/entry-state/state.sql +0 -6
- package/digest-sql/token-counts/token-counts.sql +0 -2
- package/dist/core/EmbeddingCall.d.ts +0 -23
- package/dist/core/EmbeddingCall.d.ts.map +0 -1
- package/dist/core/EmbeddingCall.js +0 -149
- package/dist/core/EmbeddingCall.js.map +0 -1
- package/dist/core/OverflowTurn.d.ts +0 -11
- package/dist/core/OverflowTurn.d.ts.map +0 -1
- package/dist/core/OverflowTurn.js +0 -80
- package/dist/core/OverflowTurn.js.map +0 -1
- package/dist/core/Owner.d.ts +0 -8
- package/dist/core/Owner.d.ts.map +0 -1
- package/dist/core/Owner.js +0 -46
- package/dist/core/Owner.js.map +0 -1
- package/dist/core/SendBroadcastHandler.d.ts.map +0 -1
- package/dist/core/SendBroadcastHandler.js +0 -159
- package/dist/core/SendBroadcastHandler.js.map +0 -1
- package/dist/core/VectorPump.d.ts +0 -28
- package/dist/core/VectorPump.d.ts.map +0 -1
- package/dist/core/VectorPump.js +0 -130
- package/dist/core/VectorPump.js.map +0 -1
- package/dist/core/owner.sql +0 -11
- package/dist/core/worker-settings.d.ts +0 -10
- package/dist/core/worker-settings.d.ts.map +0 -1
- package/dist/core/worker-settings.js +0 -53
- package/dist/core/worker-settings.js.map +0 -1
- package/dist/schemes/_entry-chunk.d.ts +0 -14
- package/dist/schemes/_entry-chunk.d.ts.map +0 -1
- package/dist/schemes/_entry-chunk.js +0 -165
- package/dist/schemes/_entry-chunk.js.map +0 -1
- package/dist/schemes/_entry-semantic.d.ts +0 -63
- package/dist/schemes/_entry-semantic.d.ts.map +0 -1
- package/dist/schemes/_entry-semantic.js +0 -299
- package/dist/schemes/_entry-semantic.js.map +0 -1
- package/dist/schemes/_entry-semantic.sql +0 -99
- package/dist/schemes/cosine.d.ts +0 -4
- package/dist/schemes/cosine.d.ts.map +0 -1
- package/dist/schemes/cosine.js +0 -9
- package/dist/schemes/cosine.js.map +0 -1
- package/dist/server/WorkerCapabilities.d.ts +0 -21
- package/dist/server/WorkerCapabilities.d.ts.map +0 -1
- package/dist/server/WorkerCapabilities.js.map +0 -1
- package/dist/server/WorkerResidency.d.ts +0 -40
- package/dist/server/WorkerResidency.d.ts.map +0 -1
- package/dist/server/WorkerResidency.js +0 -283
- package/dist/server/WorkerResidency.js.map +0 -1
- package/dist/server/seam-log-read.sql +0 -21
- package/dist/server/worker-capabilities.sql +0 -28
- package/migrations/001_schema.sql +0 -2207
package/dist/core/TurnRunner.js
CHANGED
|
@@ -1,28 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
var _a;
|
|
2
|
+
import { TurnDisposition } from "@plurnk/plurnk-contracts";
|
|
3
|
+
import NativeContent from "./NativeContent.js";
|
|
4
|
+
import { PlurnkParser } from "@plurnk/plurnk-parser";
|
|
5
|
+
import { PathSyntax, PlurnkParseError, UNKNOWN_POSITION } from "@plurnk/plurnk-contracts";
|
|
2
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
7
|
import { aggregateProviderAccounting } from "@plurnk/plurnk-providers";
|
|
4
8
|
const comparePosition = (a, b) => a.line - b.line || a.column - b.column;
|
|
5
9
|
import Meta from "@plurnk/plurnk-meta";
|
|
6
|
-
import EntryCrud from "../schemes/_entry-crud.js";
|
|
7
10
|
import GitMembership from "./git-membership.js";
|
|
8
11
|
import { acceptedKinds } from "./attachments.js";
|
|
9
12
|
import GitState from "./git-state.js";
|
|
10
13
|
import WorkspaceSettings from "./workspace-settings.js";
|
|
11
|
-
import {
|
|
14
|
+
import { taskTiming } from "./LoopLifecycle.js";
|
|
15
|
+
import { generatedPathname, promptLoopPrefix } from "./plurnk-uri.js";
|
|
16
|
+
import PromptFrames from "./PromptFrames.js";
|
|
12
17
|
import { readFile } from "node:fs/promises";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
import { resolve as resolvePath } from "node:path";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
// {§operator-grammar} — an operator's own GBNF is a file path: absolute, `~`-relative, or
|
|
21
|
+
// relative to the daemon's working directory. The service ships no grammar profile, so a bare
|
|
22
|
+
// name (no separator) is refused by name rather than resolved against anything.
|
|
23
|
+
export const resolveOperatorGrammarPath = (value) => {
|
|
24
|
+
if (value === "~" || value.startsWith("~/"))
|
|
25
|
+
return resolvePath(homedir(), value.slice(2));
|
|
26
|
+
if (value.startsWith("/") || value.startsWith(".") || value.includes("/"))
|
|
27
|
+
return resolvePath(value);
|
|
28
|
+
throw new Error(`PLURNK_PROVIDERS_GBNF=${value} names a bundled grammar profile; the service ships none (#588). Give the path of a grammar file you wrote.`);
|
|
29
|
+
};
|
|
22
30
|
// Shared module imported by both Engine and bin/digest.ts, so wire
|
|
23
31
|
// projection and digest projection are structurally one function — no
|
|
24
32
|
// drift between wire and digest possible.
|
|
25
33
|
import PacketWire from "./packet-wire.js";
|
|
34
|
+
import ReasoningView from "./ReasoningView.js";
|
|
26
35
|
import Results, { OperationFailureError } from "./results.js";
|
|
27
36
|
import Turn from "./Turn.js";
|
|
28
37
|
import StoredPacket from "./StoredPacket.js";
|
|
@@ -30,39 +39,29 @@ import { observed, observedSync } from "../observe/spans.js";
|
|
|
30
39
|
import { GEN_AI_REQUEST_SPAN, genAiRequestOptions, settleGenAiResponse } from "../observe/genai.js";
|
|
31
40
|
import { PROVIDER_CALLS, recordCounter } from "../observe/metrics.js";
|
|
32
41
|
import ModelCall, { ModelCallPersistenceError, ProviderAccountingIntegrityError } from "./ModelCall.js";
|
|
33
|
-
import
|
|
42
|
+
import WorkerName from "./WorkerName.js";
|
|
34
43
|
import TurnOps from "./TurnOps.js";
|
|
35
44
|
import CapabilityPolicies from "./CapabilityPolicies.js";
|
|
36
45
|
import CapabilityResolver from "./CapabilityResolver.js";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// the owner rides the owner_id column, the address carries only the loop coordinate.
|
|
40
|
-
const promptTarget = (loopSeq, promptOrdinal) => {
|
|
41
|
-
const storage = promptPathname(loopSeq, promptOrdinal);
|
|
46
|
+
// {§prompt-address}: the same literal address in every observer's packet.
|
|
47
|
+
const promptTarget = (workerName, storage) => {
|
|
42
48
|
return {
|
|
43
|
-
kind: "url", raw: `prompt://${storage}`,
|
|
49
|
+
kind: "url", raw: `prompt://${workerName}${storage}`,
|
|
44
50
|
scheme: "prompt", username: null, password: null,
|
|
45
|
-
hostname:
|
|
51
|
+
hostname: workerName, port: null,
|
|
46
52
|
pathname: storage, query: null, fragment: null,
|
|
47
53
|
};
|
|
48
54
|
};
|
|
49
55
|
const regexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
50
|
-
const workerCatalogTarget = (namespace
|
|
51
|
-
|
|
52
|
-
return null;
|
|
53
|
-
const leaf = availableNames.length === 0 || admittedNames.length === availableNames.length
|
|
54
|
-
? "*.md"
|
|
55
|
-
: admittedNames.length === 1
|
|
56
|
-
? `${admittedNames[0]}.md`
|
|
57
|
-
: `{${admittedNames.join(",")}}.md`;
|
|
58
|
-
const pathname = generatedPathname(`/${namespace}/${leaf}`);
|
|
56
|
+
const workerCatalogTarget = (namespace) => {
|
|
57
|
+
const pathname = generatedPathname(`/${namespace}/*.md`);
|
|
59
58
|
return {
|
|
60
59
|
kind: "url",
|
|
61
|
-
raw: `worker
|
|
60
|
+
raw: `worker://${pathname}`,
|
|
62
61
|
scheme: "worker",
|
|
63
62
|
username: null,
|
|
64
63
|
password: null,
|
|
65
|
-
hostname:
|
|
64
|
+
hostname: null,
|
|
66
65
|
port: null,
|
|
67
66
|
pathname,
|
|
68
67
|
query: null,
|
|
@@ -115,35 +114,12 @@ const readFilesItems = () => {
|
|
|
115
114
|
return normalizeFilesItems(Number.parseInt(raw, 10));
|
|
116
115
|
};
|
|
117
116
|
import { ProviderError, scopeEnvToAlias } from "@plurnk/plurnk-providers";
|
|
118
|
-
import { validateGbnf } from "@plurnk/gbnf";
|
|
119
117
|
import ProviderInstantiate from "./ProviderInstantiate.js";
|
|
120
118
|
import TurnMaterialization from "./TurnMaterialization.js";
|
|
121
119
|
import BareBatchRunner from "./BareBatchRunner.js";
|
|
122
|
-
import {
|
|
120
|
+
import { TURN_STATUS_IMPLICIT_CONTINUE } from "./turn-signals.js";
|
|
123
121
|
import AdmittedTurnExecutor from "./AdmittedTurnExecutor.js";
|
|
124
|
-
|
|
125
|
-
// template contributes a prefix that llama-server preserves in its response.
|
|
126
|
-
// The artifact names the alternate root that composes those bytes for evidence
|
|
127
|
-
// grading; arbitrary grammars without the declaration retain their transport root.
|
|
128
|
-
const grammarConstraint = (grammar) => {
|
|
129
|
-
const declarations = [...grammar.matchAll(/^# @plurnk-response-root ([A-Za-z][A-Za-z0-9-]*)$/gm)];
|
|
130
|
-
if (declarations.length === 0)
|
|
131
|
-
return { transport: grammar, response: grammar };
|
|
132
|
-
if (declarations.length !== 1)
|
|
133
|
-
throw new Error("GBNF constraint declares multiple @plurnk-response-root values");
|
|
134
|
-
const responseRoot = declarations[0][1];
|
|
135
|
-
if (!grammar.split("\n").some((line) => line.startsWith(`${responseRoot} ::=`))) {
|
|
136
|
-
throw new Error(`GBNF constraint declares missing response root ${responseRoot}`);
|
|
137
|
-
}
|
|
138
|
-
const roots = [...grammar.matchAll(/^root ::= ([A-Za-z][A-Za-z0-9-]*)$/gm)];
|
|
139
|
-
if (roots.length !== 1)
|
|
140
|
-
throw new Error("GBNF constraint must declare exactly one simple root");
|
|
141
|
-
return {
|
|
142
|
-
transport: grammar,
|
|
143
|
-
response: grammar.replace(roots[0][0], `root ::= ${responseRoot}`),
|
|
144
|
-
};
|
|
145
|
-
};
|
|
146
|
-
const TOKEN_BUDGET_OVERFLOW_HARD_DETAIL = "Context Token Budget Overflow: tokensActiveTotal exceeded tokensActiveMax, and the causal overflow turn could not make enough room.";
|
|
122
|
+
const TOKEN_BUDGET_OVERFLOW_HARD_DETAIL = "Context Token Budget Overflow: logTokensTotal exceeds logTokensMax; retained context cannot fit.";
|
|
147
123
|
const curationOverflowFailure = (pressure) => Results.failure("engine:context", "token-budget-overflow", 413, TOKEN_BUDGET_OVERFLOW_HARD_DETAIL, {}, {
|
|
148
124
|
usage: pressure.weight,
|
|
149
125
|
ceiling: pressure.budget,
|
|
@@ -180,7 +156,23 @@ const readProviderRecoveryBackoff = () => readMilliseconds("PLURNK_SERVICE_PROVI
|
|
|
180
156
|
const RECOVERABLE_PROVIDER_FAILURES = new Set(["rate_limit", "network_failure", "deadline_exceeded", "resource_interrupted"]);
|
|
181
157
|
// The wall's abort reason — runLoop branches a mid-turn teardown to the 504 terminal on it.
|
|
182
158
|
export const LOOP_TIMEOUT_REASON = "loop_timeout";
|
|
183
|
-
|
|
159
|
+
// Every turn result carries the same shape; a diversion names only what differs.
|
|
160
|
+
const turnResult = ({ createdTurnIds, turnId }, status, facts = {}) => ({
|
|
161
|
+
createdTurnIds,
|
|
162
|
+
turnId,
|
|
163
|
+
producer: "model",
|
|
164
|
+
kind: "inference",
|
|
165
|
+
status,
|
|
166
|
+
outcomes: [],
|
|
167
|
+
fingerprint: "",
|
|
168
|
+
capacityHardStop: false,
|
|
169
|
+
providerParked: false,
|
|
170
|
+
emptyTurn: false,
|
|
171
|
+
emissionAttempts: 0,
|
|
172
|
+
emissionExhausted: false,
|
|
173
|
+
...facts,
|
|
174
|
+
});
|
|
175
|
+
class TurnRunner {
|
|
184
176
|
#db;
|
|
185
177
|
#schemes;
|
|
186
178
|
#mimetypes;
|
|
@@ -202,7 +194,8 @@ export default class TurnRunner {
|
|
|
202
194
|
#warmWorkspace;
|
|
203
195
|
#dispatch;
|
|
204
196
|
#resolveWorkerProviderIdentity;
|
|
205
|
-
|
|
197
|
+
// {§operator-grammar} — one read per path per daemon; the file is the operator's and static.
|
|
198
|
+
#grammarCache = new Map();
|
|
206
199
|
#materialization;
|
|
207
200
|
#bareBatch;
|
|
208
201
|
#admitted;
|
|
@@ -228,8 +221,8 @@ export default class TurnRunner {
|
|
|
228
221
|
this.#warmWorkspace = warmWorkspace;
|
|
229
222
|
this.#dispatch = dispatch;
|
|
230
223
|
this.#resolveWorkerProviderIdentity = resolveWorkerProviderIdentity;
|
|
231
|
-
this.#materialization = new TurnMaterialization({ db: this.#db,
|
|
232
|
-
this.#bareBatch = new BareBatchRunner({ db: this.#db, providerAttributions: this.#providerAttributions.bind(this), providerFailure:
|
|
224
|
+
this.#materialization = new TurnMaterialization({ db: this.#db, weighContent: this.#weighContent });
|
|
225
|
+
this.#bareBatch = new BareBatchRunner({ db: this.#db, providerAttributions: this.#providerAttributions.bind(this), providerFailure: _a.#providerFailure });
|
|
233
226
|
this.#admitted = new AdmittedTurnExecutor({ db: this.#db, schemes: this.#schemes, notices: this.#notices, problems: this.#problems, dispatcher: this.#dispatcher, bareBatch: this.#bareBatch });
|
|
234
227
|
}
|
|
235
228
|
async #recordInference(args) {
|
|
@@ -243,66 +236,31 @@ export default class TurnRunner {
|
|
|
243
236
|
workerId: args.workerId,
|
|
244
237
|
loopId: args.loopId,
|
|
245
238
|
packetCount: packetCount.count,
|
|
239
|
+
...taskTiming(packetCount),
|
|
246
240
|
});
|
|
247
241
|
}
|
|
248
|
-
// {§
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
TurnRunner.#railGapWarned.add(message);
|
|
255
|
-
process.stderr.write(`plurnk-engine: rail verdict unavailable — the configured grammar did not parse in @plurnk/gbnf (${message})\n`);
|
|
256
|
-
}
|
|
257
|
-
static #requireGrammarEvidence(response, allowWithheld) {
|
|
258
|
-
const evidence = response.grammarEvidence;
|
|
259
|
-
if (evidence === undefined) {
|
|
260
|
-
throw new Error("provider contract violation: configured GBNF response omitted grammar evidence");
|
|
261
|
-
}
|
|
262
|
-
const input = [...evidence.input];
|
|
263
|
-
if (!Number.isInteger(evidence.contentStart)
|
|
264
|
-
|| evidence.contentStart < 0
|
|
265
|
-
|| evidence.contentStart > input.length
|
|
266
|
-
|| typeof evidence.transported !== "boolean"
|
|
267
|
-
|| input.slice(evidence.contentStart).join("") !== response.assistant.content) {
|
|
268
|
-
throw new Error("provider contract violation: grammar evidence does not map exactly to assistant.content");
|
|
269
|
-
}
|
|
270
|
-
if (!allowWithheld && !evidence.transported) {
|
|
271
|
-
throw new Error("provider contract violation: configured GBNF was not transported outside explicit debug mode");
|
|
272
|
-
}
|
|
273
|
-
return evidence;
|
|
274
|
-
}
|
|
275
|
-
async #grammarConstraint(provider) {
|
|
276
|
-
// A real alias scopes its rail; an alias-free direct route uses the
|
|
277
|
-
// global provider configuration. Unrelated alias settings never apply.
|
|
278
|
-
// {§grammar-configuration-admission}
|
|
242
|
+
// {§operator-grammar} — the operator's grammar text for this provider, or undefined when
|
|
243
|
+
// none is configured. A real alias scopes its knob; an alias-free direct route uses the
|
|
244
|
+
// global provider configuration. Unrelated alias settings never apply
|
|
245
|
+
// ({§grammar-configuration-admission}). An unreadable file throws: a configured grammar
|
|
246
|
+
// never silently degrades.
|
|
247
|
+
async #operatorGrammar(provider) {
|
|
279
248
|
ProviderInstantiate.assertGrammarConfigurationScope(provider);
|
|
280
249
|
const alias = ProviderInstantiate.configurationAliasOf(provider);
|
|
281
250
|
const scoped = alias === undefined
|
|
282
251
|
? process.env
|
|
283
|
-
: scopeEnvToAlias(process.env, alias, [
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
]);
|
|
287
|
-
const variant = scoped.PLURNK_PROVIDERS_GBNF;
|
|
288
|
-
if (variant === undefined || variant === "" || variant === "0")
|
|
252
|
+
: scopeEnvToAlias(process.env, alias, ["PLURNK_PROVIDERS_GBNF"]);
|
|
253
|
+
const configured = scoped.PLURNK_PROVIDERS_GBNF;
|
|
254
|
+
if (configured === undefined || configured === "" || configured === "0")
|
|
289
255
|
return undefined;
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
&& scoped.PLURNK_PROVIDERS_GBNF_DEBUG !== "0";
|
|
293
|
-
const hit = this.#gbnfCache.get(variant);
|
|
256
|
+
const path = resolveOperatorGrammarPath(configured);
|
|
257
|
+
const hit = this.#grammarCache.get(path);
|
|
294
258
|
if (hit !== undefined)
|
|
295
|
-
return
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const path = resolveGrammarRailPath(variant);
|
|
301
|
-
const text = await readFile(path, "utf8"); // unresolvable/unreadable throws — a configured rail never silently degrades
|
|
302
|
-
const constraint = grammarConstraint(text);
|
|
303
|
-
this.#gbnfCache.set(variant, constraint);
|
|
304
|
-
process.stderr.write(`plurnk-engine: GBNF constraint: ${alias || "(bare)"} → ${variant} (${text.length} chars)\n`);
|
|
305
|
-
return { ...constraint, allowWithheld };
|
|
259
|
+
return hit;
|
|
260
|
+
const text = await readFile(path, "utf8");
|
|
261
|
+
this.#grammarCache.set(path, text);
|
|
262
|
+
process.stderr.write(`plurnk-engine: operator grammar: ${alias || "(bare)"} → ${path} (${text.length} chars)\n`);
|
|
263
|
+
return text;
|
|
306
264
|
}
|
|
307
265
|
// A lineage's no-parent root; a root worker resolves to itself. Fail hard
|
|
308
266
|
// when corruption leaves a worker without one. {§worker-primary}
|
|
@@ -342,10 +300,9 @@ export default class TurnRunner {
|
|
|
342
300
|
}
|
|
343
301
|
return { line, column };
|
|
344
302
|
}
|
|
345
|
-
// {§packet-attachment-parts}
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
async #wireMessages(packet, workspaceId, provider) {
|
|
303
|
+
// {§packet-attachment-parts}: native parts come from the READ's immutable snapshot,
|
|
304
|
+
// never from a source that may have changed since the observation.
|
|
305
|
+
async #wireMessages(packet, ctx, provider) {
|
|
349
306
|
const accepted = acceptedKinds(provider.inputModalities);
|
|
350
307
|
if (accepted.length === 0 || !(packet.attachments ?? []).some((attachment) => accepted.includes(attachment.kind))) {
|
|
351
308
|
return {
|
|
@@ -355,28 +312,69 @@ export default class TurnRunner {
|
|
|
355
312
|
}
|
|
356
313
|
const nativeInputs = new Set();
|
|
357
314
|
const messages = await PacketWire.wireMessages(packet, async (attachment) => {
|
|
358
|
-
const
|
|
359
|
-
const source = handler?.byteSource?.(attachment.pathname, { db: this.#db, workspaceId });
|
|
360
|
-
if (source === undefined)
|
|
361
|
-
return null;
|
|
362
|
-
const total = await source.size();
|
|
363
|
-
if (total === null || total === 0)
|
|
364
|
-
return null;
|
|
365
|
-
const bytes = await source.read(1, total);
|
|
315
|
+
const bytes = await NativeContent.read(ctx.db, attachment.contentHash);
|
|
366
316
|
nativeInputs.add(attachment.coordinate);
|
|
367
317
|
return bytes;
|
|
368
318
|
}, (kind) => accepted.includes(kind));
|
|
369
319
|
return { messages, nativeInputs: [...nativeInputs] };
|
|
370
320
|
}
|
|
371
|
-
|
|
321
|
+
// One inference cycle: the six phases in order, over the records declared above.
|
|
322
|
+
// A diversion (curation overflow, a provider failure, an exhausted emission)
|
|
323
|
+
// completes the turn and returns; anything else that throws fails every turn
|
|
324
|
+
// this cycle opened.
|
|
325
|
+
async runTurn({ provider, childProvider = provider, messages, recap = "", workspaceId, workerId, loopId, signal, onDispatch, onSettled, turnNumber = 1, invalidEmissionRecoveryEntryId, }) {
|
|
326
|
+
const args = {
|
|
327
|
+
provider, childProvider, messages, recap, workspaceId, workerId, loopId, signal, onDispatch, onSettled,
|
|
328
|
+
turnNumber, invalidEmissionRecoveryEntryId,
|
|
329
|
+
};
|
|
330
|
+
const createdTurnIds = [];
|
|
331
|
+
try {
|
|
332
|
+
const container = await this.#openTurnContainer(args, createdTurnIds);
|
|
333
|
+
const gitStatus = await this.#deriveWorkspace(args, container.systemCtx);
|
|
334
|
+
if (container.initializationTurn !== null)
|
|
335
|
+
await this.#runInitializationTurn(args, container, container.initializationTurn);
|
|
336
|
+
const request = await this.#composeRequest(args, container, gitStatus);
|
|
337
|
+
const overflow = this.#packets.curationOverflow(request.packet);
|
|
338
|
+
if (overflow !== null)
|
|
339
|
+
return await this.#failCuration(request, overflow);
|
|
340
|
+
const attempts = await this.#prepareProviderAttempts(args, request);
|
|
341
|
+
let emission;
|
|
342
|
+
try {
|
|
343
|
+
emission = await this.#attemptProvider(args, request, attempts);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
return await this.#settleProviderFailure(error, args, request, attempts);
|
|
347
|
+
}
|
|
348
|
+
if (!emission.split.emissionValid)
|
|
349
|
+
return await this.#rejectExhaustedEmission(args, request, emission);
|
|
350
|
+
await this.#recordAdmittedEmission(args, request, emission);
|
|
351
|
+
return await this.#settleAdmittedTurn(args, request, emission);
|
|
352
|
+
}
|
|
353
|
+
catch (cause) {
|
|
354
|
+
const completionFailures = [];
|
|
355
|
+
for (const createdTurnId of createdTurnIds) {
|
|
356
|
+
try {
|
|
357
|
+
await Turn.failOpen(this.#db, createdTurnId);
|
|
358
|
+
}
|
|
359
|
+
catch (completionCause) {
|
|
360
|
+
completionFailures.push(completionCause);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (completionFailures.length > 0) {
|
|
364
|
+
throw new AggregateError([cause, ...completionFailures], "turn execution failed and its open turn containers could not all be completed");
|
|
365
|
+
}
|
|
366
|
+
throw cause;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Phase 1 — the producer-neutral turn container. Every producer opens the same
|
|
370
|
+
// durable turn and completes it only after its ordered operations settle; model
|
|
371
|
+
// packets and provider metadata are optional inference evidence, not turn identity.
|
|
372
|
+
async #openTurnContainer(args, createdTurnIds) {
|
|
373
|
+
const { workspaceId, workerId, loopId, turnNumber, invalidEmissionRecoveryEntryId } = args;
|
|
374
|
+
const workerName = await WorkerName.forId(this.#db, workerId);
|
|
372
375
|
const transientOpenLogEntryId = typeof invalidEmissionRecoveryEntryId === "number"
|
|
373
376
|
? invalidEmissionRecoveryEntryId
|
|
374
377
|
: null;
|
|
375
|
-
// === Producer-neutral turn container ===
|
|
376
|
-
//
|
|
377
|
-
// Every producer opens the same durable turn and completes it only
|
|
378
|
-
// after its ordered operations settle. Model packets and provider
|
|
379
|
-
// metadata are optional inference evidence, not turn identity.
|
|
380
378
|
const initialSequence = await this.#db.engine_next_turn_sequence.get({ loop_id: loopId });
|
|
381
379
|
if (initialSequence === undefined)
|
|
382
380
|
throw new Error("Engine.runTurn: next turn sequence is unavailable");
|
|
@@ -392,1193 +390,1052 @@ export default class TurnRunner {
|
|
|
392
390
|
worker_id: workerId,
|
|
393
391
|
});
|
|
394
392
|
const workerFirstInference = priorInference?.present === 0;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
mimetypes: this.#mimetypes,
|
|
431
|
-
defaultChannelFor: (s) => this.#schemes.defaultChannelFor(s, workerId),
|
|
432
|
-
pushNotice: (notice) => this.#notices.push(workspaceId, workerId, loopId, notice),
|
|
433
|
-
requestInteraction: (request) => this.#interactions.request(request, { workspaceId, workerId, loopId, turnId: contextTurnId }, this.#loopSignal(loopId)),
|
|
434
|
-
});
|
|
435
|
-
let systemCtx = systemContext(initializationTurn?.id ?? modelTurn.id);
|
|
436
|
-
// {§prompt-entry} — the prompt entry exists before any turn of the loop
|
|
437
|
-
// runs, so the initialization COPY archives a real source; its `prompt`
|
|
438
|
-
// log row is published to the model turn below (one durable publication
|
|
439
|
-
// per loop, decided by that row).
|
|
440
|
-
const promptPublication = turnNumber === 1 && loopRow?.prompt_published === 0
|
|
441
|
-
&& typeof loopRow.prompt === "string" && loopRow.prompt.length > 0
|
|
442
|
-
? {
|
|
443
|
-
content: loopRow.prompt,
|
|
444
|
-
source: loopRow.prompt_source,
|
|
445
|
-
path: promptTarget(loopRow.sequence, 1),
|
|
393
|
+
// {§worker-initialization-entry} — turn zero is an ordinary `_plurnk`
|
|
394
|
+
// operation turn. Its model-facing zero is a phase label; durable turn
|
|
395
|
+
// coordinates remain one-based.
|
|
396
|
+
const initializationTurn = initialSequence.next === 1 && workerFirstInference
|
|
397
|
+
? await Turn.open(this.#db, { loopId, producer: "_plurnk", kind: "initialization" })
|
|
398
|
+
: null;
|
|
399
|
+
if (initializationTurn !== null)
|
|
400
|
+
createdTurnIds.push(initializationTurn.id);
|
|
401
|
+
const initializationPolicies = initializationTurn === null
|
|
402
|
+
? []
|
|
403
|
+
: (await CapabilityPolicies.layers(this.#db, workspaceId)).map((layer) => layer.policy);
|
|
404
|
+
const modelTurn = initializationTurn === null
|
|
405
|
+
? await Turn.open(this.#db, { loopId, producer: "model", kind: "inference" })
|
|
406
|
+
: null;
|
|
407
|
+
if (modelTurn !== null)
|
|
408
|
+
createdTurnIds.push(modelTurn.id);
|
|
409
|
+
// {§env-delta-log-pull} — creation atomically owns the ordinary worker
|
|
410
|
+
// baseline or fork snapshot. Packet assembly consumes, never invents,
|
|
411
|
+
// that durable observation boundary.
|
|
412
|
+
const ambientCursor = await this.#db.engine_initialize_ambient_cursor.get({ workspace_id: workspaceId, worker_id: workerId });
|
|
413
|
+
if (ambientCursor?.ambient_event_cursor === null || ambientCursor === undefined) {
|
|
414
|
+
throw new Error(`worker ${workerId} has no durable ambient observation boundary`);
|
|
415
|
+
}
|
|
416
|
+
const systemCtx = this.#schemeContext(args, initializationTurn?.id ?? modelTurn.id);
|
|
417
|
+
// {§prompt-entry} — the prompt entry exists before any turn of the loop
|
|
418
|
+
// runs; its `prompt` log row is published to the model turn below (one
|
|
419
|
+
// durable publication per loop, decided by that row).
|
|
420
|
+
const promptPublication = turnNumber === 1 && loopRow?.prompt_published === 0
|
|
421
|
+
&& typeof loopRow.prompt === "string" && loopRow.prompt.length > 0
|
|
422
|
+
? {
|
|
423
|
+
content: loopRow.prompt,
|
|
424
|
+
source: loopRow.prompt_source,
|
|
425
|
+
path: promptTarget(workerName, await PromptFrames.write(systemCtx, {
|
|
426
|
+
loopSequence: loopRow.sequence, ordinal: 1, content: loopRow.prompt,
|
|
427
|
+
source: loopRow.prompt_source, pathname: loopRow.prompt_pathname,
|
|
446
428
|
openPaths: assertOpenPaths(JSON.parse(loopRow.open_paths), `Loop ${loopId} open_paths`),
|
|
447
|
-
}
|
|
448
|
-
:
|
|
449
|
-
if (promptPublication !== null) {
|
|
450
|
-
const entry = {
|
|
451
|
-
channels: { body: { content: promptPublication.content, mimetype: "text/markdown" } },
|
|
452
|
-
attributes: {
|
|
453
|
-
openPaths: promptPublication.openPaths,
|
|
454
|
-
...(promptPublication.source === null ? {} : { source: promptPublication.source }),
|
|
455
|
-
},
|
|
456
|
-
};
|
|
457
|
-
await EntryCrud.writeEntry({ authority: "", pathname: promptPublication.path.pathname }, entry, systemCtx, "prompt", workerId);
|
|
429
|
+
})),
|
|
430
|
+
openPaths: assertOpenPaths(JSON.parse(loopRow.open_paths), `Loop ${loopId} open_paths`),
|
|
458
431
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
432
|
+
: null;
|
|
433
|
+
const initializationStatements = [];
|
|
434
|
+
// {§worker-initialization-entry} — the worker's first turn is the worked
|
|
435
|
+
// example itself: the actual orienting operations and an ordinary TASK.
|
|
436
|
+
// {§turn0-agents-stunt} — the project AGENTS.md (materialized by LoopDocs as
|
|
437
|
+
// worker:///_plurnk/agents.md) gets one foisted READ on the worker's first
|
|
438
|
+
// loop, so local repo guidance is visible turn-0 content. Global policy
|
|
439
|
+
// stays in the system prompt; nothing else is force-read.
|
|
440
|
+
if (initializationTurn !== null) {
|
|
441
|
+
const agentsEntry = await this.#db.crud_find_workspace_entry.get({
|
|
442
|
+
workspace_id: workspaceId,
|
|
443
|
+
scheme: "worker",
|
|
444
|
+
authority: "",
|
|
445
|
+
pathname: generatedPathname("/agents.md"),
|
|
446
|
+
});
|
|
447
|
+
if (agentsEntry !== undefined) {
|
|
448
|
+
const agentsTarget = {
|
|
449
|
+
kind: "url", raw: "worker:///_plurnk/agents.md", scheme: "worker",
|
|
450
|
+
username: null, password: null, hostname: null, port: null,
|
|
451
|
+
pathname: generatedPathname("/agents.md"), query: null, fragment: null,
|
|
474
452
|
};
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
// space shown as scratch. An append onto an absent entry creates it.
|
|
479
|
-
// Authored first among the operations because it executes first
|
|
480
|
-
// ({§op-mode-phases}: Mutate precedes Observe).
|
|
481
|
-
if (promptPublication !== null) {
|
|
482
|
-
const archive = {
|
|
483
|
-
op: "COPY", delimiter: "", annotation: null,
|
|
484
|
-
source: { target: promptPublication.path, metadata: null, lineMarker: null },
|
|
485
|
-
destination: {
|
|
486
|
-
target: { kind: "url", raw: "worker://~/prompts.md", scheme: "worker", username: null, password: null, hostname: "~", port: null, pathname: "/prompts.md", query: null, fragment: null },
|
|
487
|
-
metadata: null,
|
|
488
|
-
lineMarker: { marks: [-1] },
|
|
489
|
-
},
|
|
490
|
-
position: UNKNOWN_POSITION,
|
|
491
|
-
};
|
|
492
|
-
initializationStatements.push(archive);
|
|
493
|
-
}
|
|
494
|
-
// {§turn0-agents-stunt} — the project AGENTS.md (materialized by LoopDocs as
|
|
495
|
-
// worker://~/_plurnk/agents.md) gets one foisted READ on the worker's first
|
|
496
|
-
// loop, so local repo guidance is visible turn-0 content. Global policy
|
|
497
|
-
// stays in the system prompt; nothing else is force-read.
|
|
498
|
-
if (workerFirstInference) {
|
|
499
|
-
const agentsEntry = await this.#db.crud_find_workspace_entry.get({
|
|
500
|
-
workspace_id: workspaceId,
|
|
501
|
-
owner_id: workerId,
|
|
502
|
-
scheme: "worker",
|
|
503
|
-
authority: "",
|
|
504
|
-
pathname: generatedPathname("/agents.md"),
|
|
505
|
-
});
|
|
506
|
-
if (agentsEntry !== undefined) {
|
|
507
|
-
const agentsTarget = {
|
|
508
|
-
kind: "url", raw: "worker://~/_plurnk/agents.md", scheme: "worker",
|
|
509
|
-
username: null, password: null, hostname: "~", port: null,
|
|
510
|
-
pathname: generatedPathname("/agents.md"), query: null, fragment: null,
|
|
511
|
-
};
|
|
512
|
-
const agentsRead = {
|
|
513
|
-
op: "READ", delimiter: "", annotation: null, target: agentsTarget,
|
|
514
|
-
metadata: null, lineMarker: null, body: null, position: UNKNOWN_POSITION,
|
|
515
|
-
};
|
|
516
|
-
initializationStatements.push(agentsRead);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
// The persistent search-index pass (_search-index.maintain) attaches
|
|
521
|
-
// every readable entry/log projection to complete graph/FTS/vector derivations.
|
|
522
|
-
// NOT an action: no log entry, no sequence slot,
|
|
523
|
-
// not dispatched. There is no materialized manifest entry — the catalog
|
|
524
|
-
// is served on demand by FIND: recursive when asked, shallow-mapped below.
|
|
525
|
-
// {§semantic-embed-dedup} — one pass-wide semantic plan binds every
|
|
526
|
-
// chunk counter to the derivation identity it produces.
|
|
527
|
-
// SPEC {§membership} D4/D5 — git-ls-files workspace membership, resolved at
|
|
528
|
-
// prompt-composition (EMI is eager + exhaustive — git is the only bound). When the
|
|
529
|
-
// workspace's project_root is a git working tree, tracked files are
|
|
530
|
-
// members without a client `add`; active members are materialized
|
|
531
|
-
// (disk → body channel) so they appear in the catalog. No-ops
|
|
532
|
-
// on headless / non-git workspaces. Runs BEFORE the derivation pump so
|
|
533
|
-
// this turn's packet reflects them.
|
|
534
|
-
// Joining here is the correctness boundary: passive workspace creation and
|
|
535
|
-
// attachment spend nothing, while the model never runs against partial
|
|
536
|
-
// graph/vector coverage.
|
|
537
|
-
await this.#warmWorkspace(systemCtx, false);
|
|
538
|
-
// The warm materialized membership before deriving. This second pass is the
|
|
539
|
-
// ordinary cheap change detector and captures any drift that landed meanwhile.
|
|
540
|
-
const fsDivergences = await GitMembership.indexGitMembership(systemCtx);
|
|
541
|
-
// {§packet-git-status} — one post-reconciliation snapshot supplies both
|
|
542
|
-
// the compact packet summary and each causal file event's exact XY state.
|
|
543
|
-
const gitStatus = await GitState.status(this.#db, workspaceId, this.#loopSignal(loopId));
|
|
544
|
-
await this.#materialization.logFsFictions(workspaceId, fsDivergences, gitStatus);
|
|
545
|
-
// The refresh above may have changed bodies (including model/client edits
|
|
546
|
-
// since the startup warm). Re-derive to completion before packet/model
|
|
547
|
-
// construction. Membership is already current, so this pass does not
|
|
548
|
-
// consume the filesystem divergences a second time.
|
|
549
|
-
await this.#warmWorkspace(systemCtx, true, false);
|
|
550
|
-
// Turn-0 catalog preview (PLURNK_SERVICE_FILES_ITEMS, {§actor-boundary-catalog-preview}):
|
|
551
|
-
// Eight bodyless FIND surveys in the worker's packetless initialization turn establish the Agent
|
|
552
|
-
// Skills, the plurnk references, the enabled tools, agents, and members, then the project, commons,
|
|
553
|
-
// and private surfaces, in that order.
|
|
554
|
-
// Their `init` classification lets the model curate this opening survey as one log set.
|
|
555
|
-
if (initializationTurn !== null) {
|
|
556
|
-
// {§operator-config-workspace-files-items} — workspace filesItems replaces the env default.
|
|
557
|
-
const { filesItems: workspaceMI } = await WorkspaceSettings.read(this.#db, workspaceId);
|
|
558
|
-
const filesItems = workspaceMI !== null ? normalizeFilesItems(workspaceMI) : readFilesItems();
|
|
559
|
-
if (filesItems !== null && workerFirstInference) { // {§actor-boundary-catalog-preview} — once per worker
|
|
560
|
-
const catalogSchemes = await this.#db.engine_scheme_catalog_summary.all({ workspace_id: workspaceId });
|
|
561
|
-
const fileItems = catalogSchemes.find(({ scheme }) => scheme === "file")?.shallow_items ?? 0;
|
|
562
|
-
const fileCap = filesItems > 0 && fileItems > 0 ? Math.min(filesItems, fileItems) : null;
|
|
563
|
-
// {§tools-resource-materialization} — a PLURNK_MCP_EXPANDED server
|
|
564
|
-
// contributes one FIND over its family document matching the
|
|
565
|
-
// `### EXEC0` headings: one row per tool — heading, annotation,
|
|
566
|
-
// signature — paged like every survey. No document is delivered
|
|
567
|
-
// unasked; per-target child documents do not exist.
|
|
568
|
-
const registry = this.#executors();
|
|
569
|
-
const references = await this.#packets.referenceEntries(workspaceId, workerId);
|
|
570
|
-
const referenceNames = (namespace) => {
|
|
571
|
-
const prefix = generatedPathname(`/${namespace}/`);
|
|
572
|
-
return [...new Set(references.flatMap(({ pathname }) => {
|
|
573
|
-
if (!pathname.startsWith(prefix) || !pathname.endsWith(".md"))
|
|
574
|
-
return [];
|
|
575
|
-
const name = pathname.slice(prefix.length, -3);
|
|
576
|
-
return name.includes("/") ? [] : [name];
|
|
577
|
-
}))].toSorted();
|
|
578
|
-
};
|
|
579
|
-
const schemeExamples = new Map(this.#schemes.examples(workerId).map(({ name, source }) => [name, source]));
|
|
580
|
-
const runtimeAdmitted = (runtime) => {
|
|
581
|
-
const tools = registry?.toolRegistry(runtime, workerId);
|
|
582
|
-
return tools === null || tools === undefined
|
|
583
|
-
? this.#capabilities.allowsRuntimeAcross(runtime, null, workerId, initializationPolicies)
|
|
584
|
-
: tools.tools.some((tool) => this.#capabilities.allowsRuntimeAcross(runtime, tool.target, workerId, initializationPolicies));
|
|
585
|
-
};
|
|
586
|
-
const referenceAdmitted = (namespace, name) => {
|
|
587
|
-
const entry = registry?.entry(name, workerId);
|
|
588
|
-
const entryNamespace = entry?.resourcesPath === "/tools" ? "tools" : "plurnk";
|
|
589
|
-
if (entry !== undefined && entryNamespace === namespace)
|
|
590
|
-
return runtimeAdmitted(name);
|
|
591
|
-
const source = schemeExamples.get(name);
|
|
592
|
-
return source === undefined
|
|
593
|
-
|| this.#capabilities.allowsExampleAcross(source, workerId, initializationPolicies);
|
|
594
|
-
};
|
|
595
|
-
const plurnkReferences = referenceNames("plurnk");
|
|
596
|
-
const toolReferences = referenceNames("tools");
|
|
597
|
-
const plurnkCatalog = workerCatalogTarget("plurnk", plurnkReferences, plurnkReferences.filter((name) => referenceAdmitted("plurnk", name)));
|
|
598
|
-
const toolsCatalog = workerCatalogTarget("tools", toolReferences, toolReferences.filter((name) => referenceAdmitted("tools", name)));
|
|
599
|
-
const toolExpansions = [];
|
|
600
|
-
for (const tag of registry?.availableRuntimes(workerId) ?? []) {
|
|
601
|
-
const entry = registry?.entry(tag, workerId);
|
|
602
|
-
if (entry?.resourcesPath !== "/tools" || entry.expandTools !== true)
|
|
603
|
-
continue;
|
|
604
|
-
const tools = registry?.toolRegistry(tag, workerId);
|
|
605
|
-
const admittedTools = tools?.tools.filter((tool) => this.#capabilities.allowsRuntimeAcross(tag, tool.target, workerId, initializationPolicies)) ?? [];
|
|
606
|
-
if (admittedTools.length === 0)
|
|
607
|
-
continue;
|
|
608
|
-
const pattern = tools !== null && tools !== undefined && admittedTools.length !== tools.tools.length
|
|
609
|
-
? `^### EXEC0 \\[${regexLiteral(tag)}\\] \\((?:${admittedTools
|
|
610
|
-
.map((tool) => regexLiteral(PathSyntax.escapeTarget(tool.target)))
|
|
611
|
-
.join("|")})\\).*\\n.*$`
|
|
612
|
-
: "^### EXEC0 .*\\n.*$";
|
|
613
|
-
toolExpansions.push({
|
|
614
|
-
statement: {
|
|
615
|
-
op: "FIND", delimiter: "", annotation: null,
|
|
616
|
-
target: { kind: "url", raw: `worker://~/_plurnk/tools/${tag}.md`, scheme: "worker", username: null, password: null, hostname: "~", port: null, pathname: generatedPathname(`/tools/${tag}.md`), query: null, fragment: null },
|
|
617
|
-
metadata: null,
|
|
618
|
-
body: { dialect: "regex", raw: `/${pattern.replaceAll("/", "\\/")}/m`, pattern, flags: "m" },
|
|
619
|
-
lineMarker: null, position: UNKNOWN_POSITION,
|
|
620
|
-
},
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
const surveys = [
|
|
624
|
-
{
|
|
625
|
-
statement: {
|
|
626
|
-
op: "FIND", delimiter: "", annotation: null,
|
|
627
|
-
target: { kind: "url", raw: "worker://~/_plurnk/skills/*.md", scheme: "worker", username: null, password: null, hostname: "~", port: null, pathname: generatedPathname("/skills/*.md"), query: null, fragment: null },
|
|
628
|
-
metadata: null,
|
|
629
|
-
body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
630
|
-
},
|
|
631
|
-
},
|
|
632
|
-
...(plurnkCatalog === null ? [] : [{
|
|
633
|
-
statement: {
|
|
634
|
-
op: "FIND", delimiter: "", annotation: null,
|
|
635
|
-
target: plurnkCatalog,
|
|
636
|
-
metadata: null,
|
|
637
|
-
body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
638
|
-
},
|
|
639
|
-
}]),
|
|
640
|
-
...(toolsCatalog === null ? [] : [{
|
|
641
|
-
// {§tools-resource-materialization} — enabled tool families
|
|
642
|
-
// (MCP servers) survey at family level; each row's summary is
|
|
643
|
-
// the server one-liner or its flagship invocation form, so the
|
|
644
|
-
// discovery row itself orients. Servers named in
|
|
645
|
-
// PLURNK_MCP_EXPANDED add a second survey of their complete
|
|
646
|
-
// tool tree.
|
|
647
|
-
statement: {
|
|
648
|
-
op: "FIND", delimiter: "", annotation: null,
|
|
649
|
-
target: toolsCatalog,
|
|
650
|
-
metadata: null,
|
|
651
|
-
body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
652
|
-
},
|
|
653
|
-
}]),
|
|
654
|
-
...toolExpansions,
|
|
655
|
-
{
|
|
656
|
-
// {§a2a-agents-catalog} — enabled outbound agents survey at
|
|
657
|
-
// alias level; each row's summary is the agent's identity line,
|
|
658
|
-
// the exact card stays pullable through READ a2a://<alias>.
|
|
659
|
-
statement: {
|
|
660
|
-
op: "FIND", delimiter: "", annotation: null,
|
|
661
|
-
target: { kind: "url", raw: "worker://~/_plurnk/agents/*.md", scheme: "worker", username: null, password: null, hostname: "~", port: null, pathname: generatedPathname("/agents/*.md"), query: null, fragment: null },
|
|
662
|
-
metadata: null,
|
|
663
|
-
body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
// {§members-projection} — enabled members definitions survey at alias
|
|
668
|
-
// level; each row's summary is what its glob resolved to.
|
|
669
|
-
statement: {
|
|
670
|
-
op: "FIND", delimiter: "", annotation: null,
|
|
671
|
-
target: { kind: "url", raw: "worker://~/_plurnk/members/*.md", scheme: "worker", username: null, password: null, hostname: "~", port: null, pathname: generatedPathname("/members/*.md"), query: null, fragment: null },
|
|
672
|
-
metadata: null,
|
|
673
|
-
body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
{
|
|
677
|
-
statement: {
|
|
678
|
-
op: "FIND", delimiter: "", annotation: "workspace files",
|
|
679
|
-
target: { kind: "local", raw: "*" },
|
|
680
|
-
metadata: null,
|
|
681
|
-
body: null,
|
|
682
|
-
lineMarker: fileCap === null ? null : { marks: [1, fileCap] },
|
|
683
|
-
position: UNKNOWN_POSITION,
|
|
684
|
-
},
|
|
685
|
-
},
|
|
686
|
-
{
|
|
687
|
-
statement: {
|
|
688
|
-
op: "FIND", delimiter: "", annotation: "workspace entries",
|
|
689
|
-
target: { kind: "url", raw: "worker:///*", scheme: "worker", username: null, password: null, hostname: null, port: null, pathname: "/*", query: null, fragment: null },
|
|
690
|
-
metadata: null,
|
|
691
|
-
body: null, lineMarker: null, position: UNKNOWN_POSITION,
|
|
692
|
-
},
|
|
693
|
-
},
|
|
694
|
-
{
|
|
695
|
-
statement: {
|
|
696
|
-
op: "FIND", delimiter: "", annotation: "private worker entries",
|
|
697
|
-
target: { kind: "url", raw: "worker://~/*", scheme: "worker", username: null, password: null, hostname: "~", port: null, pathname: "/*", query: null, fragment: null },
|
|
698
|
-
metadata: null,
|
|
699
|
-
body: null, lineMarker: null, position: UNKNOWN_POSITION,
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
];
|
|
703
|
-
initializationStatements.push(...surveys.map(({ statement }) => statement));
|
|
704
|
-
}
|
|
705
|
-
const send = {
|
|
706
|
-
op: "SEND", delimiter: "", annotation: null,
|
|
707
|
-
status: 102, target: null, metadata: null, lineMarker: null,
|
|
708
|
-
body: { raw: "Next: Address the prompt.", json: null },
|
|
709
|
-
position: UNKNOWN_POSITION,
|
|
453
|
+
const agentsRead = {
|
|
454
|
+
op: "READ", aside: null, target: agentsTarget,
|
|
455
|
+
metadata: null, lineMarker: null, matcher: null, body: null, position: UNKNOWN_POSITION,
|
|
710
456
|
};
|
|
711
|
-
initializationStatements.push(
|
|
712
|
-
const admittedInitializationStatements = initializationStatements.filter(initializationAdmits);
|
|
713
|
-
const source = TurnOps.renderInternal(admittedInitializationStatements);
|
|
714
|
-
const admitted = TurnOps.parseInternal(source);
|
|
715
|
-
const result = await this.executeAdmittedTurn({
|
|
716
|
-
statements: admitted,
|
|
717
|
-
source,
|
|
718
|
-
sourceFolded: false,
|
|
719
|
-
origin: "_plurnk",
|
|
720
|
-
workspaceId,
|
|
721
|
-
workerId,
|
|
722
|
-
loopId,
|
|
723
|
-
turnId: initializationTurn.id,
|
|
724
|
-
fromSequence: 1,
|
|
725
|
-
failOnOperationError: true,
|
|
726
|
-
signal: this.#loopSignal(loopId),
|
|
727
|
-
onDispatch,
|
|
728
|
-
onSettled,
|
|
729
|
-
});
|
|
730
|
-
if (result.status !== TURN_STATUS_IMPLICIT_CONTINUE) {
|
|
731
|
-
throw new Error(`initialization SEND returned ${result.status}; expected ${TURN_STATUS_IMPLICIT_CONTINUE}`);
|
|
732
|
-
}
|
|
457
|
+
initializationStatements.push(agentsRead);
|
|
733
458
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
workerName, transientOpenLogEntryId, loopRow, createdTurnIds,
|
|
462
|
+
initializationTurn, initializationPolicies, initializationStatements,
|
|
463
|
+
modelTurn, systemCtx, promptPublication,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
// The `_plurnk` scheme context for one turn. Threaded per turn, never engine
|
|
467
|
+
// state, so concurrent loops on different providers each read their own honest
|
|
468
|
+
// tokenizer values.
|
|
469
|
+
#schemeContext({ workspaceId, workerId, loopId }, turnId) {
|
|
470
|
+
return {
|
|
471
|
+
db: this.#db, workspaceId, workerId, loopId, turnId,
|
|
472
|
+
writer: "_plurnk",
|
|
473
|
+
signal: this.#loopSignal(loopId),
|
|
474
|
+
streamEventNotify: this.#streamEventNotify,
|
|
475
|
+
wakeWorkerNotify: this.#wakeWorkerNotify,
|
|
476
|
+
weigh: this.#weighContent,
|
|
477
|
+
mimetypes: this.#mimetypes,
|
|
478
|
+
defaultChannelFor: (s) => this.#schemes.defaultChannelFor(s, workspaceId),
|
|
479
|
+
pushNotice: (notice) => this.#notices.push(workspaceId, workerId, loopId, notice),
|
|
480
|
+
requestInteraction: (request, signal = this.#loopSignal(loopId)) => this.#interactions.request(request, { workspaceId, workerId, loopId, turnId }, signal),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
// Phase 2 — membership and derivation to completion, then the one git snapshot
|
|
484
|
+
// every packet rebuild reads.
|
|
485
|
+
async #deriveWorkspace({ workspaceId, loopId }, systemCtx) {
|
|
486
|
+
// The persistent search-index pass (_search-index.maintain) attaches
|
|
487
|
+
// every readable entry/log projection to complete graph/FTS derivations.
|
|
488
|
+
// NOT an action: no log entry, no sequence slot,
|
|
489
|
+
// not dispatched. There is no materialized manifest entry — the catalog
|
|
490
|
+
// is served on demand by FIND: recursive when asked, shallow-mapped below.
|
|
491
|
+
// SPEC {§membership} D4/D5 — git-ls-files workspace membership, resolved at
|
|
492
|
+
// prompt-composition (EMI is eager + exhaustive — git is the only bound). When the
|
|
493
|
+
// workspace's project_root is a git working tree, tracked files are
|
|
494
|
+
// members without a client `add`; active members are materialized
|
|
495
|
+
// (disk → body channel) so they appear in the catalog. No-ops
|
|
496
|
+
// on headless / non-git workspaces. Runs BEFORE the derivation pump so
|
|
497
|
+
// this turn's packet reflects them.
|
|
498
|
+
// Joining here is the correctness boundary: passive workspace creation and
|
|
499
|
+
// attachment spend nothing, while the model never runs against partial
|
|
500
|
+
// graph/vector coverage.
|
|
501
|
+
await this.#warmWorkspace(systemCtx, false);
|
|
502
|
+
// The warm materialized membership before deriving. This second pass is the
|
|
503
|
+
// ordinary cheap change detector and captures any drift that landed meanwhile.
|
|
504
|
+
const fsDivergences = await GitMembership.indexGitMembership(systemCtx);
|
|
505
|
+
// {§packet-git-status} — one post-reconciliation snapshot supplies both
|
|
506
|
+
// the compact packet summary and each causal file event's exact XY state.
|
|
507
|
+
const gitStatus = await GitState.status(this.#db, workspaceId, this.#loopSignal(loopId));
|
|
508
|
+
await this.#materialization.logFsFictions(workspaceId, fsDivergences, gitStatus);
|
|
509
|
+
// The refresh above may have changed bodies (including model/client edits
|
|
510
|
+
// since the startup warm). Re-derive to completion before packet/model
|
|
511
|
+
// construction. Membership is already current, so this pass does not
|
|
512
|
+
// consume the filesystem divergences a second time.
|
|
513
|
+
await this.#warmWorkspace(systemCtx, true, false);
|
|
514
|
+
return gitStatus;
|
|
515
|
+
}
|
|
516
|
+
// {§worker-initialization-entry} — the worker's first turn is the worked example
|
|
517
|
+
// itself: the actual orienting operations and an ordinary TASK, executed as a
|
|
518
|
+
// complete turn before the model boundary.
|
|
519
|
+
async #runInitializationTurn(args, container, initializationTurn) {
|
|
520
|
+
const { provider, workspaceId, workerId, loopId, onDispatch, onSettled } = args;
|
|
521
|
+
const { loopRow, initializationStatements, initializationPolicies } = container;
|
|
522
|
+
// Turn-0 catalog preview (PLURNK_SERVICE_FILES_ITEMS, {§actor-boundary-catalog-preview}):
|
|
523
|
+
// Eight bodyless FIND surveys in the worker's packetless initialization turn establish the Agent
|
|
524
|
+
// Skills, the plurnk references, the enabled tools, agents, and members, then the project, commons,
|
|
525
|
+
// and named scratch, in that order.
|
|
526
|
+
// Their `init` classification lets the model curate this opening survey as one log set.
|
|
527
|
+
// {§operator-config-workspace-files-items} — workspace filesItems replaces the env default.
|
|
528
|
+
const { filesItems: workspaceMI } = await WorkspaceSettings.read(this.#db, workspaceId);
|
|
529
|
+
const filesItems = workspaceMI !== null ? normalizeFilesItems(workspaceMI) : readFilesItems();
|
|
530
|
+
if (filesItems !== null) { // {§actor-boundary-catalog-preview} — once per worker
|
|
531
|
+
initializationStatements.push(...await this.#catalogSurveys(args, container, filesItems));
|
|
532
|
+
}
|
|
533
|
+
const task = {
|
|
534
|
+
op: "TASK", aside: null, target: null, metadata: null, lineMarker: null,
|
|
535
|
+
body: [{ content: "Address the prompt.", status: "in_progress" }],
|
|
536
|
+
position: UNKNOWN_POSITION,
|
|
537
|
+
};
|
|
538
|
+
const pathname = `/${loopRow.sequence}/${initializationTurn.sequence}`;
|
|
539
|
+
await Turn.recordSource(this.#db, initializationTurn.id, "reasoning", ReasoningView.initialSource());
|
|
540
|
+
// {§reasoning-initial-read} — the pattern READ needs a text/plain projection to run the regex.
|
|
541
|
+
const pluck = await this.#mimetypes.getHandler("text/plain") !== null;
|
|
542
|
+
const reasoningRead = ReasoningView.initialRead(provider, loopRow.sequence, initializationTurn.sequence, pluck);
|
|
543
|
+
if (reasoningRead !== null)
|
|
544
|
+
initializationStatements.push(reasoningRead);
|
|
545
|
+
initializationStatements.push({
|
|
546
|
+
op: "READ", aside: "inspect this turn's emission", matcher: null, body: null, metadata: null,
|
|
547
|
+
target: {
|
|
548
|
+
kind: "url", raw: `ops://${pathname}`, scheme: "ops", pathname,
|
|
549
|
+
username: null, password: null, hostname: null, port: null, query: null, fragment: null,
|
|
550
|
+
},
|
|
551
|
+
lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
552
|
+
});
|
|
553
|
+
// {§prompt-entry} — the prompt reaches the model as its `prompt` row in the first
|
|
554
|
+
// model turn; initialization does not READ it a second time.
|
|
555
|
+
initializationStatements.push(task);
|
|
556
|
+
const admittedInitializationStatements = initializationStatements.filter((statement) => this.#capabilities.allowsAcross(statement, workspaceId, initializationPolicies));
|
|
557
|
+
const source = TurnOps.renderInternal(admittedInitializationStatements);
|
|
558
|
+
const admitted = TurnOps.parseInternal(source);
|
|
559
|
+
const result = await this.executeAdmittedTurn({
|
|
560
|
+
statements: admitted,
|
|
561
|
+
source,
|
|
562
|
+
origin: "_plurnk",
|
|
563
|
+
workspaceId,
|
|
564
|
+
workerId,
|
|
565
|
+
loopId,
|
|
566
|
+
turnId: initializationTurn.id,
|
|
567
|
+
fromSequence: 1,
|
|
568
|
+
failOnOperationError: true,
|
|
569
|
+
signal: this.#loopSignal(loopId),
|
|
570
|
+
onDispatch,
|
|
571
|
+
onSettled,
|
|
572
|
+
});
|
|
573
|
+
if (result.status !== TURN_STATUS_IMPLICIT_CONTINUE) {
|
|
574
|
+
throw new Error(`initialization TASK returned ${result.status}; expected ${TURN_STATUS_IMPLICIT_CONTINUE}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// {§actor-boundary-catalog-preview} — the opening surveys, each admitted only when
|
|
578
|
+
// its scheme is registered for the workspace. A positive filesItems caps the
|
|
579
|
+
// project rows.
|
|
580
|
+
async #catalogSurveys({ workspaceId }, { workerName, initializationPolicies }, filesItems) {
|
|
581
|
+
const catalogSchemes = await this.#db.engine_scheme_catalog_summary.all({ workspace_id: workspaceId });
|
|
582
|
+
const fileItems = catalogSchemes.find(({ scheme }) => scheme === "file")?.shallow_items ?? 0;
|
|
583
|
+
const fileCap = filesItems > 0 && fileItems > 0 ? Math.min(filesItems, fileItems) : null;
|
|
584
|
+
const surveys = [
|
|
765
585
|
{
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
586
|
+
op: "FIND", aside: null,
|
|
587
|
+
target: { kind: "url", raw: "skill://*/SKILL.md", scheme: "skill", username: null, password: null, hostname: "*", port: null, pathname: "/SKILL.md", query: null, fragment: null },
|
|
588
|
+
metadata: null,
|
|
589
|
+
matcher: null, body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
op: "FIND", aside: null,
|
|
593
|
+
target: workerCatalogTarget("plurnk"),
|
|
594
|
+
metadata: null,
|
|
595
|
+
matcher: null, body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
// {§tools-resource-materialization} — enabled tool families
|
|
599
|
+
// (MCP servers) survey at family level; each row's summary is
|
|
600
|
+
// the server one-liner or its flagship invocation form, so the
|
|
601
|
+
// discovery row itself orients. Servers named in
|
|
602
|
+
// PLURNK_MCP_EXPANDED add a second survey of their complete
|
|
603
|
+
// tool tree.
|
|
604
|
+
op: "FIND", aside: null,
|
|
605
|
+
target: workerCatalogTarget("tools"),
|
|
606
|
+
metadata: null,
|
|
607
|
+
matcher: null, body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
608
|
+
},
|
|
609
|
+
...this.#toolExpansions(workspaceId, initializationPolicies),
|
|
610
|
+
{
|
|
611
|
+
// {§a2a-agents-catalog} — enabled outbound agents survey at
|
|
612
|
+
// alias level; each row's summary is the agent's identity line,
|
|
613
|
+
// the exact card stays pullable through READ a2a://<alias>.
|
|
614
|
+
op: "FIND", aside: null,
|
|
615
|
+
target: { kind: "url", raw: "worker:///_plurnk/agents/*.md", scheme: "worker", username: null, password: null, hostname: null, port: null, pathname: generatedPathname("/agents/*.md"), query: null, fragment: null },
|
|
616
|
+
metadata: null,
|
|
617
|
+
matcher: null, body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
// {§members-projection} — enabled members definitions survey at alias
|
|
621
|
+
// level; each row's summary is what its glob resolved to.
|
|
622
|
+
op: "FIND", aside: null,
|
|
623
|
+
target: { kind: "url", raw: "worker:///_plurnk/members/*.md", scheme: "worker", username: null, password: null, hostname: null, port: null, pathname: generatedPathname("/members/*.md"), query: null, fragment: null },
|
|
624
|
+
metadata: null,
|
|
625
|
+
matcher: null, body: null, lineMarker: { marks: [1, -1] }, position: UNKNOWN_POSITION,
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
op: "FIND", aside: "project root member files",
|
|
629
|
+
target: { kind: "local", raw: "*" },
|
|
630
|
+
metadata: null,
|
|
631
|
+
matcher: null,
|
|
632
|
+
body: null,
|
|
633
|
+
lineMarker: fileCap === null ? null : { marks: [1, fileCap] },
|
|
634
|
+
position: UNKNOWN_POSITION,
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
op: "FIND", aside: "shared worker Extended Context",
|
|
638
|
+
target: { kind: "url", raw: "worker:///*", scheme: "worker", username: null, password: null, hostname: null, port: null, pathname: "/*", query: null, fragment: null },
|
|
639
|
+
metadata: null,
|
|
640
|
+
matcher: null, body: null, lineMarker: null, position: UNKNOWN_POSITION,
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
op: "FIND", aside: "private worker Extended Context",
|
|
644
|
+
target: { kind: "url", raw: `worker://${workerName}/*`, scheme: "worker", username: null, password: null, hostname: workerName, port: null, pathname: "/*", query: null, fragment: null },
|
|
645
|
+
metadata: null,
|
|
646
|
+
matcher: null, body: null, lineMarker: null, position: UNKNOWN_POSITION,
|
|
647
|
+
},
|
|
648
|
+
];
|
|
649
|
+
return surveys.filter(({ target }) => this.#schemes.get(target?.kind === "url" ? target.scheme : "file", workspaceId) !== undefined);
|
|
650
|
+
}
|
|
651
|
+
// {§tools-resource-materialization} — expanded families project complete
|
|
652
|
+
// invocation blocks, paged through ordinary FIND result ranges: one pattern FIND
|
|
653
|
+
// per family, naming the admitted tools when the policy narrows them.
|
|
654
|
+
#toolExpansions(workspaceId, policies) {
|
|
655
|
+
const registry = this.#executors();
|
|
656
|
+
const expansions = [];
|
|
657
|
+
for (const tag of registry?.availableRuntimes(workspaceId) ?? []) {
|
|
658
|
+
const entry = registry?.entry(tag, workspaceId);
|
|
659
|
+
if (entry?.resourcesPath !== "/tools" || entry.expandTools !== true)
|
|
660
|
+
continue;
|
|
661
|
+
const tools = registry?.toolRegistry(tag, workspaceId);
|
|
662
|
+
const admittedTools = tools?.tools.filter((tool) => this.#capabilities.allowsRuntimeAcross(tag, tool.target, workspaceId, policies)) ?? [];
|
|
663
|
+
if (admittedTools.length === 0)
|
|
664
|
+
continue;
|
|
665
|
+
const targetFilter = tools !== null && tools !== undefined && admittedTools.length !== tools.tools.length
|
|
666
|
+
? " \\((?:" + admittedTools.map((tool) => regexLiteral(PathSyntax.escapeTarget(tool.target))).join("|") + ")\\)"
|
|
667
|
+
: " ";
|
|
668
|
+
const pattern = "^(\x60{3,})" + regexLiteral(tag) + targetFilter + "[^\\n]*?(?:\\1|\\n[\\s\\S]*?\\n\\1)$";
|
|
669
|
+
expansions.push({
|
|
670
|
+
op: "FIND", aside: null,
|
|
671
|
+
target: { kind: "url", raw: `worker:///_plurnk/tools/${tag}.md`, scheme: "worker", username: null, password: null, hostname: null, port: null, pathname: generatedPathname(`/tools/${tag}.md`), query: null, fragment: null },
|
|
672
|
+
metadata: null,
|
|
673
|
+
matcher: { dialect: "regex", raw: `/${pattern.replaceAll("/", "\\/")}/m`, pattern, flags: "m" },
|
|
674
|
+
body: null, lineMarker: null, position: UNKNOWN_POSITION,
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return expansions;
|
|
678
|
+
}
|
|
679
|
+
// Phase 3 — the inference turn and its request: the prompts the model has not
|
|
680
|
+
// seen, their open-path READs, the ambient and stream deltas, then the packet
|
|
681
|
+
// ({§packet-stored-shape}).
|
|
682
|
+
async #composeRequest(args, container, gitStatus) {
|
|
683
|
+
const { workspaceId, workerId, loopId } = args;
|
|
684
|
+
// Initialization is a complete preceding turn, not a set of rows
|
|
685
|
+
// interleaved with the model boundary. The engine may now acquire the
|
|
686
|
+
// inference turn inside the same warmed workspace cycle.
|
|
687
|
+
const modelTurn = container.modelTurn ?? await Turn.open(this.#db, { loopId, producer: "model", kind: "inference" });
|
|
688
|
+
if (container.modelTurn === null)
|
|
689
|
+
container.createdTurnIds.push(modelTurn.id);
|
|
690
|
+
const { id: turnId, sequence: seq } = modelTurn;
|
|
691
|
+
const systemCtx = this.#schemeContext(args, turnId);
|
|
692
|
+
const loopSeq = (await this.#db.engine_loop_sequence.get({ loop_id: loopId }))?.sequence ?? loopId;
|
|
693
|
+
const prompts = await this.#publishPrompts(args, container, turnId, loopSeq);
|
|
694
|
+
let nextActionIndex = await this.#readOpenPaths(args, turnId, prompts.openPaths, prompts.nextActionIndex);
|
|
695
|
+
// {§env-delta-log-pull} — materialize ambient observations before packet
|
|
696
|
+
// composition and reserve their action indices. {§exec-stream} owns the
|
|
697
|
+
// distinct byte-cursor path for this worker's streams.
|
|
698
|
+
// {§exec-poll} — an execution `<0>` is turn-scoped: reap the worker's open turn-scoped streams (necessarily
|
|
699
|
+
// from a prior turn — this runs before the turn's own spawns) so a `<0>` never survives into
|
|
700
|
+
// the subsequent turn. The terminal output then surfaces initially visible via the stream-delta path.
|
|
701
|
+
await this.#reapTurnScopedStreams(workerId);
|
|
702
|
+
nextActionIndex += await this.#materialization.materializeEnvironmentDeltas({ workspaceId, workerId, loopId, turnId, fromSequence: nextActionIndex });
|
|
703
|
+
nextActionIndex += await this.#materialization.materializeStreamDeltas({ workspaceId, workerId, loopId, turnId, fromSequence: nextActionIndex });
|
|
704
|
+
// The post-reconciliation Git snapshot above is threaded into the packet
|
|
705
|
+
// and every budget rebuild; overflow never shells again.
|
|
706
|
+
// Notices are non-terminal observations, never operation-failure truth.
|
|
707
|
+
// Drain once and thread the same set through every overflow rebuild.
|
|
708
|
+
const notices = this.#notices.drain(loopId)
|
|
709
|
+
.filter((event) => event.level !== "info");
|
|
710
|
+
// Build the model request packet ({§packet-stored-shape}). The log build
|
|
711
|
+
// queries log_entries scoped to the worker — the prompt entry just
|
|
712
|
+
// written (if turn 1) is part of that query result.
|
|
713
|
+
const facts = { turnId, seq, gitStatus, notices, transientOpenLogEntryId: container.transientOpenLogEntryId, promptProjection: "automatic" };
|
|
714
|
+
let packet = await this.#buildPacket(args, facts);
|
|
715
|
+
// {§context-output-admission} — output admission changes no operation
|
|
716
|
+
// outcome, authored inventory, or turn identity.
|
|
717
|
+
if (await this.#packets.admitOutput(packet, turnId))
|
|
718
|
+
packet = await this.#buildPacket(args, facts);
|
|
719
|
+
return { ...facts, createdTurnIds: container.createdTurnIds, loopSeq, systemCtx, nextActionIndex, packet };
|
|
720
|
+
}
|
|
721
|
+
// Pre-model writes. Each prompt the model has not seen yet becomes a `prompt`
|
|
722
|
+
// operation row whose target is its durable prompt:// entry; model operations
|
|
723
|
+
// continue the same turn sequence after these rows. Returns the next action index
|
|
724
|
+
// and the frames' open paths.
|
|
725
|
+
async #publishPrompts({ workerId, loopId, onDispatch, onSettled }, { workerName, promptPublication }, turnId, loopSeq) {
|
|
726
|
+
let nextActionIndex = 1;
|
|
727
|
+
const openPaths = [];
|
|
728
|
+
if (promptPublication !== null) { // {§prompt-entry} — one durable publication per loop
|
|
729
|
+
openPaths.push(...promptPublication.openPaths);
|
|
730
|
+
const promptLogId = await this.#materialization.writePromptLog({
|
|
839
731
|
workerId,
|
|
840
732
|
loopId,
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
promptProjection,
|
|
733
|
+
turnId,
|
|
734
|
+
sequence: nextActionIndex++,
|
|
735
|
+
target: promptPublication.path,
|
|
736
|
+
content: promptPublication.content,
|
|
737
|
+
source: promptPublication.source,
|
|
847
738
|
});
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
statements: admitted,
|
|
866
|
-
source,
|
|
867
|
-
sourceFolded: true,
|
|
868
|
-
origin: "_plurnk",
|
|
869
|
-
workspaceId,
|
|
870
|
-
workerId,
|
|
871
|
-
loopId,
|
|
872
|
-
turnId,
|
|
873
|
-
fromSequence: nextActionIndex,
|
|
874
|
-
failOnOperationError: true,
|
|
875
|
-
signal: this.#loopSignal(loopId),
|
|
876
|
-
onDispatch,
|
|
877
|
-
onSettled,
|
|
878
|
-
});
|
|
879
|
-
if (executed.status !== TURN_STATUS_IMPLICIT_CONTINUE) {
|
|
880
|
-
throw new Error(`overflow SEND returned ${executed.status}; expected ${TURN_STATUS_IMPLICIT_CONTINUE}`);
|
|
881
|
-
}
|
|
882
|
-
const rebuilt = await buildPacket();
|
|
883
|
-
const remaining = this.#packets.curationOverflow(rebuilt, provider);
|
|
884
|
-
const curationFailure = kills.length === 0 || remaining !== null
|
|
885
|
-
? curationOverflowFailure(remaining ?? pressure)
|
|
886
|
-
: undefined;
|
|
887
|
-
return {
|
|
888
|
-
createdTurnIds,
|
|
889
|
-
turnId,
|
|
890
|
-
producer: "_plurnk",
|
|
891
|
-
kind: "overflow",
|
|
892
|
-
status: curationFailure?.status ?? TURN_STATUS_IMPLICIT_CONTINUE,
|
|
893
|
-
outcomes: [],
|
|
894
|
-
fingerprint: "",
|
|
895
|
-
capacityHardStop: false,
|
|
896
|
-
providerParked: false,
|
|
897
|
-
steerStruck: false,
|
|
898
|
-
emissionAttempts: 0,
|
|
899
|
-
emissionExhausted: false,
|
|
900
|
-
...(curationFailure === undefined ? {} : { curationFailure }),
|
|
901
|
-
};
|
|
739
|
+
onDispatch?.(promptLogId);
|
|
740
|
+
await onSettled?.(promptLogId);
|
|
741
|
+
}
|
|
742
|
+
// {§prompt-loop-containment}: the loop contains every prompt that arrived
|
|
743
|
+
// while it ran. Publish each undelivered frame oldest-first exactly once.
|
|
744
|
+
const prefix = promptLoopPrefix(loopSeq);
|
|
745
|
+
const undelivered = (await this.#db.drain_undelivered_prompts_for_loop.all({
|
|
746
|
+
worker_id: workerId,
|
|
747
|
+
pattern: `${prefix}%`,
|
|
748
|
+
prefix_len: prefix.length,
|
|
749
|
+
loop_id: loopId,
|
|
750
|
+
}))
|
|
751
|
+
.filter((row) => typeof row.content === "string" && row.content.length > 0);
|
|
752
|
+
for (const injectedRow of undelivered) {
|
|
753
|
+
const attributes = parsePromptAttributes(injectedRow.attributes, `Prompt ${injectedRow.pathname} attributes`);
|
|
754
|
+
if (attributes.openPaths !== undefined) {
|
|
755
|
+
openPaths.push(...assertOpenPaths(attributes.openPaths, `Prompt ${injectedRow.pathname} openPaths`));
|
|
902
756
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const { workerId: providerWorkerId, primaryWorkerId } = providerIdentity;
|
|
934
|
-
const classifyProviderAttempt = async (id, attemptSplit, emissionAttempt, accepted) => {
|
|
935
|
-
const result = await this.#db.engine_classify_turn_attempt_response.run({
|
|
936
|
-
id,
|
|
937
|
-
accepted: accepted ? 1 : 0,
|
|
938
|
-
parse_errors: JSON.stringify(attemptSplit.parseErrors),
|
|
939
|
-
});
|
|
940
|
-
if (result.changes !== 1) {
|
|
941
|
-
throw new Error(`emission attempt ${id} was not awaiting classification`);
|
|
942
|
-
}
|
|
943
|
-
emissionAttempts = emissionAttempt;
|
|
944
|
-
};
|
|
945
|
-
const recoverCapacityPacket = async () => {
|
|
946
|
-
let baselineMessages = modelMessages;
|
|
947
|
-
if (promptProjection === "automatic") {
|
|
948
|
-
promptProjection = "withheld";
|
|
949
|
-
const candidate = await buildPacket();
|
|
950
|
-
const candidateRequest = await this.#wireMessages(candidate, workspaceId, provider);
|
|
951
|
-
if (JSON.stringify(candidateRequest.messages) !== JSON.stringify(baselineMessages)) {
|
|
952
|
-
requestPacket = candidate;
|
|
953
|
-
materializedRequest = candidateRequest;
|
|
954
|
-
modelMessages = materializedRequest.messages;
|
|
955
|
-
nativeInputs = materializedRequest.nativeInputs;
|
|
956
|
-
return true;
|
|
957
|
-
}
|
|
958
|
-
baselineMessages = candidateRequest.messages;
|
|
959
|
-
}
|
|
960
|
-
return false;
|
|
757
|
+
const promptLogId = await this.#materialization.writePromptLog({
|
|
758
|
+
workerId,
|
|
759
|
+
loopId,
|
|
760
|
+
turnId,
|
|
761
|
+
sequence: nextActionIndex++,
|
|
762
|
+
target: promptTarget(workerName, injectedRow.pathname),
|
|
763
|
+
content: injectedRow.content,
|
|
764
|
+
source: promptSourceFromAttributes(attributes, `Prompt ${injectedRow.pathname} attributes`),
|
|
765
|
+
});
|
|
766
|
+
onDispatch?.(promptLogId);
|
|
767
|
+
await onSettled?.(promptLogId);
|
|
768
|
+
}
|
|
769
|
+
return { nextActionIndex, openPaths };
|
|
770
|
+
}
|
|
771
|
+
// {§methods-loop-run-open-paths}: selected workspace paths belong to the prompt
|
|
772
|
+
// frame. Publish the frame, then dispatch ordinary core READs in that same turn;
|
|
773
|
+
// missing/non-member paths retain their normal 4xx. Returns the next action index.
|
|
774
|
+
async #readOpenPaths({ workspaceId, workerId, loopId, onDispatch, onSettled }, turnId, openPaths, fromSequence) {
|
|
775
|
+
let nextActionIndex = fromSequence;
|
|
776
|
+
for (const raw of openPaths) {
|
|
777
|
+
const pathname = raw.startsWith("/") ? raw : `/${raw}`;
|
|
778
|
+
const fileRead = {
|
|
779
|
+
op: "READ", aside: null, lineMarker: null, matcher: null,
|
|
780
|
+
target: {
|
|
781
|
+
kind: "url", raw: `file://${pathname}`, scheme: "file",
|
|
782
|
+
username: null, password: null, hostname: null, port: null,
|
|
783
|
+
pathname, query: null, fragment: null,
|
|
784
|
+
},
|
|
785
|
+
metadata: null,
|
|
786
|
+
body: null, position: UNKNOWN_POSITION,
|
|
961
787
|
};
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
});
|
|
1134
|
-
const wait = Math.min(recoveryBackoff * 2 ** (recoveryFailures - 1), recoveryBackoff * 12);
|
|
1135
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1136
|
-
source: "engine:provider",
|
|
1137
|
-
kind: "provider_unavailable",
|
|
1138
|
-
level: "warn",
|
|
1139
|
-
message: `${failure.problem?.title ?? "Provider failure"}: retrying in ${Math.round(wait / 1000)}s (${Math.round(elapsed / 1000)}s of the ${Math.round(recoveryBudget / 1000)}s recovery budget spent).`,
|
|
1140
|
-
});
|
|
1141
|
-
// An abort during the wait re-enters generate, which refuses on the aborted signal.
|
|
1142
|
-
await delay(wait, undefined, { signal: providerSignal }).catch(() => undefined);
|
|
1143
|
-
// The response is still owed for this exact input. Failure rows remain
|
|
1144
|
-
// durable, but recursively materializing them would mutate and cache-bust
|
|
1145
|
-
// the request being recovered. {§provider-recovery}
|
|
1146
|
-
continue;
|
|
1147
|
-
}
|
|
1148
|
-
if (!(error instanceof ProviderError)
|
|
1149
|
-
|| error.kind !== "capacity_exceeded"
|
|
1150
|
-
|| providerSignal?.aborted === true
|
|
1151
|
-
|| !await recoverCapacityPacket()) {
|
|
1152
|
-
throw error;
|
|
1153
|
-
}
|
|
1154
|
-
const failure = TurnRunner.#providerFailure(error, providerSignal);
|
|
1155
|
-
await currentModelCall.fail(failure, error.capacity ?? null);
|
|
1156
|
-
providerCallInFlight = false;
|
|
1157
|
-
await this.#problems.record({
|
|
1158
|
-
workerId,
|
|
1159
|
-
loopId,
|
|
1160
|
-
turnId,
|
|
1161
|
-
sequence: nextActionIndex++,
|
|
1162
|
-
origin: "_plurnk",
|
|
1163
|
-
source: "provider",
|
|
1164
|
-
result: failure,
|
|
1165
|
-
});
|
|
1166
|
-
// Include the durable recovery signal in the replacement
|
|
1167
|
-
// request while preserving the selected recovery posture.
|
|
1168
|
-
requestPacket = await buildPacket();
|
|
1169
|
-
materializedRequest = await this.#wireMessages(requestPacket, workspaceId, provider);
|
|
1170
|
-
modelMessages = materializedRequest.messages;
|
|
1171
|
-
nativeInputs = materializedRequest.nativeInputs;
|
|
1172
|
-
continue;
|
|
1173
|
-
}
|
|
1174
|
-
finally {
|
|
1175
|
-
endReasoning();
|
|
1176
|
-
}
|
|
1177
|
-
if (recoveryFailures > 0) {
|
|
1178
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1179
|
-
source: "engine:provider",
|
|
1180
|
-
kind: "provider_recovered",
|
|
1181
|
-
level: "info",
|
|
1182
|
-
message: `Provider recovered after ${recoveryFailures} failed call${recoveryFailures === 1 ? "" : "s"}.`,
|
|
1183
|
-
});
|
|
1184
|
-
recoveryFailures = 0;
|
|
1185
|
-
recoveryStartedAt = null;
|
|
1186
|
-
}
|
|
1187
|
-
response = completedResponse;
|
|
1188
|
-
turnWireAccounting.push(...completedResponse.accounting);
|
|
1189
|
-
await currentModelCall.observeResponse(completedResponse, null, nativeInputs);
|
|
1190
|
-
railEvidence = railGrammar === undefined
|
|
1191
|
-
? undefined
|
|
1192
|
-
: TurnRunner.#requireGrammarEvidence(completedResponse, railAllowWithheld);
|
|
1193
|
-
splitResponse = this.#splitResponse(completedResponse);
|
|
1194
|
-
await classifyProviderAttempt(attemptRow.id, splitResponse, attempt, splitResponse.emissionValid);
|
|
1195
|
-
if (splitResponse.emissionValid)
|
|
1196
|
-
break;
|
|
1197
|
-
attempt++;
|
|
1198
|
-
}
|
|
1199
|
-
if (!signal?.aborted) {
|
|
1200
|
-
const wire = aggregateProviderAccounting(turnWireAccounting);
|
|
1201
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1202
|
-
source: "engine:turn",
|
|
1203
|
-
kind: "turn_generated",
|
|
1204
|
-
level: "info",
|
|
1205
|
-
message: "parsing model response",
|
|
1206
|
-
// {§turn-accounting-notice} (#465) — the exact settled derivation,
|
|
1207
|
-
// never a second stored fact: a live watcher accrues loop cost per turn.
|
|
1208
|
-
accounting: {
|
|
1209
|
-
requests: turnWireAccounting.length,
|
|
1210
|
-
costUsd: wire.costUsd,
|
|
1211
|
-
inputTokens: wire.usage?.inputTokens ?? null,
|
|
1212
|
-
outputTokens: wire.usage?.outputTokens ?? null,
|
|
1213
|
-
reasoningTokens: wire.usage?.outputTokenDetails?.reasoningTokens ?? null,
|
|
1214
|
-
cacheReadTokens: wire.usage?.inputTokenDetails?.cacheReadTokens ?? null,
|
|
1215
|
-
},
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
catch (err) {
|
|
1220
|
-
// This handler owns only provider-call failures. Parser, cost, SQL,
|
|
1221
|
-
// and engine-contract failures retain their original source.
|
|
1222
|
-
if (err instanceof ModelCallPersistenceError || err instanceof ProviderAccountingIntegrityError)
|
|
1223
|
-
throw err;
|
|
1224
|
-
if (!providerCallInFlight)
|
|
1225
|
-
throw err;
|
|
1226
|
-
providerCallInFlight = false;
|
|
1227
|
-
if (providerAttemptId === null || providerModelCall === null) {
|
|
1228
|
-
throw new Error("provider call failed without durable model-call and attempt identities", { cause: err });
|
|
1229
|
-
}
|
|
1230
|
-
const failure = TurnRunner.#providerFailure(err, providerSignal);
|
|
1231
|
-
const capacityFailure = err instanceof ProviderError && err.kind === "capacity_exceeded";
|
|
1232
|
-
// {§provider-interrupted-attempt} — a provider-declared interruption
|
|
1233
|
-
// carries response evidence without becoming a completed exchange.
|
|
1234
|
-
// Persist it as an unaccepted attempt before settling the failure.
|
|
1235
|
-
if (err instanceof ProviderError && err.attempt !== undefined) {
|
|
1236
|
-
response = err.attempt;
|
|
1237
|
-
await providerModelCall.observeResponse(response, failure, nativeInputs);
|
|
1238
|
-
splitResponse = this.#splitResponse(response);
|
|
1239
|
-
await classifyProviderAttempt(providerAttemptId, splitResponse, currentEmissionAttempt, false);
|
|
1240
|
-
}
|
|
1241
|
-
else {
|
|
1242
|
-
await providerModelCall.fail(failure, err instanceof ProviderError ? err.capacity ?? null : null);
|
|
1243
|
-
if (!capacityFailure)
|
|
1244
|
-
emissionAttempts = currentEmissionAttempt;
|
|
1245
|
-
}
|
|
1246
|
-
// {§turn-never-blank} — a ProviderError means no completed exchange exists.
|
|
1247
|
-
// Persist its exact RFC 9457 result before propagating it. Grammar evidence
|
|
1248
|
-
// and its engine-owned verdict exist only on completed responses
|
|
1249
|
-
// ({§rail-truth-engine-verdict}). Cancellation is lifecycle truth, not a
|
|
1250
|
-
// provider failure. Close the
|
|
1251
|
-
// attempted turn without inventing an assistant response, then let
|
|
1252
|
-
// runLoop/Daemon settle the exact 504/499 loop result.
|
|
1253
|
-
if (providerSignal?.aborted) {
|
|
1254
|
-
const status = providerSignal.reason === LOOP_TIMEOUT_REASON ? 504 : 499;
|
|
1255
|
-
await this.#recordInference({
|
|
1256
|
-
workspaceId, workerId, loopId, turnId,
|
|
1257
|
-
evidence: {
|
|
1258
|
-
packet: StoredPacket.stringify(requestPacket),
|
|
1259
|
-
usageCurationBudget: this.#packets.curationBudgetFor(provider),
|
|
1260
|
-
finishReason: splitResponse?.callMetadata.finishReason ?? null,
|
|
1261
|
-
model: splitResponse?.callMetadata.model ?? provider.model,
|
|
1262
|
-
meta: JSON.stringify(response?.meta ?? {}),
|
|
1263
|
-
},
|
|
1264
|
-
});
|
|
1265
|
-
await Turn.complete(this.#db, turnId, status);
|
|
1266
|
-
throw err;
|
|
1267
|
-
}
|
|
1268
|
-
const recorded = await this.#problems.record({
|
|
1269
|
-
workerId,
|
|
1270
|
-
loopId,
|
|
1271
|
-
turnId,
|
|
1272
|
-
sequence: nextActionIndex,
|
|
1273
|
-
origin: "_plurnk",
|
|
1274
|
-
source: "provider",
|
|
1275
|
-
result: failure,
|
|
1276
|
-
});
|
|
1277
|
-
// The provider call was attempted, but no completed exchange exists.
|
|
1278
|
-
// Persist the exact request half and failure status; omitting assistant
|
|
1279
|
-
// is materially different from fabricating an empty model turn.
|
|
1280
|
-
await this.#recordInference({
|
|
1281
|
-
workspaceId, workerId, loopId, turnId,
|
|
1282
|
-
evidence: {
|
|
1283
|
-
packet: StoredPacket.stringify(requestPacket),
|
|
1284
|
-
usageCurationBudget: this.#packets.curationBudgetFor(provider),
|
|
1285
|
-
finishReason: splitResponse?.callMetadata.finishReason ?? null,
|
|
1286
|
-
model: splitResponse?.callMetadata.model ?? provider.model,
|
|
1287
|
-
meta: JSON.stringify(response?.meta ?? {}),
|
|
1288
|
-
},
|
|
1289
|
-
});
|
|
1290
|
-
await Turn.complete(this.#db, turnId, providerParked ? 202 : recorded.result.status);
|
|
1291
|
-
if (providerParked) {
|
|
1292
|
-
// {§provider-recovery} — the recovery budget is spent: the loop parks exactly like a
|
|
1293
|
-
// [202] wait and resumes on the next prompt or wake; the failure stays durable.
|
|
1294
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1295
|
-
source: "engine:provider",
|
|
1296
|
-
kind: "provider_unavailable",
|
|
1297
|
-
level: "error",
|
|
1298
|
-
message: `${recorded.result.problem?.title ?? "Provider failure"}: the ${Math.round(recoveryBudget / 1000)}s recovery budget is spent; the loop is parked and resumes on the next prompt or wake.`,
|
|
1299
|
-
});
|
|
1300
|
-
return {
|
|
1301
|
-
createdTurnIds,
|
|
1302
|
-
turnId,
|
|
1303
|
-
producer: "model",
|
|
1304
|
-
kind: "inference",
|
|
1305
|
-
status: 202,
|
|
1306
|
-
outcomes: [],
|
|
1307
|
-
fingerprint: "",
|
|
1308
|
-
capacityHardStop: false, providerParked: true,
|
|
1309
|
-
providerFailure: recorded.result,
|
|
1310
|
-
steerStruck: false,
|
|
1311
|
-
emissionAttempts,
|
|
1312
|
-
emissionExhausted: false,
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
if (capacityFailure) {
|
|
1316
|
-
return {
|
|
1317
|
-
createdTurnIds,
|
|
1318
|
-
turnId,
|
|
1319
|
-
producer: "model",
|
|
1320
|
-
kind: "inference",
|
|
1321
|
-
status: recorded.result.status,
|
|
1322
|
-
outcomes: [],
|
|
1323
|
-
fingerprint: "",
|
|
1324
|
-
capacityHardStop: true,
|
|
1325
|
-
providerParked: false,
|
|
1326
|
-
capacityFailure: recorded.result,
|
|
1327
|
-
steerStruck: false,
|
|
1328
|
-
emissionAttempts,
|
|
1329
|
-
emissionExhausted: false,
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
if (err instanceof ProviderError
|
|
1333
|
-
&& err.kind === "invalid_response"
|
|
1334
|
-
&& providerSignal?.aborted !== true) {
|
|
1335
|
-
// {§engine-rails} Contract Strikes: the provider violated its response
|
|
1336
|
-
// contract. The failure is durable and the turn is complete; the strike
|
|
1337
|
-
// rail — never an instant loop death — rules whether the streak ends it.
|
|
1338
|
-
return {
|
|
1339
|
-
createdTurnIds,
|
|
1340
|
-
turnId,
|
|
1341
|
-
producer: "model",
|
|
1342
|
-
kind: "inference",
|
|
1343
|
-
status: recorded.result.status,
|
|
1344
|
-
outcomes: [{ op: null, status: recorded.result.status, problemType: recorded.result.problem?.type ?? null }],
|
|
1345
|
-
fingerprint: `provider-contract-violation:${turnId}`,
|
|
1346
|
-
capacityHardStop: false,
|
|
1347
|
-
providerParked: false,
|
|
1348
|
-
providerFailure: recorded.result,
|
|
1349
|
-
steerStruck: false,
|
|
1350
|
-
emissionAttempts,
|
|
1351
|
-
emissionExhausted: false,
|
|
1352
|
-
};
|
|
1353
|
-
}
|
|
1354
|
-
throw new OperationFailureError(recorded.result, { cause: err });
|
|
788
|
+
await this.#dispatch({
|
|
789
|
+
statement: fileRead, workspaceId, workerId, loopId, turnId,
|
|
790
|
+
sequence: nextActionIndex, origin: "_plurnk", onDispatch, onSettled,
|
|
791
|
+
});
|
|
792
|
+
nextActionIndex++;
|
|
793
|
+
}
|
|
794
|
+
return nextActionIndex;
|
|
795
|
+
}
|
|
796
|
+
#buildPacket({ messages, recap, workspaceId, workerId, loopId, provider }, facts) {
|
|
797
|
+
return this.#packets.buildRequestPacket({
|
|
798
|
+
initialMessages: messages,
|
|
799
|
+
recap,
|
|
800
|
+
workspaceId,
|
|
801
|
+
workerId,
|
|
802
|
+
loopId,
|
|
803
|
+
currentTurnSeq: facts.seq,
|
|
804
|
+
provider,
|
|
805
|
+
gitStatus: facts.gitStatus,
|
|
806
|
+
notices: facts.notices,
|
|
807
|
+
transientOpenLogEntryId: facts.transientOpenLogEntryId,
|
|
808
|
+
promptProjection: facts.promptProjection,
|
|
809
|
+
turnId: facts.turnId,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
// Retained context cannot fit: the turn completes on the curation failure and
|
|
813
|
+
// the loop rules the terminal.
|
|
814
|
+
async #failCuration(request, overflow) {
|
|
815
|
+
const curationFailure = curationOverflowFailure(overflow);
|
|
816
|
+
await Turn.complete(this.#db, request.turnId, curationFailure.status);
|
|
817
|
+
return turnResult(request, curationFailure.status, { curationFailure });
|
|
818
|
+
}
|
|
819
|
+
// Phase 4's bookkeeping: the wire request, the recovery knobs, the signal the
|
|
820
|
+
// provider sees, the client id and the worker's provider identity.
|
|
821
|
+
async #prepareProviderAttempts({ provider, workspaceId, workerId, loopId, signal }, request) {
|
|
822
|
+
const wire = await this.#wireMessages(request.packet, request.systemCtx, provider);
|
|
823
|
+
// {§provider-recovery} — this turn's recovery clock: the first recoverable provider
|
|
824
|
+
// failure starts it; the budget and backoff are the operator's.
|
|
825
|
+
const recoveryBudget = readProviderRecovery();
|
|
826
|
+
const recoveryBackoff = readProviderRecoveryBackoff();
|
|
827
|
+
const providerSignal = this.#loopSignal(loopId) ?? signal;
|
|
828
|
+
// {§client-metadata}
|
|
829
|
+
const { client } = await WorkspaceSettings.read(this.#db, workspaceId);
|
|
830
|
+
const { workerId: providerWorkerId, primaryWorkerId } = await this.#resolveWorkerProviderIdentity(workerId);
|
|
831
|
+
return {
|
|
832
|
+
wire,
|
|
833
|
+
response: undefined,
|
|
834
|
+
split: undefined,
|
|
835
|
+
railGrammar: undefined,
|
|
836
|
+
railEvidence: undefined,
|
|
837
|
+
emissionAttempts: 0,
|
|
838
|
+
callInFlight: false,
|
|
839
|
+
modelCallSequence: 0,
|
|
840
|
+
currentEmissionAttempt: 0,
|
|
841
|
+
attemptId: null,
|
|
842
|
+
modelCall: null,
|
|
843
|
+
recoveryStartedAt: null,
|
|
844
|
+
recoveryFailures: 0,
|
|
845
|
+
parked: false,
|
|
846
|
+
attributions: [],
|
|
847
|
+
recoveryBudget,
|
|
848
|
+
recoveryBackoff,
|
|
849
|
+
signal: providerSignal,
|
|
850
|
+
client,
|
|
851
|
+
providerWorkerId,
|
|
852
|
+
primaryWorkerId,
|
|
853
|
+
turnWireAccounting: [],
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
// Phase 4 — the provider attempt loop. Each iteration is one logical call: a
|
|
857
|
+
// recoverable failure or a capacity rebuild re-issues the same emission attempt,
|
|
858
|
+
// an invalid emission spends one, and a valid emission ends the loop
|
|
859
|
+
// ({§invalid-emission-attempts}).
|
|
860
|
+
async #attemptProvider(args, request, attempts) {
|
|
861
|
+
const { provider, workspaceId, workerId, loopId, signal } = args;
|
|
862
|
+
// {§turn-lifecycle}: bracket the complete provider-attempt window with liveness notices.
|
|
863
|
+
if (!signal?.aborted)
|
|
864
|
+
this.#notices.push(workspaceId, workerId, loopId, { source: "engine:turn", kind: "turn_awaiting_model", level: "info", message: "awaiting model response" });
|
|
865
|
+
attempts.railGrammar = await this.#operatorGrammar(provider);
|
|
866
|
+
const attemptLimit = readEmissionAttempts();
|
|
867
|
+
const strikeStreak = await this.#strikes.streak(loopId);
|
|
868
|
+
for (let attempt = 1; attempt <= attemptLimit;) {
|
|
869
|
+
const verdict = await this.#issueProviderCall(args, request, attempts, attempt, strikeStreak);
|
|
870
|
+
if (verdict === "admitted")
|
|
871
|
+
break;
|
|
872
|
+
if (verdict === "rejected")
|
|
873
|
+
attempt++;
|
|
874
|
+
}
|
|
875
|
+
if (!signal?.aborted) {
|
|
876
|
+
const wire = aggregateProviderAccounting(attempts.turnWireAccounting);
|
|
877
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
878
|
+
source: "engine:turn",
|
|
879
|
+
kind: "turn_generated",
|
|
880
|
+
level: "info",
|
|
881
|
+
message: "parsing model response",
|
|
882
|
+
// {§turn-accounting-notice} (#465) — the exact settled derivation,
|
|
883
|
+
// never a second stored fact: a live watcher accrues loop cost per turn.
|
|
884
|
+
accounting: {
|
|
885
|
+
requests: attempts.turnWireAccounting.length,
|
|
886
|
+
costUsd: wire.costUsd,
|
|
887
|
+
inputTokens: wire.usage?.inputTokens ?? null,
|
|
888
|
+
outputTokens: wire.usage?.outputTokens ?? null,
|
|
889
|
+
reasoningTokens: wire.usage?.outputTokenDetails?.reasoningTokens ?? null,
|
|
890
|
+
cacheReadTokens: wire.usage?.inputTokenDetails?.cacheReadTokens ?? null,
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (attempts.response === undefined || attempts.split === undefined || attempts.modelCall === null) {
|
|
895
|
+
throw new Error("provider attempt loop completed without a response");
|
|
896
|
+
}
|
|
897
|
+
const emission = {
|
|
898
|
+
response: attempts.response,
|
|
899
|
+
split: attempts.split,
|
|
900
|
+
modelCallId: attempts.modelCall.id,
|
|
901
|
+
railGrammar: attempts.railGrammar,
|
|
902
|
+
railEvidence: attempts.railEvidence,
|
|
903
|
+
emissionAttempts: attempts.emissionAttempts,
|
|
904
|
+
signal: attempts.signal,
|
|
905
|
+
primaryWorkerId: attempts.primaryWorkerId,
|
|
906
|
+
};
|
|
907
|
+
if (emission.split.packetAssistant.reasoning?.length) {
|
|
908
|
+
await Turn.recordSource(this.#db, request.turnId, "reasoning", emission.split.packetAssistant.reasoning, { modelCallId: emission.modelCallId });
|
|
909
|
+
}
|
|
910
|
+
return emission;
|
|
911
|
+
}
|
|
912
|
+
// One logical provider call: its durable model call and attempt row, the exchange
|
|
913
|
+
// under the reasoning observer, and its classification. Capacity recovery and
|
|
914
|
+
// {§provider-recovery} re-issue the same emission attempt; the parser's admission
|
|
915
|
+
// is the one verdict on a completed exchange.
|
|
916
|
+
async #issueProviderCall(args, request, attempts, attempt, strikeStreak) {
|
|
917
|
+
const { provider, workspaceId, workerId, loopId } = args;
|
|
918
|
+
// Capacity recovery may rebuild and resend the request without
|
|
919
|
+
// consuming a grammar-emission attempt. Every logical provider
|
|
920
|
+
// call still receives its own durable sequence and accounting.
|
|
921
|
+
attempts.currentEmissionAttempt = attempt;
|
|
922
|
+
attempts.modelCallSequence++;
|
|
923
|
+
const attributionContext = Object.freeze({
|
|
924
|
+
workspaceId: String(workspaceId),
|
|
925
|
+
workerId: attempts.providerWorkerId,
|
|
926
|
+
primaryWorkerId: attempts.primaryWorkerId,
|
|
927
|
+
loop: request.loopSeq,
|
|
928
|
+
turn: request.seq,
|
|
929
|
+
attempt: attempts.modelCallSequence,
|
|
930
|
+
});
|
|
931
|
+
attempts.attributions = await this.#attemptAttributions(provider, attributionContext);
|
|
932
|
+
request.packet = { ...request.packet, attributions: attempts.attributions };
|
|
933
|
+
const modelCall = await ModelCall.open(this.#db, {
|
|
934
|
+
turnId: request.turnId,
|
|
935
|
+
kind: "emission",
|
|
936
|
+
attributions: attempts.attributions,
|
|
937
|
+
model: provider.model,
|
|
938
|
+
});
|
|
939
|
+
attempts.modelCall = modelCall;
|
|
940
|
+
const attemptRow = await this.#db.engine_open_turn_attempt.get({
|
|
941
|
+
model_call_id: modelCall.id,
|
|
942
|
+
});
|
|
943
|
+
if (attemptRow === undefined) {
|
|
944
|
+
throw new Error(`Engine.runTurn: provider call ${attempts.modelCallSequence} did not open`);
|
|
945
|
+
}
|
|
946
|
+
attempts.attemptId = attemptRow.id;
|
|
947
|
+
attempts.callInFlight = true;
|
|
948
|
+
const reasoning = this.#reasoningObserver(args, request.turnId, modelCall);
|
|
949
|
+
let completedResponse;
|
|
950
|
+
try {
|
|
951
|
+
completedResponse = await this.#generate(args, request, attempts, modelCall, reasoning, strikeStreak);
|
|
952
|
+
}
|
|
953
|
+
catch (error) {
|
|
954
|
+
if (error instanceof ProviderError
|
|
955
|
+
&& RECOVERABLE_PROVIDER_FAILURES.has(error.kind)
|
|
956
|
+
&& attempts.signal?.aborted !== true) {
|
|
957
|
+
await this.#recoverProviderFailure(args, request, attempts, modelCall, attemptRow.id, error);
|
|
958
|
+
return "reissued";
|
|
1355
959
|
}
|
|
1356
|
-
if (
|
|
1357
|
-
|
|
960
|
+
if (!(error instanceof ProviderError)
|
|
961
|
+
|| error.kind !== "capacity_exceeded"
|
|
962
|
+
|| attempts.signal?.aborted === true
|
|
963
|
+
|| !await this.#recoverCapacityPacket(args, request, attempts)) {
|
|
964
|
+
throw error;
|
|
1358
965
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
966
|
+
await this.#resendForCapacity(args, request, attempts, modelCall, error);
|
|
967
|
+
return "reissued";
|
|
968
|
+
}
|
|
969
|
+
finally {
|
|
970
|
+
reasoning.end();
|
|
971
|
+
}
|
|
972
|
+
if (attempts.recoveryFailures > 0) {
|
|
973
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
974
|
+
source: "engine:provider",
|
|
975
|
+
kind: "provider_recovered",
|
|
976
|
+
level: "info",
|
|
977
|
+
message: `Provider recovered after ${attempts.recoveryFailures} failed call${attempts.recoveryFailures === 1 ? "" : "s"}.`,
|
|
978
|
+
});
|
|
979
|
+
attempts.recoveryFailures = 0;
|
|
980
|
+
attempts.recoveryStartedAt = null;
|
|
981
|
+
}
|
|
982
|
+
attempts.response = completedResponse;
|
|
983
|
+
attempts.turnWireAccounting.push(...completedResponse.accounting);
|
|
984
|
+
await modelCall.observeResponse(completedResponse, null, attempts.wire.nativeInputs);
|
|
985
|
+
attempts.railEvidence = attempts.railGrammar === undefined ? undefined : completedResponse.grammarEvidence;
|
|
986
|
+
const split = this.#splitResponse(completedResponse, this.#executors()?.availableRuntimes(workspaceId) ?? []);
|
|
987
|
+
attempts.split = split;
|
|
988
|
+
await this.#classifyProviderAttempt(attempts, attemptRow.id, split, attempt, split.emissionValid);
|
|
989
|
+
return split.emissionValid ? "admitted" : "rejected";
|
|
990
|
+
}
|
|
991
|
+
// {§notifications-reasoning-event} — only the parent emission is conversational;
|
|
992
|
+
// BARE has no observer on its isolated calls. Reasoning deltas cite the physical
|
|
993
|
+
// request they stream in; a new request or the call's end closes the open span.
|
|
994
|
+
#reasoningObserver({ workspaceId, workerId, loopId }, turnId, modelCall) {
|
|
995
|
+
let reasoningStarted = false;
|
|
996
|
+
let reasoningRequestSequence = 0;
|
|
997
|
+
const end = () => {
|
|
998
|
+
if (!reasoningStarted)
|
|
999
|
+
return;
|
|
1000
|
+
this.#reasoningEventNotify(workspaceId, {
|
|
1001
|
+
workerId,
|
|
1002
|
+
loopId,
|
|
1003
|
+
turnId,
|
|
1004
|
+
modelCallId: modelCall.id,
|
|
1005
|
+
requestSequence: reasoningRequestSequence,
|
|
1006
|
+
phase: "end",
|
|
1007
|
+
});
|
|
1008
|
+
reasoningStarted = false;
|
|
1009
|
+
};
|
|
1010
|
+
const observeRequest = async (...args) => {
|
|
1011
|
+
end();
|
|
1012
|
+
const settle = await modelCall.observeRequest(...args);
|
|
1013
|
+
reasoningRequestSequence = modelCall.requestSequence;
|
|
1014
|
+
return settle;
|
|
1015
|
+
};
|
|
1016
|
+
const observeReasoning = this.#reasoningEventNotify === undefined
|
|
1017
|
+
? undefined
|
|
1018
|
+
: (delta) => {
|
|
1019
|
+
if (reasoningRequestSequence === 0) {
|
|
1020
|
+
throw new Error("provider emitted reasoning before opening its physical request");
|
|
1021
|
+
}
|
|
1022
|
+
if (!reasoningStarted) {
|
|
1023
|
+
reasoningStarted = true;
|
|
1024
|
+
this.#reasoningEventNotify(workspaceId, {
|
|
1368
1025
|
workerId,
|
|
1369
1026
|
loopId,
|
|
1370
1027
|
turnId,
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
? { reasoningItems: response.assistant.reasoningEncrypted }
|
|
1375
|
-
: {}),
|
|
1028
|
+
modelCallId: modelCall.id,
|
|
1029
|
+
requestSequence: reasoningRequestSequence,
|
|
1030
|
+
phase: "start",
|
|
1376
1031
|
});
|
|
1377
|
-
// {§invalid-emission-attempts} — the informed turn carries the parser's own
|
|
1378
|
-
// diagnostic and position: the model sees WHY, not only that it was refused.
|
|
1379
|
-
const diagnostic = splitResponse.parseErrors[0];
|
|
1380
|
-
const cut = allowanceCutMessage(splitResponse.callMetadata.finishReason, response.capacity.responseMax ?? provider.outputBudget);
|
|
1381
|
-
if (cut !== null) {
|
|
1382
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1383
|
-
source: "engine:capacity",
|
|
1384
|
-
kind: "output_truncated",
|
|
1385
|
-
level: "error",
|
|
1386
|
-
message: `${cut}; no operations were performed`,
|
|
1387
|
-
});
|
|
1388
|
-
}
|
|
1389
|
-
else {
|
|
1390
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1391
|
-
source: "engine:grammar",
|
|
1392
|
-
kind: "invalid_emission",
|
|
1393
|
-
level: "error",
|
|
1394
|
-
message: diagnostic === undefined
|
|
1395
|
-
? INVALID_EMISSION_RECOVERY_MESSAGE
|
|
1396
|
-
: `${INVALID_EMISSION_RECOVERY_MESSAGE} Parser: ${diagnostic.message}`,
|
|
1397
|
-
...(diagnostic !== undefined && diagnostic.line > 0
|
|
1398
|
-
? { position: { type: "content-offset", line: diagnostic.line, column: diagnostic.column } }
|
|
1399
|
-
: {}),
|
|
1400
|
-
});
|
|
1401
|
-
}
|
|
1402
1032
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
evidence: {
|
|
1407
|
-
packet: StoredPacket.stringify(requestPacket),
|
|
1408
|
-
usageCurationBudget: this.#packets.curationBudgetFor(provider),
|
|
1409
|
-
finishReason: splitResponse.callMetadata.finishReason,
|
|
1410
|
-
model: splitResponse.callMetadata.model,
|
|
1411
|
-
meta: JSON.stringify(response.meta ?? {}),
|
|
1412
|
-
},
|
|
1413
|
-
});
|
|
1414
|
-
await Turn.complete(this.#db, turnId, status);
|
|
1415
|
-
return {
|
|
1416
|
-
createdTurnIds,
|
|
1033
|
+
this.#reasoningEventNotify(workspaceId, {
|
|
1034
|
+
workerId,
|
|
1035
|
+
loopId,
|
|
1417
1036
|
turnId,
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
fingerprint: `frame-contract-violation:${turnId}`,
|
|
1423
|
-
capacityHardStop: false,
|
|
1424
|
-
providerParked: false,
|
|
1425
|
-
steerStruck: false,
|
|
1426
|
-
emissionAttempts,
|
|
1427
|
-
emissionExhausted: true,
|
|
1428
|
-
...(rejectedModelEntryId === undefined ? {} : { rejectedModelEntryId }),
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
// {§packet-stored-shape} — admitted emission data extends the packet;
|
|
1432
|
-
// provider-call metadata remains on the Turn row.
|
|
1433
|
-
const { packetAssistant, callMetadata, parseNotices, recoverableParseErrors, } = splitResponse; // raw assistant content is opaque — split, never interpreted — {§provider-guarantees-assistantraw-opaque}
|
|
1434
|
-
for (const notice of parseNotices) {
|
|
1435
|
-
this.#notices.push(workspaceId, workerId, loopId, notice);
|
|
1436
|
-
}
|
|
1437
|
-
// Non-fatal provider transport notices on an accepted turn. Forward each
|
|
1438
|
-
// Notice with a content-offset `line:col`;
|
|
1439
|
-
// the model resolves it against its own emission — READ the body-suppressed turnOps row at the
|
|
1440
|
-
// cited lines ({§turn-ops-entry}) — not an embedded snippet that would duplicate the emission.
|
|
1441
|
-
for (const notice of response.notices ?? []) {
|
|
1442
|
-
const located = typeof notice.position === "number"
|
|
1443
|
-
? this.#offsetToLineColumn(packetAssistant.content, notice.position)
|
|
1444
|
-
: null;
|
|
1445
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1446
|
-
source: notice.source,
|
|
1447
|
-
kind: notice.kind,
|
|
1448
|
-
message: notice.message ?? "",
|
|
1449
|
-
level: notice.level,
|
|
1450
|
-
...(located !== null
|
|
1451
|
-
? { position: { type: "content-offset", line: located.line, column: located.column } }
|
|
1452
|
-
: {}),
|
|
1037
|
+
modelCallId: modelCall.id,
|
|
1038
|
+
requestSequence: reasoningRequestSequence,
|
|
1039
|
+
phase: "content",
|
|
1040
|
+
delta,
|
|
1453
1041
|
});
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1042
|
+
};
|
|
1043
|
+
return { observeRequest, observeReasoning, end };
|
|
1044
|
+
}
|
|
1045
|
+
// The exchange under its GenAI span: the packet's observations are recorded, the
|
|
1046
|
+
// provider generates against the wire request, and the call's accounting is
|
|
1047
|
+
// asserted whether it resolved or failed.
|
|
1048
|
+
async #generate({ provider, workspaceId }, request, attempts, modelCall, reasoning, strikeStreak) {
|
|
1049
|
+
return await observed(// {§observability-boundary}
|
|
1050
|
+
GEN_AI_REQUEST_SPAN, { model: provider.model, attempt: attempts.modelCallSequence }, async (span) => {
|
|
1051
|
+
try {
|
|
1052
|
+
await this.#packets.recordObservations(request.packet);
|
|
1053
|
+
const generated = await provider.generate({
|
|
1054
|
+
messages: attempts.wire.messages,
|
|
1055
|
+
workerId: attempts.providerWorkerId,
|
|
1056
|
+
primaryWorkerId: attempts.primaryWorkerId,
|
|
1057
|
+
signal: attempts.signal,
|
|
1058
|
+
grammar: attempts.railGrammar,
|
|
1059
|
+
strikes: strikeStreak,
|
|
1060
|
+
attributions: attempts.attributions.length > 0
|
|
1061
|
+
? attempts.attributions
|
|
1062
|
+
: undefined,
|
|
1063
|
+
client: attempts.client ?? undefined,
|
|
1064
|
+
workspaceId: String(workspaceId),
|
|
1065
|
+
loop: request.loopSeq,
|
|
1066
|
+
turn: request.seq,
|
|
1067
|
+
observeRequest: reasoning.observeRequest,
|
|
1068
|
+
observeReasoning: reasoning.observeReasoning,
|
|
1069
|
+
callKind: "emission",
|
|
1070
|
+
}); // {§provider-surface-generate} {§provider-guarantees-signal-wired} {§provider-guarantees-serial-attempts} {§attribution} {§client-metadata}
|
|
1071
|
+
modelCall.assertAccounting(generated.accounting);
|
|
1072
|
+
attempts.callInFlight = false;
|
|
1073
|
+
recordCounter(PROVIDER_CALLS, {
|
|
1074
|
+
model: provider.model,
|
|
1075
|
+
attempt: attempts.modelCallSequence,
|
|
1076
|
+
status: "resolved",
|
|
1462
1077
|
});
|
|
1078
|
+
span.setAttribute("status", "resolved");
|
|
1079
|
+
settleGenAiResponse(span, generated);
|
|
1080
|
+
return generated;
|
|
1463
1081
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
if (railEvidence === undefined)
|
|
1469
|
-
throw new Error("configured GBNF response has no final grammar evidence");
|
|
1470
|
-
if (railResponseGrammar === undefined)
|
|
1471
|
-
throw new Error("configured GBNF has no response grammar");
|
|
1472
|
-
let verdict = null;
|
|
1473
|
-
try {
|
|
1474
|
-
verdict = validateGbnf(railResponseGrammar, railEvidence.input);
|
|
1475
|
-
}
|
|
1476
|
-
catch (cause) {
|
|
1477
|
-
TurnRunner.#warnRailVerdictGapOnce(cause.message);
|
|
1478
|
-
}
|
|
1479
|
-
railKeys = {
|
|
1480
|
-
railsAttached: railEvidence.transported ? "client" : "withheld",
|
|
1481
|
-
railsVerdict: verdict?.status ?? "unverifiable",
|
|
1482
|
-
};
|
|
1483
|
-
if (verdict !== null && verdict.status !== "accept" && allowanceCut === null) {
|
|
1484
|
-
const contentPosition = verdict.pos >= railEvidence.contentStart
|
|
1485
|
-
? verdict.pos - railEvidence.contentStart
|
|
1486
|
-
: null;
|
|
1487
|
-
const located = contentPosition === null
|
|
1488
|
-
? null
|
|
1489
|
-
: this.#offsetToLineColumn(packetAssistant.content, contentPosition);
|
|
1490
|
-
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1491
|
-
source: "engine:rails",
|
|
1492
|
-
kind: "grammar_unenforced",
|
|
1493
|
-
message: verdict.status === "reject"
|
|
1494
|
-
? `emission rejects the grammar at raw code point ${verdict.pos}`
|
|
1495
|
-
: `emission is an incomplete grammar sentence (ends at raw code point ${verdict.pos})`,
|
|
1496
|
-
level: "warn",
|
|
1497
|
-
...(located === null
|
|
1498
|
-
? {}
|
|
1499
|
-
: { position: { type: "content-offset", line: located.line, column: located.column } }),
|
|
1500
|
-
});
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
if (error instanceof ProviderError) {
|
|
1084
|
+
modelCall.assertAccounting(error.accounting);
|
|
1085
|
+
attempts.turnWireAccounting.push(...error.accounting);
|
|
1501
1086
|
}
|
|
1087
|
+
throw error;
|
|
1502
1088
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
}
|
|
1519
|
-
//
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1089
|
+
}, genAiRequestOptions(ProviderInstantiate.aliasOf(provider) ?? "plurnk", provider.model));
|
|
1090
|
+
}
|
|
1091
|
+
// {§provider-recovery} — a transient provider failure never ends the loop: record
|
|
1092
|
+
// it, wait, and re-issue the same turn; once the budget is spent the failure
|
|
1093
|
+
// handler parks the loop instead of failing it.
|
|
1094
|
+
async #recoverProviderFailure({ workspaceId, workerId, loopId }, request, attempts, modelCall, attemptId, error) {
|
|
1095
|
+
attempts.recoveryStartedAt ??= Date.now();
|
|
1096
|
+
const elapsed = Date.now() - attempts.recoveryStartedAt;
|
|
1097
|
+
if (elapsed >= attempts.recoveryBudget) {
|
|
1098
|
+
attempts.parked = true;
|
|
1099
|
+
throw error;
|
|
1100
|
+
}
|
|
1101
|
+
attempts.recoveryFailures += 1;
|
|
1102
|
+
const failure = _a.#providerFailure(error, attempts.signal);
|
|
1103
|
+
if (error.attempt !== undefined) {
|
|
1104
|
+
// {§provider-interrupted-attempt} — the interrupted response stays durable
|
|
1105
|
+
// as an unaccepted attempt; it is never admitted or replayed.
|
|
1106
|
+
await modelCall.observeResponse(error.attempt, failure, attempts.wire.nativeInputs);
|
|
1107
|
+
await this.#classifyProviderAttempt(attempts, attemptId, this.#splitResponse(error.attempt, this.#executors()?.availableRuntimes(workspaceId) ?? []), attempts.currentEmissionAttempt, false);
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
await modelCall.fail(failure, error.capacity ?? null);
|
|
1111
|
+
}
|
|
1112
|
+
attempts.callInFlight = false;
|
|
1113
|
+
await this.#problems.record({
|
|
1114
|
+
workerId,
|
|
1115
|
+
loopId,
|
|
1116
|
+
turnId: request.turnId,
|
|
1117
|
+
sequence: request.nextActionIndex++,
|
|
1118
|
+
origin: "_plurnk",
|
|
1119
|
+
source: "provider",
|
|
1120
|
+
result: failure,
|
|
1121
|
+
});
|
|
1122
|
+
const wait = Math.min(attempts.recoveryBackoff * 2 ** (attempts.recoveryFailures - 1), attempts.recoveryBackoff * 12);
|
|
1123
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1124
|
+
source: "engine:provider",
|
|
1125
|
+
kind: "provider_unavailable",
|
|
1126
|
+
level: "warn",
|
|
1127
|
+
message: `${failure.problem?.title ?? "Provider failure"}: retrying in ${Math.round(wait / 1000)}s (${Math.round(elapsed / 1000)}s of the ${Math.round(attempts.recoveryBudget / 1000)}s recovery budget spent).`,
|
|
1128
|
+
});
|
|
1129
|
+
// An abort during the wait re-enters generate, which refuses on the aborted signal.
|
|
1130
|
+
await delay(wait, undefined, { signal: attempts.signal }).catch(() => undefined);
|
|
1131
|
+
// The response is still owed for this exact input. Failure rows remain
|
|
1132
|
+
// durable, but recursively materializing them would mutate and cache-bust
|
|
1133
|
+
// the request being recovered. {§provider-recovery}
|
|
1134
|
+
}
|
|
1135
|
+
// The first capacity rejection withholds the automatic prompt projection; the
|
|
1136
|
+
// rebuilt request is resent only when it actually changed the wire messages.
|
|
1137
|
+
async #recoverCapacityPacket(args, request, attempts) {
|
|
1138
|
+
if (request.promptProjection !== "automatic")
|
|
1139
|
+
return false;
|
|
1140
|
+
request.promptProjection = "withheld";
|
|
1141
|
+
const candidate = await this.#buildPacket(args, request);
|
|
1142
|
+
const candidateRequest = await this.#wireMessages(candidate, request.systemCtx, args.provider);
|
|
1143
|
+
if (JSON.stringify(candidateRequest.messages) === JSON.stringify(attempts.wire.messages))
|
|
1144
|
+
return false;
|
|
1145
|
+
request.packet = candidate;
|
|
1146
|
+
attempts.wire = candidateRequest;
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
// The capacity rejection is durable as a failed call and a problem row; the
|
|
1150
|
+
// replacement request is rebuilt to include that recovery signal while keeping
|
|
1151
|
+
// the selected projection.
|
|
1152
|
+
async #resendForCapacity(args, request, attempts, modelCall, error) {
|
|
1153
|
+
const { workerId, loopId, provider } = args;
|
|
1154
|
+
const failure = _a.#providerFailure(error, attempts.signal);
|
|
1155
|
+
await modelCall.fail(failure, error.capacity ?? null);
|
|
1156
|
+
attempts.callInFlight = false;
|
|
1157
|
+
await this.#problems.record({
|
|
1158
|
+
workerId,
|
|
1159
|
+
loopId,
|
|
1160
|
+
turnId: request.turnId,
|
|
1161
|
+
sequence: request.nextActionIndex++,
|
|
1162
|
+
origin: "_plurnk",
|
|
1163
|
+
source: "provider",
|
|
1164
|
+
result: failure,
|
|
1165
|
+
});
|
|
1166
|
+
// Include the durable recovery signal in the replacement
|
|
1167
|
+
// request while preserving the selected recovery posture.
|
|
1168
|
+
request.packet = await this.#buildPacket(args, request);
|
|
1169
|
+
attempts.wire = await this.#wireMessages(request.packet, request.systemCtx, provider);
|
|
1170
|
+
}
|
|
1171
|
+
// The attempt row's verdict: accepted, or rejected with the parser's errors. The
|
|
1172
|
+
// classified emission attempt is the turn's spent count.
|
|
1173
|
+
async #classifyProviderAttempt(attempts, id, split, emissionAttempt, accepted) {
|
|
1174
|
+
const result = await this.#db.engine_classify_turn_attempt_response.run({
|
|
1175
|
+
id,
|
|
1176
|
+
accepted: accepted ? 1 : 0,
|
|
1177
|
+
parse_errors: JSON.stringify(split.parseErrors),
|
|
1178
|
+
});
|
|
1179
|
+
if (result.changes !== 1) {
|
|
1180
|
+
throw new Error(`emission attempt ${id} was not awaiting classification`);
|
|
1181
|
+
}
|
|
1182
|
+
attempts.emissionAttempts = emissionAttempt;
|
|
1183
|
+
}
|
|
1184
|
+
// The provider-call failure handler. It owns only failures raised while a call
|
|
1185
|
+
// was in flight; parser, cost, SQL, and engine-contract failures retain their
|
|
1186
|
+
// original source.
|
|
1187
|
+
async #settleProviderFailure(err, args, request, attempts) {
|
|
1188
|
+
if (err instanceof ModelCallPersistenceError || err instanceof ProviderAccountingIntegrityError)
|
|
1189
|
+
throw err;
|
|
1190
|
+
if (!attempts.callInFlight)
|
|
1191
|
+
throw err;
|
|
1192
|
+
attempts.callInFlight = false;
|
|
1193
|
+
if (attempts.attemptId === null || attempts.modelCall === null) {
|
|
1194
|
+
throw new Error("provider call failed without durable model-call and attempt identities", { cause: err });
|
|
1195
|
+
}
|
|
1196
|
+
const { provider, workspaceId, workerId, loopId } = args;
|
|
1197
|
+
const { turnId } = request;
|
|
1198
|
+
const failure = _a.#providerFailure(err, attempts.signal);
|
|
1199
|
+
const capacityFailure = err instanceof ProviderError && err.kind === "capacity_exceeded";
|
|
1200
|
+
// {§provider-interrupted-attempt} — a provider-declared interruption
|
|
1201
|
+
// carries response evidence without becoming a completed exchange.
|
|
1202
|
+
// Persist it as an unaccepted attempt before settling the failure.
|
|
1203
|
+
if (err instanceof ProviderError && err.attempt !== undefined) {
|
|
1204
|
+
attempts.response = err.attempt;
|
|
1205
|
+
await attempts.modelCall.observeResponse(err.attempt, failure, attempts.wire.nativeInputs);
|
|
1206
|
+
attempts.split = this.#splitResponse(err.attempt, this.#executors()?.availableRuntimes(workspaceId) ?? []);
|
|
1207
|
+
await this.#classifyProviderAttempt(attempts, attempts.attemptId, attempts.split, attempts.currentEmissionAttempt, false);
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
await attempts.modelCall.fail(failure, err instanceof ProviderError ? err.capacity ?? null : null);
|
|
1211
|
+
if (!capacityFailure)
|
|
1212
|
+
attempts.emissionAttempts = attempts.currentEmissionAttempt;
|
|
1213
|
+
}
|
|
1214
|
+
const evidence = this.#requestEvidence(provider, request.packet, attempts.split, attempts.response);
|
|
1215
|
+
// {§turn-never-blank} — a ProviderError means no completed exchange exists.
|
|
1216
|
+
// Persist its exact RFC 9457 result before propagating it. Grammar transport
|
|
1217
|
+
// evidence exists only on completed responses ({§operator-grammar}).
|
|
1218
|
+
// Cancellation is lifecycle truth, not a provider failure. Close the
|
|
1219
|
+
// attempted turn without inventing an assistant response, then let
|
|
1220
|
+
// runLoop/Daemon settle the exact 504/499 loop result.
|
|
1221
|
+
if (attempts.signal?.aborted) {
|
|
1222
|
+
const status = attempts.signal.reason === LOOP_TIMEOUT_REASON ? 504 : 499;
|
|
1223
|
+
await this.#recordInference({ workspaceId, workerId, loopId, turnId, evidence });
|
|
1224
|
+
await Turn.complete(this.#db, turnId, status);
|
|
1225
|
+
throw err;
|
|
1226
|
+
}
|
|
1227
|
+
const recorded = await this.#problems.record({
|
|
1228
|
+
workerId,
|
|
1229
|
+
loopId,
|
|
1230
|
+
turnId,
|
|
1231
|
+
sequence: request.nextActionIndex,
|
|
1232
|
+
origin: "_plurnk",
|
|
1233
|
+
source: "provider",
|
|
1234
|
+
result: failure,
|
|
1235
|
+
});
|
|
1236
|
+
// The provider call was attempted, but no completed exchange exists.
|
|
1237
|
+
// Persist the exact request half and failure status; omitting assistant
|
|
1238
|
+
// is materially different from fabricating an empty model turn.
|
|
1239
|
+
await this.#recordInference({ workspaceId, workerId, loopId, turnId, evidence });
|
|
1240
|
+
await Turn.complete(this.#db, turnId, attempts.parked ? 202 : recorded.result.status);
|
|
1241
|
+
const { emissionAttempts } = attempts;
|
|
1242
|
+
if (attempts.parked) {
|
|
1243
|
+
// {§provider-recovery} — the recovery budget is spent: the loop parks exactly like a
|
|
1244
|
+
// [202] wait and resumes on the next prompt or wake; the failure stays durable.
|
|
1245
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1246
|
+
source: "engine:provider",
|
|
1247
|
+
kind: "provider_unavailable",
|
|
1248
|
+
level: "error",
|
|
1249
|
+
message: `${recorded.result.problem?.title ?? "Provider failure"}: the ${Math.round(attempts.recoveryBudget / 1000)}s recovery budget is spent; the loop is parked and resumes on the next prompt or wake.`,
|
|
1551
1250
|
});
|
|
1552
|
-
return {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1251
|
+
return turnResult(request, 202, { providerParked: true, providerFailure: recorded.result, emissionAttempts });
|
|
1252
|
+
}
|
|
1253
|
+
if (capacityFailure) {
|
|
1254
|
+
return turnResult(request, recorded.result.status, { capacityHardStop: true, capacityFailure: recorded.result, emissionAttempts });
|
|
1255
|
+
}
|
|
1256
|
+
if (err instanceof ProviderError && err.kind === "invalid_response") {
|
|
1257
|
+
// {§engine-rails} Contract Strikes: the provider violated its response
|
|
1258
|
+
// contract. The failure is durable and the turn is complete; the strike
|
|
1259
|
+
// rail — never an instant loop death — rules whether the streak ends it.
|
|
1260
|
+
return turnResult(request, recorded.result.status, {
|
|
1261
|
+
outcomes: [{ op: null, status: recorded.result.status, problemType: recorded.result.problem?.type ?? null }],
|
|
1262
|
+
fingerprint: `provider-contract-violation:${turnId}`,
|
|
1263
|
+
providerFailure: recorded.result,
|
|
1563
1264
|
emissionAttempts,
|
|
1564
|
-
|
|
1565
|
-
};
|
|
1265
|
+
});
|
|
1566
1266
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1267
|
+
throw new OperationFailureError(recorded.result, { cause: err });
|
|
1268
|
+
}
|
|
1269
|
+
// Inference evidence for a turn without an admitted exchange: the exact request
|
|
1270
|
+
// half, the last attempt's metadata when one exists, the provider's own model otherwise.
|
|
1271
|
+
#requestEvidence(provider, packet, split, response) {
|
|
1272
|
+
return {
|
|
1273
|
+
packet: StoredPacket.stringify(packet),
|
|
1274
|
+
sections: StoredPacket.sections(packet),
|
|
1275
|
+
usageCurationBudget: this.#packets.curationBudgetFor(packet),
|
|
1276
|
+
finishReason: split?.callMetadata.finishReason ?? null,
|
|
1277
|
+
model: split?.callMetadata.model ?? provider.model,
|
|
1278
|
+
meta: JSON.stringify(response?.meta ?? {}),
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
// Phase 5, refused — {§invalid-emission-attempts}: every exhaustion publishes the
|
|
1282
|
+
// raw final response and its recovery fact; {§engine-rails} Contract Strikes rule
|
|
1283
|
+
// how many consecutive frame-contract violations the loop survives.
|
|
1284
|
+
async #rejectExhaustedEmission({ provider, workspaceId, workerId, loopId }, request, emission) {
|
|
1285
|
+
const { split, response } = emission;
|
|
1286
|
+
const { turnId } = request;
|
|
1287
|
+
const rejectedModelEntryId = await this.#dispatcher.writeEmissionAttempt({
|
|
1288
|
+
verbatim: split.packetAssistant.content,
|
|
1289
|
+
workerId,
|
|
1290
|
+
loopId,
|
|
1291
|
+
turnId,
|
|
1292
|
+
sequence: request.nextActionIndex,
|
|
1293
|
+
modelCallId: emission.modelCallId,
|
|
1294
|
+
});
|
|
1295
|
+
// {§invalid-emission-attempts} — the informed turn carries the parser's own
|
|
1296
|
+
// diagnostic and position: the model sees WHY, not only that it was refused.
|
|
1297
|
+
const diagnostic = split.parseErrors[0];
|
|
1298
|
+
const cut = allowanceCutMessage(split.callMetadata.finishReason, response.capacity.responseMax ?? provider.outputBudget);
|
|
1299
|
+
if (cut !== null) {
|
|
1300
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1301
|
+
source: "engine:capacity",
|
|
1302
|
+
kind: "output_truncated",
|
|
1303
|
+
level: "error",
|
|
1304
|
+
message: `${cut}; no operations were performed`,
|
|
1305
|
+
});
|
|
1581
1306
|
}
|
|
1307
|
+
else {
|
|
1308
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1309
|
+
source: "engine:grammar",
|
|
1310
|
+
kind: "invalid_emission",
|
|
1311
|
+
level: "error",
|
|
1312
|
+
message: diagnostic === undefined
|
|
1313
|
+
? INVALID_EMISSION_RECOVERY_MESSAGE
|
|
1314
|
+
: `${INVALID_EMISSION_RECOVERY_MESSAGE} Parser: ${diagnostic.message}`,
|
|
1315
|
+
...(diagnostic !== undefined && diagnostic.line > 0
|
|
1316
|
+
? { position: { type: "content-offset", line: diagnostic.line, column: diagnostic.column } }
|
|
1317
|
+
: {}),
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
await this.#recordInference({ workspaceId, workerId, loopId, turnId, evidence: this.#requestEvidence(provider, request.packet, split, response) });
|
|
1321
|
+
await Turn.complete(this.#db, turnId, TURN_STATUS_IMPLICIT_CONTINUE);
|
|
1322
|
+
return turnResult(request, TURN_STATUS_IMPLICIT_CONTINUE, {
|
|
1323
|
+
outcomes: [{ op: null, status: 500, problemType: "https://problems.plurnk.xyz/engine/invalid-emission-exhausted" }],
|
|
1324
|
+
fingerprint: `frame-contract-violation:${turnId}`,
|
|
1325
|
+
emissionAttempts: emission.emissionAttempts,
|
|
1326
|
+
emissionExhausted: true,
|
|
1327
|
+
rejectedModelEntryId,
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
// Phase 5, admitted — the emission's notices, then {§packet-stored-shape}: admitted
|
|
1331
|
+
// emission data extends the packet while provider-call metadata remains on the
|
|
1332
|
+
// Turn row.
|
|
1333
|
+
async #recordAdmittedEmission({ provider, workspaceId, workerId, loopId }, request, emission) {
|
|
1334
|
+
const { split, response } = emission;
|
|
1335
|
+
const { packetAssistant, callMetadata, parseNotices } = split; // raw assistant content is opaque — split, never interpreted — {§provider-guarantees-assistantraw-opaque}
|
|
1336
|
+
for (const notice of parseNotices) {
|
|
1337
|
+
this.#notices.push(workspaceId, workerId, loopId, notice);
|
|
1338
|
+
}
|
|
1339
|
+
const allowanceCut = allowanceCutMessage(callMetadata.finishReason, response.capacity.responseMax ?? provider.outputBudget);
|
|
1340
|
+
// {§empty-turn} — a response cut at the output allowance with nothing admitted names the
|
|
1341
|
+
// cut, so the model reads the ceiling, never a parser symptom (#478).
|
|
1342
|
+
if (split.emptyTurn && allowanceCut !== null) {
|
|
1343
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1344
|
+
source: "engine:turn",
|
|
1345
|
+
kind: "output_truncated",
|
|
1346
|
+
level: "warn",
|
|
1347
|
+
message: `${allowanceCut}; no operations were performed`,
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
// Non-fatal provider transport notices on an accepted turn. Forward each
|
|
1351
|
+
// Notice with a content-offset `line:col`;
|
|
1352
|
+
// the model resolves it against its own emission — READ ops:///<loop>/<turn> at the
|
|
1353
|
+
// cited lines ({§turn-ops-entry}) — not an embedded snippet that would duplicate the emission.
|
|
1354
|
+
for (const notice of response.notices ?? []) {
|
|
1355
|
+
const located = typeof notice.position === "number"
|
|
1356
|
+
? this.#offsetToLineColumn(packetAssistant.content, notice.position)
|
|
1357
|
+
: null;
|
|
1358
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1359
|
+
source: notice.source,
|
|
1360
|
+
kind: notice.kind,
|
|
1361
|
+
message: notice.message ?? "",
|
|
1362
|
+
level: notice.level,
|
|
1363
|
+
...(located !== null
|
|
1364
|
+
? { position: { type: "content-offset", line: located.line, column: located.column } }
|
|
1365
|
+
: {}),
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
if (allowanceCut !== null) {
|
|
1369
|
+
this.#notices.push(workspaceId, workerId, loopId, {
|
|
1370
|
+
source: "engine:capacity",
|
|
1371
|
+
kind: "output_truncated",
|
|
1372
|
+
message: allowanceCut,
|
|
1373
|
+
level: "warn",
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
// {§operator-grammar} — transport evidence only: the turn records whether the operator's
|
|
1377
|
+
// grammar reached the wire. Nothing grades the response against it; the parser's
|
|
1378
|
+
// admission is the one verdict (#588).
|
|
1379
|
+
const railKeys = emission.railGrammar === undefined
|
|
1380
|
+
? undefined
|
|
1381
|
+
: { railsAttached: emission.railEvidence?.transported === true ? "client" : "withheld" };
|
|
1382
|
+
// Attach the admitted inference evidence. The turn remains open until
|
|
1383
|
+
// the producer-neutral admitted-turn executor settles every operation
|
|
1384
|
+
// and its exact source artifact.
|
|
1385
|
+
const packet = StoredPacket.admit(request.packet, packetAssistant, response.assistantRaw);
|
|
1386
|
+
await this.#recordInference({
|
|
1387
|
+
workspaceId, workerId, loopId, turnId: request.turnId,
|
|
1388
|
+
evidence: {
|
|
1389
|
+
packet: StoredPacket.stringify(packet),
|
|
1390
|
+
sections: StoredPacket.sections(packet),
|
|
1391
|
+
usageCurationBudget: this.#packets.curationBudgetFor(request.packet), // {§tokenomics-client-gauge}
|
|
1392
|
+
finishReason: callMetadata.finishReason,
|
|
1393
|
+
model: callMetadata.model,
|
|
1394
|
+
// Opaque provider metadata plus the grammar transport key.
|
|
1395
|
+
// {§meta-passthrough}, {§operator-grammar}
|
|
1396
|
+
meta: JSON.stringify({ ...(response.meta ?? {}), ...(railKeys ?? {}) }),
|
|
1397
|
+
},
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
// Phase 6 — the admitted program runs under the workspace's command ceiling and
|
|
1401
|
+
// the turn settles on the executor's verdict.
|
|
1402
|
+
async #settleAdmittedTurn(args, request, emission) {
|
|
1403
|
+
const { childProvider, workspaceId, workerId, loopId, onDispatch, onSettled } = args;
|
|
1404
|
+
const { split } = emission;
|
|
1405
|
+
// {§operator-config-workspace-max-commands} — workspace maxCommands
|
|
1406
|
+
// narrows the operator ceiling before the admitted program reaches the
|
|
1407
|
+
// shared executor.
|
|
1408
|
+
const maxCommands = Math.min(readMaxCommands(), (await WorkspaceSettings.read(this.#db, workspaceId)).maxCommands ?? Number.POSITIVE_INFINITY);
|
|
1409
|
+
const executed = await this.executeAdmittedTurn({
|
|
1410
|
+
statements: split.packetAssistant.ops,
|
|
1411
|
+
source: split.sourceBacked ? split.packetAssistant.content : null,
|
|
1412
|
+
sourceModelCallId: emission.modelCallId,
|
|
1413
|
+
origin: "model",
|
|
1414
|
+
workspaceId,
|
|
1415
|
+
workerId,
|
|
1416
|
+
loopId,
|
|
1417
|
+
turnId: request.turnId,
|
|
1418
|
+
fromSequence: request.nextActionIndex,
|
|
1419
|
+
maxCommands,
|
|
1420
|
+
recoverableParseErrors: split.recoverableParseErrors,
|
|
1421
|
+
emptyTurn: split.emptyTurn,
|
|
1422
|
+
bare: {
|
|
1423
|
+
provider: childProvider,
|
|
1424
|
+
primaryWorkerId: emission.primaryWorkerId,
|
|
1425
|
+
loopSequence: request.loopSeq,
|
|
1426
|
+
turnSequence: request.seq,
|
|
1427
|
+
signal: emission.signal,
|
|
1428
|
+
},
|
|
1429
|
+
signal: emission.signal,
|
|
1430
|
+
onDispatch,
|
|
1431
|
+
onSettled,
|
|
1432
|
+
});
|
|
1433
|
+
return turnResult(request, executed.status, {
|
|
1434
|
+
outcomes: executed.outcomes,
|
|
1435
|
+
fingerprint: executed.fingerprint,
|
|
1436
|
+
emptyTurn: executed.emptyTurn,
|
|
1437
|
+
emissionAttempts: emission.emissionAttempts,
|
|
1438
|
+
});
|
|
1582
1439
|
}
|
|
1583
1440
|
// Split the wire-level ProviderResponse into the two destinations:
|
|
1584
1441
|
// packet.assistant gets the model's emission (content, ops, reasoning);
|
|
@@ -1592,13 +1449,11 @@ export default class TurnRunner {
|
|
|
1592
1449
|
// its assistant payload to skip the parse roundtrip. The wire Provider
|
|
1593
1450
|
// contract has no `ops` field; only Mock exposes one. Real providers
|
|
1594
1451
|
// always take the parse path because their `assistant.ops` is undefined.
|
|
1595
|
-
#splitResponse(response) {
|
|
1452
|
+
#splitResponse(response, executors = []) {
|
|
1596
1453
|
const { assistant } = response;
|
|
1597
1454
|
const preParsedOps = assistant.ops;
|
|
1598
1455
|
const ops = [];
|
|
1599
|
-
//
|
|
1600
|
-
// client as a log entry. No special hoisting into the reasoning field. Only
|
|
1601
|
-
// structured operations are executable; interstitial text is not an operation.
|
|
1456
|
+
// Only structured operations are executable; interstitial text is not an operation.
|
|
1602
1457
|
// Full PlurnkParseError context is preserved on rejected attempt evidence;
|
|
1603
1458
|
// warnings remain admissible Notices. {§parse-diagnostics}
|
|
1604
1459
|
const parseErrors = [];
|
|
@@ -1611,7 +1466,9 @@ export default class TurnRunner {
|
|
|
1611
1466
|
// {§observability-boundary} — the parse is observed without its input;
|
|
1612
1467
|
// only the resulting statement count is attributable.
|
|
1613
1468
|
const parsed = observedSync("contracts.parse", {}, (span) => {
|
|
1614
|
-
|
|
1469
|
+
// {§fence-heading-in-body} {§interstitial-fence} — the executors this workspace can run
|
|
1470
|
+
// are heading tags to the parser; anything else tagged is a code block.
|
|
1471
|
+
const result = PlurnkParser.parse(assistant.content, { executors });
|
|
1615
1472
|
span.setAttribute("statements", result.items.filter((item) => item.kind === "statement").length);
|
|
1616
1473
|
return result;
|
|
1617
1474
|
});
|
|
@@ -1637,7 +1494,7 @@ export default class TurnRunner {
|
|
|
1637
1494
|
});
|
|
1638
1495
|
}
|
|
1639
1496
|
else {
|
|
1640
|
-
parseErrors.push({ message: err.message, line: err.line, column: err.column, source: err.source });
|
|
1497
|
+
parseErrors.push({ message: err.message, line: err.line, column: err.column, source: err.source, ...(err.code === undefined ? {} : { code: err.code }) });
|
|
1641
1498
|
}
|
|
1642
1499
|
}
|
|
1643
1500
|
else {
|
|
@@ -1655,25 +1512,23 @@ export default class TurnRunner {
|
|
|
1655
1512
|
}
|
|
1656
1513
|
}
|
|
1657
1514
|
const sourceStatementCount = ops.filter(({ position }) => position.line > 0).length;
|
|
1658
|
-
const
|
|
1659
|
-
const
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
?
|
|
1664
|
-
: undefined;
|
|
1665
|
-
const recoveredSend = terminalSend?.position.line === 0;
|
|
1666
|
-
// {§turn-shape} — PLAN is a SHOULD: a turn without one is complete once it ends in a
|
|
1667
|
-
// terminal SEND, and the recoverable window then starts at the turn's first statement.
|
|
1668
|
-
const recoverableParseErrors = terminalSend !== undefined && !hasUnparsedTail
|
|
1669
|
-
? parseErrors.filter((error) => (error.message === PlurnkParser.MISSING_SEND
|
|
1670
|
-
|| ((plan === undefined || comparePosition(error, plan.position) > 0)
|
|
1671
|
-
&& (recoveredSend || comparePosition(error, terminalSend.position) < 0)))).toSorted(comparePosition)
|
|
1515
|
+
const dispositions = ops.filter(TurnDisposition.is);
|
|
1516
|
+
const trustworthyBoundary = dispositions.length <= 1 && !hasUnparsedTail;
|
|
1517
|
+
// {§turn-shape} — bounded operation errors are recoverable and ride with the
|
|
1518
|
+
// admitted program; document-boundary failures still reject it.
|
|
1519
|
+
const recoverableParseErrors = trustworthyBoundary
|
|
1520
|
+
? parseErrors.filter((error) => error.code !== "invalid-turn-structure").toSorted(comparePosition)
|
|
1672
1521
|
: [];
|
|
1522
|
+
// {§empty-turn} — no operation and no other hard error: admitted as an empty turn, never
|
|
1523
|
+
// resampled; its advisories ({§bare-heading-advisory}) ride as notices.
|
|
1524
|
+
const emptyTurn = preParsedOps === undefined
|
|
1525
|
+
&& trustworthyBoundary
|
|
1526
|
+
&& sourceStatementCount === 0
|
|
1527
|
+
&& parseErrors.every((error) => error.message === PlurnkParser.NO_VALID_OPERATION);
|
|
1673
1528
|
const emissionValid = preParsedOps !== undefined
|
|
1674
|
-
||
|
|
1529
|
+
|| emptyTurn
|
|
1530
|
+
|| (trustworthyBoundary
|
|
1675
1531
|
&& sourceStatementCount > 0
|
|
1676
|
-
&& !hasUnparsedTail
|
|
1677
1532
|
&& recoverableParseErrors.length === parseErrors.length);
|
|
1678
1533
|
const reasoning = assistant.reasoning ?? null;
|
|
1679
1534
|
return {
|
|
@@ -1681,19 +1536,20 @@ export default class TurnRunner {
|
|
|
1681
1536
|
sourceBacked: preParsedOps === undefined,
|
|
1682
1537
|
callMetadata: { finishReason: assistant.finishReason, model: assistant.model },
|
|
1683
1538
|
parseErrors,
|
|
1684
|
-
recoverableParseErrors: emissionValid ? recoverableParseErrors : [],
|
|
1539
|
+
recoverableParseErrors: emissionValid && !emptyTurn ? recoverableParseErrors : [],
|
|
1540
|
+
emptyTurn,
|
|
1685
1541
|
parseNotices,
|
|
1686
1542
|
// The ANTLR model-turn parser is authoritative. At least one source
|
|
1687
|
-
// operation
|
|
1688
|
-
//
|
|
1689
|
-
//
|
|
1543
|
+
// operation is required; TASK omission continues silently. Bounded
|
|
1544
|
+
// statement failures become durable operation results.
|
|
1545
|
+
// Boundary loss and an unparsed tail still reject
|
|
1690
1546
|
// wholesale. Pre-parsed ops are Mock's trusted test seam.
|
|
1691
1547
|
emissionValid,
|
|
1692
1548
|
};
|
|
1693
1549
|
}
|
|
1694
1550
|
// #note12 — plugin reference docs are materialized beneath
|
|
1695
|
-
// worker
|
|
1696
|
-
// {§exec-poll} —
|
|
1551
|
+
// worker:///_plurnk/plurnk/ by LoopDocs.
|
|
1552
|
+
// {§exec-poll} — an execution `<0>` is turn-scoped: abort the worker's open turn-scoped streams via their
|
|
1697
1553
|
// owning scheme (the same registry-routed abort the total reap uses). Called at each pre-turn
|
|
1698
1554
|
// before the turn's own spawns, so every open turn-scoped sub here is from a prior turn — it
|
|
1699
1555
|
// never survives into the subsequent turn. Fire-and-forget: the spawn finalizes async and its
|
|
@@ -1706,4 +1562,6 @@ export default class TurnRunner {
|
|
|
1706
1562
|
return this.#admitted.executeAdmittedTurn(...args);
|
|
1707
1563
|
}
|
|
1708
1564
|
}
|
|
1565
|
+
_a = TurnRunner;
|
|
1566
|
+
export default TurnRunner;
|
|
1709
1567
|
//# sourceMappingURL=TurnRunner.js.map
|