@amsterdamdatalabs/enact-operator 0.1.2
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/README.md +33 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/lanes/autopilot/index.d.ts +95 -0
- package/dist/lanes/autopilot/index.d.ts.map +1 -0
- package/dist/lanes/autopilot/index.js +499 -0
- package/dist/lanes/autopilot/index.js.map +1 -0
- package/dist/lanes/ralph/index.d.ts +118 -0
- package/dist/lanes/ralph/index.d.ts.map +1 -0
- package/dist/lanes/ralph/index.js +680 -0
- package/dist/lanes/ralph/index.js.map +1 -0
- package/dist/lanes/team/index.d.ts +110 -0
- package/dist/lanes/team/index.d.ts.map +1 -0
- package/dist/lanes/team/index.js +748 -0
- package/dist/lanes/team/index.js.map +1 -0
- package/dist/lanes/team/roles.d.ts +15 -0
- package/dist/lanes/team/roles.d.ts.map +1 -0
- package/dist/lanes/team/roles.js +89 -0
- package/dist/lanes/team/roles.js.map +1 -0
- package/dist/lanes/ultrawork/burst.d.ts +69 -0
- package/dist/lanes/ultrawork/burst.d.ts.map +1 -0
- package/dist/lanes/ultrawork/burst.js +254 -0
- package/dist/lanes/ultrawork/burst.js.map +1 -0
- package/dist/lanes/ultrawork/index.d.ts +159 -0
- package/dist/lanes/ultrawork/index.d.ts.map +1 -0
- package/dist/lanes/ultrawork/index.js +919 -0
- package/dist/lanes/ultrawork/index.js.map +1 -0
- package/dist/lanes/ultrawork/phases.d.ts +57 -0
- package/dist/lanes/ultrawork/phases.d.ts.map +1 -0
- package/dist/lanes/ultrawork/phases.js +116 -0
- package/dist/lanes/ultrawork/phases.js.map +1 -0
- package/dist/mcp/activation.d.ts +5 -0
- package/dist/mcp/activation.d.ts.map +1 -0
- package/dist/mcp/activation.js +234 -0
- package/dist/mcp/activation.js.map +1 -0
- package/dist/mcp/cli.d.ts +11 -0
- package/dist/mcp/cli.d.ts.map +1 -0
- package/dist/mcp/cli.js +140 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/mcp/continuation.d.ts +5 -0
- package/dist/mcp/continuation.d.ts.map +1 -0
- package/dist/mcp/continuation.js +360 -0
- package/dist/mcp/continuation.js.map +1 -0
- package/dist/mcp/jobs.d.ts +5 -0
- package/dist/mcp/jobs.d.ts.map +1 -0
- package/dist/mcp/jobs.js +212 -0
- package/dist/mcp/jobs.js.map +1 -0
- package/dist/mcp/laneSupport.d.ts +19 -0
- package/dist/mcp/laneSupport.d.ts.map +1 -0
- package/dist/mcp/laneSupport.js +21 -0
- package/dist/mcp/laneSupport.js.map +1 -0
- package/dist/mcp/lanes/autopilot.d.ts +5 -0
- package/dist/mcp/lanes/autopilot.d.ts.map +1 -0
- package/dist/mcp/lanes/autopilot.js +227 -0
- package/dist/mcp/lanes/autopilot.js.map +1 -0
- package/dist/mcp/lanes/ralph.d.ts +5 -0
- package/dist/mcp/lanes/ralph.d.ts.map +1 -0
- package/dist/mcp/lanes/ralph.js +392 -0
- package/dist/mcp/lanes/ralph.js.map +1 -0
- package/dist/mcp/lanes/team.d.ts +5 -0
- package/dist/mcp/lanes/team.d.ts.map +1 -0
- package/dist/mcp/lanes/team.js +413 -0
- package/dist/mcp/lanes/team.js.map +1 -0
- package/dist/mcp/lanes/ultrawork.d.ts +5 -0
- package/dist/mcp/lanes/ultrawork.d.ts.map +1 -0
- package/dist/mcp/lanes/ultrawork.js +497 -0
- package/dist/mcp/lanes/ultrawork.js.map +1 -0
- package/dist/mcp/linkage.d.ts +5 -0
- package/dist/mcp/linkage.d.ts.map +1 -0
- package/dist/mcp/linkage.js +126 -0
- package/dist/mcp/linkage.js.map +1 -0
- package/dist/mcp/planning.d.ts +5 -0
- package/dist/mcp/planning.d.ts.map +1 -0
- package/dist/mcp/planning.js +584 -0
- package/dist/mcp/planning.js.map +1 -0
- package/dist/mcp/rpcTransport.d.ts +17 -0
- package/dist/mcp/rpcTransport.d.ts.map +1 -0
- package/dist/mcp/rpcTransport.js +90 -0
- package/dist/mcp/rpcTransport.js.map +1 -0
- package/dist/mcp/schemas/ralphVerify.d.ts +32 -0
- package/dist/mcp/schemas/ralphVerify.d.ts.map +1 -0
- package/dist/mcp/schemas/ralphVerify.js +20 -0
- package/dist/mcp/schemas/ralphVerify.js.map +1 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +176 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/system.d.ts +5 -0
- package/dist/mcp/system.d.ts.map +1 -0
- package/dist/mcp/system.js +445 -0
- package/dist/mcp/system.js.map +1 -0
- package/dist/mcp/toolFilter.d.ts +2 -0
- package/dist/mcp/toolFilter.d.ts.map +1 -0
- package/dist/mcp/toolFilter.js +2 -0
- package/dist/mcp/toolFilter.js.map +1 -0
- package/dist/mcp/toolPartitions.d.ts +20 -0
- package/dist/mcp/toolPartitions.d.ts.map +1 -0
- package/dist/mcp/toolPartitions.js +280 -0
- package/dist/mcp/toolPartitions.js.map +1 -0
- package/dist/mcp/toolRuntime.d.ts +9 -0
- package/dist/mcp/toolRuntime.d.ts.map +1 -0
- package/dist/mcp/toolRuntime.js +179 -0
- package/dist/mcp/toolRuntime.js.map +1 -0
- package/dist/mcp/workflow.d.ts +12 -0
- package/dist/mcp/workflow.d.ts.map +1 -0
- package/dist/mcp/workflow.js +723 -0
- package/dist/mcp/workflow.js.map +1 -0
- package/dist/operator/hooks/hookDispatcher.d.ts +39 -0
- package/dist/operator/hooks/hookDispatcher.d.ts.map +1 -0
- package/dist/operator/hooks/hookDispatcher.js +58 -0
- package/dist/operator/hooks/hookDispatcher.js.map +1 -0
- package/dist/operator/hooks/hookGating.d.ts +28 -0
- package/dist/operator/hooks/hookGating.d.ts.map +1 -0
- package/dist/operator/hooks/hookGating.js +61 -0
- package/dist/operator/hooks/hookGating.js.map +1 -0
- package/dist/operator/hooks/hooks.d.ts +20 -0
- package/dist/operator/hooks/hooks.d.ts.map +1 -0
- package/dist/operator/hooks/hooks.js +124 -0
- package/dist/operator/hooks/hooks.js.map +1 -0
- package/dist/operator/hooks/toolGuardHooks.d.ts +86 -0
- package/dist/operator/hooks/toolGuardHooks.d.ts.map +1 -0
- package/dist/operator/hooks/toolGuardHooks.js +163 -0
- package/dist/operator/hooks/toolGuardHooks.js.map +1 -0
- package/dist/operator/hud.d.ts +115 -0
- package/dist/operator/hud.d.ts.map +1 -0
- package/dist/operator/hud.js +229 -0
- package/dist/operator/hud.js.map +1 -0
- package/dist/operator/missions.d.ts +125 -0
- package/dist/operator/missions.d.ts.map +1 -0
- package/dist/operator/missions.js +304 -0
- package/dist/operator/missions.js.map +1 -0
- package/dist/operator/operatorCore.d.ts +11 -0
- package/dist/operator/operatorCore.d.ts.map +1 -0
- package/dist/operator/operatorCore.js +9 -0
- package/dist/operator/operatorCore.js.map +1 -0
- package/dist/operator/operatorRuntimeCore.d.ts +17 -0
- package/dist/operator/operatorRuntimeCore.d.ts.map +1 -0
- package/dist/operator/operatorRuntimeCore.js +17 -0
- package/dist/operator/operatorRuntimeCore.js.map +1 -0
- package/dist/operator/runtime.d.ts +74 -0
- package/dist/operator/runtime.d.ts.map +1 -0
- package/dist/operator/runtime.js +357 -0
- package/dist/operator/runtime.js.map +1 -0
- package/dist/shared/core/bootstrap.d.ts +16 -0
- package/dist/shared/core/bootstrap.d.ts.map +1 -0
- package/dist/shared/core/bootstrap.js +77 -0
- package/dist/shared/core/bootstrap.js.map +1 -0
- package/dist/shared/core/contract.d.ts +71 -0
- package/dist/shared/core/contract.d.ts.map +1 -0
- package/dist/shared/core/contract.js +452 -0
- package/dist/shared/core/contract.js.map +1 -0
- package/dist/shared/core/events.d.ts +57 -0
- package/dist/shared/core/events.d.ts.map +1 -0
- package/dist/shared/core/events.js +36 -0
- package/dist/shared/core/events.js.map +1 -0
- package/dist/shared/core/jobs.d.ts +46 -0
- package/dist/shared/core/jobs.d.ts.map +1 -0
- package/dist/shared/core/jobs.js +137 -0
- package/dist/shared/core/jobs.js.map +1 -0
- package/dist/shared/core/json.d.ts +12 -0
- package/dist/shared/core/json.d.ts.map +1 -0
- package/dist/shared/core/json.js +52 -0
- package/dist/shared/core/json.js.map +1 -0
- package/dist/shared/core/layoutMigration.d.ts +11 -0
- package/dist/shared/core/layoutMigration.d.ts.map +1 -0
- package/dist/shared/core/layoutMigration.js +56 -0
- package/dist/shared/core/layoutMigration.js.map +1 -0
- package/dist/shared/core/lifecycle.d.ts +61 -0
- package/dist/shared/core/lifecycle.d.ts.map +1 -0
- package/dist/shared/core/lifecycle.js +123 -0
- package/dist/shared/core/lifecycle.js.map +1 -0
- package/dist/shared/core/packagePaths.d.ts +2 -0
- package/dist/shared/core/packagePaths.d.ts.map +1 -0
- package/dist/shared/core/packagePaths.js +9 -0
- package/dist/shared/core/packagePaths.js.map +1 -0
- package/dist/shared/core/terminology.d.ts +38 -0
- package/dist/shared/core/terminology.d.ts.map +1 -0
- package/dist/shared/core/terminology.js +54 -0
- package/dist/shared/core/terminology.js.map +1 -0
- package/dist/shared/core/types.d.ts +30 -0
- package/dist/shared/core/types.d.ts.map +1 -0
- package/dist/shared/core/types.js +2 -0
- package/dist/shared/core/types.js.map +1 -0
- package/dist/shared/diagnostics/doctor.d.ts +19 -0
- package/dist/shared/diagnostics/doctor.d.ts.map +1 -0
- package/dist/shared/diagnostics/doctor.js +82 -0
- package/dist/shared/diagnostics/doctor.js.map +1 -0
- package/dist/shared/diagnostics/hostRollout.d.ts +16 -0
- package/dist/shared/diagnostics/hostRollout.d.ts.map +1 -0
- package/dist/shared/diagnostics/hostRollout.js +78 -0
- package/dist/shared/diagnostics/hostRollout.js.map +1 -0
- package/dist/shared/observability/emit.d.ts +3 -0
- package/dist/shared/observability/emit.d.ts.map +1 -0
- package/dist/shared/observability/emit.js +17 -0
- package/dist/shared/observability/emit.js.map +1 -0
- package/dist/shared/observability/testWorkspace.d.ts +4 -0
- package/dist/shared/observability/testWorkspace.d.ts.map +1 -0
- package/dist/shared/observability/testWorkspace.js +35 -0
- package/dist/shared/observability/testWorkspace.js.map +1 -0
- package/dist/shared/state/installRegistry.d.ts +22 -0
- package/dist/shared/state/installRegistry.d.ts.map +1 -0
- package/dist/shared/state/installRegistry.js +51 -0
- package/dist/shared/state/installRegistry.js.map +1 -0
- package/dist/shared/state/session.d.ts +18 -0
- package/dist/shared/state/session.d.ts.map +1 -0
- package/dist/shared/state/session.js +127 -0
- package/dist/shared/state/session.js.map +1 -0
- package/dist/shared/state/state.d.ts +16 -0
- package/dist/shared/state/state.d.ts.map +1 -0
- package/dist/shared/state/state.js +91 -0
- package/dist/shared/state/state.js.map +1 -0
- package/dist/shared/state/tasks.d.ts +113 -0
- package/dist/shared/state/tasks.d.ts.map +1 -0
- package/dist/shared/state/tasks.js +274 -0
- package/dist/shared/state/tasks.js.map +1 -0
- package/dist/shared/workflow/activation/activeSkill.d.ts +46 -0
- package/dist/shared/workflow/activation/activeSkill.d.ts.map +1 -0
- package/dist/shared/workflow/activation/activeSkill.js +158 -0
- package/dist/shared/workflow/activation/activeSkill.js.map +1 -0
- package/dist/shared/workflow/activation/gateProfiles.d.ts +26 -0
- package/dist/shared/workflow/activation/gateProfiles.d.ts.map +1 -0
- package/dist/shared/workflow/activation/gateProfiles.js +102 -0
- package/dist/shared/workflow/activation/gateProfiles.js.map +1 -0
- package/dist/shared/workflow/activation/modeMatrix.d.ts +28 -0
- package/dist/shared/workflow/activation/modeMatrix.d.ts.map +1 -0
- package/dist/shared/workflow/activation/modeMatrix.js +91 -0
- package/dist/shared/workflow/activation/modeMatrix.js.map +1 -0
- package/dist/shared/workflow/activation/skillActivation.d.ts +74 -0
- package/dist/shared/workflow/activation/skillActivation.d.ts.map +1 -0
- package/dist/shared/workflow/activation/skillActivation.js +485 -0
- package/dist/shared/workflow/activation/skillActivation.js.map +1 -0
- package/dist/shared/workflow/activation/skillVariant.d.ts +18 -0
- package/dist/shared/workflow/activation/skillVariant.d.ts.map +1 -0
- package/dist/shared/workflow/activation/skillVariant.js +44 -0
- package/dist/shared/workflow/activation/skillVariant.js.map +1 -0
- package/dist/shared/workflow/authority/authority.d.ts +34 -0
- package/dist/shared/workflow/authority/authority.d.ts.map +1 -0
- package/dist/shared/workflow/authority/authority.js +64 -0
- package/dist/shared/workflow/authority/authority.js.map +1 -0
- package/dist/shared/workflow/closure/closureManifest.d.ts +39 -0
- package/dist/shared/workflow/closure/closureManifest.d.ts.map +1 -0
- package/dist/shared/workflow/closure/closureManifest.js +62 -0
- package/dist/shared/workflow/closure/closureManifest.js.map +1 -0
- package/dist/shared/workflow/closure/closureRequirements.d.ts +12 -0
- package/dist/shared/workflow/closure/closureRequirements.d.ts.map +1 -0
- package/dist/shared/workflow/closure/closureRequirements.js +30 -0
- package/dist/shared/workflow/closure/closureRequirements.js.map +1 -0
- package/dist/shared/workflow/closure/contractParity.d.ts +24 -0
- package/dist/shared/workflow/closure/contractParity.d.ts.map +1 -0
- package/dist/shared/workflow/closure/contractParity.js +238 -0
- package/dist/shared/workflow/closure/contractParity.js.map +1 -0
- package/dist/shared/workflow/closure/contractParityContext.d.ts +8 -0
- package/dist/shared/workflow/closure/contractParityContext.d.ts.map +1 -0
- package/dist/shared/workflow/closure/contractParityContext.js +50 -0
- package/dist/shared/workflow/closure/contractParityContext.js.map +1 -0
- package/dist/shared/workflow/closure/scopeGuard.d.ts +11 -0
- package/dist/shared/workflow/closure/scopeGuard.d.ts.map +1 -0
- package/dist/shared/workflow/closure/scopeGuard.js +69 -0
- package/dist/shared/workflow/closure/scopeGuard.js.map +1 -0
- package/dist/shared/workflow/closure/ultragoalArtifact.d.ts +13 -0
- package/dist/shared/workflow/closure/ultragoalArtifact.d.ts.map +1 -0
- package/dist/shared/workflow/closure/ultragoalArtifact.js +31 -0
- package/dist/shared/workflow/closure/ultragoalArtifact.js.map +1 -0
- package/dist/shared/workflow/closure/workflowReconcile.d.ts +70 -0
- package/dist/shared/workflow/closure/workflowReconcile.d.ts.map +1 -0
- package/dist/shared/workflow/closure/workflowReconcile.js +267 -0
- package/dist/shared/workflow/closure/workflowReconcile.js.map +1 -0
- package/dist/shared/workflow/continuation/continuation.d.ts +109 -0
- package/dist/shared/workflow/continuation/continuation.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/continuation.js +550 -0
- package/dist/shared/workflow/continuation/continuation.js.map +1 -0
- package/dist/shared/workflow/continuation/continuationEventReservations.d.ts +17 -0
- package/dist/shared/workflow/continuation/continuationEventReservations.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/continuationEventReservations.js +88 -0
- package/dist/shared/workflow/continuation/continuationEventReservations.js.map +1 -0
- package/dist/shared/workflow/continuation/continuationFollowUp.d.ts +114 -0
- package/dist/shared/workflow/continuation/continuationFollowUp.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/continuationFollowUp.js +330 -0
- package/dist/shared/workflow/continuation/continuationFollowUp.js.map +1 -0
- package/dist/shared/workflow/continuation/continuationOrchestrator.d.ts +66 -0
- package/dist/shared/workflow/continuation/continuationOrchestrator.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/continuationOrchestrator.js +144 -0
- package/dist/shared/workflow/continuation/continuationOrchestrator.js.map +1 -0
- package/dist/shared/workflow/continuation/hookContinuation.d.ts +44 -0
- package/dist/shared/workflow/continuation/hookContinuation.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/hookContinuation.js +85 -0
- package/dist/shared/workflow/continuation/hookContinuation.js.map +1 -0
- package/dist/shared/workflow/continuation/stopPolicy.d.ts +38 -0
- package/dist/shared/workflow/continuation/stopPolicy.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/stopPolicy.js +435 -0
- package/dist/shared/workflow/continuation/stopPolicy.js.map +1 -0
- package/dist/shared/workflow/continuation/stopVerdict.d.ts +31 -0
- package/dist/shared/workflow/continuation/stopVerdict.d.ts.map +1 -0
- package/dist/shared/workflow/continuation/stopVerdict.js +98 -0
- package/dist/shared/workflow/continuation/stopVerdict.js.map +1 -0
- package/dist/shared/workflow/delegation/delegatedSession.d.ts +118 -0
- package/dist/shared/workflow/delegation/delegatedSession.d.ts.map +1 -0
- package/dist/shared/workflow/delegation/delegatedSession.js +496 -0
- package/dist/shared/workflow/delegation/delegatedSession.js.map +1 -0
- package/dist/shared/workflow/delegation/delegationPrompt.d.ts +3 -0
- package/dist/shared/workflow/delegation/delegationPrompt.d.ts.map +1 -0
- package/dist/shared/workflow/delegation/delegationPrompt.js +15 -0
- package/dist/shared/workflow/delegation/delegationPrompt.js.map +1 -0
- package/dist/shared/workflow/delegation/sessionTasks.d.ts +40 -0
- package/dist/shared/workflow/delegation/sessionTasks.d.ts.map +1 -0
- package/dist/shared/workflow/delegation/sessionTasks.js +73 -0
- package/dist/shared/workflow/delegation/sessionTasks.js.map +1 -0
- package/dist/shared/workflow/delegation/subagentRuntime.d.ts +26 -0
- package/dist/shared/workflow/delegation/subagentRuntime.d.ts.map +1 -0
- package/dist/shared/workflow/delegation/subagentRuntime.js +231 -0
- package/dist/shared/workflow/delegation/subagentRuntime.js.map +1 -0
- package/dist/shared/workflow/delivery/adoAuxiliaryRuntime.d.ts +79 -0
- package/dist/shared/workflow/delivery/adoAuxiliaryRuntime.d.ts.map +1 -0
- package/dist/shared/workflow/delivery/adoAuxiliaryRuntime.js +166 -0
- package/dist/shared/workflow/delivery/adoAuxiliaryRuntime.js.map +1 -0
- package/dist/shared/workflow/delivery/finalWave.d.ts +57 -0
- package/dist/shared/workflow/delivery/finalWave.d.ts.map +1 -0
- package/dist/shared/workflow/delivery/finalWave.js +123 -0
- package/dist/shared/workflow/delivery/finalWave.js.map +1 -0
- package/dist/shared/workflow/delivery/lifecyclePushMap.d.ts +68 -0
- package/dist/shared/workflow/delivery/lifecyclePushMap.d.ts.map +1 -0
- package/dist/shared/workflow/delivery/lifecyclePushMap.js +82 -0
- package/dist/shared/workflow/delivery/lifecyclePushMap.js.map +1 -0
- package/dist/shared/workflow/delivery/prSidecar.d.ts +7 -0
- package/dist/shared/workflow/delivery/prSidecar.d.ts.map +1 -0
- package/dist/shared/workflow/delivery/prSidecar.js +4 -0
- package/dist/shared/workflow/delivery/prSidecar.js.map +1 -0
- package/dist/shared/workflow/delivery/reviewStaleness.d.ts +37 -0
- package/dist/shared/workflow/delivery/reviewStaleness.d.ts.map +1 -0
- package/dist/shared/workflow/delivery/reviewStaleness.js +94 -0
- package/dist/shared/workflow/delivery/reviewStaleness.js.map +1 -0
- package/dist/shared/workflow/planning/hyperplan.d.ts +40 -0
- package/dist/shared/workflow/planning/hyperplan.d.ts.map +1 -0
- package/dist/shared/workflow/planning/hyperplan.js +133 -0
- package/dist/shared/workflow/planning/hyperplan.js.map +1 -0
- package/dist/shared/workflow/planning/notepad.d.ts +17 -0
- package/dist/shared/workflow/planning/notepad.d.ts.map +1 -0
- package/dist/shared/workflow/planning/notepad.js +84 -0
- package/dist/shared/workflow/planning/notepad.js.map +1 -0
- package/dist/shared/workflow/planning/planFormat.d.ts +3 -0
- package/dist/shared/workflow/planning/planFormat.d.ts.map +1 -0
- package/dist/shared/workflow/planning/planFormat.js +14 -0
- package/dist/shared/workflow/planning/planFormat.js.map +1 -0
- package/package.json +155 -0
- package/runtime/hooks/_continuation.mjs +2 -0
- package/runtime/hooks/_lib.mjs +2 -0
- package/runtime/hooks/_toolGuards.mjs +2 -0
- package/runtime/hooks/lanes/resolveSkillActivation.mjs +241 -0
- package/runtime/hooks/lanes/user-prompt-submit.mjs +326 -0
- package/runtime/hooks/layout-dir.mjs +2 -0
- package/runtime/hooks/lifecycle/pre-compact.mjs +121 -0
- package/runtime/hooks/lifecycle/session-start.mjs +356 -0
- package/runtime/hooks/lifecycle/stop.mjs +223 -0
- package/runtime/hooks/observability.mjs +2 -0
- package/runtime/hooks/post-tool-use-failure.mjs +5 -0
- package/runtime/hooks/post-tool-use.mjs +4 -0
- package/runtime/hooks/pre-compact.mjs +4 -0
- package/runtime/hooks/pre-tool-use.mjs +4 -0
- package/runtime/hooks/resolveSkillActivation.mjs +4 -0
- package/runtime/hooks/root-dir.mjs +2 -0
- package/runtime/hooks/session-start.mjs +4 -0
- package/runtime/hooks/shared/_continuation.mjs +341 -0
- package/runtime/hooks/shared/_lib.mjs +60 -0
- package/runtime/hooks/shared/_toolGuards.mjs +237 -0
- package/runtime/hooks/shared/config.mjs +143 -0
- package/runtime/hooks/shared/layout-dir.mjs +12 -0
- package/runtime/hooks/shared/observability.mjs +257 -0
- package/runtime/hooks/shared/root-dir.mjs +9 -0
- package/runtime/hooks/stop.mjs +4 -0
- package/runtime/hooks/subagent-tracker.mjs +4 -0
- package/runtime/hooks/subagents/subagent-tracker.mjs +106 -0
- package/runtime/hooks/tools/post-tool-use-failure.mjs +251 -0
- package/runtime/hooks/tools/post-tool-use.mjs +205 -0
- package/runtime/hooks/tools/pre-tool-use.mjs +268 -0
- package/runtime/hooks/user-prompt-submit.mjs +4 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* _toolGuards.mjs — Self-contained runtime tool-guard suite (no build step).
|
|
4
|
+
*
|
|
5
|
+
* This is the SINGLE SOURCE OF TRUTH for the five guard functions at runtime.
|
|
6
|
+
* src/toolGuardHooks.ts re-exports logic by importing from this file so that
|
|
7
|
+
* guard logic lives here once.
|
|
8
|
+
*
|
|
9
|
+
* Twin note: if TS-importing-this-.mjs ever breaks the build, keep src/toolGuardHooks.ts
|
|
10
|
+
* as a deliberate twin and add a comment in both files pointing to each other.
|
|
11
|
+
*
|
|
12
|
+
* Run: node -e "import('./_toolGuards.mjs').then(m => console.log('OK', typeof m.runToolGuards))"
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
|
|
18
|
+
// ── toolOutputTruncator ───────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Truncates large tool output — but ONLY for successful (exitCode === 0) runs.
|
|
22
|
+
* Error output (non-zero exitCode) is always preserved in full so diagnostics
|
|
23
|
+
* are never lost.
|
|
24
|
+
*
|
|
25
|
+
* @param {{ exitCode: number, output: string, maxLength?: number }} input
|
|
26
|
+
* @returns {{ output: string, truncated: boolean }}
|
|
27
|
+
*/
|
|
28
|
+
export function toolOutputTruncator({ exitCode, output, maxLength = 8_000 }) {
|
|
29
|
+
if (exitCode !== 0) {
|
|
30
|
+
return { output, truncated: false };
|
|
31
|
+
}
|
|
32
|
+
if (output.length <= maxLength) {
|
|
33
|
+
return { output, truncated: false };
|
|
34
|
+
}
|
|
35
|
+
const truncated = `${output.slice(0, maxLength)}\n[output truncated: ${output.length - maxLength} chars omitted]`;
|
|
36
|
+
return { output: truncated, truncated: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── jsonErrorRecovery ─────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Attempts to parse a JSON string. On failure, surfaces the ORIGINAL parse
|
|
43
|
+
* error text — never silently returns a default.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} raw
|
|
46
|
+
* @returns {{ attempted: boolean, recovered: boolean, value?: unknown, errorMessage?: string }}
|
|
47
|
+
*/
|
|
48
|
+
export function jsonErrorRecovery(raw) {
|
|
49
|
+
const trimmed = String(raw ?? '').trim();
|
|
50
|
+
if (!trimmed || !/^[\[{]/.test(trimmed)) {
|
|
51
|
+
return { attempted: false, recovered: false };
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const value = JSON.parse(trimmed);
|
|
55
|
+
return { attempted: true, recovered: true, value };
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
58
|
+
return { attempted: true, recovered: false, errorMessage };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── directoryAgentsInjector ───────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Injects AGENTS.md content from the working directory when the file exists.
|
|
66
|
+
* No injection when AGENTS.md is absent.
|
|
67
|
+
*
|
|
68
|
+
* @param {{ workingDirectory: string }} input
|
|
69
|
+
* @returns {{ injected: boolean, content?: string }}
|
|
70
|
+
*/
|
|
71
|
+
export function directoryAgentsInjector({ workingDirectory }) {
|
|
72
|
+
const filePath = join(workingDirectory, 'AGENTS.md');
|
|
73
|
+
if (!existsSync(filePath)) {
|
|
74
|
+
return { injected: false };
|
|
75
|
+
}
|
|
76
|
+
const content = readFileSync(filePath, 'utf8');
|
|
77
|
+
return { injected: true, content };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── directoryReadmeInjector ───────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Injects README.md content from the working directory when the file exists.
|
|
84
|
+
* No injection when README.md is absent.
|
|
85
|
+
*
|
|
86
|
+
* @param {{ workingDirectory: string }} input
|
|
87
|
+
* @returns {{ injected: boolean, content?: string }}
|
|
88
|
+
*/
|
|
89
|
+
export function directoryReadmeInjector({ workingDirectory }) {
|
|
90
|
+
const filePath = join(workingDirectory, 'README.md');
|
|
91
|
+
if (!existsSync(filePath)) {
|
|
92
|
+
return { injected: false };
|
|
93
|
+
}
|
|
94
|
+
const content = readFileSync(filePath, 'utf8');
|
|
95
|
+
return { injected: true, content };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── readImageResizer ──────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
// Threshold: only replace if the base64 payload exceeds this character count.
|
|
101
|
+
const IMAGE_RESIZE_THRESHOLD = 200;
|
|
102
|
+
|
|
103
|
+
// Image data URI prefix pattern
|
|
104
|
+
const IMAGE_DATA_URI_RE = /^data:image\/[a-z]+;base64,/i;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Detects image data URIs in tool output and replaces oversized ones with a
|
|
108
|
+
* compact placeholder. Does NOT alter non-image tool output.
|
|
109
|
+
*
|
|
110
|
+
* @param {{ toolName: string, output: string, maxDimension?: number }} input
|
|
111
|
+
* @returns {{ output: string, resized: boolean }}
|
|
112
|
+
*/
|
|
113
|
+
export function readImageResizer({ output }) {
|
|
114
|
+
if (!IMAGE_DATA_URI_RE.test(output)) {
|
|
115
|
+
return { output, resized: false };
|
|
116
|
+
}
|
|
117
|
+
// Strip the data URI prefix to measure the raw base64 payload length
|
|
118
|
+
const payloadStart = output.indexOf(',') + 1;
|
|
119
|
+
const payloadLength = output.length - payloadStart;
|
|
120
|
+
if (payloadLength <= IMAGE_RESIZE_THRESHOLD) {
|
|
121
|
+
return { output, resized: false };
|
|
122
|
+
}
|
|
123
|
+
// Replace the oversized data URI with a compact placeholder
|
|
124
|
+
const placeholder = `[image data omitted: ${output.length} chars]`;
|
|
125
|
+
return { output: placeholder, resized: true };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── looksLikeJson ─────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Cheap structural check: is this string plausibly a JSON document?
|
|
132
|
+
* Plain tool stdout (ls/find/git output) and empty payloads are NOT JSON and
|
|
133
|
+
* must never be flagged as "parse errors". Only output that is trimmed,
|
|
134
|
+
* non-empty, and starts with an object/array opener is worth attempting.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} raw
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
export function looksLikeJson(raw) {
|
|
140
|
+
if (typeof raw !== 'string') return false;
|
|
141
|
+
const trimmed = raw.trim();
|
|
142
|
+
if (trimmed.length === 0) return false;
|
|
143
|
+
const first = trimmed[0];
|
|
144
|
+
return first === '{' || first === '[';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Dispatcher integration ────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build handler entries for a PostToolUse event context.
|
|
151
|
+
*
|
|
152
|
+
* @param {{ toolName: string, output: string, exitCode: number, workingDirectory?: string }} ctx
|
|
153
|
+
* @returns {Array<{ id: string, run: () => Promise<{ hookSpecificOutput: { additionalContext: string }, exitCode: number, stderr: string }> }>}
|
|
154
|
+
*/
|
|
155
|
+
export function buildToolGuardHandlers(ctx) {
|
|
156
|
+
const workingDirectory = ctx.workingDirectory ?? process.cwd();
|
|
157
|
+
|
|
158
|
+
return [
|
|
159
|
+
{
|
|
160
|
+
id: 'toolOutputTruncator',
|
|
161
|
+
run: async () => {
|
|
162
|
+
const result = toolOutputTruncator({ exitCode: ctx.exitCode, output: ctx.output });
|
|
163
|
+
return {
|
|
164
|
+
hookSpecificOutput: { additionalContext: result.truncated ? '[output truncated]' : '' },
|
|
165
|
+
exitCode: 0,
|
|
166
|
+
stderr: '',
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'jsonErrorRecovery',
|
|
172
|
+
run: async () => {
|
|
173
|
+
// Only attempt recovery on output that is actually JSON-shaped. Plain
|
|
174
|
+
// text / empty tool stdout must never be reported as a parse error.
|
|
175
|
+
if (!looksLikeJson(ctx.output)) {
|
|
176
|
+
return { hookSpecificOutput: { additionalContext: '' }, exitCode: 0, stderr: '' };
|
|
177
|
+
}
|
|
178
|
+
const result = jsonErrorRecovery(ctx.output);
|
|
179
|
+
return {
|
|
180
|
+
hookSpecificOutput: {
|
|
181
|
+
additionalContext: !result.recovered && result.errorMessage
|
|
182
|
+
? `[json parse error: ${result.errorMessage}]`
|
|
183
|
+
: '',
|
|
184
|
+
},
|
|
185
|
+
exitCode: 0,
|
|
186
|
+
stderr: '',
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
// NOTE: directoryAgentsInjector / directoryReadmeInjector are intentionally
|
|
191
|
+
// NOT in the PostToolUse chain. AGENTS.md / README.md are loaded once at
|
|
192
|
+
// SessionStart; re-injecting the full file after every tool call floods the
|
|
193
|
+
// context window. The standalone functions remain exported for SessionStart
|
|
194
|
+
// / on-demand consumers.
|
|
195
|
+
{
|
|
196
|
+
id: 'readImageResizer',
|
|
197
|
+
run: async () => {
|
|
198
|
+
const result = readImageResizer({ toolName: ctx.toolName, output: ctx.output });
|
|
199
|
+
return {
|
|
200
|
+
hookSpecificOutput: { additionalContext: result.resized ? '[image resized for context]' : '' },
|
|
201
|
+
exitCode: 0,
|
|
202
|
+
stderr: '',
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Run all guard handlers in order, aggregate their additionalContext strings.
|
|
211
|
+
*
|
|
212
|
+
* @param {{ toolName: string, output: string, exitCode: number, workingDirectory?: string }} ctx
|
|
213
|
+
* @returns {Promise<{ hookSpecificOutput: { additionalContext: string } }>}
|
|
214
|
+
*/
|
|
215
|
+
export async function runToolGuards(ctx) {
|
|
216
|
+
const handlers = buildToolGuardHandlers(ctx);
|
|
217
|
+
const parts = [];
|
|
218
|
+
|
|
219
|
+
for (const handler of handlers) {
|
|
220
|
+
try {
|
|
221
|
+
const result = await handler.run();
|
|
222
|
+
if (result.exitCode !== 0) {
|
|
223
|
+
parts.push(`[hook error exit=${result.exitCode}: ${result.stderr.slice(0, 120)}]`);
|
|
224
|
+
} else if (result.hookSpecificOutput.additionalContext) {
|
|
225
|
+
parts.push(result.hookSpecificOutput.additionalContext);
|
|
226
|
+
}
|
|
227
|
+
} catch (err) {
|
|
228
|
+
parts.push(`[hook error: ${err instanceof Error ? err.message : String(err)}]`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
hookSpecificOutput: {
|
|
234
|
+
additionalContext: parts.join('\n'),
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import * as TOML from "@iarna/toml";
|
|
5
|
+
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function deepMerge(base, overlay) {
|
|
11
|
+
const out = { ...base };
|
|
12
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
13
|
+
const existing = out[key];
|
|
14
|
+
if (isRecord(existing) && isRecord(value)) {
|
|
15
|
+
out[key] = deepMerge(existing, value);
|
|
16
|
+
} else {
|
|
17
|
+
out[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readTomlIfExists(path) {
|
|
24
|
+
if (!existsSync(path)) return null;
|
|
25
|
+
try {
|
|
26
|
+
return TOML.parse(readFileSync(path, "utf8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function configFileName() {
|
|
33
|
+
return process.env.NODE_ENV === "production" ? "config.prd.toml" : "config.toml";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function defaultWatchdogDir() {
|
|
37
|
+
return join(homedir(), ".enact", "watchdog");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeWatchdogDir(resolvedPath) {
|
|
41
|
+
return resolvedPath.endsWith(".ndjson") ? dirname(resolvedPath) : resolvedPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findNearestAncestorWithFile(startDir, fileName) {
|
|
45
|
+
let current = resolve(startDir);
|
|
46
|
+
while (true) {
|
|
47
|
+
const candidate = join(current, fileName);
|
|
48
|
+
if (existsSync(candidate)) return candidate;
|
|
49
|
+
const parent = dirname(current);
|
|
50
|
+
if (parent === current) break;
|
|
51
|
+
current = parent;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function findWorkspaceConfigPath(startDir = process.cwd()) {
|
|
57
|
+
return findNearestAncestorWithFile(startDir, configFileName());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function findGitRoot(startDir = process.cwd()) {
|
|
61
|
+
const start = resolve(startDir);
|
|
62
|
+
const tmpRoot = resolve(tmpdir());
|
|
63
|
+
let cur = start;
|
|
64
|
+
while (true) {
|
|
65
|
+
const candidate = join(cur, ".git");
|
|
66
|
+
if (existsSync(candidate)) return cur;
|
|
67
|
+
const parent = dirname(cur);
|
|
68
|
+
if (parent === cur) break;
|
|
69
|
+
if (parent === tmpRoot && cur !== tmpRoot) break;
|
|
70
|
+
cur = parent;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resolveWorkspaceRoot(startDir = process.cwd()) {
|
|
76
|
+
const workspaceConfigPath = findWorkspaceConfigPath(startDir);
|
|
77
|
+
if (workspaceConfigPath) return dirname(workspaceConfigPath);
|
|
78
|
+
return findGitRoot(startDir) ?? resolve(startDir);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function loadConfigContext(startDir = process.cwd()) {
|
|
82
|
+
const globalConfigDir = join(homedir(), ".enact", "operator");
|
|
83
|
+
const globalConfigPath = join(globalConfigDir, configFileName());
|
|
84
|
+
const workspaceConfigPath = findWorkspaceConfigPath(startDir);
|
|
85
|
+
return {
|
|
86
|
+
globalConfigDir,
|
|
87
|
+
workspaceConfigPath,
|
|
88
|
+
workspaceConfigDir: workspaceConfigPath ? dirname(workspaceConfigPath) : null,
|
|
89
|
+
globalConfig: readTomlIfExists(globalConfigPath),
|
|
90
|
+
workspaceConfig: workspaceConfigPath ? readTomlIfExists(workspaceConfigPath) : null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function expandPathValue(raw, sourceDir) {
|
|
95
|
+
const trimmed = raw.trim();
|
|
96
|
+
if (trimmed === "~") return homedir();
|
|
97
|
+
if (trimmed.startsWith("~/")) return join(homedir(), trimmed.slice(2));
|
|
98
|
+
if (isAbsolute(trimmed)) return trimmed;
|
|
99
|
+
return resolve(sourceDir, trimmed);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveDeclaredString(workspaceValue, globalValue, ctx) {
|
|
103
|
+
if (typeof workspaceValue === "string" && workspaceValue.trim() && ctx.workspaceConfigDir) {
|
|
104
|
+
return expandPathValue(workspaceValue, ctx.workspaceConfigDir);
|
|
105
|
+
}
|
|
106
|
+
if (typeof globalValue === "string" && globalValue.trim()) {
|
|
107
|
+
return expandPathValue(globalValue, ctx.globalConfigDir);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function readMergedOperatorConfig(startDir = process.cwd()) {
|
|
113
|
+
const ctx = loadConfigContext(startDir);
|
|
114
|
+
const parts = [ctx.globalConfig, ctx.workspaceConfig].filter(Boolean);
|
|
115
|
+
if (parts.length === 0) return null;
|
|
116
|
+
let merged = {};
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
merged = deepMerge(merged, part);
|
|
119
|
+
}
|
|
120
|
+
return merged;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function resolveWatchdogDir(startDir = process.cwd()) {
|
|
124
|
+
const ctx = loadConfigContext(startDir);
|
|
125
|
+
const resolved = resolveDeclaredString(
|
|
126
|
+
ctx.workspaceConfig?.agent?.watchdog_dir,
|
|
127
|
+
ctx.globalConfig?.agent?.watchdog_dir,
|
|
128
|
+
ctx,
|
|
129
|
+
);
|
|
130
|
+
if (resolved) return normalizeWatchdogDir(resolved);
|
|
131
|
+
return defaultWatchdogDir();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function readWorkspaceStorage(root = resolveWorkspaceRoot()) {
|
|
135
|
+
const workspaceConfigPath = findWorkspaceConfigPath(root);
|
|
136
|
+
if (!workspaceConfigPath) return join(root, ".enact", "operator");
|
|
137
|
+
const workspaceConfig = readTomlIfExists(workspaceConfigPath);
|
|
138
|
+
const storage = workspaceConfig?.operator?.storage;
|
|
139
|
+
if (typeof storage !== "string" || !storage.trim()) {
|
|
140
|
+
return join(root, ".enact", "operator");
|
|
141
|
+
}
|
|
142
|
+
return expandPathValue(storage, dirname(workspaceConfigPath));
|
|
143
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { rootDir } from "./root-dir.mjs";
|
|
3
|
+
import { readWorkspaceStorage } from "./config.mjs";
|
|
4
|
+
|
|
5
|
+
/** Canonical operator layout root for hook scripts (declared by [operator].storage). */
|
|
6
|
+
export function layoutDir(root = rootDir()) {
|
|
7
|
+
return readWorkspaceStorage(root);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function layoutPath(root, ...segments) {
|
|
11
|
+
return join(layoutDir(root), ...segments);
|
|
12
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { readMergedOperatorConfig, resolveWatchdogDir } from "./config.mjs";
|
|
5
|
+
|
|
6
|
+
function readWorkspaceConfig(startDir = process.cwd()) {
|
|
7
|
+
return readMergedOperatorConfig(startDir);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function operatorFlag(name, startDir = process.cwd()) {
|
|
11
|
+
const config = readWorkspaceConfig(startDir);
|
|
12
|
+
const value = config?.operator?.[name];
|
|
13
|
+
return typeof value === "boolean" ? value : false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveSessionId(payload) {
|
|
17
|
+
return typeof payload?.session_id === "string" && payload.session_id.trim()
|
|
18
|
+
? payload.session_id
|
|
19
|
+
: "unknown";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveRunId(payload) {
|
|
23
|
+
if (typeof payload?.transcript_id === "string" && payload.transcript_id.trim()) {
|
|
24
|
+
return payload.transcript_id;
|
|
25
|
+
}
|
|
26
|
+
return resolveSessionId(payload);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function requiredFieldWarnings(event) {
|
|
30
|
+
const required = ["schema_version", "source", "event_type", "session_id", "run_id", "ts"];
|
|
31
|
+
if (!operatorFlag("observability_debug", event?.payload?.cwd ?? process.cwd())) return [];
|
|
32
|
+
return required.filter((key) => !event[key]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fallbackEmitEvent(event, startDir = process.cwd()) {
|
|
36
|
+
for (const missing of requiredFieldWarnings(event)) {
|
|
37
|
+
console.error(`[observability] warn: missing field ${missing}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const config = readWorkspaceConfig(startDir);
|
|
41
|
+
const watchdogDir = resolveWatchdogDir(startDir);
|
|
42
|
+
mkdirSync(watchdogDir, { recursive: true });
|
|
43
|
+
const ndjsonPath = join(watchdogDir, "operator-events.ndjson");
|
|
44
|
+
appendFileSync(ndjsonPath, `${JSON.stringify(event)}\n`, "utf8");
|
|
45
|
+
|
|
46
|
+
const iggyUrl = config?.watchdog?.iggy_http_url;
|
|
47
|
+
if (typeof iggyUrl !== "string" || !iggyUrl.trim()) {
|
|
48
|
+
throw new Error("Missing [watchdog].iggy_http_url in merged operator config");
|
|
49
|
+
}
|
|
50
|
+
const body = {
|
|
51
|
+
partitioning: { kind: "balanced", value: "" },
|
|
52
|
+
messages: [
|
|
53
|
+
{
|
|
54
|
+
id: 0,
|
|
55
|
+
payload: Buffer.from(JSON.stringify(event)).toString("base64"),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const authHeader = await resolveIggyAuthHeader(iggyUrl, startDir);
|
|
62
|
+
const response = await fetch(`${iggyUrl}/streams/enact-telemetry/topics/worker_telemetry/messages`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
Authorization: authHeader,
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify(body),
|
|
69
|
+
signal: AbortSignal.timeout(500),
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(`Iggy publish failed with ${response.status}`);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
if (message.includes("ECONNREFUSED") || message.includes("fetch failed")) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.error(`[observability] ${message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function resolveIggyAuthHeader(baseUrl, startDir = process.cwd()) {
|
|
84
|
+
const config = readWorkspaceConfig(startDir);
|
|
85
|
+
const rawToken = process.env.IGGY_HTTP_ACCESS_TOKEN ?? process.env.IGGY_ACCESS_TOKEN;
|
|
86
|
+
if (typeof rawToken === "string" && rawToken.trim()) {
|
|
87
|
+
return `Bearer ${rawToken.trim()}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const username = typeof config?.watchdog?.iggy_username === "string" && config.watchdog.iggy_username.trim()
|
|
91
|
+
? config.watchdog.iggy_username
|
|
92
|
+
: "iggy";
|
|
93
|
+
const password = typeof config?.watchdog?.iggy_password === "string" && config.watchdog.iggy_password.trim()
|
|
94
|
+
? config.watchdog.iggy_password
|
|
95
|
+
: "iggy";
|
|
96
|
+
const response = await fetch(`${baseUrl}/users/login`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({ username, password }),
|
|
100
|
+
signal: AbortSignal.timeout(500),
|
|
101
|
+
});
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
throw new Error(`Iggy login failed with ${response.status}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const payload = await response.json();
|
|
107
|
+
const token = payload?.access_token?.token;
|
|
108
|
+
if (!token) {
|
|
109
|
+
throw new Error("Iggy login response missing access token");
|
|
110
|
+
}
|
|
111
|
+
return `Bearer ${token}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function loadEmitEvent() {
|
|
115
|
+
const repoRoot = join(dirname(new URL(import.meta.url).pathname), "..", "..", "..");
|
|
116
|
+
try {
|
|
117
|
+
const mod = await import(join(repoRoot, "dist", "shared", "observability", "emit.js"));
|
|
118
|
+
if (typeof mod.emitEvent === "function") {
|
|
119
|
+
return mod.emitEvent;
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Fall through to inline fallback emitter when dist is absent in tests/dev.
|
|
123
|
+
}
|
|
124
|
+
return fallbackEmitEvent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function emitOperatorEvent(event) {
|
|
128
|
+
const emitEvent = await loadEmitEvent();
|
|
129
|
+
await emitEvent(event, event?.payload?.cwd ?? process.cwd());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function emitHookFired({
|
|
133
|
+
payload,
|
|
134
|
+
hookEvent,
|
|
135
|
+
outcome,
|
|
136
|
+
latencyMs,
|
|
137
|
+
toolName,
|
|
138
|
+
preset = "enact-operator",
|
|
139
|
+
extraPayload = {},
|
|
140
|
+
}) {
|
|
141
|
+
await emitOperatorEvent({
|
|
142
|
+
schema_version: "enact.raw.v1",
|
|
143
|
+
source: "enact-operator",
|
|
144
|
+
event_type: "hook.fired",
|
|
145
|
+
session_id: resolveSessionId(payload),
|
|
146
|
+
run_id: resolveRunId(payload),
|
|
147
|
+
ts: new Date().toISOString(),
|
|
148
|
+
payload: {
|
|
149
|
+
hook_event: hookEvent,
|
|
150
|
+
preset,
|
|
151
|
+
outcome,
|
|
152
|
+
latency_ms: latencyMs,
|
|
153
|
+
...(toolName ? { tool_name: toolName } : {}),
|
|
154
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
155
|
+
...extraPayload,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function emitHookFailed({
|
|
161
|
+
payload,
|
|
162
|
+
hookEvent,
|
|
163
|
+
error,
|
|
164
|
+
latencyMs,
|
|
165
|
+
toolName,
|
|
166
|
+
preset = "enact-operator",
|
|
167
|
+
extraPayload = {},
|
|
168
|
+
}) {
|
|
169
|
+
await emitOperatorEvent({
|
|
170
|
+
schema_version: "enact.raw.v1",
|
|
171
|
+
source: "enact-operator",
|
|
172
|
+
event_type: "hook.failed",
|
|
173
|
+
session_id: resolveSessionId(payload),
|
|
174
|
+
run_id: resolveRunId(payload),
|
|
175
|
+
ts: new Date().toISOString(),
|
|
176
|
+
payload: {
|
|
177
|
+
hook_event: hookEvent,
|
|
178
|
+
preset,
|
|
179
|
+
tool_name: toolName ?? null,
|
|
180
|
+
cwd: payload?.cwd ?? process.cwd(),
|
|
181
|
+
error_message: error instanceof Error ? error.message : String(error),
|
|
182
|
+
latency_ms: latencyMs,
|
|
183
|
+
...extraPayload,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function registerHookFailureTrap({
|
|
189
|
+
payload,
|
|
190
|
+
hookEvent,
|
|
191
|
+
startedAt,
|
|
192
|
+
toolName,
|
|
193
|
+
preset = "enact-operator",
|
|
194
|
+
extraPayload = {},
|
|
195
|
+
}) {
|
|
196
|
+
let completed = false;
|
|
197
|
+
|
|
198
|
+
const emitFailureAndExit = async (reason) => {
|
|
199
|
+
if (completed) return;
|
|
200
|
+
completed = true;
|
|
201
|
+
try {
|
|
202
|
+
console.error(reason instanceof Error ? (reason.stack ?? reason.message) : String(reason));
|
|
203
|
+
await emitHookFailed({
|
|
204
|
+
payload,
|
|
205
|
+
hookEvent,
|
|
206
|
+
error: reason,
|
|
207
|
+
latencyMs: Date.now() - startedAt,
|
|
208
|
+
toolName,
|
|
209
|
+
preset,
|
|
210
|
+
extraPayload,
|
|
211
|
+
});
|
|
212
|
+
} finally {
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const uncaughtHandler = (error) => {
|
|
218
|
+
void emitFailureAndExit(error);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const rejectionHandler = (reason) => {
|
|
222
|
+
void emitFailureAndExit(reason);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
process.once("uncaughtException", uncaughtHandler);
|
|
226
|
+
process.once("unhandledRejection", rejectionHandler);
|
|
227
|
+
|
|
228
|
+
return () => {
|
|
229
|
+
completed = true;
|
|
230
|
+
process.removeListener("uncaughtException", uncaughtHandler);
|
|
231
|
+
process.removeListener("unhandledRejection", rejectionHandler);
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function emitStopVerdict({
|
|
236
|
+
payload,
|
|
237
|
+
verdict,
|
|
238
|
+
blockers,
|
|
239
|
+
stopHookActive,
|
|
240
|
+
latencyMs,
|
|
241
|
+
}) {
|
|
242
|
+
await emitOperatorEvent({
|
|
243
|
+
schema_version: "enact.raw.v1",
|
|
244
|
+
source: "enact-operator",
|
|
245
|
+
event_type: "stop.verdict",
|
|
246
|
+
session_id: resolveSessionId(payload),
|
|
247
|
+
run_id: resolveRunId(payload),
|
|
248
|
+
ts: new Date().toISOString(),
|
|
249
|
+
payload: {
|
|
250
|
+
'openinference.span.kind': 'CHAIN',
|
|
251
|
+
'enact.verdict': verdict,
|
|
252
|
+
'enact.blockers': blockers,
|
|
253
|
+
'enact.stop_hook_active': stopHookActive,
|
|
254
|
+
'enact.duration_ms': latencyMs,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|