@agimon-ai/doompi-team 0.0.1-alpha.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/LICENSE +25 -0
- package/README.md +46 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs +1 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +1 -0
- package/dist/agents/discovery.cjs +2 -0
- package/dist/agents/discovery.cjs.map +1 -0
- package/dist/agents/discovery.mjs +2 -0
- package/dist/agents/discovery.mjs.map +1 -0
- package/dist/agents/frontmatter.cjs +10 -0
- package/dist/agents/frontmatter.cjs.map +1 -0
- package/dist/agents/frontmatter.mjs +10 -0
- package/dist/agents/frontmatter.mjs.map +1 -0
- package/dist/agents/loader.cjs +2 -0
- package/dist/agents/loader.cjs.map +1 -0
- package/dist/agents/loader.mjs +2 -0
- package/dist/agents/loader.mjs.map +1 -0
- package/dist/agents/memory.cjs +3 -0
- package/dist/agents/memory.cjs.map +1 -0
- package/dist/agents/memory.mjs +3 -0
- package/dist/agents/memory.mjs.map +1 -0
- package/dist/agents/projectRoot.cjs +2 -0
- package/dist/agents/projectRoot.cjs.map +1 -0
- package/dist/agents/projectRoot.mjs +2 -0
- package/dist/agents/projectRoot.mjs.map +1 -0
- package/dist/agents/settings.cjs +2 -0
- package/dist/agents/settings.cjs.map +1 -0
- package/dist/agents/settings.mjs +2 -0
- package/dist/agents/settings.mjs.map +1 -0
- package/dist/agents/skills.cjs +0 -0
- package/dist/agents/skills.cjs.map +1 -0
- package/dist/agents/skills.mjs +5 -0
- package/dist/agents/skills.mjs.map +1 -0
- package/dist/agents/types.d.cts +121 -0
- package/dist/agents/types.d.cts.map +1 -0
- package/dist/agents/types.d.mts +121 -0
- package/dist/agents/types.d.mts.map +1 -0
- package/dist/api/backgroundWork.cjs +1 -0
- package/dist/api/backgroundWork.d.cts +2 -0
- package/dist/api/backgroundWork.d.mts +2 -0
- package/dist/api/backgroundWork.mjs +1 -0
- package/dist/api/capabilityCeiling.cjs +1 -0
- package/dist/api/capabilityCeiling.d.cts +3 -0
- package/dist/api/capabilityCeiling.d.mts +3 -0
- package/dist/api/capabilityCeiling.mjs +2 -0
- package/dist/api/capabilityCeiling.mjs.map +1 -0
- package/dist/api/delegation.cjs +1 -0
- package/dist/api/delegation.d.cts +2 -0
- package/dist/api/delegation.d.mts +2 -0
- package/dist/api/delegation.mjs +1 -0
- package/dist/api/teamSnapshot.cjs +1 -0
- package/dist/api/teamSnapshot.d.cts +2 -0
- package/dist/api/teamSnapshot.d.mts +2 -0
- package/dist/api/teamSnapshot.mjs +2 -0
- package/dist/api/teamSnapshot.mjs.map +1 -0
- package/dist/container/bindOrRebind.cjs +2 -0
- package/dist/container/bindOrRebind.cjs.map +1 -0
- package/dist/container/bindOrRebind.mjs +2 -0
- package/dist/container/bindOrRebind.mjs.map +1 -0
- package/dist/container/globalBridge.cjs +2 -0
- package/dist/container/globalBridge.cjs.map +1 -0
- package/dist/container/globalBridge.d.cts +19 -0
- package/dist/container/globalBridge.d.cts.map +1 -0
- package/dist/container/globalBridge.d.mts +19 -0
- package/dist/container/globalBridge.d.mts.map +1 -0
- package/dist/container/globalBridge.mjs +2 -0
- package/dist/container/globalBridge.mjs.map +1 -0
- package/dist/container/index.cjs +2 -0
- package/dist/container/index.cjs.map +1 -0
- package/dist/container/index.d.cts +38 -0
- package/dist/container/index.d.cts.map +1 -0
- package/dist/container/index.d.mts +38 -0
- package/dist/container/index.d.mts.map +1 -0
- package/dist/container/index.mjs +2 -0
- package/dist/container/index.mjs.map +1 -0
- package/dist/container/modules/agents.cjs +2 -0
- package/dist/container/modules/agents.cjs.map +1 -0
- package/dist/container/modules/agents.d.cts +1 -0
- package/dist/container/modules/agents.d.mts +1 -0
- package/dist/container/modules/agents.mjs +2 -0
- package/dist/container/modules/agents.mjs.map +1 -0
- package/dist/container/modules/core.cjs +2 -0
- package/dist/container/modules/core.cjs.map +1 -0
- package/dist/container/modules/core.d.cts +1 -0
- package/dist/container/modules/core.d.mts +1 -0
- package/dist/container/modules/core.mjs +2 -0
- package/dist/container/modules/core.mjs.map +1 -0
- package/dist/container/modules/extensions.cjs +2 -0
- package/dist/container/modules/extensions.cjs.map +1 -0
- package/dist/container/modules/extensions.mjs +2 -0
- package/dist/container/modules/extensions.mjs.map +1 -0
- package/dist/container/modules/intercom.cjs +2 -0
- package/dist/container/modules/intercom.cjs.map +1 -0
- package/dist/container/modules/intercom.mjs +2 -0
- package/dist/container/modules/intercom.mjs.map +1 -0
- package/dist/container/modules/runs.cjs +2 -0
- package/dist/container/modules/runs.cjs.map +1 -0
- package/dist/container/modules/runs.mjs +2 -0
- package/dist/container/modules/runs.mjs.map +1 -0
- package/dist/container/types.cjs +2 -0
- package/dist/container/types.cjs.map +1 -0
- package/dist/container/types.d.cts +45 -0
- package/dist/container/types.d.cts.map +1 -0
- package/dist/container/types.d.mts +45 -0
- package/dist/container/types.d.mts.map +1 -0
- package/dist/container/types.mjs +2 -0
- package/dist/container/types.mjs.map +1 -0
- package/dist/env.cjs +2 -0
- package/dist/env.cjs.map +1 -0
- package/dist/env.d.cts +65 -0
- package/dist/env.d.cts.map +1 -0
- package/dist/env.d.mts +65 -0
- package/dist/env.d.mts.map +1 -0
- package/dist/env.mjs +2 -0
- package/dist/env.mjs.map +1 -0
- package/dist/extension.cjs +2 -0
- package/dist/extension.cjs.map +1 -0
- package/dist/extension.mjs +2 -0
- package/dist/extension.mjs.map +1 -0
- package/dist/extensions/config.cjs +2 -0
- package/dist/extensions/config.cjs.map +1 -0
- package/dist/extensions/config.mjs +2 -0
- package/dist/extensions/config.mjs.map +1 -0
- package/dist/extensions/delegationBridge.cjs +2 -0
- package/dist/extensions/delegationBridge.cjs.map +1 -0
- package/dist/extensions/delegationBridge.mjs +2 -0
- package/dist/extensions/delegationBridge.mjs.map +1 -0
- package/dist/extensions/doom.cjs +2 -0
- package/dist/extensions/doom.cjs.map +1 -0
- package/dist/extensions/doom.d.cts +8 -0
- package/dist/extensions/doom.d.cts.map +1 -0
- package/dist/extensions/doom.d.mts +8 -0
- package/dist/extensions/doom.d.mts.map +1 -0
- package/dist/extensions/doom.mjs +2 -0
- package/dist/extensions/doom.mjs.map +1 -0
- package/dist/extensions/fablePlanBridge.cjs +2 -0
- package/dist/extensions/fablePlanBridge.cjs.map +1 -0
- package/dist/extensions/fablePlanBridge.mjs +2 -0
- package/dist/extensions/fablePlanBridge.mjs.map +1 -0
- package/dist/extensions/managementActions.cjs +2 -0
- package/dist/extensions/managementActions.cjs.map +1 -0
- package/dist/extensions/managementActions.mjs +2 -0
- package/dist/extensions/managementActions.mjs.map +1 -0
- package/dist/extensions/operationJournal.cjs +2 -0
- package/dist/extensions/operationJournal.cjs.map +1 -0
- package/dist/extensions/operationJournal.mjs +2 -0
- package/dist/extensions/operationJournal.mjs.map +1 -0
- package/dist/extensions/orchestratorPrompt.cjs +35 -0
- package/dist/extensions/orchestratorPrompt.cjs.map +1 -0
- package/dist/extensions/orchestratorPrompt.mjs +35 -0
- package/dist/extensions/orchestratorPrompt.mjs.map +1 -0
- package/dist/extensions/pi.cjs +2 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +2 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/schemas.cjs +1 -0
- package/dist/extensions/schemas.mjs +2 -0
- package/dist/extensions/schemas.mjs.map +1 -0
- package/dist/extensions/spawnPlan.cjs +3 -0
- package/dist/extensions/spawnPlan.cjs.map +1 -0
- package/dist/extensions/spawnPlan.mjs +3 -0
- package/dist/extensions/spawnPlan.mjs.map +1 -0
- package/dist/extensions/standard.cjs +2 -0
- package/dist/extensions/standard.cjs.map +1 -0
- package/dist/extensions/standard.d.cts +8 -0
- package/dist/extensions/standard.d.cts.map +1 -0
- package/dist/extensions/standard.d.mts +8 -0
- package/dist/extensions/standard.d.mts.map +1 -0
- package/dist/extensions/standard.mjs +2 -0
- package/dist/extensions/standard.mjs.map +1 -0
- package/dist/extensions/statusViews.cjs +7 -0
- package/dist/extensions/statusViews.cjs.map +1 -0
- package/dist/extensions/statusViews.mjs +7 -0
- package/dist/extensions/statusViews.mjs.map +1 -0
- package/dist/extensions/subagentPromptRuntime.cjs +18 -0
- package/dist/extensions/subagentPromptRuntime.cjs.map +1 -0
- package/dist/extensions/subagentPromptRuntime.d.cts +58 -0
- package/dist/extensions/subagentPromptRuntime.d.cts.map +1 -0
- package/dist/extensions/subagentPromptRuntime.d.mts +58 -0
- package/dist/extensions/subagentPromptRuntime.d.mts.map +1 -0
- package/dist/extensions/subagentPromptRuntime.mjs +18 -0
- package/dist/extensions/subagentPromptRuntime.mjs.map +1 -0
- package/dist/extensions/subagentPromptRuntimeEntry.cjs +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.cjs.map +1 -0
- package/dist/extensions/subagentPromptRuntimeEntry.d.cts +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.d.mts +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.mjs +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.mjs.map +1 -0
- package/dist/extensions/subagentTool.cjs +7 -0
- package/dist/extensions/subagentTool.cjs.map +1 -0
- package/dist/extensions/subagentTool.mjs +7 -0
- package/dist/extensions/subagentTool.mjs.map +1 -0
- package/dist/extensions/toolDescription.cjs +2 -0
- package/dist/extensions/toolDescription.cjs.map +1 -0
- package/dist/extensions/toolDescription.mjs +2 -0
- package/dist/extensions/toolDescription.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +34 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/intercom/intercomBridge.cjs +2 -0
- package/dist/intercom/intercomBridge.cjs.map +1 -0
- package/dist/intercom/intercomBridge.mjs +2 -0
- package/dist/intercom/intercomBridge.mjs.map +1 -0
- package/dist/intercom/nativeTeamChannel.cjs +5 -0
- package/dist/intercom/nativeTeamChannel.cjs.map +1 -0
- package/dist/intercom/nativeTeamChannel.d.cts +22 -0
- package/dist/intercom/nativeTeamChannel.d.cts.map +1 -0
- package/dist/intercom/nativeTeamChannel.d.mts +22 -0
- package/dist/intercom/nativeTeamChannel.d.mts.map +1 -0
- package/dist/intercom/nativeTeamChannel.mjs +5 -0
- package/dist/intercom/nativeTeamChannel.mjs.map +1 -0
- package/dist/intercom/supervisorControlChannel.cjs +2 -0
- package/dist/intercom/supervisorControlChannel.cjs.map +1 -0
- package/dist/intercom/supervisorControlChannel.mjs +2 -0
- package/dist/intercom/supervisorControlChannel.mjs.map +1 -0
- package/dist/protocol/backgroundWork.cjs +2 -0
- package/dist/protocol/backgroundWork.cjs.map +1 -0
- package/dist/protocol/backgroundWork.d.cts +52 -0
- package/dist/protocol/backgroundWork.d.cts.map +1 -0
- package/dist/protocol/backgroundWork.d.mts +52 -0
- package/dist/protocol/backgroundWork.d.mts.map +1 -0
- package/dist/protocol/backgroundWork.mjs +2 -0
- package/dist/protocol/backgroundWork.mjs.map +1 -0
- package/dist/protocol/capabilityCeiling.cjs +2 -0
- package/dist/protocol/capabilityCeiling.cjs.map +1 -0
- package/dist/protocol/capabilityCeiling.d.cts +51 -0
- package/dist/protocol/capabilityCeiling.d.cts.map +1 -0
- package/dist/protocol/capabilityCeiling.d.mts +51 -0
- package/dist/protocol/capabilityCeiling.d.mts.map +1 -0
- package/dist/protocol/capabilityCeiling.mjs +2 -0
- package/dist/protocol/capabilityCeiling.mjs.map +1 -0
- package/dist/protocol/delegation.cjs +2 -0
- package/dist/protocol/delegation.cjs.map +1 -0
- package/dist/protocol/delegation.d.cts +269 -0
- package/dist/protocol/delegation.d.cts.map +1 -0
- package/dist/protocol/delegation.d.mts +269 -0
- package/dist/protocol/delegation.d.mts.map +1 -0
- package/dist/protocol/delegation.mjs +2 -0
- package/dist/protocol/delegation.mjs.map +1 -0
- package/dist/runs/background/asyncExecution.cjs +2 -0
- package/dist/runs/background/asyncExecution.cjs.map +1 -0
- package/dist/runs/background/asyncExecution.mjs +2 -0
- package/dist/runs/background/asyncExecution.mjs.map +1 -0
- package/dist/runs/background/asyncJobTracker.cjs +2 -0
- package/dist/runs/background/asyncJobTracker.cjs.map +1 -0
- package/dist/runs/background/asyncJobTracker.mjs +2 -0
- package/dist/runs/background/asyncJobTracker.mjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.cjs +2 -0
- package/dist/runs/background/cliRunnerEntry.cjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.d.cts +1 -0
- package/dist/runs/background/cliRunnerEntry.d.mts +1 -0
- package/dist/runs/background/cliRunnerEntry.mjs +2 -0
- package/dist/runs/background/cliRunnerEntry.mjs.map +1 -0
- package/dist/runs/background/completionBatcher.cjs +2 -0
- package/dist/runs/background/completionBatcher.cjs.map +1 -0
- package/dist/runs/background/completionBatcher.mjs +2 -0
- package/dist/runs/background/completionBatcher.mjs.map +1 -0
- package/dist/runs/background/notify.cjs +4 -0
- package/dist/runs/background/notify.cjs.map +1 -0
- package/dist/runs/background/notify.mjs +4 -0
- package/dist/runs/background/notify.mjs.map +1 -0
- package/dist/runs/background/parentWatchdog.cjs +2 -0
- package/dist/runs/background/parentWatchdog.cjs.map +1 -0
- package/dist/runs/background/parentWatchdog.mjs +2 -0
- package/dist/runs/background/parentWatchdog.mjs.map +1 -0
- package/dist/runs/background/processTerminal.cjs +2 -0
- package/dist/runs/background/processTerminal.cjs.map +1 -0
- package/dist/runs/background/processTerminal.mjs +2 -0
- package/dist/runs/background/processTerminal.mjs.map +1 -0
- package/dist/runs/background/resultWatcher.cjs +2 -0
- package/dist/runs/background/resultWatcher.cjs.map +1 -0
- package/dist/runs/background/resultWatcher.mjs +2 -0
- package/dist/runs/background/resultWatcher.mjs.map +1 -0
- package/dist/runs/background/runIdResolver.cjs +2 -0
- package/dist/runs/background/runIdResolver.cjs.map +1 -0
- package/dist/runs/background/runIdResolver.mjs +2 -0
- package/dist/runs/background/runIdResolver.mjs.map +1 -0
- package/dist/runs/background/runnerBootstrap.cjs +2 -0
- package/dist/runs/background/runnerBootstrap.cjs.map +1 -0
- package/dist/runs/background/runnerBootstrap.mjs +2 -0
- package/dist/runs/background/runnerBootstrap.mjs.map +1 -0
- package/dist/runs/background/runnerExecution.cjs +2 -0
- package/dist/runs/background/runnerExecution.cjs.map +1 -0
- package/dist/runs/background/runnerExecution.mjs +2 -0
- package/dist/runs/background/runnerExecution.mjs.map +1 -0
- package/dist/runs/background/runnerReporting.cjs +3 -0
- package/dist/runs/background/runnerReporting.cjs.map +1 -0
- package/dist/runs/background/runnerReporting.mjs +3 -0
- package/dist/runs/background/runnerReporting.mjs.map +1 -0
- package/dist/runs/background/runnerTeamMembership.cjs +2 -0
- package/dist/runs/background/runnerTeamMembership.cjs.map +1 -0
- package/dist/runs/background/runnerTeamMembership.mjs +2 -0
- package/dist/runs/background/runnerTeamMembership.mjs.map +1 -0
- package/dist/runs/background/spawnHandshake.cjs +2 -0
- package/dist/runs/background/spawnHandshake.cjs.map +1 -0
- package/dist/runs/background/spawnHandshake.mjs +2 -0
- package/dist/runs/background/spawnHandshake.mjs.map +1 -0
- package/dist/runs/background/staleRunReconciler.cjs +2 -0
- package/dist/runs/background/staleRunReconciler.cjs.map +1 -0
- package/dist/runs/background/staleRunReconciler.mjs +2 -0
- package/dist/runs/background/staleRunReconciler.mjs.map +1 -0
- package/dist/runs/background/statusReader.cjs +2 -0
- package/dist/runs/background/statusReader.cjs.map +1 -0
- package/dist/runs/background/statusReader.mjs +2 -0
- package/dist/runs/background/statusReader.mjs.map +1 -0
- package/dist/runs/background/statusWriter.cjs +2 -0
- package/dist/runs/background/statusWriter.cjs.map +1 -0
- package/dist/runs/background/statusWriter.mjs +2 -0
- package/dist/runs/background/statusWriter.mjs.map +1 -0
- package/dist/runs/background/subagentWait.cjs +2 -0
- package/dist/runs/background/subagentWait.cjs.map +1 -0
- package/dist/runs/background/subagentWait.mjs +2 -0
- package/dist/runs/background/subagentWait.mjs.map +1 -0
- package/dist/runs/background/terminalPersistence.cjs +2 -0
- package/dist/runs/background/terminalPersistence.cjs.map +1 -0
- package/dist/runs/background/terminalPersistence.mjs +2 -0
- package/dist/runs/background/terminalPersistence.mjs.map +1 -0
- package/dist/runs/registry/processGroup.cjs +2 -0
- package/dist/runs/registry/processGroup.cjs.map +1 -0
- package/dist/runs/registry/processGroup.mjs +2 -0
- package/dist/runs/registry/processGroup.mjs.map +1 -0
- package/dist/runs/registry/runRegistry.cjs +2 -0
- package/dist/runs/registry/runRegistry.cjs.map +1 -0
- package/dist/runs/registry/runRegistry.mjs +2 -0
- package/dist/runs/registry/runRegistry.mjs.map +1 -0
- package/dist/runs/registry/sessionLifecycle.cjs +2 -0
- package/dist/runs/registry/sessionLifecycle.cjs.map +1 -0
- package/dist/runs/registry/sessionLifecycle.mjs +2 -0
- package/dist/runs/registry/sessionLifecycle.mjs.map +1 -0
- package/dist/runs/registry/suspendedRuns.cjs +4 -0
- package/dist/runs/registry/suspendedRuns.cjs.map +1 -0
- package/dist/runs/registry/suspendedRuns.mjs +4 -0
- package/dist/runs/registry/suspendedRuns.mjs.map +1 -0
- package/dist/runs/sdkRunnerEntry.cjs +2 -0
- package/dist/runs/sdkRunnerEntry.cjs.map +1 -0
- package/dist/runs/sdkRunnerEntry.d.cts +1 -0
- package/dist/runs/sdkRunnerEntry.d.mts +1 -0
- package/dist/runs/sdkRunnerEntry.mjs +2 -0
- package/dist/runs/sdkRunnerEntry.mjs.map +1 -0
- package/dist/runs/shared/claudeFableProfile.cjs +4 -0
- package/dist/runs/shared/claudeFableProfile.cjs.map +1 -0
- package/dist/runs/shared/claudeFableProfile.mjs +4 -0
- package/dist/runs/shared/claudeFableProfile.mjs.map +1 -0
- package/dist/runs/shared/depthGuard.cjs +2 -0
- package/dist/runs/shared/depthGuard.cjs.map +1 -0
- package/dist/runs/shared/depthGuard.mjs +2 -0
- package/dist/runs/shared/depthGuard.mjs.map +1 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs +2 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs.map +1 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs +2 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs.map +1 -0
- package/dist/runs/shared/modelFallback.cjs +2 -0
- package/dist/runs/shared/modelFallback.cjs.map +1 -0
- package/dist/runs/shared/modelFallback.mjs +2 -0
- package/dist/runs/shared/modelFallback.mjs.map +1 -0
- package/dist/runs/shared/modelScope.cjs +2 -0
- package/dist/runs/shared/modelScope.cjs.map +1 -0
- package/dist/runs/shared/modelScope.mjs +2 -0
- package/dist/runs/shared/modelScope.mjs.map +1 -0
- package/dist/runs/shared/nestedPath.cjs +2 -0
- package/dist/runs/shared/nestedPath.cjs.map +1 -0
- package/dist/runs/shared/nestedPath.mjs +2 -0
- package/dist/runs/shared/nestedPath.mjs.map +1 -0
- package/dist/runs/shared/piArgs.cjs +2 -0
- package/dist/runs/shared/piArgs.cjs.map +1 -0
- package/dist/runs/shared/piArgs.mjs +2 -0
- package/dist/runs/shared/piArgs.mjs.map +1 -0
- package/dist/runs/shared/piSpawn.cjs +2 -0
- package/dist/runs/shared/piSpawn.cjs.map +1 -0
- package/dist/runs/shared/piSpawn.mjs +2 -0
- package/dist/runs/shared/piSpawn.mjs.map +1 -0
- package/dist/runs/shared/runWithConcurrency.cjs +2 -0
- package/dist/runs/shared/runWithConcurrency.cjs.map +1 -0
- package/dist/runs/shared/runWithConcurrency.mjs +2 -0
- package/dist/runs/shared/runWithConcurrency.mjs.map +1 -0
- package/dist/runs/shared/runtimeRegistry.cjs +2 -0
- package/dist/runs/shared/runtimeRegistry.cjs.map +1 -0
- package/dist/runs/shared/runtimeRegistry.mjs +2 -0
- package/dist/runs/shared/runtimeRegistry.mjs.map +1 -0
- package/dist/runs/shared/structuredOutput.cjs +2 -0
- package/dist/runs/shared/structuredOutput.cjs.map +1 -0
- package/dist/runs/shared/structuredOutput.mjs +2 -0
- package/dist/runs/shared/structuredOutput.mjs.map +1 -0
- package/dist/runs/shared/toolAvailability.cjs +2 -0
- package/dist/runs/shared/toolAvailability.cjs.map +1 -0
- package/dist/runs/shared/toolAvailability.mjs +2 -0
- package/dist/runs/shared/toolAvailability.mjs.map +1 -0
- package/dist/runs/shared/toolBudget.cjs +2 -0
- package/dist/runs/shared/toolBudget.cjs.map +1 -0
- package/dist/runs/shared/toolBudget.mjs +2 -0
- package/dist/runs/shared/toolBudget.mjs.map +1 -0
- package/dist/shared/artifacts.cjs +2 -0
- package/dist/shared/artifacts.cjs.map +1 -0
- package/dist/shared/artifacts.mjs +2 -0
- package/dist/shared/artifacts.mjs.map +1 -0
- package/dist/shared/atomicJson.cjs +2 -0
- package/dist/shared/atomicJson.cjs.map +1 -0
- package/dist/shared/atomicJson.mjs +2 -0
- package/dist/shared/atomicJson.mjs.map +1 -0
- package/dist/shared/boundedKeySet.cjs +2 -0
- package/dist/shared/boundedKeySet.cjs.map +1 -0
- package/dist/shared/boundedKeySet.mjs +2 -0
- package/dist/shared/boundedKeySet.mjs.map +1 -0
- package/dist/shared/childTranscript.cjs +5 -0
- package/dist/shared/childTranscript.cjs.map +1 -0
- package/dist/shared/childTranscript.mjs +5 -0
- package/dist/shared/childTranscript.mjs.map +1 -0
- package/dist/shared/configDir.cjs +2 -0
- package/dist/shared/configDir.cjs.map +1 -0
- package/dist/shared/configDir.mjs +2 -0
- package/dist/shared/configDir.mjs.map +1 -0
- package/dist/shared/errors.cjs +2 -0
- package/dist/shared/errors.cjs.map +1 -0
- package/dist/shared/errors.mjs +2 -0
- package/dist/shared/errors.mjs.map +1 -0
- package/dist/shared/fileSystemRetry.cjs +2 -0
- package/dist/shared/fileSystemRetry.cjs.map +1 -0
- package/dist/shared/fileSystemRetry.mjs +2 -0
- package/dist/shared/fileSystemRetry.mjs.map +1 -0
- package/dist/shared/formatters.cjs +2 -0
- package/dist/shared/formatters.cjs.map +1 -0
- package/dist/shared/formatters.mjs +2 -0
- package/dist/shared/formatters.mjs.map +1 -0
- package/dist/shared/lruCache.cjs +2 -0
- package/dist/shared/lruCache.cjs.map +1 -0
- package/dist/shared/lruCache.mjs +2 -0
- package/dist/shared/lruCache.mjs.map +1 -0
- package/dist/shared/modelInfo.cjs +2 -0
- package/dist/shared/modelInfo.cjs.map +1 -0
- package/dist/shared/modelInfo.mjs +2 -0
- package/dist/shared/modelInfo.mjs.map +1 -0
- package/dist/shared/modelResolution.cjs +2 -0
- package/dist/shared/modelResolution.cjs.map +1 -0
- package/dist/shared/modelResolution.mjs +2 -0
- package/dist/shared/modelResolution.mjs.map +1 -0
- package/dist/shared/paths.cjs +2 -0
- package/dist/shared/paths.cjs.map +1 -0
- package/dist/shared/paths.mjs +2 -0
- package/dist/shared/paths.mjs.map +1 -0
- package/dist/shared/pollScheduler.cjs +2 -0
- package/dist/shared/pollScheduler.cjs.map +1 -0
- package/dist/shared/pollScheduler.mjs +2 -0
- package/dist/shared/pollScheduler.mjs.map +1 -0
- package/dist/shared/scopeOwner.cjs +2 -0
- package/dist/shared/scopeOwner.cjs.map +1 -0
- package/dist/shared/scopeOwner.mjs +2 -0
- package/dist/shared/scopeOwner.mjs.map +1 -0
- package/dist/shared/versioned.cjs +2 -0
- package/dist/shared/versioned.cjs.map +1 -0
- package/dist/shared/versioned.mjs +2 -0
- package/dist/shared/versioned.mjs.map +1 -0
- package/dist/slash/chainExpression.cjs +2 -0
- package/dist/slash/chainExpression.cjs.map +1 -0
- package/dist/slash/chainExpression.mjs +2 -0
- package/dist/slash/chainExpression.mjs.map +1 -0
- package/dist/slash/doctor.cjs +5 -0
- package/dist/slash/doctor.cjs.map +1 -0
- package/dist/slash/doctor.mjs +5 -0
- package/dist/slash/doctor.mjs.map +1 -0
- package/dist/slash/slashCommands.cjs +4 -0
- package/dist/slash/slashCommands.cjs.map +1 -0
- package/dist/slash/slashCommands.mjs +4 -0
- package/dist/slash/slashCommands.mjs.map +1 -0
- package/dist/slash/spawnRequestMapping.cjs +2 -0
- package/dist/slash/spawnRequestMapping.cjs.map +1 -0
- package/dist/slash/spawnRequestMapping.mjs +2 -0
- package/dist/slash/spawnRequestMapping.mjs.map +1 -0
- package/dist/slash/subagentLaunch.cjs +2 -0
- package/dist/slash/subagentLaunch.cjs.map +1 -0
- package/dist/slash/subagentLaunch.mjs +2 -0
- package/dist/slash/subagentLaunch.mjs.map +1 -0
- package/dist/tui/agentCatalog.cjs +2 -0
- package/dist/tui/agentCatalog.cjs.map +1 -0
- package/dist/tui/agentCatalog.mjs +2 -0
- package/dist/tui/agentCatalog.mjs.map +1 -0
- package/dist/tui/agentResourceProjection.cjs +2 -0
- package/dist/tui/agentResourceProjection.cjs.map +1 -0
- package/dist/tui/agentResourceProjection.mjs +2 -0
- package/dist/tui/agentResourceProjection.mjs.map +1 -0
- package/dist/tui/completionNotice.cjs +5 -0
- package/dist/tui/completionNotice.cjs.map +1 -0
- package/dist/tui/completionNotice.mjs +5 -0
- package/dist/tui/completionNotice.mjs.map +1 -0
- package/dist/tui/fleet.cjs +3 -0
- package/dist/tui/fleet.cjs.map +1 -0
- package/dist/tui/fleet.mjs +3 -0
- package/dist/tui/fleet.mjs.map +1 -0
- package/dist/tui/fleetAgentView.cjs +2 -0
- package/dist/tui/fleetAgentView.cjs.map +1 -0
- package/dist/tui/fleetAgentView.mjs +2 -0
- package/dist/tui/fleetAgentView.mjs.map +1 -0
- package/dist/tui/fleetStatus.cjs +2 -0
- package/dist/tui/fleetStatus.cjs.map +1 -0
- package/dist/tui/fleetStatus.mjs +2 -0
- package/dist/tui/fleetStatus.mjs.map +1 -0
- package/dist/tui/fleetTranscript.cjs +5 -0
- package/dist/tui/fleetTranscript.cjs.map +1 -0
- package/dist/tui/fleetTranscript.mjs +5 -0
- package/dist/tui/fleetTranscript.mjs.map +1 -0
- package/dist/tui/register.cjs +2 -0
- package/dist/tui/register.cjs.map +1 -0
- package/dist/tui/register.mjs +2 -0
- package/dist/tui/register.mjs.map +1 -0
- package/dist/types.d.cts +59 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +59 -0
- package/dist/types.d.mts.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeTeamChannel.mjs","names":[],"sources":["../../src/intercom/nativeTeamChannel.ts"],"sourcesContent":["/**\n * Peer messaging between the named members of one root session's team.\n *\n * A team is every agent process descended from a single root session. Each\n * member owns an inbox directory; peers drop message files into it and the\n * member's poll loop delivers them into its own agent turn. `ask` blocks the\n * sender until the recipient writes a reply file.\n *\n * DESIGN PATTERNS:\n * - A member's name IS its id. Names are what an operator and a model both use\n * to address a teammate, so a second opaque identifier would only ever be a\n * translation layer that can drift\n * - Deliver, then mark seen. Delivery is the side effect that must not be lost,\n * so nothing is recorded as handled until the transport has accepted it\n * - Every record on disk carries a version through `parseVersioned`, so a\n * process running an older build skips a record it cannot read rather than\n * misreading it\n * - One writer per file. A member writes only its own member record, its own\n * heartbeat and other members' inboxes. No file is read-modify-written by two\n * processes, so no update can be lost\n *\n * PERFORMANCE:\n * - The poll skips a file by name before opening it, so an inbox that has\n * accumulated files that are not for us costs one readdir per poll rather than\n * one parse per file per poll\n * - Dead members' directories are swept on a timer, which is what stops that\n * readdir from growing for the life of a long session\n *\n * SEVEN FAILURE MODES THIS MODULE IS BUILT AROUND. Each is called out again at\n * the code that handles it, because each one is a plausible \"simplification\"\n * that would silently reintroduce message loss:\n * 1. Marking a message seen before delivering it loses it permanently when the\n * transport throws, because a seen message is never retried.\n * 2. Ordering by wall-clock alone reorders two messages from the same sender\n * written in the same millisecond.\n * 3. Leaving a file that is not addressed to us unseen re-parses it on every\n * poll, forever.\n * 4. Putting the sender's token in the envelope hands every recipient the value\n * it needs to impersonate that sender.\n * 5. An unbounded seen set grows for the life of the session.\n * 6. Read-modify-writing a shared heartbeat file lets a stale writer resurrect\n * a dead member.\n * 7. Never collecting dead members leaves their inboxes and records on disk.\n *\n * AVOID:\n * - Any module-level mutable state; the runtime owns everything stateful so a\n * second session in the same process cannot inherit the first one's inbox\n * - Deleting a file that failed validation because of a team mismatch; that file\n * belongs to another session and is not ours to destroy\n * - Treating an atomic write as a lock. It prevents a torn read, nothing else\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport { injectable } from 'inversify';\nimport type { TSchema } from 'typebox';\nimport {\n SUBAGENT_TEAM_ID_ENV,\n SUBAGENT_TEAM_MAIN_MEMBER_ENV,\n SUBAGENT_TEAM_MEMBER_ID_ENV,\n SUBAGENT_TEAM_MEMBER_TOKEN_ENV,\n SUBAGENT_TEAM_ROOT_SESSION_ENV,\n} from '../env.ts';\nimport { writePrivateAtomicJson } from '../shared/atomicJson.ts';\nimport { BoundedKeySet } from '../shared/boundedKeySet.ts';\nimport { LruCache } from '../shared/lruCache.ts';\nimport { requireCurrentSessionScope, scopeTeamDir, tryCurrentSessionScope } from '../shared/paths.ts';\nimport { parseVersioned } from '../shared/versioned.ts';\nimport { DoomTeamExpectedError, invalidRequest } from '../shared/errors.ts';\n\n/** Wire literal. The model calls the tool by this name, so it is not renamed. */\nexport const NATIVE_TEAM_TOOL_NAME = 'intercom';\n\n/** Wire literals. Present in every envelope so a foreign file is recognisable. */\nconst TEAM_MESSAGE_TYPE = 'subagent.team.message';\nconst TEAM_REPLY_TYPE = 'subagent.team.reply';\n/** Custom message types the host renders. */\nconst TEAM_MESSAGE_CUSTOM_TYPE = 'intercom_message';\nconst TEAM_UNDELIVERABLE_CUSTOM_TYPE = 'intercom_undeliverable';\n\nconst MEMBERS_DIR = 'members';\nconst HEARTBEATS_DIR = 'heartbeats';\nconst INBOXES_DIR = 'inboxes';\nconst REPLIES_DIR = 'replies';\nconst RECEIPTS_DIR = 'receipts';\nconst TEAM_FILE = 'team.json';\nconst JSON_FILE_SUFFIX = '.json';\n\n/** Reserved for the root session. A child that asks for it is refused. */\nconst MAIN_MEMBER_ID = 'main';\n/** Substituted when a derived name would land on the reserved root name. */\nconst RESERVED_NAME_FALLBACK = 'main-agent';\nconst FALLBACK_MEMBER_NAME = 'agent';\nconst MAX_MEMBER_NAME_LENGTH = 48;\nconst FANOUT_SUFFIX_SEPARATOR = '-';\n/** Fanout suffixes are 1-based, so the first child of a fanout reads as `-1`. */\nconst FIRST_FANOUT_ORDINAL = 1;\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst MAX_MESSAGE_BYTES = 64 * 1024;\nconst MAX_TASK_ID_BYTES = 256;\nconst MAX_TASK_SUBJECT_BYTES = 4 * 1024;\nconst MESSAGE_TTL_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_ASK_TIMEOUT_MS = 10 * 60 * 1000;\nconst HEARTBEAT_INTERVAL_MS = 5_000;\n/**\n * Exported: `task-board.ts` gates a claim guard on the same staleness window,\n * and must never drift from it. Two definitions of \"how stale is too stale\"\n * would let a claim guard and this module's own liveness check disagree about\n * who is alive.\n */\nexport const HEARTBEAT_STALE_MS = 30_000;\nconst CHANNEL_POLL_MS = 250;\nconst REPLY_POLL_MS = 250;\n\n/**\n * How many times one message may fail delivery before it is given up on.\n *\n * The retry exists for a transient host refusal, and the bound exists because a\n * message the host will never accept would otherwise be re-delivered on every\n * poll for the life of the session, starving every message behind it.\n */\nconst MAX_DELIVERY_ATTEMPTS = 5;\nconst FIRST_DELIVERY_ATTEMPT = 1;\n/** Comfortably above the number of inbox files that can be live at once. */\nconst SEEN_FILE_CAPACITY = 1024;\nconst DELIVERY_ATTEMPT_CAPACITY = 256;\nconst UNDELIVERABLE_HISTORY_LIMIT = 32;\n\nconst TEAM_GC_INTERVAL_MS = 60_000;\n/**\n * How long past its liveness deadline a member's state is kept.\n *\n * Deliberately much larger than `HEARTBEAT_STALE_MS`: a member that is merely\n * slow to start, or stopped at a breakpoint, must not have its inbox deleted\n * out from under a peer that is mid-`ask`.\n */\nconst TEAM_GC_GRACE_MS = 5 * 60 * 1000;\n\nconst TEAM_ROOT_VERSION = 1;\nconst MEMBER_RECORD_VERSION = 1;\nconst HEARTBEAT_RECORD_VERSION = 1;\n/**\n * Version 2 of the envelope. Version 1 carried the sender's token and had no\n * sequence number; both are load-bearing changes, so an old envelope is\n * rejected rather than read with the missing fields defaulted.\n */\nconst ENVELOPE_VERSION = 2;\n\nconst TEAM_ACTIONS = ['members', 'send', 'ask', 'reply', 'pending'] as const;\nconst SAFE_ID_PATTERN = /^[A-Za-z0-9._-]{1,256}$/;\nconst UNSAFE_NAME_CHARS = /[^a-z0-9._-]+/g;\nconst SURROUNDING_SEPARATORS = /^[-.]+|[-.]+$/g;\nconst TEAM_ID_HASH_LENGTH = 32;\nconst TOKEN_BYTES = 32;\nconst HASH_ALGORITHM = 'sha256';\nconst RECEIPT_VERSION = 1;\nconst RECEIPT_DELIVERED = 'delivered';\nconst RECEIPT_FAILED = 'failed';\nconst TEAM_ID_PREFIX = 'session-';\n\nexport type TeamMemberRole = 'main' | 'subagent';\n\nexport interface TeamRootContext {\n version: typeof TEAM_ROOT_VERSION;\n teamId: string;\n rootSessionId: string;\n mainMemberId: string;\n}\n\nexport interface TeamTaskMetadata {\n id: string;\n subject: string;\n}\n\nexport interface TeamMemberContext extends TeamRootContext {\n memberId: string;\n token: string;\n role: TeamMemberRole;\n agent?: string;\n runId?: string;\n childIndex?: number;\n parentMemberId?: string;\n task?: TeamTaskMetadata;\n}\n\ninterface TeamRecord extends TeamRootContext {\n createdAt: number;\n updatedAt: number;\n}\n\n/**\n * The member's durable identity.\n *\n * FIX 6: no `heartbeatAt`. Liveness moved to a per-member file so that writing a\n * heartbeat never rewrites identity, and so a writer can only ever affect its\n * own liveness.\n */\nexport interface TeamMemberRecord extends Omit<TeamMemberContext, 'token' | 'version'> {\n version: typeof MEMBER_RECORD_VERSION;\n tokenHash: string;\n pid: number;\n active: boolean;\n joinedAt: number;\n leftAt?: number;\n}\n\nexport interface NativeTeamMemberSnapshot {\n name: string;\n role: TeamMemberRole;\n agent?: string;\n runId?: string;\n task?: TeamTaskMetadata;\n}\n\nexport interface NativeTeamSnapshot {\n members: NativeTeamMemberSnapshot[];\n}\n\n/**\n * One member's liveness, in a file only that member writes.\n *\n * FIX 6: the predecessor kept `heartbeatAt` inside the member record and\n * refreshed it with a read-modify-write. Two writers racing that read could\n * republish a stale snapshot, which resurrected a member that had already\n * marked itself inactive. `tokenHash` is repeated here so a heartbeat left\n * behind by a previous owner of the same name does not count for the new one.\n */\nexport interface TeamHeartbeatRecord {\n version: typeof HEARTBEAT_RECORD_VERSION;\n memberId: string;\n tokenHash: string;\n pid: number;\n heartbeatAt: number;\n}\n\ntype TeamMessageKind = 'send' | 'ask';\n\n/**\n * FIX 4: `senderTokenHash`, never the token itself.\n *\n * The predecessor embedded the sender's bearer token, so every recipient came\n * out of a single message holding the value that authenticates that sender to\n * `assertCurrentMember`, to `dispose`, and to any future capability keyed on it.\n * A hash is enough for the recipient's actual question, which is only \"does this\n * match the tokenHash in the sender's member record\". What it deliberately does\n * NOT claim is unforgeability against a process that can already write into this\n * team directory: everyone in the team runs as the same OS user, so the\n * boundary here is the token, not the directory.\n */\ninterface TeamMessageEnvelope {\n type: typeof TEAM_MESSAGE_TYPE;\n version: typeof ENVELOPE_VERSION;\n id: string;\n teamId: string;\n kind: TeamMessageKind;\n fromMemberId: string;\n toMemberId: string;\n senderTokenHash: string;\n /**\n * FIX 2: monotonic per sender, assigned in the order the sender queued.\n *\n * `createdAt` has millisecond resolution, so two messages from one sender in\n * the same millisecond are indistinguishable by time and were delivered in\n * whatever order the directory happened to list them.\n */\n seq: number;\n message: string;\n createdAt: number;\n expiresAt: number;\n}\n\ninterface TeamReplyEnvelope {\n type: typeof TEAM_REPLY_TYPE;\n version: typeof ENVELOPE_VERSION;\n requestId: string;\n teamId: string;\n fromMemberId: string;\n toMemberId: string;\n senderTokenHash: string;\n message: string;\n createdAt: number;\n}\n\n/** A message the transport refused often enough that it was given up on. */\nexport interface UndeliverableTeamMessage {\n messageId: string;\n fromMemberId: string;\n kind: TeamMessageKind;\n attempts: number;\n reason: string;\n at: number;\n}\n\ninterface TeamToolParams {\n action: (typeof TEAM_ACTIONS)[number];\n to?: string;\n message?: string;\n requestId?: string;\n timeoutMs?: number;\n}\n\n/**\n * Hand-written JSON Schema rather than a TypeBox builder, so the wire shape is\n * readable in one place. The cast is the reason `execute` narrows its params\n * with a real guard instead of trusting the static type.\n */\nconst TeamToolParamsSchema = {\n oneOf: [\n ...(['members', 'pending'] as const).map((action) => ({\n type: 'object',\n properties: { action: { const: action } },\n required: ['action'],\n additionalProperties: false,\n })),\n {\n type: 'object',\n properties: { action: { const: 'send' }, to: { type: 'string' }, message: { type: 'string' } },\n required: ['action', 'to', 'message'],\n additionalProperties: false,\n },\n {\n type: 'object',\n properties: {\n action: { const: 'ask' },\n to: { type: 'string' },\n message: { type: 'string' },\n timeoutMs: { type: 'integer', minimum: 1 },\n },\n required: ['action', 'to', 'message'],\n additionalProperties: false,\n },\n {\n type: 'object',\n properties: { action: { const: 'reply' }, requestId: { type: 'string' }, message: { type: 'string' } },\n required: ['action', 'requestId', 'message'],\n additionalProperties: false,\n },\n ],\n} as unknown as TSchema;\n\n// ---------------------------------------------------------------------------\n// Narrowing helpers. Everything read off disk or out of a tool call arrives as\n// `unknown` and is narrowed by one of these; nothing is asserted into shape.\n// ---------------------------------------------------------------------------\n\nfunction isSafeId(value: unknown): value is string {\n return typeof value === 'string' && SAFE_ID_PATTERN.test(value);\n}\n\nfunction isInteger(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value);\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isOptionalInteger(value: unknown): value is number | undefined {\n return value === undefined || isInteger(value);\n}\n\nfunction isMemberRole(value: unknown): value is TeamMemberRole {\n return value === 'main' || value === 'subagent';\n}\n\nfunction isMessageKind(value: unknown): value is TeamMessageKind {\n return value === 'send' || value === 'ask';\n}\n\nfunction withinBytes(value: string, limit: number): boolean {\n return Buffer.byteLength(value, 'utf-8') <= limit;\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction textEnv(name: string): string | undefined {\n const value = process.env[name]?.trim();\n return value ? value : undefined;\n}\n\nfunction hash(value: string): string {\n return createHash(HASH_ALGORITHM).update(value).digest('hex');\n}\n\nfunction newToken(): string {\n return randomBytes(TOKEN_BYTES).toString('base64url');\n}\n\nfunction teamIdForSession(rootSessionId: string): string {\n return `${TEAM_ID_PREFIX}${hash(rootSessionId).slice(0, TEAM_ID_HASH_LENGTH)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Member names. A name is the id, so naming rules are identity rules.\n// ---------------------------------------------------------------------------\n\n/**\n * Reduce any label to a usable member name.\n *\n * Lowercase because a name is typed by hand and by a model, and two members\n * differing only in case would be two identities that read as one.\n */\nexport function normalizeTeamMemberName(raw: string): string {\n const normalized = raw\n .trim()\n .toLowerCase()\n .replace(UNSAFE_NAME_CHARS, FANOUT_SUFFIX_SEPARATOR)\n .replace(SURROUNDING_SEPARATORS, '')\n .slice(0, MAX_MEMBER_NAME_LENGTH)\n .replace(SURROUNDING_SEPARATORS, '');\n return normalized || FALLBACK_MEMBER_NAME;\n}\n\n/** Append `-1`..`-N` while keeping the whole name inside the length budget. */\nfunction withFanoutOrdinal(base: string, fanoutIndex: number): string {\n const suffix = `${FANOUT_SUFFIX_SEPARATOR}${fanoutIndex + FIRST_FANOUT_ORDINAL}`;\n const room = MAX_MEMBER_NAME_LENGTH - suffix.length;\n const trimmed = base.slice(0, Math.max(room, 0)).replace(SURROUNDING_SEPARATORS, '');\n return `${trimmed || FALLBACK_MEMBER_NAME}${suffix}`;\n}\n\n/**\n * `main` belongs to the root session and to nothing else.\n *\n * An explicit request for it is an error, because silently handing the caller a\n * different name would leave it addressing a member that does not exist. A name\n * that merely derived to `main` is substituted instead, since the caller never\n * asked for that name and cannot act on the refusal.\n */\nfunction guardReservedName(name: string, explicit: boolean): string {\n if (name !== MAIN_MEMBER_ID) return name;\n if (explicit) throw new Error(`Native team member name '${MAIN_MEMBER_ID}' is reserved for the root session.`);\n return RESERVED_NAME_FALLBACK;\n}\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n/**\n * This team's directory, under the owning session's scope.\n *\n * A team is every agent descended from one root session, so the team tree\n * belongs inside that session's scope rather than in a parallel\n * `team-channels/` root shared by every concurrent session. The `teamId`\n * segment is kept even though a scope holds exactly one team: it is the value\n * every record on disk is validated against (see `readNativeTeamRootFromEnvironment`),\n * so keeping path and record agreeing costs one segment and removes a way for\n * a stale tree to be read as the current one.\n */\nexport function teamDir(teamId: string): string {\n if (!isSafeId(teamId)) throw new Error('Invalid native team id.');\n return path.join(scopeTeamDir(requireCurrentSessionScope()), teamId);\n}\n\nfunction memberPath(root: TeamRootContext, memberId: string): string {\n if (!isSafeId(memberId)) throw new Error('Invalid native team member id.');\n return path.join(teamDir(root.teamId), MEMBERS_DIR, `${memberId}${JSON_FILE_SUFFIX}`);\n}\n\nfunction heartbeatPath(root: TeamRootContext, memberId: string): string {\n if (!isSafeId(memberId)) throw new Error('Invalid native team member id.');\n return path.join(teamDir(root.teamId), HEARTBEATS_DIR, `${memberId}${JSON_FILE_SUFFIX}`);\n}\n\nfunction inboxDir(root: TeamRootContext, memberId: string): string {\n if (!isSafeId(memberId)) throw new Error('Invalid native team member id.');\n return path.join(teamDir(root.teamId), INBOXES_DIR, memberId);\n}\n\nfunction messageFileName(messageId: string): string {\n return `${messageId}${JSON_FILE_SUFFIX}`;\n}\n\nfunction messagePath(root: TeamRootContext, memberId: string, messageId: string): string {\n if (!isSafeId(messageId)) throw new Error('Invalid native team message id.');\n return path.join(inboxDir(root, memberId), messageFileName(messageId));\n}\n\nfunction replyPath(root: TeamRootContext, requestId: string): string {\n if (!isSafeId(requestId)) throw new Error('Invalid native team request id.');\n return path.join(teamDir(root.teamId), REPLIES_DIR, `${requestId}${JSON_FILE_SUFFIX}`);\n}\n\nfunction receiptPath(root: TeamRootContext, senderId: string, messageId: string): string {\n if (!isSafeId(senderId) || !isSafeId(messageId)) throw new Error('Invalid intercom receipt identity.');\n return path.join(teamDir(root.teamId), RECEIPTS_DIR, senderId, `${messageId}${JSON_FILE_SUFFIX}`);\n}\n\nfunction operationMessageId(memberId: string, operationId: string): string {\n return createHash(HASH_ALGORITHM).update(`${memberId}:${operationId}`).digest('hex');\n}\n\nfunction ensureTeamDirectories(root: TeamRootContext): void {\n const rootDir = teamDir(root.teamId);\n for (const child of [MEMBERS_DIR, HEARTBEATS_DIR, INBOXES_DIR, REPLIES_DIR, RECEIPTS_DIR]) {\n fs.mkdirSync(path.join(rootDir, child), { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n }\n}\n\nfunction readJsonRecord(file: string): Record<string, unknown> | undefined {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(file, 'utf-8'));\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : undefined;\n } catch {\n // A missing, half-written or corrupt file is a normal state for a directory\n // several processes write to. The caller treats undefined as \"not readable\n // yet\" and either retries on the next poll or removes the file.\n return undefined;\n }\n}\n\nfunction removeFile(file: string): boolean {\n try {\n fs.rmSync(file, { force: true });\n return true;\n } catch (error) {\n // Already gone counts as removed. Anything else is reported so a caller\n // that must not re-deliver can tell the difference.\n return (error as NodeJS.ErrnoException).code === 'ENOENT';\n }\n}\n\nfunction removeDirectory(dir: string): void {\n try {\n fs.rmSync(dir, { recursive: true, force: true });\n } catch {\n // Best effort. A directory that resists removal is retried by the next\n // sweep; failing the sweep would stop the rest of the cleanup.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Record parsing\n// ---------------------------------------------------------------------------\n\nfunction parseTeamRecord(value: Record<string, unknown> | undefined): TeamRecord | undefined {\n const versioned = parseVersioned(value, [TEAM_ROOT_VERSION]);\n if (!versioned.ok || !value) return undefined;\n if (\n !isSafeId(value.teamId) ||\n !isNonEmptyString(value.rootSessionId) ||\n value.mainMemberId !== MAIN_MEMBER_ID ||\n !isFiniteNumber(value.createdAt) ||\n !isFiniteNumber(value.updatedAt)\n )\n return undefined;\n return {\n version: TEAM_ROOT_VERSION,\n teamId: value.teamId,\n rootSessionId: value.rootSessionId,\n mainMemberId: MAIN_MEMBER_ID,\n createdAt: value.createdAt,\n updatedAt: value.updatedAt,\n };\n}\n\nfunction parseTaskMetadata(value: unknown): TeamTaskMetadata | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;\n const task: Record<string, unknown> = value as Record<string, unknown>;\n if (!isNonEmptyString(task.id) || !isNonEmptyString(task.subject)) return undefined;\n if (!withinBytes(task.id, MAX_TASK_ID_BYTES) || !withinBytes(task.subject, MAX_TASK_SUBJECT_BYTES)) return undefined;\n return { id: task.id, subject: task.subject };\n}\n\nfunction parseMemberRecord(value: Record<string, unknown> | undefined): TeamMemberRecord | undefined {\n const versioned = parseVersioned(value, [MEMBER_RECORD_VERSION]);\n if (!versioned.ok || !value) return undefined;\n if (\n !isSafeId(value.teamId) ||\n !isNonEmptyString(value.rootSessionId) ||\n !isNonEmptyString(value.mainMemberId) ||\n !isSafeId(value.memberId) ||\n !isMemberRole(value.role) ||\n !isNonEmptyString(value.tokenHash) ||\n !isInteger(value.pid) ||\n typeof value.active !== 'boolean' ||\n !isFiniteNumber(value.joinedAt) ||\n !isOptionalString(value.agent) ||\n !isOptionalString(value.runId) ||\n !isOptionalInteger(value.childIndex) ||\n !isOptionalString(value.parentMemberId)\n )\n return undefined;\n const task = value.task === undefined ? undefined : parseTaskMetadata(value.task);\n if (value.task !== undefined && !task) return undefined;\n return {\n version: MEMBER_RECORD_VERSION,\n teamId: value.teamId,\n rootSessionId: value.rootSessionId,\n mainMemberId: value.mainMemberId,\n memberId: value.memberId,\n role: value.role,\n tokenHash: value.tokenHash,\n pid: value.pid,\n active: value.active,\n joinedAt: value.joinedAt,\n ...(isFiniteNumber(value.leftAt) ? { leftAt: value.leftAt } : {}),\n ...(value.agent !== undefined ? { agent: value.agent } : {}),\n ...(value.runId !== undefined ? { runId: value.runId } : {}),\n ...(value.childIndex !== undefined ? { childIndex: value.childIndex } : {}),\n ...(value.parentMemberId !== undefined ? { parentMemberId: value.parentMemberId } : {}),\n ...(task ? { task } : {}),\n };\n}\n\nfunction parseHeartbeatRecord(value: Record<string, unknown> | undefined): TeamHeartbeatRecord | undefined {\n const versioned = parseVersioned(value, [HEARTBEAT_RECORD_VERSION]);\n if (!versioned.ok || !value) return undefined;\n if (\n !isSafeId(value.memberId) ||\n !isNonEmptyString(value.tokenHash) ||\n !isInteger(value.pid) ||\n !isFiniteNumber(value.heartbeatAt)\n )\n return undefined;\n return {\n version: HEARTBEAT_RECORD_VERSION,\n memberId: value.memberId,\n tokenHash: value.tokenHash,\n pid: value.pid,\n heartbeatAt: value.heartbeatAt,\n };\n}\n\nfunction parseMessageEnvelope(value: Record<string, unknown> | undefined): TeamMessageEnvelope | undefined {\n const versioned = parseVersioned(value, [ENVELOPE_VERSION]);\n if (!versioned.ok || !value) return undefined;\n if (\n value.type !== TEAM_MESSAGE_TYPE ||\n !isSafeId(value.id) ||\n !isSafeId(value.teamId) ||\n !isMessageKind(value.kind) ||\n !isSafeId(value.fromMemberId) ||\n !isSafeId(value.toMemberId) ||\n !isNonEmptyString(value.senderTokenHash) ||\n !isInteger(value.seq) ||\n typeof value.message !== 'string' ||\n !withinBytes(value.message, MAX_MESSAGE_BYTES) ||\n !isFiniteNumber(value.createdAt) ||\n !isFiniteNumber(value.expiresAt)\n )\n return undefined;\n return {\n type: TEAM_MESSAGE_TYPE,\n version: ENVELOPE_VERSION,\n id: value.id,\n teamId: value.teamId,\n kind: value.kind,\n fromMemberId: value.fromMemberId,\n toMemberId: value.toMemberId,\n senderTokenHash: value.senderTokenHash,\n seq: value.seq,\n message: value.message,\n createdAt: value.createdAt,\n expiresAt: value.expiresAt,\n };\n}\n\nfunction parseReplyEnvelope(value: Record<string, unknown> | undefined): TeamReplyEnvelope | undefined {\n const versioned = parseVersioned(value, [ENVELOPE_VERSION]);\n if (!versioned.ok || !value) return undefined;\n if (\n value.type !== TEAM_REPLY_TYPE ||\n !isSafeId(value.requestId) ||\n !isSafeId(value.teamId) ||\n !isSafeId(value.fromMemberId) ||\n !isSafeId(value.toMemberId) ||\n !isNonEmptyString(value.senderTokenHash) ||\n typeof value.message !== 'string' ||\n !withinBytes(value.message, MAX_MESSAGE_BYTES) ||\n !isFiniteNumber(value.createdAt)\n )\n return undefined;\n return {\n type: TEAM_REPLY_TYPE,\n version: ENVELOPE_VERSION,\n requestId: value.requestId,\n teamId: value.teamId,\n fromMemberId: value.fromMemberId,\n toMemberId: value.toMemberId,\n senderTokenHash: value.senderTokenHash,\n message: value.message,\n createdAt: value.createdAt,\n };\n}\n\nfunction parseTeamToolParams(raw: unknown): TeamToolParams {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n throw invalidRequest('Intercom params must be an object.', 'Use one documented intercom action shape.');\n }\n const params: Record<string, unknown> = raw as Record<string, unknown>;\n const action = TEAM_ACTIONS.find((candidate) => candidate === params.action);\n if (!action) {\n throw new DoomTeamExpectedError(\n 'unsupported_operation',\n `Unsupported intercom action: ${String(params.action)}.`,\n false,\n 'Use members, send, ask, pending, or reply.',\n );\n }\n if (!isOptionalString(params.to) || !isOptionalString(params.message) || !isOptionalString(params.requestId))\n throw invalidRequest(\n 'Intercom to, message and requestId values must be strings.',\n 'Correct the action fields and retry.',\n );\n if (params.timeoutMs !== undefined && (!isInteger(params.timeoutMs) || params.timeoutMs < 1)) {\n throw invalidRequest('Intercom timeoutMs must be a positive integer.', 'Correct timeoutMs and retry.');\n }\n const allowed = new Set(\n action === 'members' || action === 'pending'\n ? ['action']\n : action === 'reply'\n ? ['action', 'requestId', 'message']\n : action === 'ask'\n ? ['action', 'to', 'message', 'timeoutMs']\n : ['action', 'to', 'message'],\n );\n const unknown = Object.keys(params).filter((field) => !allowed.has(field));\n if (unknown.length) {\n throw invalidRequest(\n `Intercom action '${action}' does not accept: ${unknown.join(', ')}.`,\n 'Remove unknown fields and retry.',\n );\n }\n return {\n action,\n ...(params.to !== undefined ? { to: params.to } : {}),\n ...(params.message !== undefined ? { message: params.message } : {}),\n ...(params.requestId !== undefined ? { requestId: params.requestId } : {}),\n ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Liveness\n//\n// `readMember`, `readHeartbeat` and `processAlive` are exported: they are the\n// only readers of the member and heartbeat record formats this module owns,\n// and `task-board.ts` reads through them for its own claim guard rather than\n// keeping a second parser for the same on-disk shape. What is NOT exported is\n// `memberIsActive`: it encodes THIS module's own liveness policy (fall back to\n// `leftAt ?? joinedAt` when there is no heartbeat yet, because a member that\n// has only just registered deserves a grace period before it is judged dead).\n// `task-board.ts` needs a different policy for the same readers -- a missing\n// heartbeat reads as dead, because it only ever gates a claim or a prune, and\n// \"unknown means dead\" is the safe direction there. Sharing the predicate\n// itself instead of the readers would force one of those two correct policies\n// to lose.\n// ---------------------------------------------------------------------------\n\nexport function readMember(root: TeamRootContext, memberId: string): TeamMemberRecord | undefined {\n return parseMemberRecord(readJsonRecord(memberPath(root, memberId)));\n}\n\nexport function readHeartbeat(root: TeamRootContext, member: TeamMemberRecord): TeamHeartbeatRecord | undefined {\n const record = parseHeartbeatRecord(readJsonRecord(heartbeatPath(root, member.memberId)));\n // A heartbeat left by a previous owner of this name proves nothing about the\n // member that holds it now, which is exactly the resurrection FIX 6 targets.\n if (!record || record.memberId !== member.memberId || record.tokenHash !== member.tokenHash) return undefined;\n return record;\n}\n\nexport function processAlive(pid: number): boolean {\n if (pid === process.pid) return true;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n // EPERM means the pid exists and belongs to someone else, which still\n // answers the question being asked.\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** Last moment this member proved it was running, from its own writes only. */\nfunction lastProofOfLife(root: TeamRootContext, member: TeamMemberRecord): number {\n return readHeartbeat(root, member)?.heartbeatAt ?? member.leftAt ?? member.joinedAt;\n}\n\nfunction memberIsActive(root: TeamRootContext, member: TeamMemberRecord, now: number): boolean {\n return member.active && processAlive(member.pid) && now - lastProofOfLife(root, member) <= HEARTBEAT_STALE_MS;\n}\n\nfunction writeMemberRecord(record: TeamMemberRecord): void {\n fs.mkdirSync(inboxDir(record, record.memberId), { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n writePrivateAtomicJson(memberPath(record, record.memberId), record);\n}\n\nfunction writeHeartbeat(root: TeamRootContext, member: TeamMemberRecord, at: number): void {\n const record: TeamHeartbeatRecord = {\n version: HEARTBEAT_RECORD_VERSION,\n memberId: member.memberId,\n tokenHash: member.tokenHash,\n pid: member.pid,\n heartbeatAt: at,\n };\n writePrivateAtomicJson(heartbeatPath(root, member.memberId), record);\n}\n\n/** Exported: `task-board.ts` walks this to find every member's task claim without duplicating the readdir + parse. */\nexport function listMemberRecords(root: TeamRootContext): TeamMemberRecord[] {\n const dir = path.join(teamDir(root.teamId), MEMBERS_DIR);\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n // No members directory means no team on disk yet, which is not an error for\n // a caller that is only asking who is around.\n return [];\n }\n return entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(JSON_FILE_SUFFIX))\n .map((entry) => parseMemberRecord(readJsonRecord(path.join(dir, entry.name))))\n .filter((member): member is TeamMemberRecord => Boolean(member))\n .filter((member) => member.teamId === root.teamId && member.rootSessionId === root.rootSessionId);\n}\n\nfunction listActiveMembers(root: TeamRootContext, now: number): TeamMemberRecord[] {\n return listMemberRecords(root)\n .filter((member) => memberIsActive(root, member, now))\n .sort((left, right) => left.joinedAt - right.joinedAt || left.memberId.localeCompare(right.memberId));\n}\n\nexport function readActiveNativeTeamSnapshot(now = Date.now()): NativeTeamSnapshot | undefined {\n const root = readNativeTeamRootFromEnvironment();\n if (!root) return undefined;\n return { members: listActiveMembers(root, now).map(publicMember) };\n}\n\n/**\n * FIX 7: drop the state of members that are long gone.\n *\n * Without this, every inbox directory and member record a session ever created\n * stays on disk, so `listMemberRecords` and the inbox readdir both grow for the\n * life of the machine's temp directory. The liveness rule is the same one used\n * everywhere else, plus a grace period, so nothing here can collect a member\n * that a peer still considers reachable.\n */\nfunction collectDeadMembers(root: TeamRootContext, selfMemberId: string, now: number): number {\n let collected = 0;\n for (const member of listMemberRecords(root)) {\n if (member.memberId === selfMemberId) continue;\n if (memberIsActive(root, member, now)) continue;\n if (now - lastProofOfLife(root, member) <= HEARTBEAT_STALE_MS + TEAM_GC_GRACE_MS) continue;\n removeDirectory(inboxDir(root, member.memberId));\n removeFile(heartbeatPath(root, member.memberId));\n removeFile(memberPath(root, member.memberId));\n collected += 1;\n }\n return collected;\n}\n\n// ---------------------------------------------------------------------------\n// Team and membership lifecycle\n// ---------------------------------------------------------------------------\n\nexport function ensureNativeTeamRoot(rootSessionId: string): TeamRootContext {\n const normalizedSessionId = rootSessionId.trim();\n if (!normalizedSessionId) throw new Error('A root session id is required for native team communication.');\n const root: TeamRootContext = {\n version: TEAM_ROOT_VERSION,\n teamId: teamIdForSession(normalizedSessionId),\n rootSessionId: normalizedSessionId,\n mainMemberId: MAIN_MEMBER_ID,\n };\n ensureTeamDirectories(root);\n const file = path.join(teamDir(root.teamId), TEAM_FILE);\n const existing = parseTeamRecord(readJsonRecord(file));\n if (existing && existing.rootSessionId !== normalizedSessionId)\n throw new Error('Native team identity collision detected.');\n const now = Date.now();\n const record: TeamRecord = { ...root, createdAt: existing?.createdAt ?? now, updatedAt: now };\n writePrivateAtomicJson(file, record);\n return root;\n}\n\nexport function nativeTeamRootEnvironment(root: TeamRootContext): Record<string, string> {\n return {\n [SUBAGENT_TEAM_ID_ENV]: root.teamId,\n [SUBAGENT_TEAM_ROOT_SESSION_ENV]: root.rootSessionId,\n [SUBAGENT_TEAM_MAIN_MEMBER_ENV]: root.mainMemberId,\n };\n}\n\n/**\n * The environment a child needs to act as this member.\n *\n * The token travels only to the process that IS this member, which is what\n * makes FIX 4 coherent: the secret goes down the spawn path, never sideways in\n * an envelope.\n */\nexport function nativeTeamMemberEnvironment(context: TeamMemberContext): Record<string, string> {\n return {\n ...nativeTeamRootEnvironment(context),\n [SUBAGENT_TEAM_MEMBER_ID_ENV]: context.memberId,\n [SUBAGENT_TEAM_MEMBER_TOKEN_ENV]: context.token,\n };\n}\n\nexport function clearNativeTeamMemberEnvironment(): Record<string, undefined> {\n return {\n [SUBAGENT_TEAM_MEMBER_ID_ENV]: undefined,\n [SUBAGENT_TEAM_MEMBER_TOKEN_ENV]: undefined,\n };\n}\n\nexport function applyNativeTeamRootEnvironment(root: TeamRootContext): void {\n Object.assign(process.env, nativeTeamRootEnvironment(root));\n}\n\nexport function clearNativeTeamRootEnvironment(root?: TeamRootContext): void {\n if (root && process.env[SUBAGENT_TEAM_ID_ENV] !== root.teamId) return;\n delete process.env[SUBAGENT_TEAM_ID_ENV];\n delete process.env[SUBAGENT_TEAM_ROOT_SESSION_ENV];\n delete process.env[SUBAGENT_TEAM_MAIN_MEMBER_ENV];\n}\n\nexport function readNativeTeamRootFromEnvironment(): TeamRootContext | undefined {\n const teamId = textEnv(SUBAGENT_TEAM_ID_ENV);\n const rootSessionId = textEnv(SUBAGENT_TEAM_ROOT_SESSION_ENV);\n const mainMemberId = textEnv(SUBAGENT_TEAM_MAIN_MEMBER_ENV);\n if (!teamId || !isSafeId(teamId) || !rootSessionId || mainMemberId !== MAIN_MEMBER_ID) return undefined;\n if (teamId !== teamIdForSession(rootSessionId)) return undefined;\n const record = parseTeamRecord(readJsonRecord(path.join(teamDir(teamId), TEAM_FILE)));\n if (!record || record.rootSessionId !== rootSessionId || record.teamId !== teamId) return undefined;\n return { version: TEAM_ROOT_VERSION, teamId, rootSessionId, mainMemberId };\n}\n\nfunction validateTaskMetadata(task: TeamTaskMetadata | undefined): TeamTaskMetadata | undefined {\n if (!task) return undefined;\n const id = task.id.trim();\n const subject = task.subject.trim();\n if (!id || !withinBytes(id, MAX_TASK_ID_BYTES))\n throw new Error(`Native team task id must be between 1 and ${MAX_TASK_ID_BYTES} bytes.`);\n if (!subject || !withinBytes(subject, MAX_TASK_SUBJECT_BYTES))\n throw new Error(`Native team task subject must be between 1 and ${MAX_TASK_SUBJECT_BYTES} bytes.`);\n return { id, subject };\n}\n\nexport interface RegisterNativeTeamMemberInput {\n root: TeamRootContext;\n role: TeamMemberRole;\n /** Explicit name. Becomes the member id verbatim once normalized. */\n name?: string;\n agent?: string;\n runId?: string;\n childIndex?: number;\n /** Present when this member is child N of a fanout; drives the `-N` suffix. */\n fanoutIndex?: number;\n parentMemberId?: string;\n task?: TeamTaskMetadata;\n pid?: number;\n}\n\n/**\n * Resolve the member id, which is also the member's name.\n *\n * Precedence is explicit name, then the agent name, because the caller that\n * passed a name meant it, while an agent name is only a sensible default.\n */\nfunction resolveMemberId(input: RegisterNativeTeamMemberInput): string {\n if (input.role === 'main') return input.root.mainMemberId;\n const explicit = input.name !== undefined;\n if (!explicit && !input.agent) throw new Error('An intercom member needs a name or an agent to derive one from.');\n const base = guardReservedName(normalizeTeamMemberName(input.name ?? input.agent ?? FALLBACK_MEMBER_NAME), explicit);\n if (input.runId) {\n const suffix = normalizeTeamMemberName(input.runId).slice(0, 8);\n const room = MAX_MEMBER_NAME_LENGTH - suffix.length - FANOUT_SUFFIX_SEPARATOR.length;\n return `${base.slice(0, room).replace(SURROUNDING_SEPARATORS, '') || FALLBACK_MEMBER_NAME}-${suffix}`;\n }\n return input.fanoutIndex === undefined ? base : withFanoutOrdinal(base, input.fanoutIndex);\n}\n\n/**\n * Claim a name and join the team. Latest claimant wins.\n *\n * Taking a name over rotates the token, so the previous holder's next\n * `assertCurrentMember` fails and it stops acting as that member. Its inbox is\n * purged in the same step: those messages were addressed to a different process\n * with a different task, and delivering them to the new holder would be worse\n * than dropping them, since the sender is still alive to be told.\n */\nexport function registerNativeTeamMember(input: RegisterNativeTeamMemberInput): {\n context: TeamMemberContext;\n dispose: () => void;\n} {\n const memberId = resolveMemberId(input);\n if (!isSafeId(memberId)) throw new Error('A safe native team member id is required.');\n const memberToken = newToken();\n const tokenHash = hash(memberToken);\n const now = Date.now();\n const task = validateTaskMetadata(input.task);\n const context: TeamMemberContext = {\n ...input.root,\n memberId,\n token: memberToken,\n role: input.role,\n ...(input.agent ? { agent: input.agent } : {}),\n ...(input.runId ? { runId: input.runId } : {}),\n ...(input.childIndex !== undefined ? { childIndex: input.childIndex } : {}),\n ...(input.parentMemberId ? { parentMemberId: input.parentMemberId } : {}),\n ...(task ? { task } : {}),\n };\n const previous = readMember(input.root, memberId);\n if (previous && previous.tokenHash !== tokenHash) removeDirectory(inboxDir(input.root, memberId));\n\n const { token: _unusedToken, version: _unusedVersion, ...identity } = context;\n const record: TeamMemberRecord = {\n ...identity,\n version: MEMBER_RECORD_VERSION,\n tokenHash,\n pid: input.pid ?? process.pid,\n active: true,\n joinedAt: now,\n };\n writeMemberRecord(record);\n writeHeartbeat(input.root, record, now);\n\n let disposed = false;\n return {\n context,\n dispose: () => {\n if (disposed) return;\n disposed = true;\n const current = readMember(input.root, memberId);\n // The name may already have been taken over. Writing then would hand the\n // new holder an inactive record it never asked for.\n if (!current || current.tokenHash !== tokenHash) return;\n const leftAt = Date.now();\n writeMemberRecord({ ...current, active: false, leftAt });\n writeHeartbeat(input.root, current, leftAt);\n },\n };\n}\n\nexport function readNativeTeamMemberFromEnvironment(): TeamMemberContext | undefined {\n const root = readNativeTeamRootFromEnvironment();\n const memberId = textEnv(SUBAGENT_TEAM_MEMBER_ID_ENV);\n const memberToken = textEnv(SUBAGENT_TEAM_MEMBER_TOKEN_ENV);\n if (!root || !memberId || !isSafeId(memberId) || !memberToken) return undefined;\n const member = readMember(root, memberId);\n if (!member || member.teamId !== root.teamId || member.rootSessionId !== root.rootSessionId) return undefined;\n if (member.tokenHash !== hash(memberToken) || !member.active) return undefined;\n return {\n ...root,\n memberId,\n token: memberToken,\n role: member.role,\n ...(member.agent ? { agent: member.agent } : {}),\n ...(member.runId ? { runId: member.runId } : {}),\n ...(member.childIndex !== undefined ? { childIndex: member.childIndex } : {}),\n ...(member.parentMemberId ? { parentMemberId: member.parentMemberId } : {}),\n ...(member.task ? { task: member.task } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Messaging\n// ---------------------------------------------------------------------------\n\nfunction assertCurrentMember(context: TeamMemberContext, now: number): TeamMemberRecord {\n const member = readMember(context, context.memberId);\n if (!member || member.tokenHash !== hash(context.token) || !memberIsActive(context, member, now))\n throw new Error('This native team member is no longer active.');\n return member;\n}\n\nfunction resolveTarget(context: TeamMemberContext, to: string | undefined, now: number): TeamMemberRecord {\n const query = to?.trim();\n if (!query) throw invalidRequest('to is required for intercom send and ask.', 'Provide an explicit recipient.');\n const members = listActiveMembers(context, now).filter((member) => member.memberId !== context.memberId);\n const normalized = normalizeTeamMemberName(query);\n // The name is the id, so an id match is the answer whenever there is one.\n const exact = members.find((member) => member.memberId === query || member.memberId === normalized);\n if (exact) return exact;\n const lowered = query.toLowerCase();\n const matches = members.filter(\n (member) =>\n member.agent?.toLowerCase() === lowered ||\n member.runId?.toLowerCase() === lowered ||\n (lowered === MAIN_MEMBER_ID && member.role === 'main'),\n );\n if (matches.length === 1) return matches[0] as TeamMemberRecord;\n if (matches.length > 1) {\n throw new DoomTeamExpectedError(\n 'recipient_ambiguous',\n `Multiple active intercom members match '${query}': ${matches.map((member) => member.memberId).join(', ')}.`,\n false,\n 'Retry with one exact member id.',\n );\n }\n throw new DoomTeamExpectedError(\n 'recipient_not_found',\n `Active intercom member '${query}' was not found.`,\n true,\n 'Call intercom({\"action\":\"members\"}) and retry with an active member id.',\n );\n}\n\nfunction validateMessage(message: string | undefined): string {\n const normalized = message?.trim();\n if (!normalized)\n throw invalidRequest('message is required for intercom communication.', 'Provide a nonblank message.');\n if (!withinBytes(normalized, MAX_MESSAGE_BYTES)) {\n throw invalidRequest('Intercom message exceeds 64 KiB.', 'Shorten the message and retry.');\n }\n return normalized;\n}\n\n/**\n * Does this envelope really come from the member it names?\n *\n * FIX 4: compares hashes. The caller separately knows the file was found in its\n * own inbox directory, which is the other half of the check the predecessor\n * replaced with a bearer token it then leaked to the recipient.\n */\nfunction senderAuthenticated(root: TeamRootContext, memberId: string, senderTokenHash: string): boolean {\n const member = readMember(root, memberId);\n return Boolean(member && member.tokenHash === senderTokenHash);\n}\n\ninterface InboxScan {\n /** Deliverable, ordered per FIX 2. */\n messages: TeamMessageEnvelope[];\n /**\n * Files that parsed but belong to another team or another member.\n *\n * FIX 3: they are NOT removed, because they are not ours to destroy, and they\n * are reported so the caller can mark them seen. The predecessor left them in\n * place and unmarked, so every poll re-read and re-parsed every one of them.\n */\n foreignFiles: string[];\n}\n\nfunction scanInbox(context: TeamMemberContext, now: number, skip?: (fileName: string) => boolean): InboxScan {\n const dir = inboxDir(context, context.memberId);\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n // The inbox is created at registration; a missing one means this member has\n // been collected or has not finished joining, and there is nothing to read.\n return { messages: [], foreignFiles: [] };\n }\n const messages: TeamMessageEnvelope[] = [];\n const foreignFiles: string[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(JSON_FILE_SUFFIX)) continue;\n // Checked before the file is opened: this is what keeps an already-handled\n // file from costing a read and a parse on every poll.\n if (skip?.(entry.name)) continue;\n const file = path.join(dir, entry.name);\n const envelope = parseMessageEnvelope(readJsonRecord(file));\n if (!envelope) {\n // Unparseable JSON in our own inbox is corrupt, not foreign: the atomic\n // writer's partial files are named `.tmp` and never seen here.\n removeFile(file);\n continue;\n }\n if (envelope.teamId !== context.teamId || envelope.toMemberId !== context.memberId) {\n foreignFiles.push(entry.name);\n continue;\n }\n if (envelope.expiresAt < now || !senderAuthenticated(context, envelope.fromMemberId, envelope.senderTokenHash)) {\n // Ours, and unusable: expired past its TTL, or from a member whose record\n // is gone so the message can never be authenticated.\n removeFile(file);\n continue;\n }\n messages.push(envelope);\n }\n return { messages: orderBySenderSequence(messages), foreignFiles };\n}\n\n/** The minimum a caller needs to know an ask exists, is addressed to a member, and is unanswered. */\nexport interface PendingTeamAsk {\n id: string;\n fromMemberId: string;\n createdAt: number;\n}\n\n/**\n * Unanswered `ask`s addressed to `targetMemberId`, for a runner-side caller\n * that is not `targetMemberId` itself and does not hold its token (for\n * example a deliverable guard checking on a subagent it spawned).\n *\n * READ-ONLY, DELIBERATELY NOT `scanInbox`: `scanInbox` deletes a corrupt file,\n * an expired file, and a file whose sender no longer authenticates, as a side\n * effect of scanning. That is correct for a poll loop, which owns the inbox\n * and is allowed to clean it up, but it is exactly wrong for a diagnostic\n * read: consuming a message as a side effect of merely asking about it would\n * make the message vanish for its real recipient, which is a worse bug than\n * the one this function exists to answer. This function only ever SKIPS a\n * file it cannot use when deciding what to report; it never calls\n * `removeFile` and never touches any in-memory \"seen\" state (it has no access\n * to another process's runtime instance to touch even if it wanted to). Do\n * not refactor this to share the scan loop with `scanInbox` -- that would\n * reintroduce the deletion as a side effect of a read.\n *\n * \"Unanswered\" needs no extra flag and none should be added: `replyToRequest`\n * removes the request file on reply, and `waitForReply`'s `finally` removes\n * it on timeout or cancellation too, so a valid, unexpired, sender-\n * authenticated `ask` file still sitting in the inbox always means nobody has\n * replied to it yet. A separate `answered` boolean would just be a second\n * place that fact could go stale.\n *\n * AUTHENTICATION: `caller` must be a currently active member of this exact\n * team, verified the same way every tool action verifies its caller\n * (`assertCurrentMember`). This is DELIBERATELY NARROWER than the plain\n * `TeamRootContext` the other exported readers (`readMember`, `readHeartbeat`,\n * `listMemberRecords`) accept, and that narrowing is not an inconsistency to\n * \"fix\" by loosening this to match them: a `TeamRootContext` is cheap to\n * reconstruct from a bare `rootSessionId` (`teamIdForSession` is a pure\n * function of it), so those readers' bar is really \"knows or can guess a\n * session id\". That is an acceptable bar for a static roster (who exists,\n * their role, their task) -- the same thing `list`/`status` already hand to\n * any member. It is not an acceptable bar for this function: who is asking\n * whom, and when, is a live activity signal, not roster data, so this\n * requires proof the caller is a real, currently-registered member with a\n * token that hashes to what is on disk. It deliberately does NOT require\n * `targetMemberId`'s own token: requiring that would mean handing it out to a\n * process that is not that member, which is exactly the impersonation\n * capability removing `senderToken` from the envelope (FIX 4) was there to\n * prevent. Do not let a future \"simplification\" walk either of those back.\n */\nexport function pendingAsksAddressedTo(\n caller: TeamMemberContext,\n targetMemberId: string,\n now: number,\n): PendingTeamAsk[] {\n assertCurrentMember(caller, now);\n if (!isSafeId(targetMemberId)) return [];\n const dir = inboxDir(caller, targetMemberId);\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n // No inbox for this member: nothing pending, not an error.\n return [];\n }\n const pending: PendingTeamAsk[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(JSON_FILE_SUFFIX)) continue;\n const envelope = parseMessageEnvelope(readJsonRecord(path.join(dir, entry.name)));\n if (!envelope || envelope.kind !== 'ask') continue;\n if (envelope.teamId !== caller.teamId || envelope.toMemberId !== targetMemberId) continue;\n if (envelope.expiresAt < now) continue;\n if (!senderAuthenticated(caller, envelope.fromMemberId, envelope.senderTokenHash)) continue;\n pending.push({ id: envelope.id, fromMemberId: envelope.fromMemberId, createdAt: envelope.createdAt });\n }\n return pending.sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));\n}\n\n/**\n * FIX 2: order within a sender by `seq`, and between senders by time.\n *\n * Implemented as a merge of per-sender queues rather than one comparator,\n * because \"same sender by seq, different senders by createdAt\" is not a\n * transitive relation and `Array.sort` on it would produce an order that\n * depends on the initial file listing.\n */\nfunction orderBySenderSequence(messages: TeamMessageEnvelope[]): TeamMessageEnvelope[] {\n const bySender = new Map<string, TeamMessageEnvelope[]>();\n for (const envelope of messages) {\n const queue = bySender.get(envelope.fromMemberId);\n if (queue) queue.push(envelope);\n else bySender.set(envelope.fromMemberId, [envelope]);\n }\n const queues: TeamMessageEnvelope[][] = [];\n for (const queue of bySender.values()) {\n queue.sort((left, right) => left.seq - right.seq);\n queues.push(queue);\n }\n const ordered: TeamMessageEnvelope[] = [];\n for (;;) {\n let chosen: TeamMessageEnvelope[] | undefined;\n let chosenHead: TeamMessageEnvelope | undefined;\n for (const queue of queues) {\n const head = queue[0];\n if (!head) continue;\n if (\n !chosenHead ||\n head.createdAt < chosenHead.createdAt ||\n (head.createdAt === chosenHead.createdAt && head.fromMemberId < chosenHead.fromMemberId)\n ) {\n chosen = queue;\n chosenHead = head;\n }\n }\n if (!chosen || !chosenHead) break;\n chosen.shift();\n ordered.push(chosenHead);\n }\n return ordered;\n}\n\nfunction formatMember(member: TeamMemberRecord): string {\n const identity = member.role === 'main' ? MAIN_MEMBER_ID : (member.agent ?? member.memberId);\n const run = member.runId ? ` [${member.runId}]` : '';\n const task = member.task ? `, task ${member.task.id}: ${member.task.subject}` : '';\n return `- ${member.memberId}: ${identity}${run}${task}`;\n}\n\nfunction publicMember(member: TeamMemberRecord): NativeTeamMemberSnapshot {\n return {\n name: member.memberId,\n role: member.role,\n ...(member.agent ? { agent: member.agent } : {}),\n ...(member.runId ? { runId: member.runId } : {}),\n ...(member.task ? { task: member.task } : {}),\n };\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Native team request cancelled.'));\n return;\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const cleanup = (): void => {\n if (timer) clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n };\n const onAbort = (): void => {\n cleanup();\n reject(new Error('Native team request cancelled.'));\n };\n timer = setTimeout(() => {\n cleanup();\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nfunction formatIncomingMessage(context: TeamMemberContext, envelope: TeamMessageEnvelope): string {\n const sender = readMember(context, envelope.fromMemberId);\n const identity = `${envelope.fromMemberId}${sender?.agent ? ` (${sender.agent})` : ''}`;\n const task = sender?.task ? `, task ${sender.task.id}` : '';\n const lines = [`${envelope.kind === 'ask' ? 'Question' : 'Message'} from ${identity}${task}.`];\n lines.push('', envelope.message);\n if (envelope.kind === 'ask') {\n lines.push(\n '',\n `Reply with ${NATIVE_TEAM_TOOL_NAME}({ action: \"reply\", requestId: \"${envelope.id}\", message: \"...\" }).`,\n );\n }\n return lines.join('\\n');\n}\n\nfunction hasTool(pi: ExtensionAPI, name: string): boolean {\n try {\n return pi.getAllTools().some((tool) => tool.name === name);\n } catch {\n // Tool enumeration is unavailable before the host binds its context. Saying\n // \"not registered\" is safe: registerTool is idempotent for our name.\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Runtime\n// ---------------------------------------------------------------------------\n\nexport interface NativeTeamRuntime {\n bindMainSession(rootSessionId: string): TeamMemberContext;\n bindChildFromEnvironment(): TeamMemberContext | undefined;\n current(): TeamMemberContext | undefined;\n /** Messages given up on after `MAX_DELIVERY_ATTEMPTS`, most recent last. */\n undeliverable(): readonly UndeliverableTeamMessage[];\n dispose(): void;\n}\n\n/** Timing and limits, supplied by the service so tests can subclass and vary. */\ninterface TeamRuntimeOptions {\n pollIntervalMs: number;\n replyPollIntervalMs: number;\n heartbeatIntervalMs: number;\n gcIntervalMs: number;\n askTimeoutMs: number;\n maxDeliveryAttempts: number;\n now: () => number;\n}\n\nclass TeamChannelRuntime implements NativeTeamRuntime {\n private context: TeamMemberContext | undefined;\n private ownedMembershipDispose: (() => void) | undefined;\n private poller: ReturnType<typeof setInterval> | undefined;\n private lastHeartbeatAt = 0;\n private lastGcAt = 0;\n /** FIX 5: bounded. Keyed by file name so FIX 3 can skip before opening. */\n private readonly seenFiles = new BoundedKeySet<string>(SEEN_FILE_CAPACITY);\n private readonly deliveryAttempts = new LruCache<string, number>(DELIVERY_ATTEMPT_CAPACITY);\n private readonly undeliverableMessages: UndeliverableTeamMessage[] = [];\n /** FIX 2: this member's outbound counter, monotonic for its whole lifetime. */\n private outboundSeq = 0;\n\n constructor(\n private readonly pi: ExtensionAPI,\n private readonly options: TeamRuntimeOptions,\n ) {\n if (hasTool(pi, NATIVE_TEAM_TOOL_NAME)) {\n throw new Error(\n `[tool_conflict] A foreign '${NATIVE_TEAM_TOOL_NAME}' tool is already registered. ` +\n 'Recovery: disable the competing extension and reload Doom Team.',\n );\n }\n pi.registerTool(this.buildTool());\n }\n\n current(): TeamMemberContext | undefined {\n return this.context;\n }\n\n undeliverable(): readonly UndeliverableTeamMessage[] {\n return this.undeliverableMessages;\n }\n\n bindMainSession(rootSessionId: string): TeamMemberContext {\n const root = ensureNativeTeamRoot(rootSessionId);\n applyNativeTeamRootEnvironment(root);\n const membership = registerNativeTeamMember({ root, role: 'main' });\n return this.startBinding(membership.context, membership.dispose);\n }\n\n bindChildFromEnvironment(): TeamMemberContext | undefined {\n const childContext = readNativeTeamMemberFromEnvironment();\n if (!childContext) return undefined;\n if (this.context?.memberId === childContext.memberId && this.context.token === childContext.token)\n return this.context;\n return this.startBinding(childContext);\n }\n\n dispose(): void {\n const previous = this.context;\n this.stopBinding();\n if (previous?.role === 'main') clearNativeTeamRootEnvironment(previous);\n }\n\n private startBinding(nextContext: TeamMemberContext, dispose?: () => void): TeamMemberContext {\n this.stopBinding();\n this.context = nextContext;\n this.ownedMembershipDispose = dispose;\n this.poll();\n this.poller = setInterval(() => {\n this.poll();\n }, this.options.pollIntervalMs);\n this.poller.unref?.();\n return nextContext;\n }\n\n private stopBinding(): void {\n if (this.poller) clearInterval(this.poller);\n this.poller = undefined;\n this.ownedMembershipDispose?.();\n this.ownedMembershipDispose = undefined;\n this.seenFiles.clear();\n this.deliveryAttempts.clear();\n this.lastHeartbeatAt = 0;\n this.lastGcAt = 0;\n this.outboundSeq = 0;\n this.context = undefined;\n }\n\n private poll(): void {\n const context = this.context;\n if (!context) return;\n const now = this.options.now();\n this.heartbeat(context, now);\n this.collectGarbageIfDue(context, now);\n const scan = scanInbox(context, now, (fileName) => this.seenFiles.has(fileName));\n // FIX 3: skipped from here on, at the cost of one Set entry rather than a\n // read and a parse on every future poll.\n for (const fileName of scan.foreignFiles) this.seenFiles.add(fileName);\n for (const envelope of scan.messages) this.deliverOnce(context, envelope);\n }\n\n /**\n * FIX 1: deliver, then mark seen. Never the other way round.\n *\n * The predecessor added the id to its seen set and then called an unguarded\n * `deliver`. A throw from the host left the message marked as handled while it\n * had never been shown to anyone, and since it was seen it was never retried:\n * the message was lost with no trace. Here a failure leaves the file unseen so\n * the next poll retries it, and the attempt count is what stops a message the\n * host will never accept from blocking the inbox forever.\n */\n private deliverOnce(context: TeamMemberContext, envelope: TeamMessageEnvelope): void {\n const fileName = messageFileName(envelope.id);\n try {\n this.deliver(context, envelope);\n } catch (error) {\n const attempts = (this.deliveryAttempts.get(fileName) ?? 0) + FIRST_DELIVERY_ATTEMPT;\n if (attempts < this.options.maxDeliveryAttempts) {\n this.deliveryAttempts.set(fileName, attempts);\n return;\n }\n this.deliveryAttempts.delete(fileName);\n this.seenFiles.add(fileName);\n this.reportUndeliverable(context, envelope, attempts, error);\n return;\n }\n this.seenFiles.add(fileName);\n this.deliveryAttempts.delete(fileName);\n writePrivateAtomicJson(receiptPath(context, envelope.fromMemberId, envelope.id), {\n version: RECEIPT_VERSION,\n messageId: envelope.id,\n state: RECEIPT_DELIVERED,\n deliveredAt: this.options.now(),\n });\n // An `ask` file has to survive delivery: `replyToRequest` reads it back to\n // prove the reply answers a real question.\n if (envelope.kind === 'send') removeFile(messagePath(context, context.memberId, envelope.id));\n }\n\n private deliver(context: TeamMemberContext, envelope: TeamMessageEnvelope): void {\n const content = formatIncomingMessage(context, envelope);\n if (context.role === 'subagent') {\n // A subagent is mid-task, so the message is steered into the running turn\n // rather than starting a new one.\n this.pi.sendUserMessage(content, { deliverAs: 'steer' });\n return;\n }\n this.pi.sendMessage(\n {\n customType: TEAM_MESSAGE_CUSTOM_TYPE,\n content,\n display: true,\n details: {\n id: envelope.id,\n kind: envelope.kind,\n from: envelope.fromMemberId,\n to: envelope.toMemberId,\n seq: envelope.seq,\n },\n },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n }\n\n private reportUndeliverable(\n context: TeamMemberContext,\n envelope: TeamMessageEnvelope,\n attempts: number,\n error: unknown,\n ): void {\n const record: UndeliverableTeamMessage = {\n messageId: envelope.id,\n fromMemberId: envelope.fromMemberId,\n kind: envelope.kind,\n attempts,\n reason: errorText(error),\n at: this.options.now(),\n };\n this.undeliverableMessages.push(record);\n writePrivateAtomicJson(receiptPath(context, envelope.fromMemberId, envelope.id), {\n version: RECEIPT_VERSION,\n messageId: envelope.id,\n state: RECEIPT_FAILED,\n reason: record.reason,\n failedAt: record.at,\n });\n while (this.undeliverableMessages.length > UNDELIVERABLE_HISTORY_LIMIT) this.undeliverableMessages.shift();\n try {\n this.pi.sendMessage(\n {\n customType: TEAM_UNDELIVERABLE_CUSTOM_TYPE,\n content: `Native team message ${envelope.id} from ${envelope.fromMemberId} could not be delivered after ${attempts} attempts: ${record.reason}`,\n display: true,\n details: { ...record },\n },\n { triggerTurn: true, deliverAs: 'steer' },\n );\n } catch {\n // The notice travels over the transport that just refused the message, so\n // it can fail for the same reason. `undeliverable()` is then the only\n // record, and rethrowing here would abort the poll and stall every other\n // sender's messages behind this one.\n }\n }\n\n /** FIX 6: writes only this member's own heartbeat file. */\n private heartbeat(context: TeamMemberContext, now: number): void {\n if (now - this.lastHeartbeatAt < this.options.heartbeatIntervalMs) return;\n const member = readMember(context, context.memberId);\n if (!member || member.tokenHash !== hash(context.token) || !member.active) return;\n this.lastHeartbeatAt = now;\n writeHeartbeat(context, member, now);\n }\n\n private collectGarbageIfDue(context: TeamMemberContext, now: number): void {\n if (now - this.lastGcAt < this.options.gcIntervalMs) return;\n this.lastGcAt = now;\n try {\n collectDeadMembers(context, context.memberId, now);\n } catch (error) {\n // FIX 7 is housekeeping, not delivery. A sweep that fails is retried on\n // the next interval; letting it escape would stop the poll that delivers\n // messages, which is strictly worse than a directory left on disk.\n this.undeliverableMessages.push({\n messageId: '',\n fromMemberId: context.memberId,\n kind: 'send',\n attempts: 0,\n reason: `Native team directory sweep failed: ${errorText(error)}`,\n at: now,\n });\n while (this.undeliverableMessages.length > UNDELIVERABLE_HISTORY_LIMIT) this.undeliverableMessages.shift();\n }\n }\n\n private nextSeq(): number {\n this.outboundSeq += 1;\n return this.outboundSeq;\n }\n\n private queueMessage(\n context: TeamMemberContext,\n target: TeamMemberRecord,\n kind: TeamMessageKind,\n message: string,\n now: number,\n messageId: string,\n ): TeamMessageEnvelope {\n assertCurrentMember(context, now);\n if (!memberIsActive(context, target, now))\n throw new Error(`Native team member '${target.memberId}' is no longer active.`);\n const envelope: TeamMessageEnvelope = {\n type: TEAM_MESSAGE_TYPE,\n version: ENVELOPE_VERSION,\n id: messageId,\n teamId: context.teamId,\n kind,\n fromMemberId: context.memberId,\n toMemberId: target.memberId,\n senderTokenHash: hash(context.token),\n seq: this.nextSeq(),\n message,\n createdAt: now,\n expiresAt: now + MESSAGE_TTL_MS,\n };\n fs.mkdirSync(inboxDir(context, target.memberId), { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n writePrivateAtomicJson(messagePath(context, target.memberId, envelope.id), envelope);\n return envelope;\n }\n\n private replyToRequest(\n context: TeamMemberContext,\n requestId: string | undefined,\n message: string,\n now: number,\n ): TeamReplyEnvelope {\n if (!requestId || !isSafeId(requestId)) throw new Error('requestId must be a valid intercom request id.');\n assertCurrentMember(context, now);\n const requestFile = messagePath(context, context.memberId, requestId);\n const request = parseMessageEnvelope(readJsonRecord(requestFile));\n if (\n !request ||\n request.kind !== 'ask' ||\n request.teamId !== context.teamId ||\n request.toMemberId !== context.memberId ||\n !senderAuthenticated(context, request.fromMemberId, request.senderTokenHash)\n )\n throw new DoomTeamExpectedError(\n 'recipient_not_found',\n `No pending intercom ask matches requestId '${requestId}'.`,\n false,\n 'Call intercom({\"action\":\"pending\"}) and retry with an exact requestId.',\n );\n const reply: TeamReplyEnvelope = {\n type: TEAM_REPLY_TYPE,\n version: ENVELOPE_VERSION,\n requestId,\n teamId: context.teamId,\n fromMemberId: context.memberId,\n toMemberId: request.fromMemberId,\n senderTokenHash: hash(context.token),\n message,\n createdAt: now,\n };\n writePrivateAtomicJson(replyPath(context, requestId), reply);\n removeFile(requestFile);\n this.seenFiles.delete(messageFileName(requestId));\n return reply;\n }\n\n private async waitForReply(\n context: TeamMemberContext,\n target: TeamMemberRecord,\n request: TeamMessageEnvelope,\n signal: AbortSignal | undefined,\n timeoutMs?: number,\n ): Promise<TeamReplyEnvelope> {\n const file = replyPath(context, request.id);\n const deadline = this.options.now() + (timeoutMs ?? this.options.askTimeoutMs);\n while (this.options.now() <= deadline) {\n if (signal?.aborted) throw new Error('Native team request cancelled.');\n const reply = parseReplyEnvelope(readJsonRecord(file));\n if (\n reply &&\n reply.teamId === context.teamId &&\n reply.requestId === request.id &&\n reply.fromMemberId === target.memberId &&\n reply.toMemberId === context.memberId &&\n senderAuthenticated(context, reply.fromMemberId, reply.senderTokenHash)\n ) {\n removeFile(file);\n return reply;\n }\n const currentTarget = readMember(context, target.memberId);\n if (!currentTarget || !memberIsActive(context, currentTarget, this.options.now()))\n throw new DoomTeamExpectedError(\n 'communication_unavailable',\n `Intercom member '${target.memberId}' became unreachable before replying.`,\n true,\n 'Call intercom({\"action\":\"members\"}) and decide whether to retry with an active member.',\n );\n await delay(this.options.replyPollIntervalMs, signal);\n }\n throw new DoomTeamExpectedError(\n 'reply_timeout',\n `Timed out waiting for intercom member '${target.memberId}' to reply.`,\n true,\n 'Check member status before deciding whether to ask again.',\n );\n }\n\n private buildTool(): ToolDefinition<typeof TeamToolParamsSchema, Record<string, unknown>> {\n return {\n name: NATIVE_TEAM_TOOL_NAME,\n label: 'Intercom',\n description:\n 'Communicate with active agents in this root session. Use members, send, ask, pending, or reply. The stable root identity is main.',\n promptGuidelines: [\n 'Send only new progress, blockers, or decisions. Do not repeat the task brief, sender task, or delegation instructions because the recipient already has them.',\n ],\n parameters: TeamToolParamsSchema,\n execute: async (operationId, rawParams, signal, onUpdate): Promise<AgentToolResult<Record<string, unknown>>> => {\n const context = this.context;\n if (!context) {\n throw new DoomTeamExpectedError(\n 'communication_unavailable',\n 'Intercom is not active for this session.',\n true,\n 'Wait for session startup or reload Doom Team.',\n );\n }\n const params = parseTeamToolParams(rawParams);\n const now = this.options.now();\n assertCurrentMember(context, now);\n\n if (params.action === 'members') {\n const members = listActiveMembers(context, now);\n return {\n content: [\n { type: 'text', text: members.length ? members.map(formatMember).join('\\n') : 'No active members.' },\n ],\n details: { members: members.map(publicMember) },\n };\n }\n if (params.action === 'pending') {\n // Deliberately scans without the seen filter: a pending ask has been\n // delivered already, so filtering by seen would hide every one of them.\n const asks = scanInbox(context, now).messages.filter((message) => message.kind === 'ask');\n const lines = asks.map(\n (ask) =>\n `- ${ask.id}: from ${ask.fromMemberId}. Reply with ${NATIVE_TEAM_TOOL_NAME}({ action: \"reply\", requestId: \"${ask.id}\", message: \"...\" }).`,\n );\n return {\n content: [{ type: 'text', text: lines.length ? lines.join('\\n') : 'No pending native team asks.' }],\n details: { pending: asks.map((ask) => ({ id: ask.id, from: ask.fromMemberId, message: ask.message })) },\n };\n }\n\n const message = validateMessage(params.message);\n if (params.action === 'reply') {\n const reply = this.replyToRequest(context, params.requestId, message, now);\n return {\n content: [{ type: 'text', text: `Replied to native team request ${reply.requestId}.` }],\n details: { requestId: reply.requestId, to: reply.toMemberId },\n };\n }\n\n const target = resolveTarget(context, params.to, now);\n if (params.action === 'ask') {\n onUpdate?.({\n content: [{ type: 'text', text: `Waiting for ${target.memberId} to reply...` }],\n details: { action: 'ask', to: target.memberId, partial: true },\n });\n }\n const messageId = operationMessageId(context.memberId, operationId);\n const existingReceipt = readJsonRecord(receiptPath(context, context.memberId, messageId));\n if (existingReceipt?.state === RECEIPT_FAILED) {\n const reason = typeof existingReceipt.reason === 'string' ? existingReceipt.reason : 'unknown error';\n throw new DoomTeamExpectedError(\n 'delivery_unconfirmed',\n `Intercom message ${messageId} failed: ${reason}`,\n false,\n 'Call intercom({\"action\":\"members\"}) and do not resend automatically.',\n );\n }\n if (params.action === 'send' && existingReceipt?.state === RECEIPT_DELIVERED) {\n return {\n content: [{ type: 'text', text: `Message ${messageId} delivered to ${target.memberId}.` }],\n details: { state: RECEIPT_DELIVERED, delivered: true, messageId, to: target.memberId },\n };\n }\n const request = this.queueMessage(context, target, params.action, message, now, messageId);\n if (params.action === 'send') {\n return {\n content: [\n {\n type: 'text',\n text: `Message ${request.id} queued for ${target.memberId}; delivery is not yet confirmed. Do not resend.`,\n },\n ],\n details: {\n state: 'queued',\n delivered: false,\n messageId: request.id,\n to: target.memberId,\n seq: request.seq,\n },\n };\n }\n try {\n const reply = await this.waitForReply(context, target, request, signal, params.timeoutMs);\n return {\n content: [{ type: 'text', text: `Reply from ${target.memberId}:\\n${reply.message}` }],\n details: { requestId: request.id, from: target.memberId, reply: reply.message },\n };\n } finally {\n // Whether it was answered, timed out or cancelled, the ask must not\n // stay in the target's inbox waiting for a reply nobody is reading.\n removeFile(messagePath(context, target.memberId, request.id));\n }\n },\n };\n }\n}\n\nexport interface INativeTeamChannelService {\n /** Create (or reuse) the runtime bound to one host instance. */\n createRuntime(pi: ExtensionAPI): NativeTeamRuntime;\n /** Bind this process as a team member when its environment says it is one. */\n registerClient(pi: ExtensionAPI): void;\n}\n\n/**\n * Owner of every team runtime in the process.\n *\n * WHY THIS IS A SERVICE AND NOT A MODULE-LEVEL WEAKMAP:\n * The predecessor kept the per-host runtime registry in a module-level WeakMap,\n * so the registry outlived any single session and could not be reset between\n * tests or between two sessions in one process. Timing lives here too: the\n * container resolves this by type and inversify cannot supply a primitive, so\n * intervals are protected members that a test subclass overrides, not\n * constructor arguments.\n */\n@injectable()\nexport class NativeTeamChannelService implements INativeTeamChannelService {\n private readonly runtimes = new WeakMap<ExtensionAPI, NativeTeamRuntime>();\n\n protected readonly pollIntervalMs: number = CHANNEL_POLL_MS;\n protected readonly replyPollIntervalMs: number = REPLY_POLL_MS;\n protected readonly heartbeatIntervalMs: number = HEARTBEAT_INTERVAL_MS;\n protected readonly gcIntervalMs: number = TEAM_GC_INTERVAL_MS;\n protected readonly askTimeoutMs: number = DEFAULT_ASK_TIMEOUT_MS;\n protected readonly maxDeliveryAttempts: number = MAX_DELIVERY_ATTEMPTS;\n\n protected now(): number {\n return Date.now();\n }\n\n createRuntime(pi: ExtensionAPI): NativeTeamRuntime {\n const existing = this.runtimes.get(pi);\n if (existing) return existing;\n const runtime = new TeamChannelRuntime(pi, {\n pollIntervalMs: this.pollIntervalMs,\n replyPollIntervalMs: this.replyPollIntervalMs,\n heartbeatIntervalMs: this.heartbeatIntervalMs,\n gcIntervalMs: this.gcIntervalMs,\n askTimeoutMs: this.askTimeoutMs,\n maxDeliveryAttempts: this.maxDeliveryAttempts,\n now: () => this.now(),\n });\n this.runtimes.set(pi, runtime);\n pi.on('session_shutdown', () => {\n this.runtimes.get(pi)?.dispose();\n });\n return runtime;\n }\n\n registerClient(pi: ExtensionAPI): void {\n // Checked BEFORE readNativeTeamMemberFromEnvironment, which is not\n // scope-free: it resolves `teamDir()` and so requires a scope just to\n // answer \"not a member\". Activation runs before `session_start`, and the\n // parent puts its own PI_SUBAGENT_TEAM_* vars on `process.env`\n // (`applyNativeTeamRootEnvironment`), so a reload re-enters here with team\n // env set and no scope yet. No scope means this process is not a bound\n // child - a real one adopts its scope from the spawn environment first.\n if (!tryCurrentSessionScope()) return;\n if (!readNativeTeamMemberFromEnvironment()) return;\n this.createRuntime(pi).bindChildFromEnvironment();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../shared/configDir.cjs`),n=require(`../shared/atomicJson.cjs`),r=require(`../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),i=require(`../container/types.cjs`),a=require(`../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),o=require(`../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`);let s=require(`node:fs`);s=e.__toESM(s,1);let c=require(`node:path`);c=e.__toESM(c,1);let l=require(`inversify`),u=require(`node:crypto`);process.platform;const d=`steer-acks`,f=128*1024,p=1e6,m=1e3;function h(e){return c.join(e,`control`)}function g(e){return c.join(h(e),`interrupt.json`)}function _(e){return c.join(h(e),`timeout.json`)}function v(e){return c.join(h(e),`stop.json`)}function y(e){return c.join(h(e),`steer-requests`)}function b(e){return c.join(h(e),`steer-inbox-closed.json`)}function x(e,t){return D(t),c.join(h(e),`steer-targets`,String(t))}function S(e){return c.join(h(e),`steer-capabilities`)}function C(e,t){return D(t),c.join(S(e),`${t}.json`)}function w(e,t){return D(t),c.join(h(e),d,String(t))}function T(e){return`${Buffer.from(e).toString(`base64url`)}.json`}function E(e,t){if(!/^[^\s]+$/.test(t)||t.length>256)throw Error(`steer acknowledgment requestId is invalid.`);return c.join(e,T(t))}function D(e){if(!Number.isInteger(e)||e<0||e>p)throw Error(`steer child index must be a non-negative integer.`)}function O(e){return`${String(e.ts).padStart(13,`0`)}-${Buffer.from(e.id).toString(`base64url`)}.json`}function k(e){return e.type===`steer`&&typeof e.id==`string`&&/^[^\s]+$/.test(e.id)&&e.id.length<=256&&typeof e.ts==`number`&&Number.isFinite(e.ts)&&e.ts>0&&typeof e.message==`string`&&!!e.message.trim()&&Buffer.byteLength(e.message,`utf8`)<=f&&(e.targetIndex===void 0||Number.isInteger(e.targetIndex)&&e.targetIndex>=0&&e.targetIndex<=p)&&(e.targetIndexes===void 0||e.targetIndex===void 0&&Array.isArray(e.targetIndexes)&&e.targetIndexes.length>0&&e.targetIndexes.length<=m&&e.targetIndexes.every(e=>Number.isInteger(e)&&e>=0&&e<=p)&&new Set(e.targetIndexes).size===e.targetIndexes.length)&&(e.source===void 0||typeof e.source==`string`&&!!e.source.trim()&&e.source.length<=256)}function A(e,t){if(!k(t))throw Error(`steer request is malformed or exceeds transport limits.`);let r=c.join(e,O(t));return n.writeAtomicJson(r,t),r}function j(e,t){if(D(t.index),!Number.isInteger(t.pid)||t.pid<=0)throw Error(`steer capability pid must be a positive integer.`);if(!Number.isFinite(t.readyAt)||t.readyAt<=0)throw Error(`steer capability readyAt must be a finite timestamp.`);return n.writeAtomicJson(e,{type:`steer-capability`,protocolVersion:1,...t}),e}function M(e,t){if(D(t.index),!/^[^\s]+$/.test(t.requestId)||t.requestId.length>256)throw Error(`steer acknowledgment requestId is invalid.`);if(!Number.isFinite(t.ts)||t.ts<=0)throw Error(`steer acknowledgment ts must be a finite timestamp.`);if(!t.message.trim()||t.message.length>1e3)throw Error(`steer acknowledgment message is invalid.`);return n.writeAtomicJson(e,{type:`steer-ack`,protocolVersion:1,...t,message:t.message.trim()}),e}function N(e,t={},r={}){let i=g(e);return n.writeAtomicJson(i,{...t,ts:t.ts??r.now?.()??Date.now(),type:`interrupt`}),i}function P(e,t={},r={}){let i=v(e);return n.writeAtomicJson(i,{...t,ts:t.ts??r.now?.()??Date.now(),type:`stop`}),i}function F(e,t,n={}){let r=t.message.trim();if(!r)throw Error(`steer message must not be empty.`);if(Buffer.byteLength(r,`utf8`)>f)throw Error(`steer message exceeds ${f} UTF-8 bytes.`);if(t.targetIndex!==void 0&&(!Number.isInteger(t.targetIndex)||t.targetIndex<0||t.targetIndex>p))throw Error(`steer targetIndex must be an integer between 0 and 1000000.`);if(t.targetIndexes!==void 0&&(!Array.isArray(t.targetIndexes)||t.targetIndex!==void 0||t.targetIndexes.length===0||t.targetIndexes.length>m||t.targetIndexes.some(e=>!Number.isInteger(e)||e<0||e>p)||new Set(t.targetIndexes).size!==t.targetIndexes.length))throw Error(`steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.`);let i=b(e);if(s.existsSync(i))throw Error(`Async run no longer accepts steering requests.`);let a={type:`steer`,id:t.id??n.randomId?.()??(0,u.randomUUID)(),ts:t.ts??n.now?.()??Date.now(),message:r,...t.targetIndex===void 0?{}:{targetIndex:t.targetIndex},...t.targetIndexes===void 0?{}:{targetIndexes:[...t.targetIndexes]},...t.source?{source:t.source}:{}},o=A(y(e),a);if(s.existsSync(i))throw s.rmSync(o,{force:!0}),Error(`Async run stopped accepting steering before the request was committed.`);return o}function I(e){if(!e||typeof e!=`object`||Array.isArray(e))return;let t=e;if(t.type!==`steer-capability`||t.protocolVersion!==1)return;let n=t.index,r=t.pid,i=t.readyAt;if(!(typeof n!=`number`||!Number.isInteger(n)||n<0||n>p)&&!(typeof r!=`number`||!Number.isInteger(r)||r<=0)&&!(typeof i!=`number`||!Number.isFinite(i)||i<=0)&&typeof t.supported==`boolean`)return{type:`steer-capability`,protocolVersion:1,index:n,pid:r,readyAt:i,supported:t.supported}}function L(e){if(!e||typeof e!=`object`||Array.isArray(e))return;let t=e;if(t.type!==`steer-ack`||t.protocolVersion!==1||typeof t.requestId!=`string`||!/^[^\s]+$/.test(t.requestId)||t.requestId.length>256)return;let n=t.index,r=t.ts;if(!(typeof n!=`number`||!Number.isInteger(n)||n<0||n>p)&&!(typeof r!=`number`||!Number.isFinite(r)||r<=0)&&!(t.state!==`delivered`&&t.state!==`failed`)&&!(typeof t.message!=`string`||!t.message.trim()||t.message.length>1e3))return{type:`steer-ack`,protocolVersion:1,requestId:t.requestId,index:n,ts:r,state:t.state,message:t.message.trim()}}function R(e){if(!e||typeof e!=`object`||Array.isArray(e))return;let t=e;if(k(t))return{type:`steer`,id:t.id.trim(),ts:t.ts,message:t.message.trim(),...t.targetIndex===void 0?{}:{targetIndex:t.targetIndex},...t.targetIndexes===void 0?{}:{targetIndexes:[...t.targetIndexes]},...typeof t.source==`string`&&t.source.trim()?{source:t.source}:{}}}function z(e,t){try{t.rmSync(e,{recursive:!0})}catch(e){if(e.code===`ENOENT`)return;throw e}}function B(e,t=s){let n=S(e);if(!t.existsSync(n))return[];let r=[];for(let e of t.readdirSync(n).filter(e=>/^\d+\.json$/.test(e)).sort())try{let i=I(JSON.parse(t.readFileSync(c.join(n,e),`utf-8`)));i&&r.push(i)}catch{}return r}function V(e,t=s){let n=c.join(h(e),d);if(!t.existsSync(n))return[];let r=[],i;try{i=t.readdirSync(n).filter(e=>/^\d+$/.test(e))}catch{return[]}for(let e of i){let i=c.join(n,e),a;try{a=t.readdirSync(i).filter(e=>e.endsWith(`.json`)).sort()}catch{continue}for(let e of a){let n=c.join(i,e),a;try{a=L(JSON.parse(t.readFileSync(n,`utf-8`)))}catch{a=void 0}z(n,t),a&&r.push(a)}}return r}function H(e,t,n,r=s){let i=E(w(e,t),n),a;try{a=L(JSON.parse(r.readFileSync(i,`utf-8`)))}catch(e){if(e.code===`ENOENT`)return;throw e}if(z(i,r),!(!a||a.requestId!==n||a.index!==t))return a}function U(e,t=s){let n=g(e);return t.existsSync(n)?(z(n,t),!0):!1}function W(e,t=s){let n=_(e);return t.existsSync(n)?(z(n,t),!0):!1}function G(e,t=s){let n=v(e);return t.existsSync(n)?(z(n,t),!0):!1}function K(e,t,n,r){let i=c.join(e,t),a=`${i}.claim-${n}`;try{return r.renameSync(i,a),a}catch(e){if(e.code===`ENOENT`)return;throw e}}function q(e,t=s,n=(0,u.randomUUID)()){if(!t.existsSync(e))return[];let r;try{r=t.readdirSync(e).filter(e=>e.endsWith(`.json`)).sort()}catch{return[]}let i=[];for(let a of r){let r=K(e,a,n,t);if(!r)continue;let o=c.join(e,a),s;try{s=R(JSON.parse(t.readFileSync(r,`utf-8`)))}catch{s=void 0}if(!s){z(r,t);continue}let l=!1;i.push({request:s,commit:()=>{l||(l=!0,z(r,t))},release:()=>{if(!l){l=!0;try{t.renameSync(r,o)}catch{z(r,t)}}}})}return i.sort((e,t)=>e.request.ts-t.request.ts||e.request.id.localeCompare(t.request.id))}function J(e,t=s,n=(0,u.randomUUID)()){return q(y(e),t,n)}let Y=class{intervalMs=1e3;constructor(e){this.scheduler=e}watch(e,n,r={}){let i=r.fs??s,a=h(e);try{i.mkdirSync(a,{recursive:!0})}catch{}let o=`${process.pid}-${(0,u.randomUUID)()}`,c=!1,l=()=>c?!1:this.check(e,n,i,o);l();let d=this.scheduler.register({id:`control-channel:${e}`,intervalMs:r.intervalMs??this.intervalMs,run:l}),f;try{f=i.watch(t.resolveWatchPath(a,i.realpathSync.native),()=>{c||this.scheduler.wake()}),f.on?.(`error`,()=>{})}catch{f=void 0}return()=>{if(!c){c=!0;try{f?.close()}catch{}d()}}}check(e,t,n,r){let i=!1;G(e,n)&&(t.onStop?.(),i=!0),W(e,n)&&(t.onTimeout?.(),i=!0),U(e,n)&&(t.onInterrupt(),i=!0);let a;for(let o of J(e,n,r))try{t.onSteer?.(o.request),o.commit(),i=!0}catch(e){o.release(),a??=e}for(let r of B(e,n))t.onSteerCapability?.(r);if(t.onSteerAck)for(let r of V(e,n))t.onSteerAck(r),i=!0;if(a!==void 0)throw a;return i}};Y=r.__decorate([(0,l.injectable)(),o.__decorateParam(0,(0,l.inject)(i.DOOM_TEAM_TYPES.PollScheduler)),a.__decorateMetadata(`design:paramtypes`,[Object])],Y),Object.defineProperty(exports,`ControlChannelWatcher`,{enumerable:!0,get:function(){return Y}}),exports.claimSteerRequestsFromDir=q,exports.consumeSteerAck=H,exports.requestAsyncInterrupt=N,exports.requestAsyncSteer=F,exports.requestAsyncStop=P,exports.steerAckPathFromDir=E,exports.steerAcksDir=w,exports.steerCapabilityPath=C,exports.stepSteerInboxDir=x,exports.writeSteerAckAt=M,exports.writeSteerCapabilityAt=j;
|
|
2
|
+
//# sourceMappingURL=supervisorControlChannel.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisorControlChannel.cjs","names":["path","fs","resolveWatchPath","DOOM_TEAM_TYPES"],"sources":["../../src/intercom/supervisorControlChannel.ts"],"sourcesContent":["/**\n * Cross-OS control channel for async subagent runs.\n *\n * Background runs are detached OS processes. A signal-based interrupt\n * (`process.kill(pid, SIGUSR2|SIGBREAK)`) cannot be delivered cross-process on\n * Windows and throws `ENOSYS`, which would leave async runs uninterruptible\n * (no stop, no live steer) on that platform. This module is a portable,\n * file-based control inbox inside the run directory instead: the parent drops\n * a request file, the runner watches the inbox and routes it into its own\n * interrupt handling, identically on every platform. The OS signal is kept\n * only as an opportunistic fast-path; its failure is non-fatal because the\n * file inbox is authoritative.\n *\n * FIX 1 - steer messages must survive a failed dispatch:\n * The predecessor (`doom-pi-subagents/src/runs/background/control-channel.ts`)\n * deleted a steer request file BEFORE dispatching it. A throw during dispatch\n * then lost the instruction with no trace: the user's steer vanished and\n * nothing on disk or in memory recorded that it had ever arrived. Steer\n * requests here are claimed by rename instead: `claimSteerRequestsFromDir`\n * atomically moves a request out of the queue (deciding a single winner among\n * any concurrent consumers in the same step), the caller dispatches what it\n * claimed, and only `commit()` - called after dispatch returns - deletes it.\n * A dispatch that throws calls `release()`, which renames the file back to\n * its original name so the next scan retries it. This package's own delivery\n * loop (`ControlChannelWatcher.check`) follows exactly this claim -> dispatch\n * -> commit/release shape; see `src/intercom/nativeTeamChannel.ts`'s\n * \"deliver, then mark seen\" for the same principle applied to team messages.\n *\n * Residual gap: if the process dies between claim and release (not a thrown\n * error, a hard kill), the claimed file is left under its `.claim-<id>` name\n * and no longer matches a normal directory scan. Nothing here sweeps it back.\n * That is a real gap, not an oversight - recovering from a hard process kill\n * needs a second, independent process to notice, which does not exist yet in\n * this package. What this module guarantees is the fix that was asked for and\n * that a live process can retry: a message is never lost to a dispatch that\n * merely throws.\n *\n * FIX 2 - a missing file and a permission failure are different situations:\n * one is the routine \"someone else already consumed this\" case, harmless and\n * expected; the other means this run's control channel cannot be trusted and\n * must be surfaced, not swallowed. `removeIfPresent` below is the one place\n * this module deletes a control file, and it draws exactly that line: `ENOENT`\n * is absorbed, anything else is rethrown.\n *\n * FIX 3/4 - watch-first, `PollScheduler` as the bounded safety net:\n * `ControlChannelWatcher` registers one subscription per watched run with the\n * shared `PollScheduler` (see its header) instead of owning a `setInterval`,\n * the same \"watch-first, poll as a bounded safety net\" shape `PollScheduler`\n * was built for: `fs.watch` on the inbox directory calls `wake()` for low\n * latency, and the registered subscription's own `intervalMs` is deliberately\n * loose because it exists only to cover platforms and mount types where\n * watches are unreliable, not to carry normal-case latency. This was NOT\n * given the same treatment as `SpawnHandshake` (see that module's header),\n * which explicitly opts out of `PollScheduler`: a spawn handshake is a\n * per-spawn, sub-second, self-terminating wait, where sharing the scheduler's\n * idle backoff would either pin the whole scheduler near its floor for its\n * duration or starve the wait of the low latency it needs. A control-inbox\n * watch is the opposite shape - one long-lived subscriber per run, alive for\n * the run's whole lifetime, checking infrequently - which is exactly the\n * \"channels, watchers, inspectors\" category `PollScheduler` exists to\n * coordinate.\n *\n * A subscriber's `run()` throwing is not swallowed here: it is left to\n * propagate out of `check()` so `PollScheduler.tick()` catches it and records\n * it as `lastSubscriberError`, which is what makes FIX 2's rethrown\n * permission failures actually visible instead of merely \"not silently\n * deleted\".\n */\n\nimport { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../container/types.ts';\nimport { resolveWatchPath } from '../shared/configDir.ts';\nimport { writeAtomicJson } from '../shared/atomicJson.ts';\nimport type { IPollScheduler } from '../shared/pollScheduler.ts';\n\n/**\n * Opportunistic fast-path interrupt signal. On Unix `SIGUSR2` is trapped by\n * the runner; on Windows `process.kill(pid, \"SIGBREAK\")` is not deliverable\n * cross-process and throws `ENOSYS`, so the file inbox below is the real\n * channel there.\n */\nexport const INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === 'win32' ? 'SIGBREAK' : 'SIGUSR2';\n\n/** How generously the poll safety net is spaced. `fs.watch` is the fast path; see the module header. */\nconst DEFAULT_CONTROL_CHANNEL_POLL_INTERVAL_MS = 1_000;\n\nexport type ControlChannelFs = Pick<\n typeof fs,\n 'mkdirSync' | 'existsSync' | 'rmSync' | 'renameSync' | 'watch' | 'readdirSync' | 'readFileSync' | 'realpathSync'\n>;\ntype KillFn = (pid: number, signal?: NodeJS.Signals | 0) => unknown;\n\nexport interface InterruptRequest {\n type: 'interrupt';\n ts?: number;\n source?: string;\n reason?: string;\n}\n\nexport interface TimeoutRequest {\n type: 'timeout';\n ts?: number;\n source?: string;\n reason?: string;\n}\n\nexport interface StopRequest {\n type: 'stop';\n ts?: number;\n source?: string;\n reason?: string;\n}\n\nexport interface SteerRequest {\n type: 'steer';\n id: string;\n ts: number;\n message: string;\n targetIndex?: number;\n targetIndexes?: number[];\n source?: string;\n}\n\nexport interface SteerCapability {\n type: 'steer-capability';\n protocolVersion: 1;\n index: number;\n pid: number;\n readyAt: number;\n supported: boolean;\n}\n\nexport interface SteerAck {\n type: 'steer-ack';\n protocolVersion: 1;\n requestId: string;\n index: number;\n ts: number;\n state: 'delivered' | 'failed';\n message: string;\n}\n\nconst STEER_REQUESTS_DIR = 'steer-requests';\nconst STEER_TARGETS_DIR = 'steer-targets';\nconst STEER_CAPABILITIES_DIR = 'steer-capabilities';\nconst STEER_ACKS_DIR = 'steer-acks';\nconst STEER_INBOX_CLOSED_FILE = 'steer-inbox-closed.json';\nconst MAX_STEER_MESSAGE_BYTES = 128 * 1024;\n/**\n * Highest child index a steer may address.\n *\n * A bound rather than a real limit: it exists so a malformed or hostile\n * request cannot make the runner allocate or iterate over an absurd range.\n */\nconst MAX_STEER_TARGET_INDEX = 1_000_000;\n/** Most children one steer may address at once, for the same reason. */\nconst MAX_STEER_TARGET_COUNT = 1_000;\nconst MAX_STEER_REQUEST_ID_LENGTH = 256;\nconst MAX_STEER_ACK_MESSAGE_LENGTH = 1_000;\n/** Suffix appended to a steer request's file name while a consumer is dispatching it. */\nconst CLAIM_SUFFIX_PREFIX = '.claim-';\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n/** Control inbox directory inside an async run dir. */\nexport function controlInboxDir(asyncDir: string): string {\n return path.join(asyncDir, 'control');\n}\n\n/** Path of the portable interrupt request file. */\nexport function interruptRequestPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), 'interrupt.json');\n}\n\n/** Path of the portable timeout request file. */\nexport function timeoutRequestPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), 'timeout.json');\n}\n\n/** Path of the portable manual stop request file. */\nexport function stopRequestPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), 'stop.json');\n}\n\n/** Directory of parent-to-runner steering requests. */\nexport function steerRequestsDir(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);\n}\n\nexport function steerInboxClosedPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);\n}\n\nexport function closeSteerInbox(asyncDir: string, state: string): void {\n writeAtomicJson(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });\n}\n\n/** Per-child inbox consumed by the child prompt runtime inside the Pi process. */\nexport function stepSteerInboxDir(asyncDir: string, index: number): string {\n assertChildIndex(index);\n return path.join(controlInboxDir(asyncDir), STEER_TARGETS_DIR, String(index));\n}\n\nexport function steerCapabilitiesDir(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), STEER_CAPABILITIES_DIR);\n}\n\nexport function steerCapabilityPath(asyncDir: string, index: number): string {\n assertChildIndex(index);\n return path.join(steerCapabilitiesDir(asyncDir), `${index}.json`);\n}\n\nexport function steerAcksDir(asyncDir: string, index: number): string {\n assertChildIndex(index);\n return path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR, String(index));\n}\n\nfunction steerAckFileName(requestId: string): string {\n return `${Buffer.from(requestId).toString('base64url')}.json`;\n}\n\nexport function steerAckPathFromDir(dir: string, requestId: string): string {\n if (!/^[^\\s]+$/.test(requestId) || requestId.length > MAX_STEER_REQUEST_ID_LENGTH)\n throw new Error('steer acknowledgment requestId is invalid.');\n return path.join(dir, steerAckFileName(requestId));\n}\n\nfunction assertChildIndex(index: number): void {\n if (!Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX)\n throw new Error('steer child index must be a non-negative integer.');\n}\n\nfunction steerRequestFileName(request: SteerRequest): string {\n return `${String(request.ts).padStart(13, '0')}-${Buffer.from(request.id).toString('base64url')}.json`;\n}\n\nfunction validSteerRequest(request: Partial<SteerRequest>): request is SteerRequest {\n return (\n request.type === 'steer' &&\n typeof request.id === 'string' &&\n /^[^\\s]+$/.test(request.id) &&\n request.id.length <= MAX_STEER_REQUEST_ID_LENGTH &&\n typeof request.ts === 'number' &&\n Number.isFinite(request.ts) &&\n request.ts > 0 &&\n typeof request.message === 'string' &&\n Boolean(request.message.trim()) &&\n Buffer.byteLength(request.message, 'utf8') <= MAX_STEER_MESSAGE_BYTES &&\n (request.targetIndex === undefined ||\n (Number.isInteger(request.targetIndex) &&\n request.targetIndex >= 0 &&\n request.targetIndex <= MAX_STEER_TARGET_INDEX)) &&\n (request.targetIndexes === undefined ||\n (request.targetIndex === undefined &&\n Array.isArray(request.targetIndexes) &&\n request.targetIndexes.length > 0 &&\n request.targetIndexes.length <= MAX_STEER_TARGET_COUNT &&\n request.targetIndexes.every(\n (index) => Number.isInteger(index) && index >= 0 && index <= MAX_STEER_TARGET_INDEX,\n ) &&\n new Set(request.targetIndexes).size === request.targetIndexes.length)) &&\n (request.source === undefined ||\n (typeof request.source === 'string' && Boolean(request.source.trim()) && request.source.length <= 256))\n );\n}\n\nexport function writeSteerRequestToDir(dir: string, request: SteerRequest): string {\n if (!validSteerRequest(request)) throw new Error('steer request is malformed or exceeds transport limits.');\n const requestPath = path.join(dir, steerRequestFileName(request));\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function writeSteerCapabilityAt(\n filePath: string,\n capability: Omit<SteerCapability, 'type' | 'protocolVersion'>,\n): string {\n assertChildIndex(capability.index);\n if (!Number.isInteger(capability.pid) || capability.pid <= 0)\n throw new Error('steer capability pid must be a positive integer.');\n if (!Number.isFinite(capability.readyAt) || capability.readyAt <= 0)\n throw new Error('steer capability readyAt must be a finite timestamp.');\n const record: SteerCapability = { type: 'steer-capability', protocolVersion: 1, ...capability };\n writeAtomicJson(filePath, record);\n return filePath;\n}\n\nexport function writeSteerCapability(\n asyncDir: string,\n capability: Omit<SteerCapability, 'type' | 'protocolVersion'>,\n): string {\n return writeSteerCapabilityAt(steerCapabilityPath(asyncDir, capability.index), capability);\n}\n\nexport function writeSteerAckAt(filePath: string, ack: Omit<SteerAck, 'type' | 'protocolVersion'>): string {\n assertChildIndex(ack.index);\n if (!/^[^\\s]+$/.test(ack.requestId) || ack.requestId.length > MAX_STEER_REQUEST_ID_LENGTH)\n throw new Error('steer acknowledgment requestId is invalid.');\n if (!Number.isFinite(ack.ts) || ack.ts <= 0) throw new Error('steer acknowledgment ts must be a finite timestamp.');\n if (!ack.message.trim() || ack.message.length > MAX_STEER_ACK_MESSAGE_LENGTH)\n throw new Error('steer acknowledgment message is invalid.');\n const record: SteerAck = { type: 'steer-ack', protocolVersion: 1, ...ack, message: ack.message.trim() };\n writeAtomicJson(filePath, record);\n return filePath;\n}\n\nexport function writeSteerAck(asyncDir: string, ack: Omit<SteerAck, 'type' | 'protocolVersion'>): string {\n return writeSteerAckAt(path.join(steerAcksDir(asyncDir, ack.index), steerAckFileName(ack.requestId)), ack);\n}\n\n/**\n * Parent side: drop a portable interrupt request the runner's inbox watcher\n * will pick up regardless of OS. Written atomically (temp + rename), dir\n * auto-created.\n */\nexport function requestAsyncInterrupt(\n asyncDir: string,\n payload: Omit<InterruptRequest, 'type'> = {},\n deps: { now?: () => number } = {},\n): string {\n const requestPath = interruptRequestPath(asyncDir);\n const request: InterruptRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: 'interrupt' };\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function requestAsyncTimeout(\n asyncDir: string,\n payload: Omit<TimeoutRequest, 'type'> = {},\n deps: { now?: () => number } = {},\n): string {\n const requestPath = timeoutRequestPath(asyncDir);\n const request: TimeoutRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: 'timeout' };\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function requestAsyncStop(\n asyncDir: string,\n payload: Omit<StopRequest, 'type'> = {},\n deps: { now?: () => number } = {},\n): string {\n const requestPath = stopRequestPath(asyncDir);\n const request: StopRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: 'stop' };\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function requestAsyncSteer(\n asyncDir: string,\n payload: {\n message: string;\n targetIndex?: number;\n targetIndexes?: number[];\n source?: string;\n id?: string;\n ts?: number;\n },\n deps: { now?: () => number; randomId?: () => string } = {},\n): string {\n const message = payload.message.trim();\n if (!message) throw new Error('steer message must not be empty.');\n if (Buffer.byteLength(message, 'utf8') > MAX_STEER_MESSAGE_BYTES)\n throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);\n if (\n payload.targetIndex !== undefined &&\n (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > MAX_STEER_TARGET_INDEX)\n ) {\n throw new Error('steer targetIndex must be an integer between 0 and 1000000.');\n }\n if (\n payload.targetIndexes !== undefined &&\n (!Array.isArray(payload.targetIndexes) ||\n payload.targetIndex !== undefined ||\n payload.targetIndexes.length === 0 ||\n payload.targetIndexes.length > MAX_STEER_TARGET_COUNT ||\n payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX) ||\n new Set(payload.targetIndexes).size !== payload.targetIndexes.length)\n ) {\n throw new Error(\n 'steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.',\n );\n }\n const closedPath = steerInboxClosedPath(asyncDir);\n if (fs.existsSync(closedPath)) throw new Error('Async run no longer accepts steering requests.');\n const request: SteerRequest = {\n type: 'steer',\n id: payload.id ?? deps.randomId?.() ?? randomUUID(),\n ts: payload.ts ?? deps.now?.() ?? Date.now(),\n message,\n ...(payload.targetIndex !== undefined ? { targetIndex: payload.targetIndex } : {}),\n ...(payload.targetIndexes !== undefined ? { targetIndexes: [...payload.targetIndexes] } : {}),\n ...(payload.source ? { source: payload.source } : {}),\n };\n const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);\n if (fs.existsSync(closedPath)) {\n fs.rmSync(requestPath, { force: true });\n throw new Error('Async run stopped accepting steering before the request was committed.');\n }\n return requestPath;\n}\n\nexport function enqueueStepSteer(asyncDir: string, index: number, request: SteerRequest): string {\n assertChildIndex(index);\n const { targetIndexes: _targetIndexes, ...singleTargetRequest } = request;\n return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), {\n ...singleTargetRequest,\n targetIndex: index,\n type: 'steer',\n });\n}\n\n// ---------------------------------------------------------------------------\n// Parsing. Everything read off disk arrives as `unknown` and is narrowed here;\n// nothing is asserted into shape.\n// ---------------------------------------------------------------------------\n\nfunction parseSteerCapability(raw: unknown): SteerCapability | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const input = raw as Partial<SteerCapability>;\n if (input.type !== 'steer-capability' || input.protocolVersion !== 1) return undefined;\n const index = input.index;\n const pid = input.pid;\n const readyAt = input.readyAt;\n if (typeof index !== 'number' || !Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX)\n return undefined;\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return undefined;\n if (typeof readyAt !== 'number' || !Number.isFinite(readyAt) || readyAt <= 0) return undefined;\n if (typeof input.supported !== 'boolean') return undefined;\n return {\n type: 'steer-capability',\n protocolVersion: 1,\n index,\n pid,\n readyAt,\n supported: input.supported,\n };\n}\n\nfunction parseSteerAck(raw: unknown): SteerAck | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const input = raw as Partial<SteerAck>;\n if (\n input.type !== 'steer-ack' ||\n input.protocolVersion !== 1 ||\n typeof input.requestId !== 'string' ||\n !/^[^\\s]+$/.test(input.requestId) ||\n input.requestId.length > MAX_STEER_REQUEST_ID_LENGTH\n )\n return undefined;\n const index = input.index;\n const ts = input.ts;\n if (typeof index !== 'number' || !Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX)\n return undefined;\n if (typeof ts !== 'number' || !Number.isFinite(ts) || ts <= 0) return undefined;\n if (input.state !== 'delivered' && input.state !== 'failed') return undefined;\n if (typeof input.message !== 'string' || !input.message.trim() || input.message.length > 1000) return undefined;\n return {\n type: 'steer-ack',\n protocolVersion: 1,\n requestId: input.requestId,\n index,\n ts,\n state: input.state,\n message: input.message.trim(),\n };\n}\n\nfunction parseSteerRequest(raw: unknown): SteerRequest | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const input = raw as Partial<SteerRequest>;\n if (!validSteerRequest(input)) return undefined;\n return {\n type: 'steer',\n id: input.id.trim(),\n ts: input.ts,\n message: input.message.trim(),\n ...(input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {}),\n ...(input.targetIndexes !== undefined ? { targetIndexes: [...input.targetIndexes] } : {}),\n ...(typeof input.source === 'string' && input.source.trim() ? { source: input.source } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Deletion. `removeIfPresent` is the one place this module deletes a control\n// file, and it is FIX 2 in one function: `ENOENT` is the routine \"someone\n// else already consumed this\" case and is absorbed; anything else means this\n// run's control channel is broken and must be surfaced to the caller.\n// ---------------------------------------------------------------------------\n\nfunction removeIfPresent(target: string, fsImpl: Pick<typeof fs, 'rmSync'>): void {\n try {\n fsImpl.rmSync(target, { recursive: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw error;\n }\n}\n\nexport function consumeSteerCapabilities(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'readdirSync' | 'readFileSync'> = fs,\n): SteerCapability[] {\n const dir = steerCapabilitiesDir(asyncDir);\n if (!fsImpl.existsSync(dir)) return [];\n const capabilities: SteerCapability[] = [];\n for (const entry of fsImpl\n .readdirSync(dir)\n .filter((name) => /^\\d+\\.json$/.test(name))\n .sort()) {\n try {\n const capability = parseSteerCapability(JSON.parse(fsImpl.readFileSync(path.join(dir, entry), 'utf-8')));\n if (capability) capabilities.push(capability);\n } catch {\n // A partially written or malformed capability is ignored until a valid one arrives.\n }\n }\n return capabilities;\n}\n\nexport function consumeSteerAcks(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'readdirSync' | 'readFileSync' | 'rmSync'> = fs,\n): SteerAck[] {\n const root = path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR);\n if (!fsImpl.existsSync(root)) return [];\n const acks: SteerAck[] = [];\n let indexNames: string[];\n try {\n indexNames = fsImpl.readdirSync(root).filter((name) => /^\\d+$/.test(name));\n } catch {\n return [];\n }\n for (const indexName of indexNames) {\n const dir = path.join(root, indexName);\n let entries: string[];\n try {\n entries = fsImpl\n .readdirSync(dir)\n .filter((name) => name.endsWith('.json'))\n .sort();\n } catch {\n continue;\n }\n for (const entry of entries) {\n const target = path.join(dir, entry);\n let ack: SteerAck | undefined;\n try {\n ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, 'utf-8')));\n } catch {\n ack = undefined;\n }\n // FIX 2: a missing ack file just means another reader already took it;\n // any other failure means this ack's fate is unknown and must not be\n // reported as delivered, so it is not pushed and the error propagates.\n removeIfPresent(target, fsImpl);\n if (ack) acks.push(ack);\n }\n }\n return acks;\n}\n\n/**\n * Consume one request-specific acknowledgment without touching concurrent\n * callers' files. The request id determines the exact file name, so two\n * parents waiting on different steering requests cannot steal each other's\n * result.\n */\nexport function consumeSteerAck(\n asyncDir: string,\n index: number,\n requestId: string,\n fsImpl: Pick<typeof fs, 'readFileSync' | 'rmSync'> = fs,\n): SteerAck | undefined {\n const target = steerAckPathFromDir(steerAcksDir(asyncDir, index), requestId);\n let ack: SteerAck | undefined;\n try {\n ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, 'utf-8')));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n removeIfPresent(target, fsImpl);\n if (!ack || ack.requestId !== requestId || ack.index !== index) return undefined;\n return ack;\n}\n\nexport function consumeInterruptRequest(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'rmSync'> = fs,\n): boolean {\n const requestPath = interruptRequestPath(asyncDir);\n if (!fsImpl.existsSync(requestPath)) return false;\n removeIfPresent(requestPath, fsImpl);\n return true;\n}\n\nexport function consumeTimeoutRequest(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'rmSync'> = fs,\n): boolean {\n const requestPath = timeoutRequestPath(asyncDir);\n if (!fsImpl.existsSync(requestPath)) return false;\n removeIfPresent(requestPath, fsImpl);\n return true;\n}\n\nexport function consumeStopRequest(asyncDir: string, fsImpl: Pick<typeof fs, 'existsSync' | 'rmSync'> = fs): boolean {\n const requestPath = stopRequestPath(asyncDir);\n if (!fsImpl.existsSync(requestPath)) return false;\n removeIfPresent(requestPath, fsImpl);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// FIX 1: claim by rename, dispatch what was claimed, commit or release.\n// ---------------------------------------------------------------------------\n\n/** A steer request removed from the queue by an atomic rename, awaiting dispatch. */\nexport interface ClaimedSteerRequest {\n request: SteerRequest;\n /** Call once dispatch has returned successfully. Permanently deletes the claimed file. */\n commit: () => void;\n /** Call when dispatch threw. Renames the file back so the next scan retries it. */\n release: () => void;\n}\n\n/**\n * Atomically move one queued request out of `dir`, or report the loss to\n * another consumer.\n *\n * `renameSync` on `original` is what decides a single winner: if two\n * consumers both listed `dir` and both attempt this for the same\n * `entryName`, exactly one rename succeeds and the other's source no longer\n * exists, which surfaces as `ENOENT` here regardless of what destination name\n * either consumer chose.\n */\nfunction claimSteerRequestFile(\n dir: string,\n entryName: string,\n claimant: string,\n fsImpl: Pick<typeof fs, 'renameSync'>,\n): string | undefined {\n const original = path.join(dir, entryName);\n const claimed = `${original}${CLAIM_SUFFIX_PREFIX}${claimant}`;\n try {\n fsImpl.renameSync(original, claimed);\n return claimed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n}\n\n/**\n * Claim every currently queued, parseable steer request in `dir`.\n *\n * Each returned entry has already been renamed out of the queue, so nothing\n * else will claim it again; it is the caller's obligation to call exactly one\n * of `commit()` (dispatch succeeded) or `release()` (dispatch threw) for\n * every entry returned, so nothing is left claimed and stranded.\n */\nexport function claimSteerRequestsFromDir(\n dir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'renameSync' | 'rmSync' | 'readdirSync' | 'readFileSync'> = fs,\n claimant: string = randomUUID(),\n): ClaimedSteerRequest[] {\n if (!fsImpl.existsSync(dir)) return [];\n let entries: string[];\n try {\n entries = fsImpl\n .readdirSync(dir)\n .filter((name) => name.endsWith('.json'))\n .sort();\n } catch {\n // Leave requests in place so the next scan can retry the listing.\n return [];\n }\n const claims: ClaimedSteerRequest[] = [];\n for (const entryName of entries) {\n const claimedPath = claimSteerRequestFile(dir, entryName, claimant, fsImpl);\n if (!claimedPath) continue; // Lost the race to another consumer, or already gone.\n const originalPath = path.join(dir, entryName);\n let parsed: SteerRequest | undefined;\n try {\n parsed = parseSteerRequest(JSON.parse(fsImpl.readFileSync(claimedPath, 'utf-8')));\n } catch {\n parsed = undefined;\n }\n if (!parsed) {\n // Corrupt or unparseable: nothing can dispatch it, and it is not\n // something a future poll should keep retrying forever.\n removeIfPresent(claimedPath, fsImpl);\n continue;\n }\n let settled = false;\n claims.push({\n request: parsed,\n commit: () => {\n if (settled) return;\n settled = true;\n removeIfPresent(claimedPath, fsImpl);\n },\n release: () => {\n if (settled) return;\n settled = true;\n try {\n fsImpl.renameSync(claimedPath, originalPath);\n } catch {\n // The original name is occupied (astronomically unlikely - it is\n // keyed by timestamp and id) or the directory is gone. Either way\n // there is nothing left to preserve by retrying the rename; drop\n // the claimed copy rather than leave an unreachable file on disk.\n removeIfPresent(claimedPath, fsImpl);\n }\n },\n });\n }\n return claims.sort(\n (left, right) => left.request.ts - right.request.ts || left.request.id.localeCompare(right.request.id),\n );\n}\n\nexport function claimSteerRequests(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'renameSync' | 'rmSync' | 'readdirSync' | 'readFileSync'> = fs,\n claimant: string = randomUUID(),\n): ClaimedSteerRequest[] {\n return claimSteerRequestsFromDir(steerRequestsDir(asyncDir), fsImpl, claimant);\n}\n\n// ---------------------------------------------------------------------------\n// Parent-side portable delivery. Authoritative file request + best-effort OS\n// signal; the signal is only a latency optimization on Unix, and `ENOSYS` on\n// Windows is expected and swallowed because the file inbox is authoritative\n// there. Other signal failures are surfaced because they usually mean the\n// runner is not alive to consume the request.\n// ---------------------------------------------------------------------------\n\nexport function deliverInterruptRequest(input: {\n asyncDir: string;\n pid?: number;\n kill?: KillFn;\n signal?: NodeJS.Signals;\n now?: () => number;\n source?: string;\n}): void {\n const requestPath = requestAsyncInterrupt(input.asyncDir, input.source ? { source: input.source } : {}, {\n now: input.now,\n });\n if (typeof input.pid === 'number' && input.pid > 0) {\n try {\n const kill = input.kill ?? ((pid: number, signal?: NodeJS.Signals | 0) => process.kill(pid, signal));\n kill(input.pid, input.signal ?? INTERRUPT_SIGNAL);\n } catch (error) {\n if ((error as NodeJS.ErrnoException | undefined)?.code === 'ENOSYS') {\n // File inbox is authoritative when custom cross-process signals are unavailable.\n return;\n }\n try {\n fs.rmSync(requestPath, { force: true });\n } catch {\n // Best effort cleanup; the caller still gets the signal failure.\n }\n throw error;\n }\n }\n}\n\nexport function deliverTimeoutRequest(input: {\n asyncDir: string;\n pid?: number;\n kill?: KillFn;\n signal?: NodeJS.Signals;\n now?: () => number;\n source?: string;\n}): void {\n requestAsyncTimeout(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });\n}\n\nexport function deliverStopRequest(input: {\n asyncDir: string;\n pid?: number;\n kill?: KillFn;\n signal?: NodeJS.Signals;\n now?: () => number;\n source?: string;\n}): void {\n requestAsyncStop(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });\n}\n\n// ---------------------------------------------------------------------------\n// Runner side: watch the inbox and route requests into caller-supplied\n// handlers. See the module header for why this is a `PollScheduler`\n// subscriber rather than an owned timer.\n// ---------------------------------------------------------------------------\n\nexport interface ControlChannelWatchHandlers {\n onInterrupt: () => void;\n onTimeout?: () => void;\n onStop?: () => void;\n onSteer?: (request: SteerRequest) => void;\n onSteerCapability?: (capability: SteerCapability) => void;\n onSteerAck?: (ack: SteerAck) => void;\n}\n\nexport interface ControlChannelWatchOptions {\n /** Poll safety-net cadence for this watch, overriding the service default. */\n intervalMs?: number;\n fs?: ControlChannelFs;\n}\n\nexport interface IControlChannelWatcher {\n /**\n * Start watching `asyncDir`'s control inbox. Fires the matching handler for\n * each distinct request, once. Returns a disposer.\n */\n watch(asyncDir: string, handlers: ControlChannelWatchHandlers, options?: ControlChannelWatchOptions): () => void;\n}\n\n@injectable()\nexport class ControlChannelWatcher implements IControlChannelWatcher {\n /**\n * Seam for tests, as a protected member rather than a constructor\n * argument: inversify cannot resolve a primitive. See `pollScheduler.ts`\n * and `agents/discovery.ts` for the same pattern.\n */\n protected readonly intervalMs: number = DEFAULT_CONTROL_CHANNEL_POLL_INTERVAL_MS;\n\n constructor(@inject(DOOM_TEAM_TYPES.PollScheduler) private readonly scheduler: IPollScheduler) {}\n\n watch(asyncDir: string, handlers: ControlChannelWatchHandlers, options: ControlChannelWatchOptions = {}): () => void {\n const fsImpl = options.fs ?? fs;\n const dir = controlInboxDir(asyncDir);\n try {\n fsImpl.mkdirSync(dir, { recursive: true });\n } catch {\n // Best effort - check() below tolerates a missing dir.\n }\n\n const claimant = `${process.pid}-${randomUUID()}`;\n let disposed = false;\n\n const run = (): boolean => {\n if (disposed) return false;\n return this.check(asyncDir, handlers, fsImpl, claimant);\n };\n\n // A request may already be waiting before this watcher starts. Its error,\n // if any, is allowed to throw out of `watch()` here: the scheduled\n // subscription below is not registered yet to report it, and a control\n // channel broken before the first check ever runs should fail loudly\n // rather than start silently degraded.\n run();\n\n const unregister = this.scheduler.register({\n id: `control-channel:${asyncDir}`,\n intervalMs: options.intervalMs ?? this.intervalMs,\n run,\n });\n\n let watcher: fs.FSWatcher | undefined;\n try {\n watcher = fsImpl.watch(resolveWatchPath(dir, fsImpl.realpathSync.native), () => {\n if (!disposed) this.scheduler.wake();\n });\n watcher.on?.('error', () => {\n // fs.watch can emit on transient FS errors; the poll subscription\n // above keeps this live regardless.\n });\n } catch {\n watcher = undefined;\n }\n\n return () => {\n if (disposed) return;\n disposed = true;\n try {\n watcher?.close();\n } catch {\n // ignore\n }\n unregister();\n };\n }\n\n /**\n * One pass over every request kind. Returns whether anything was actually\n * consumed, which is what lets `PollScheduler`'s idle backoff reset.\n *\n * Steer capabilities are deliberately excluded from that signal: unlike\n * every other request kind, a capability file is not consumed here (it\n * represents current state, not a one-time event), so counting its mere\n * presence as \"work\" would pin this subscription at its floor interval for\n * as long as any child has ever registered one.\n *\n * A steer dispatch that throws does not abort the pass: every claim is\n * either committed or released before this returns (FIX 1), and any error\n * is collected and rethrown last so one failing message cannot starve the\n * others claimed in the same pass.\n */\n private check(\n asyncDir: string,\n handlers: ControlChannelWatchHandlers,\n fsImpl: ControlChannelFs,\n claimant: string,\n ): boolean {\n let workHappened = false;\n\n if (consumeStopRequest(asyncDir, fsImpl)) {\n handlers.onStop?.();\n workHappened = true;\n }\n if (consumeTimeoutRequest(asyncDir, fsImpl)) {\n handlers.onTimeout?.();\n workHappened = true;\n }\n if (consumeInterruptRequest(asyncDir, fsImpl)) {\n handlers.onInterrupt();\n workHappened = true;\n }\n\n let firstError: unknown;\n for (const claim of claimSteerRequests(asyncDir, fsImpl, claimant)) {\n try {\n handlers.onSteer?.(claim.request);\n claim.commit();\n workHappened = true;\n } catch (error) {\n claim.release();\n firstError ??= error;\n }\n }\n\n for (const capability of consumeSteerCapabilities(asyncDir, fsImpl)) handlers.onSteerCapability?.(capability);\n // The child runner writes acknowledgments for its parent. A watcher that\n // has no acknowledgment handler must leave those files untouched, or it\n // races the parent and consumes its own outbound acknowledgment.\n if (handlers.onSteerAck) {\n for (const ack of consumeSteerAcks(asyncDir, fsImpl)) {\n handlers.onSteerAck(ack);\n workHappened = true;\n }\n }\n\n if (firstError !== undefined) throw firstError;\n return workHappened;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{init_configDir as t,resolveWatchPath as n}from"../shared/configDir.mjs";import{init_atomicJson as r,writeAtomicJson as i}from"../shared/atomicJson.mjs";import{__decorate as a,init_decorate as o}from"../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as s,init_types as c}from"../container/types.mjs";import{__decorateMetadata as l,init_decorateMetadata as u}from"../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as d,init_decorateParam as ee}from"../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import*as f from"node:fs";import*as p from"node:path";import{inject as m,injectable as h}from"inversify";import{randomUUID as g}from"node:crypto";function _(e){return p.join(e,`control`)}function v(e){return p.join(_(e),`interrupt.json`)}function te(e){return p.join(_(e),`timeout.json`)}function y(e){return p.join(_(e),`stop.json`)}function b(e){return p.join(_(e),V)}function x(e){return p.join(_(e),G)}function ne(e,t){return E(t),p.join(_(e),H,String(t))}function S(e){return p.join(_(e),U)}function re(e,t){return E(t),p.join(S(e),`${t}.json`)}function C(e,t){return E(t),p.join(_(e),W,String(t))}function w(e){return`${Buffer.from(e).toString(`base64url`)}.json`}function T(e,t){if(!/^[^\s]+$/.test(t)||t.length>Y)throw Error(`steer acknowledgment requestId is invalid.`);return p.join(e,w(t))}function E(e){if(!Number.isInteger(e)||e<0||e>q)throw Error(`steer child index must be a non-negative integer.`)}function ie(e){return`${String(e.ts).padStart(13,`0`)}-${Buffer.from(e.id).toString(`base64url`)}.json`}function D(e){return e.type===`steer`&&typeof e.id==`string`&&/^[^\s]+$/.test(e.id)&&e.id.length<=Y&&typeof e.ts==`number`&&Number.isFinite(e.ts)&&e.ts>0&&typeof e.message==`string`&&!!e.message.trim()&&Buffer.byteLength(e.message,`utf8`)<=K&&(e.targetIndex===void 0||Number.isInteger(e.targetIndex)&&e.targetIndex>=0&&e.targetIndex<=q)&&(e.targetIndexes===void 0||e.targetIndex===void 0&&Array.isArray(e.targetIndexes)&&e.targetIndexes.length>0&&e.targetIndexes.length<=J&&e.targetIndexes.every(e=>Number.isInteger(e)&&e>=0&&e<=q)&&new Set(e.targetIndexes).size===e.targetIndexes.length)&&(e.source===void 0||typeof e.source==`string`&&!!e.source.trim()&&e.source.length<=256)}function ae(e,t){if(!D(t))throw Error(`steer request is malformed or exceeds transport limits.`);let n=p.join(e,ie(t));return i(n,t),n}function oe(e,t){if(E(t.index),!Number.isInteger(t.pid)||t.pid<=0)throw Error(`steer capability pid must be a positive integer.`);if(!Number.isFinite(t.readyAt)||t.readyAt<=0)throw Error(`steer capability readyAt must be a finite timestamp.`);return i(e,{type:`steer-capability`,protocolVersion:1,...t}),e}function se(e,t){if(E(t.index),!/^[^\s]+$/.test(t.requestId)||t.requestId.length>Y)throw Error(`steer acknowledgment requestId is invalid.`);if(!Number.isFinite(t.ts)||t.ts<=0)throw Error(`steer acknowledgment ts must be a finite timestamp.`);if(!t.message.trim()||t.message.length>X)throw Error(`steer acknowledgment message is invalid.`);return i(e,{type:`steer-ack`,protocolVersion:1,...t,message:t.message.trim()}),e}function ce(e,t={},n={}){let r=v(e);return i(r,{...t,ts:t.ts??n.now?.()??Date.now(),type:`interrupt`}),r}function le(e,t={},n={}){let r=y(e);return i(r,{...t,ts:t.ts??n.now?.()??Date.now(),type:`stop`}),r}function ue(e,t,n={}){let r=t.message.trim();if(!r)throw Error(`steer message must not be empty.`);if(Buffer.byteLength(r,`utf8`)>K)throw Error(`steer message exceeds ${K} UTF-8 bytes.`);if(t.targetIndex!==void 0&&(!Number.isInteger(t.targetIndex)||t.targetIndex<0||t.targetIndex>q))throw Error(`steer targetIndex must be an integer between 0 and 1000000.`);if(t.targetIndexes!==void 0&&(!Array.isArray(t.targetIndexes)||t.targetIndex!==void 0||t.targetIndexes.length===0||t.targetIndexes.length>J||t.targetIndexes.some(e=>!Number.isInteger(e)||e<0||e>q)||new Set(t.targetIndexes).size!==t.targetIndexes.length))throw Error(`steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.`);let i=x(e);if(f.existsSync(i))throw Error(`Async run no longer accepts steering requests.`);let a={type:`steer`,id:t.id??n.randomId?.()??g(),ts:t.ts??n.now?.()??Date.now(),message:r,...t.targetIndex===void 0?{}:{targetIndex:t.targetIndex},...t.targetIndexes===void 0?{}:{targetIndexes:[...t.targetIndexes]},...t.source?{source:t.source}:{}},o=ae(b(e),a);if(f.existsSync(i))throw f.rmSync(o,{force:!0}),Error(`Async run stopped accepting steering before the request was committed.`);return o}function de(e){if(!e||typeof e!=`object`||Array.isArray(e))return;let t=e;if(t.type!==`steer-capability`||t.protocolVersion!==1)return;let n=t.index,r=t.pid,i=t.readyAt;if(!(typeof n!=`number`||!Number.isInteger(n)||n<0||n>q)&&!(typeof r!=`number`||!Number.isInteger(r)||r<=0)&&!(typeof i!=`number`||!Number.isFinite(i)||i<=0)&&typeof t.supported==`boolean`)return{type:`steer-capability`,protocolVersion:1,index:n,pid:r,readyAt:i,supported:t.supported}}function O(e){if(!e||typeof e!=`object`||Array.isArray(e))return;let t=e;if(t.type!==`steer-ack`||t.protocolVersion!==1||typeof t.requestId!=`string`||!/^[^\s]+$/.test(t.requestId)||t.requestId.length>Y)return;let n=t.index,r=t.ts;if(!(typeof n!=`number`||!Number.isInteger(n)||n<0||n>q)&&!(typeof r!=`number`||!Number.isFinite(r)||r<=0)&&!(t.state!==`delivered`&&t.state!==`failed`)&&!(typeof t.message!=`string`||!t.message.trim()||t.message.length>1e3))return{type:`steer-ack`,protocolVersion:1,requestId:t.requestId,index:n,ts:r,state:t.state,message:t.message.trim()}}function k(e){if(!e||typeof e!=`object`||Array.isArray(e))return;let t=e;if(D(t))return{type:`steer`,id:t.id.trim(),ts:t.ts,message:t.message.trim(),...t.targetIndex===void 0?{}:{targetIndex:t.targetIndex},...t.targetIndexes===void 0?{}:{targetIndexes:[...t.targetIndexes]},...typeof t.source==`string`&&t.source.trim()?{source:t.source}:{}}}function A(e,t){try{t.rmSync(e,{recursive:!0})}catch(e){if(e.code===`ENOENT`)return;throw e}}function j(e,t=f){let n=S(e);if(!t.existsSync(n))return[];let r=[];for(let e of t.readdirSync(n).filter(e=>/^\d+\.json$/.test(e)).sort())try{let i=de(JSON.parse(t.readFileSync(p.join(n,e),`utf-8`)));i&&r.push(i)}catch{}return r}function M(e,t=f){let n=p.join(_(e),W);if(!t.existsSync(n))return[];let r=[],i;try{i=t.readdirSync(n).filter(e=>/^\d+$/.test(e))}catch{return[]}for(let e of i){let i=p.join(n,e),a;try{a=t.readdirSync(i).filter(e=>e.endsWith(`.json`)).sort()}catch{continue}for(let e of a){let n=p.join(i,e),a;try{a=O(JSON.parse(t.readFileSync(n,`utf-8`)))}catch{a=void 0}A(n,t),a&&r.push(a)}}return r}function N(e,t,n,r=f){let i=T(C(e,t),n),a;try{a=O(JSON.parse(r.readFileSync(i,`utf-8`)))}catch(e){if(e.code===`ENOENT`)return;throw e}if(A(i,r),!(!a||a.requestId!==n||a.index!==t))return a}function P(e,t=f){let n=v(e);return t.existsSync(n)?(A(n,t),!0):!1}function F(e,t=f){let n=te(e);return t.existsSync(n)?(A(n,t),!0):!1}function I(e,t=f){let n=y(e);return t.existsSync(n)?(A(n,t),!0):!1}function L(e,t,n,r){let i=p.join(e,t),a=`${i}${Z}${n}`;try{return r.renameSync(i,a),a}catch(e){if(e.code===`ENOENT`)return;throw e}}function R(e,t=f,n=g()){if(!t.existsSync(e))return[];let r;try{r=t.readdirSync(e).filter(e=>e.endsWith(`.json`)).sort()}catch{return[]}let i=[];for(let a of r){let r=L(e,a,n,t);if(!r)continue;let o=p.join(e,a),s;try{s=k(JSON.parse(t.readFileSync(r,`utf-8`)))}catch{s=void 0}if(!s){A(r,t);continue}let c=!1;i.push({request:s,commit:()=>{c||(c=!0,A(r,t))},release:()=>{if(!c){c=!0;try{t.renameSync(r,o)}catch{A(r,t)}}}})}return i.sort((e,t)=>e.request.ts-t.request.ts||e.request.id.localeCompare(t.request.id))}function z(e,t=f,n=g()){return R(b(e),t,n)}var B,V,H,U,W,G,K,q,J,Y,X,Z,Q,$=e((()=>{c(),t(),r(),u(),ee(),o(),process.platform,B=1e3,V=`steer-requests`,H=`steer-targets`,U=`steer-capabilities`,W=`steer-acks`,G=`steer-inbox-closed.json`,K=128*1024,q=1e6,J=1e3,Y=256,X=1e3,Z=`.claim-`,Q=class{intervalMs=B;constructor(e){this.scheduler=e}watch(e,t,r={}){let i=r.fs??f,a=_(e);try{i.mkdirSync(a,{recursive:!0})}catch{}let o=`${process.pid}-${g()}`,s=!1,c=()=>s?!1:this.check(e,t,i,o);c();let l=this.scheduler.register({id:`control-channel:${e}`,intervalMs:r.intervalMs??this.intervalMs,run:c}),u;try{u=i.watch(n(a,i.realpathSync.native),()=>{s||this.scheduler.wake()}),u.on?.(`error`,()=>{})}catch{u=void 0}return()=>{if(!s){s=!0;try{u?.close()}catch{}l()}}}check(e,t,n,r){let i=!1;I(e,n)&&(t.onStop?.(),i=!0),F(e,n)&&(t.onTimeout?.(),i=!0),P(e,n)&&(t.onInterrupt(),i=!0);let a;for(let o of z(e,n,r))try{t.onSteer?.(o.request),o.commit(),i=!0}catch(e){o.release(),a??=e}for(let r of j(e,n))t.onSteerCapability?.(r);if(t.onSteerAck)for(let r of M(e,n))t.onSteerAck(r),i=!0;if(a!==void 0)throw a;return i}},Q=a([h(),d(0,m(s.PollScheduler)),l(`design:paramtypes`,[Object])],Q)}));$();export{Q as ControlChannelWatcher,R as claimSteerRequestsFromDir,N as consumeSteerAck,$ as init_supervisorControlChannel,ce as requestAsyncInterrupt,ue as requestAsyncSteer,le as requestAsyncStop,T as steerAckPathFromDir,C as steerAcksDir,re as steerCapabilityPath,ne as stepSteerInboxDir,se as writeSteerAckAt,oe as writeSteerCapabilityAt};
|
|
2
|
+
//# sourceMappingURL=supervisorControlChannel.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisorControlChannel.mjs","names":[],"sources":["../../src/intercom/supervisorControlChannel.ts"],"sourcesContent":["/**\n * Cross-OS control channel for async subagent runs.\n *\n * Background runs are detached OS processes. A signal-based interrupt\n * (`process.kill(pid, SIGUSR2|SIGBREAK)`) cannot be delivered cross-process on\n * Windows and throws `ENOSYS`, which would leave async runs uninterruptible\n * (no stop, no live steer) on that platform. This module is a portable,\n * file-based control inbox inside the run directory instead: the parent drops\n * a request file, the runner watches the inbox and routes it into its own\n * interrupt handling, identically on every platform. The OS signal is kept\n * only as an opportunistic fast-path; its failure is non-fatal because the\n * file inbox is authoritative.\n *\n * FIX 1 - steer messages must survive a failed dispatch:\n * The predecessor (`doom-pi-subagents/src/runs/background/control-channel.ts`)\n * deleted a steer request file BEFORE dispatching it. A throw during dispatch\n * then lost the instruction with no trace: the user's steer vanished and\n * nothing on disk or in memory recorded that it had ever arrived. Steer\n * requests here are claimed by rename instead: `claimSteerRequestsFromDir`\n * atomically moves a request out of the queue (deciding a single winner among\n * any concurrent consumers in the same step), the caller dispatches what it\n * claimed, and only `commit()` - called after dispatch returns - deletes it.\n * A dispatch that throws calls `release()`, which renames the file back to\n * its original name so the next scan retries it. This package's own delivery\n * loop (`ControlChannelWatcher.check`) follows exactly this claim -> dispatch\n * -> commit/release shape; see `src/intercom/nativeTeamChannel.ts`'s\n * \"deliver, then mark seen\" for the same principle applied to team messages.\n *\n * Residual gap: if the process dies between claim and release (not a thrown\n * error, a hard kill), the claimed file is left under its `.claim-<id>` name\n * and no longer matches a normal directory scan. Nothing here sweeps it back.\n * That is a real gap, not an oversight - recovering from a hard process kill\n * needs a second, independent process to notice, which does not exist yet in\n * this package. What this module guarantees is the fix that was asked for and\n * that a live process can retry: a message is never lost to a dispatch that\n * merely throws.\n *\n * FIX 2 - a missing file and a permission failure are different situations:\n * one is the routine \"someone else already consumed this\" case, harmless and\n * expected; the other means this run's control channel cannot be trusted and\n * must be surfaced, not swallowed. `removeIfPresent` below is the one place\n * this module deletes a control file, and it draws exactly that line: `ENOENT`\n * is absorbed, anything else is rethrown.\n *\n * FIX 3/4 - watch-first, `PollScheduler` as the bounded safety net:\n * `ControlChannelWatcher` registers one subscription per watched run with the\n * shared `PollScheduler` (see its header) instead of owning a `setInterval`,\n * the same \"watch-first, poll as a bounded safety net\" shape `PollScheduler`\n * was built for: `fs.watch` on the inbox directory calls `wake()` for low\n * latency, and the registered subscription's own `intervalMs` is deliberately\n * loose because it exists only to cover platforms and mount types where\n * watches are unreliable, not to carry normal-case latency. This was NOT\n * given the same treatment as `SpawnHandshake` (see that module's header),\n * which explicitly opts out of `PollScheduler`: a spawn handshake is a\n * per-spawn, sub-second, self-terminating wait, where sharing the scheduler's\n * idle backoff would either pin the whole scheduler near its floor for its\n * duration or starve the wait of the low latency it needs. A control-inbox\n * watch is the opposite shape - one long-lived subscriber per run, alive for\n * the run's whole lifetime, checking infrequently - which is exactly the\n * \"channels, watchers, inspectors\" category `PollScheduler` exists to\n * coordinate.\n *\n * A subscriber's `run()` throwing is not swallowed here: it is left to\n * propagate out of `check()` so `PollScheduler.tick()` catches it and records\n * it as `lastSubscriberError`, which is what makes FIX 2's rethrown\n * permission failures actually visible instead of merely \"not silently\n * deleted\".\n */\n\nimport { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../container/types.ts';\nimport { resolveWatchPath } from '../shared/configDir.ts';\nimport { writeAtomicJson } from '../shared/atomicJson.ts';\nimport type { IPollScheduler } from '../shared/pollScheduler.ts';\n\n/**\n * Opportunistic fast-path interrupt signal. On Unix `SIGUSR2` is trapped by\n * the runner; on Windows `process.kill(pid, \"SIGBREAK\")` is not deliverable\n * cross-process and throws `ENOSYS`, so the file inbox below is the real\n * channel there.\n */\nexport const INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === 'win32' ? 'SIGBREAK' : 'SIGUSR2';\n\n/** How generously the poll safety net is spaced. `fs.watch` is the fast path; see the module header. */\nconst DEFAULT_CONTROL_CHANNEL_POLL_INTERVAL_MS = 1_000;\n\nexport type ControlChannelFs = Pick<\n typeof fs,\n 'mkdirSync' | 'existsSync' | 'rmSync' | 'renameSync' | 'watch' | 'readdirSync' | 'readFileSync' | 'realpathSync'\n>;\ntype KillFn = (pid: number, signal?: NodeJS.Signals | 0) => unknown;\n\nexport interface InterruptRequest {\n type: 'interrupt';\n ts?: number;\n source?: string;\n reason?: string;\n}\n\nexport interface TimeoutRequest {\n type: 'timeout';\n ts?: number;\n source?: string;\n reason?: string;\n}\n\nexport interface StopRequest {\n type: 'stop';\n ts?: number;\n source?: string;\n reason?: string;\n}\n\nexport interface SteerRequest {\n type: 'steer';\n id: string;\n ts: number;\n message: string;\n targetIndex?: number;\n targetIndexes?: number[];\n source?: string;\n}\n\nexport interface SteerCapability {\n type: 'steer-capability';\n protocolVersion: 1;\n index: number;\n pid: number;\n readyAt: number;\n supported: boolean;\n}\n\nexport interface SteerAck {\n type: 'steer-ack';\n protocolVersion: 1;\n requestId: string;\n index: number;\n ts: number;\n state: 'delivered' | 'failed';\n message: string;\n}\n\nconst STEER_REQUESTS_DIR = 'steer-requests';\nconst STEER_TARGETS_DIR = 'steer-targets';\nconst STEER_CAPABILITIES_DIR = 'steer-capabilities';\nconst STEER_ACKS_DIR = 'steer-acks';\nconst STEER_INBOX_CLOSED_FILE = 'steer-inbox-closed.json';\nconst MAX_STEER_MESSAGE_BYTES = 128 * 1024;\n/**\n * Highest child index a steer may address.\n *\n * A bound rather than a real limit: it exists so a malformed or hostile\n * request cannot make the runner allocate or iterate over an absurd range.\n */\nconst MAX_STEER_TARGET_INDEX = 1_000_000;\n/** Most children one steer may address at once, for the same reason. */\nconst MAX_STEER_TARGET_COUNT = 1_000;\nconst MAX_STEER_REQUEST_ID_LENGTH = 256;\nconst MAX_STEER_ACK_MESSAGE_LENGTH = 1_000;\n/** Suffix appended to a steer request's file name while a consumer is dispatching it. */\nconst CLAIM_SUFFIX_PREFIX = '.claim-';\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\n/** Control inbox directory inside an async run dir. */\nexport function controlInboxDir(asyncDir: string): string {\n return path.join(asyncDir, 'control');\n}\n\n/** Path of the portable interrupt request file. */\nexport function interruptRequestPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), 'interrupt.json');\n}\n\n/** Path of the portable timeout request file. */\nexport function timeoutRequestPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), 'timeout.json');\n}\n\n/** Path of the portable manual stop request file. */\nexport function stopRequestPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), 'stop.json');\n}\n\n/** Directory of parent-to-runner steering requests. */\nexport function steerRequestsDir(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);\n}\n\nexport function steerInboxClosedPath(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);\n}\n\nexport function closeSteerInbox(asyncDir: string, state: string): void {\n writeAtomicJson(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });\n}\n\n/** Per-child inbox consumed by the child prompt runtime inside the Pi process. */\nexport function stepSteerInboxDir(asyncDir: string, index: number): string {\n assertChildIndex(index);\n return path.join(controlInboxDir(asyncDir), STEER_TARGETS_DIR, String(index));\n}\n\nexport function steerCapabilitiesDir(asyncDir: string): string {\n return path.join(controlInboxDir(asyncDir), STEER_CAPABILITIES_DIR);\n}\n\nexport function steerCapabilityPath(asyncDir: string, index: number): string {\n assertChildIndex(index);\n return path.join(steerCapabilitiesDir(asyncDir), `${index}.json`);\n}\n\nexport function steerAcksDir(asyncDir: string, index: number): string {\n assertChildIndex(index);\n return path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR, String(index));\n}\n\nfunction steerAckFileName(requestId: string): string {\n return `${Buffer.from(requestId).toString('base64url')}.json`;\n}\n\nexport function steerAckPathFromDir(dir: string, requestId: string): string {\n if (!/^[^\\s]+$/.test(requestId) || requestId.length > MAX_STEER_REQUEST_ID_LENGTH)\n throw new Error('steer acknowledgment requestId is invalid.');\n return path.join(dir, steerAckFileName(requestId));\n}\n\nfunction assertChildIndex(index: number): void {\n if (!Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX)\n throw new Error('steer child index must be a non-negative integer.');\n}\n\nfunction steerRequestFileName(request: SteerRequest): string {\n return `${String(request.ts).padStart(13, '0')}-${Buffer.from(request.id).toString('base64url')}.json`;\n}\n\nfunction validSteerRequest(request: Partial<SteerRequest>): request is SteerRequest {\n return (\n request.type === 'steer' &&\n typeof request.id === 'string' &&\n /^[^\\s]+$/.test(request.id) &&\n request.id.length <= MAX_STEER_REQUEST_ID_LENGTH &&\n typeof request.ts === 'number' &&\n Number.isFinite(request.ts) &&\n request.ts > 0 &&\n typeof request.message === 'string' &&\n Boolean(request.message.trim()) &&\n Buffer.byteLength(request.message, 'utf8') <= MAX_STEER_MESSAGE_BYTES &&\n (request.targetIndex === undefined ||\n (Number.isInteger(request.targetIndex) &&\n request.targetIndex >= 0 &&\n request.targetIndex <= MAX_STEER_TARGET_INDEX)) &&\n (request.targetIndexes === undefined ||\n (request.targetIndex === undefined &&\n Array.isArray(request.targetIndexes) &&\n request.targetIndexes.length > 0 &&\n request.targetIndexes.length <= MAX_STEER_TARGET_COUNT &&\n request.targetIndexes.every(\n (index) => Number.isInteger(index) && index >= 0 && index <= MAX_STEER_TARGET_INDEX,\n ) &&\n new Set(request.targetIndexes).size === request.targetIndexes.length)) &&\n (request.source === undefined ||\n (typeof request.source === 'string' && Boolean(request.source.trim()) && request.source.length <= 256))\n );\n}\n\nexport function writeSteerRequestToDir(dir: string, request: SteerRequest): string {\n if (!validSteerRequest(request)) throw new Error('steer request is malformed or exceeds transport limits.');\n const requestPath = path.join(dir, steerRequestFileName(request));\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function writeSteerCapabilityAt(\n filePath: string,\n capability: Omit<SteerCapability, 'type' | 'protocolVersion'>,\n): string {\n assertChildIndex(capability.index);\n if (!Number.isInteger(capability.pid) || capability.pid <= 0)\n throw new Error('steer capability pid must be a positive integer.');\n if (!Number.isFinite(capability.readyAt) || capability.readyAt <= 0)\n throw new Error('steer capability readyAt must be a finite timestamp.');\n const record: SteerCapability = { type: 'steer-capability', protocolVersion: 1, ...capability };\n writeAtomicJson(filePath, record);\n return filePath;\n}\n\nexport function writeSteerCapability(\n asyncDir: string,\n capability: Omit<SteerCapability, 'type' | 'protocolVersion'>,\n): string {\n return writeSteerCapabilityAt(steerCapabilityPath(asyncDir, capability.index), capability);\n}\n\nexport function writeSteerAckAt(filePath: string, ack: Omit<SteerAck, 'type' | 'protocolVersion'>): string {\n assertChildIndex(ack.index);\n if (!/^[^\\s]+$/.test(ack.requestId) || ack.requestId.length > MAX_STEER_REQUEST_ID_LENGTH)\n throw new Error('steer acknowledgment requestId is invalid.');\n if (!Number.isFinite(ack.ts) || ack.ts <= 0) throw new Error('steer acknowledgment ts must be a finite timestamp.');\n if (!ack.message.trim() || ack.message.length > MAX_STEER_ACK_MESSAGE_LENGTH)\n throw new Error('steer acknowledgment message is invalid.');\n const record: SteerAck = { type: 'steer-ack', protocolVersion: 1, ...ack, message: ack.message.trim() };\n writeAtomicJson(filePath, record);\n return filePath;\n}\n\nexport function writeSteerAck(asyncDir: string, ack: Omit<SteerAck, 'type' | 'protocolVersion'>): string {\n return writeSteerAckAt(path.join(steerAcksDir(asyncDir, ack.index), steerAckFileName(ack.requestId)), ack);\n}\n\n/**\n * Parent side: drop a portable interrupt request the runner's inbox watcher\n * will pick up regardless of OS. Written atomically (temp + rename), dir\n * auto-created.\n */\nexport function requestAsyncInterrupt(\n asyncDir: string,\n payload: Omit<InterruptRequest, 'type'> = {},\n deps: { now?: () => number } = {},\n): string {\n const requestPath = interruptRequestPath(asyncDir);\n const request: InterruptRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: 'interrupt' };\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function requestAsyncTimeout(\n asyncDir: string,\n payload: Omit<TimeoutRequest, 'type'> = {},\n deps: { now?: () => number } = {},\n): string {\n const requestPath = timeoutRequestPath(asyncDir);\n const request: TimeoutRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: 'timeout' };\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function requestAsyncStop(\n asyncDir: string,\n payload: Omit<StopRequest, 'type'> = {},\n deps: { now?: () => number } = {},\n): string {\n const requestPath = stopRequestPath(asyncDir);\n const request: StopRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: 'stop' };\n writeAtomicJson(requestPath, request);\n return requestPath;\n}\n\nexport function requestAsyncSteer(\n asyncDir: string,\n payload: {\n message: string;\n targetIndex?: number;\n targetIndexes?: number[];\n source?: string;\n id?: string;\n ts?: number;\n },\n deps: { now?: () => number; randomId?: () => string } = {},\n): string {\n const message = payload.message.trim();\n if (!message) throw new Error('steer message must not be empty.');\n if (Buffer.byteLength(message, 'utf8') > MAX_STEER_MESSAGE_BYTES)\n throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);\n if (\n payload.targetIndex !== undefined &&\n (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > MAX_STEER_TARGET_INDEX)\n ) {\n throw new Error('steer targetIndex must be an integer between 0 and 1000000.');\n }\n if (\n payload.targetIndexes !== undefined &&\n (!Array.isArray(payload.targetIndexes) ||\n payload.targetIndex !== undefined ||\n payload.targetIndexes.length === 0 ||\n payload.targetIndexes.length > MAX_STEER_TARGET_COUNT ||\n payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX) ||\n new Set(payload.targetIndexes).size !== payload.targetIndexes.length)\n ) {\n throw new Error(\n 'steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.',\n );\n }\n const closedPath = steerInboxClosedPath(asyncDir);\n if (fs.existsSync(closedPath)) throw new Error('Async run no longer accepts steering requests.');\n const request: SteerRequest = {\n type: 'steer',\n id: payload.id ?? deps.randomId?.() ?? randomUUID(),\n ts: payload.ts ?? deps.now?.() ?? Date.now(),\n message,\n ...(payload.targetIndex !== undefined ? { targetIndex: payload.targetIndex } : {}),\n ...(payload.targetIndexes !== undefined ? { targetIndexes: [...payload.targetIndexes] } : {}),\n ...(payload.source ? { source: payload.source } : {}),\n };\n const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);\n if (fs.existsSync(closedPath)) {\n fs.rmSync(requestPath, { force: true });\n throw new Error('Async run stopped accepting steering before the request was committed.');\n }\n return requestPath;\n}\n\nexport function enqueueStepSteer(asyncDir: string, index: number, request: SteerRequest): string {\n assertChildIndex(index);\n const { targetIndexes: _targetIndexes, ...singleTargetRequest } = request;\n return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), {\n ...singleTargetRequest,\n targetIndex: index,\n type: 'steer',\n });\n}\n\n// ---------------------------------------------------------------------------\n// Parsing. Everything read off disk arrives as `unknown` and is narrowed here;\n// nothing is asserted into shape.\n// ---------------------------------------------------------------------------\n\nfunction parseSteerCapability(raw: unknown): SteerCapability | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const input = raw as Partial<SteerCapability>;\n if (input.type !== 'steer-capability' || input.protocolVersion !== 1) return undefined;\n const index = input.index;\n const pid = input.pid;\n const readyAt = input.readyAt;\n if (typeof index !== 'number' || !Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX)\n return undefined;\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return undefined;\n if (typeof readyAt !== 'number' || !Number.isFinite(readyAt) || readyAt <= 0) return undefined;\n if (typeof input.supported !== 'boolean') return undefined;\n return {\n type: 'steer-capability',\n protocolVersion: 1,\n index,\n pid,\n readyAt,\n supported: input.supported,\n };\n}\n\nfunction parseSteerAck(raw: unknown): SteerAck | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const input = raw as Partial<SteerAck>;\n if (\n input.type !== 'steer-ack' ||\n input.protocolVersion !== 1 ||\n typeof input.requestId !== 'string' ||\n !/^[^\\s]+$/.test(input.requestId) ||\n input.requestId.length > MAX_STEER_REQUEST_ID_LENGTH\n )\n return undefined;\n const index = input.index;\n const ts = input.ts;\n if (typeof index !== 'number' || !Number.isInteger(index) || index < 0 || index > MAX_STEER_TARGET_INDEX)\n return undefined;\n if (typeof ts !== 'number' || !Number.isFinite(ts) || ts <= 0) return undefined;\n if (input.state !== 'delivered' && input.state !== 'failed') return undefined;\n if (typeof input.message !== 'string' || !input.message.trim() || input.message.length > 1000) return undefined;\n return {\n type: 'steer-ack',\n protocolVersion: 1,\n requestId: input.requestId,\n index,\n ts,\n state: input.state,\n message: input.message.trim(),\n };\n}\n\nfunction parseSteerRequest(raw: unknown): SteerRequest | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const input = raw as Partial<SteerRequest>;\n if (!validSteerRequest(input)) return undefined;\n return {\n type: 'steer',\n id: input.id.trim(),\n ts: input.ts,\n message: input.message.trim(),\n ...(input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {}),\n ...(input.targetIndexes !== undefined ? { targetIndexes: [...input.targetIndexes] } : {}),\n ...(typeof input.source === 'string' && input.source.trim() ? { source: input.source } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Deletion. `removeIfPresent` is the one place this module deletes a control\n// file, and it is FIX 2 in one function: `ENOENT` is the routine \"someone\n// else already consumed this\" case and is absorbed; anything else means this\n// run's control channel is broken and must be surfaced to the caller.\n// ---------------------------------------------------------------------------\n\nfunction removeIfPresent(target: string, fsImpl: Pick<typeof fs, 'rmSync'>): void {\n try {\n fsImpl.rmSync(target, { recursive: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw error;\n }\n}\n\nexport function consumeSteerCapabilities(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'readdirSync' | 'readFileSync'> = fs,\n): SteerCapability[] {\n const dir = steerCapabilitiesDir(asyncDir);\n if (!fsImpl.existsSync(dir)) return [];\n const capabilities: SteerCapability[] = [];\n for (const entry of fsImpl\n .readdirSync(dir)\n .filter((name) => /^\\d+\\.json$/.test(name))\n .sort()) {\n try {\n const capability = parseSteerCapability(JSON.parse(fsImpl.readFileSync(path.join(dir, entry), 'utf-8')));\n if (capability) capabilities.push(capability);\n } catch {\n // A partially written or malformed capability is ignored until a valid one arrives.\n }\n }\n return capabilities;\n}\n\nexport function consumeSteerAcks(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'readdirSync' | 'readFileSync' | 'rmSync'> = fs,\n): SteerAck[] {\n const root = path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR);\n if (!fsImpl.existsSync(root)) return [];\n const acks: SteerAck[] = [];\n let indexNames: string[];\n try {\n indexNames = fsImpl.readdirSync(root).filter((name) => /^\\d+$/.test(name));\n } catch {\n return [];\n }\n for (const indexName of indexNames) {\n const dir = path.join(root, indexName);\n let entries: string[];\n try {\n entries = fsImpl\n .readdirSync(dir)\n .filter((name) => name.endsWith('.json'))\n .sort();\n } catch {\n continue;\n }\n for (const entry of entries) {\n const target = path.join(dir, entry);\n let ack: SteerAck | undefined;\n try {\n ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, 'utf-8')));\n } catch {\n ack = undefined;\n }\n // FIX 2: a missing ack file just means another reader already took it;\n // any other failure means this ack's fate is unknown and must not be\n // reported as delivered, so it is not pushed and the error propagates.\n removeIfPresent(target, fsImpl);\n if (ack) acks.push(ack);\n }\n }\n return acks;\n}\n\n/**\n * Consume one request-specific acknowledgment without touching concurrent\n * callers' files. The request id determines the exact file name, so two\n * parents waiting on different steering requests cannot steal each other's\n * result.\n */\nexport function consumeSteerAck(\n asyncDir: string,\n index: number,\n requestId: string,\n fsImpl: Pick<typeof fs, 'readFileSync' | 'rmSync'> = fs,\n): SteerAck | undefined {\n const target = steerAckPathFromDir(steerAcksDir(asyncDir, index), requestId);\n let ack: SteerAck | undefined;\n try {\n ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, 'utf-8')));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n removeIfPresent(target, fsImpl);\n if (!ack || ack.requestId !== requestId || ack.index !== index) return undefined;\n return ack;\n}\n\nexport function consumeInterruptRequest(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'rmSync'> = fs,\n): boolean {\n const requestPath = interruptRequestPath(asyncDir);\n if (!fsImpl.existsSync(requestPath)) return false;\n removeIfPresent(requestPath, fsImpl);\n return true;\n}\n\nexport function consumeTimeoutRequest(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'rmSync'> = fs,\n): boolean {\n const requestPath = timeoutRequestPath(asyncDir);\n if (!fsImpl.existsSync(requestPath)) return false;\n removeIfPresent(requestPath, fsImpl);\n return true;\n}\n\nexport function consumeStopRequest(asyncDir: string, fsImpl: Pick<typeof fs, 'existsSync' | 'rmSync'> = fs): boolean {\n const requestPath = stopRequestPath(asyncDir);\n if (!fsImpl.existsSync(requestPath)) return false;\n removeIfPresent(requestPath, fsImpl);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// FIX 1: claim by rename, dispatch what was claimed, commit or release.\n// ---------------------------------------------------------------------------\n\n/** A steer request removed from the queue by an atomic rename, awaiting dispatch. */\nexport interface ClaimedSteerRequest {\n request: SteerRequest;\n /** Call once dispatch has returned successfully. Permanently deletes the claimed file. */\n commit: () => void;\n /** Call when dispatch threw. Renames the file back so the next scan retries it. */\n release: () => void;\n}\n\n/**\n * Atomically move one queued request out of `dir`, or report the loss to\n * another consumer.\n *\n * `renameSync` on `original` is what decides a single winner: if two\n * consumers both listed `dir` and both attempt this for the same\n * `entryName`, exactly one rename succeeds and the other's source no longer\n * exists, which surfaces as `ENOENT` here regardless of what destination name\n * either consumer chose.\n */\nfunction claimSteerRequestFile(\n dir: string,\n entryName: string,\n claimant: string,\n fsImpl: Pick<typeof fs, 'renameSync'>,\n): string | undefined {\n const original = path.join(dir, entryName);\n const claimed = `${original}${CLAIM_SUFFIX_PREFIX}${claimant}`;\n try {\n fsImpl.renameSync(original, claimed);\n return claimed;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n}\n\n/**\n * Claim every currently queued, parseable steer request in `dir`.\n *\n * Each returned entry has already been renamed out of the queue, so nothing\n * else will claim it again; it is the caller's obligation to call exactly one\n * of `commit()` (dispatch succeeded) or `release()` (dispatch threw) for\n * every entry returned, so nothing is left claimed and stranded.\n */\nexport function claimSteerRequestsFromDir(\n dir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'renameSync' | 'rmSync' | 'readdirSync' | 'readFileSync'> = fs,\n claimant: string = randomUUID(),\n): ClaimedSteerRequest[] {\n if (!fsImpl.existsSync(dir)) return [];\n let entries: string[];\n try {\n entries = fsImpl\n .readdirSync(dir)\n .filter((name) => name.endsWith('.json'))\n .sort();\n } catch {\n // Leave requests in place so the next scan can retry the listing.\n return [];\n }\n const claims: ClaimedSteerRequest[] = [];\n for (const entryName of entries) {\n const claimedPath = claimSteerRequestFile(dir, entryName, claimant, fsImpl);\n if (!claimedPath) continue; // Lost the race to another consumer, or already gone.\n const originalPath = path.join(dir, entryName);\n let parsed: SteerRequest | undefined;\n try {\n parsed = parseSteerRequest(JSON.parse(fsImpl.readFileSync(claimedPath, 'utf-8')));\n } catch {\n parsed = undefined;\n }\n if (!parsed) {\n // Corrupt or unparseable: nothing can dispatch it, and it is not\n // something a future poll should keep retrying forever.\n removeIfPresent(claimedPath, fsImpl);\n continue;\n }\n let settled = false;\n claims.push({\n request: parsed,\n commit: () => {\n if (settled) return;\n settled = true;\n removeIfPresent(claimedPath, fsImpl);\n },\n release: () => {\n if (settled) return;\n settled = true;\n try {\n fsImpl.renameSync(claimedPath, originalPath);\n } catch {\n // The original name is occupied (astronomically unlikely - it is\n // keyed by timestamp and id) or the directory is gone. Either way\n // there is nothing left to preserve by retrying the rename; drop\n // the claimed copy rather than leave an unreachable file on disk.\n removeIfPresent(claimedPath, fsImpl);\n }\n },\n });\n }\n return claims.sort(\n (left, right) => left.request.ts - right.request.ts || left.request.id.localeCompare(right.request.id),\n );\n}\n\nexport function claimSteerRequests(\n asyncDir: string,\n fsImpl: Pick<typeof fs, 'existsSync' | 'renameSync' | 'rmSync' | 'readdirSync' | 'readFileSync'> = fs,\n claimant: string = randomUUID(),\n): ClaimedSteerRequest[] {\n return claimSteerRequestsFromDir(steerRequestsDir(asyncDir), fsImpl, claimant);\n}\n\n// ---------------------------------------------------------------------------\n// Parent-side portable delivery. Authoritative file request + best-effort OS\n// signal; the signal is only a latency optimization on Unix, and `ENOSYS` on\n// Windows is expected and swallowed because the file inbox is authoritative\n// there. Other signal failures are surfaced because they usually mean the\n// runner is not alive to consume the request.\n// ---------------------------------------------------------------------------\n\nexport function deliverInterruptRequest(input: {\n asyncDir: string;\n pid?: number;\n kill?: KillFn;\n signal?: NodeJS.Signals;\n now?: () => number;\n source?: string;\n}): void {\n const requestPath = requestAsyncInterrupt(input.asyncDir, input.source ? { source: input.source } : {}, {\n now: input.now,\n });\n if (typeof input.pid === 'number' && input.pid > 0) {\n try {\n const kill = input.kill ?? ((pid: number, signal?: NodeJS.Signals | 0) => process.kill(pid, signal));\n kill(input.pid, input.signal ?? INTERRUPT_SIGNAL);\n } catch (error) {\n if ((error as NodeJS.ErrnoException | undefined)?.code === 'ENOSYS') {\n // File inbox is authoritative when custom cross-process signals are unavailable.\n return;\n }\n try {\n fs.rmSync(requestPath, { force: true });\n } catch {\n // Best effort cleanup; the caller still gets the signal failure.\n }\n throw error;\n }\n }\n}\n\nexport function deliverTimeoutRequest(input: {\n asyncDir: string;\n pid?: number;\n kill?: KillFn;\n signal?: NodeJS.Signals;\n now?: () => number;\n source?: string;\n}): void {\n requestAsyncTimeout(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });\n}\n\nexport function deliverStopRequest(input: {\n asyncDir: string;\n pid?: number;\n kill?: KillFn;\n signal?: NodeJS.Signals;\n now?: () => number;\n source?: string;\n}): void {\n requestAsyncStop(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });\n}\n\n// ---------------------------------------------------------------------------\n// Runner side: watch the inbox and route requests into caller-supplied\n// handlers. See the module header for why this is a `PollScheduler`\n// subscriber rather than an owned timer.\n// ---------------------------------------------------------------------------\n\nexport interface ControlChannelWatchHandlers {\n onInterrupt: () => void;\n onTimeout?: () => void;\n onStop?: () => void;\n onSteer?: (request: SteerRequest) => void;\n onSteerCapability?: (capability: SteerCapability) => void;\n onSteerAck?: (ack: SteerAck) => void;\n}\n\nexport interface ControlChannelWatchOptions {\n /** Poll safety-net cadence for this watch, overriding the service default. */\n intervalMs?: number;\n fs?: ControlChannelFs;\n}\n\nexport interface IControlChannelWatcher {\n /**\n * Start watching `asyncDir`'s control inbox. Fires the matching handler for\n * each distinct request, once. Returns a disposer.\n */\n watch(asyncDir: string, handlers: ControlChannelWatchHandlers, options?: ControlChannelWatchOptions): () => void;\n}\n\n@injectable()\nexport class ControlChannelWatcher implements IControlChannelWatcher {\n /**\n * Seam for tests, as a protected member rather than a constructor\n * argument: inversify cannot resolve a primitive. See `pollScheduler.ts`\n * and `agents/discovery.ts` for the same pattern.\n */\n protected readonly intervalMs: number = DEFAULT_CONTROL_CHANNEL_POLL_INTERVAL_MS;\n\n constructor(@inject(DOOM_TEAM_TYPES.PollScheduler) private readonly scheduler: IPollScheduler) {}\n\n watch(asyncDir: string, handlers: ControlChannelWatchHandlers, options: ControlChannelWatchOptions = {}): () => void {\n const fsImpl = options.fs ?? fs;\n const dir = controlInboxDir(asyncDir);\n try {\n fsImpl.mkdirSync(dir, { recursive: true });\n } catch {\n // Best effort - check() below tolerates a missing dir.\n }\n\n const claimant = `${process.pid}-${randomUUID()}`;\n let disposed = false;\n\n const run = (): boolean => {\n if (disposed) return false;\n return this.check(asyncDir, handlers, fsImpl, claimant);\n };\n\n // A request may already be waiting before this watcher starts. Its error,\n // if any, is allowed to throw out of `watch()` here: the scheduled\n // subscription below is not registered yet to report it, and a control\n // channel broken before the first check ever runs should fail loudly\n // rather than start silently degraded.\n run();\n\n const unregister = this.scheduler.register({\n id: `control-channel:${asyncDir}`,\n intervalMs: options.intervalMs ?? this.intervalMs,\n run,\n });\n\n let watcher: fs.FSWatcher | undefined;\n try {\n watcher = fsImpl.watch(resolveWatchPath(dir, fsImpl.realpathSync.native), () => {\n if (!disposed) this.scheduler.wake();\n });\n watcher.on?.('error', () => {\n // fs.watch can emit on transient FS errors; the poll subscription\n // above keeps this live regardless.\n });\n } catch {\n watcher = undefined;\n }\n\n return () => {\n if (disposed) return;\n disposed = true;\n try {\n watcher?.close();\n } catch {\n // ignore\n }\n unregister();\n };\n }\n\n /**\n * One pass over every request kind. Returns whether anything was actually\n * consumed, which is what lets `PollScheduler`'s idle backoff reset.\n *\n * Steer capabilities are deliberately excluded from that signal: unlike\n * every other request kind, a capability file is not consumed here (it\n * represents current state, not a one-time event), so counting its mere\n * presence as \"work\" would pin this subscription at its floor interval for\n * as long as any child has ever registered one.\n *\n * A steer dispatch that throws does not abort the pass: every claim is\n * either committed or released before this returns (FIX 1), and any error\n * is collected and rethrown last so one failing message cannot starve the\n * others claimed in the same pass.\n */\n private check(\n asyncDir: string,\n handlers: ControlChannelWatchHandlers,\n fsImpl: ControlChannelFs,\n claimant: string,\n ): boolean {\n let workHappened = false;\n\n if (consumeStopRequest(asyncDir, fsImpl)) {\n handlers.onStop?.();\n workHappened = true;\n }\n if (consumeTimeoutRequest(asyncDir, fsImpl)) {\n handlers.onTimeout?.();\n workHappened = true;\n }\n if (consumeInterruptRequest(asyncDir, fsImpl)) {\n handlers.onInterrupt();\n workHappened = true;\n }\n\n let firstError: unknown;\n for (const claim of claimSteerRequests(asyncDir, fsImpl, claimant)) {\n try {\n handlers.onSteer?.(claim.request);\n claim.commit();\n workHappened = true;\n } catch (error) {\n claim.release();\n firstError ??= error;\n }\n }\n\n for (const capability of consumeSteerCapabilities(asyncDir, fsImpl)) handlers.onSteerCapability?.(capability);\n // The child runner writes acknowledgments for its parent. A watcher that\n // has no acknowledgment handler must leave those files untouched, or it\n // races the parent and consumes its own outbound acknowledgment.\n if (handlers.onSteerAck) {\n for (const ack of consumeSteerAcks(asyncDir, fsImpl)) {\n handlers.onSteerAck(ack);\n workHappened = true;\n }\n }\n\n if (firstError !== undefined) throw firstError;\n return workHappened;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../container/globalBridge.cjs`),t=`pi-subagents.background-work.v1`,n=`pi-subagents.background-work.registration.v1`,r=1e4,i=[`name`,`listActiveWork`,`wakeChannels`,`reconcile`];function a(t){return t??new e.GlobalStateBridge}function o(e){if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`Malformed background-work registry at Symbol.for("${t}").`);let n=e;if(n.version!==1||!(n.providers instanceof Map))throw Error(`Unsupported background-work registry at Symbol.for("${t}").`);return n}function s(e){return a(e).protocolSlot(t,()=>({version:1,providers:new Map}),o)}function c(e,t,n){if(typeof e!=`string`||e.length===0||e.trim()!==e)throw Error(`${t} must be a non-empty string without leading or trailing whitespace.`);if(e.length>n)throw Error(`${t} must be at most ${n} characters.`);if(e.includes(`\0`))throw Error(`${t} must not contain NUL characters.`);return e}function l(e){if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`Background-work provider must be an object.`);let t=e,n=Object.keys(t).filter(e=>!i.includes(e));if(n.length>0)throw Error(`Background-work provider has unknown fields: ${n.join(`, `)}.`);let r=c(t.name,`Background-work provider name`,128);if(typeof t.listActiveWork!=`function`)throw Error(`Background-work provider '${r}' must expose listActiveWork().`);if(t.reconcile!==void 0&&typeof t.reconcile!=`function`)throw Error(`Background-work provider '${r}' reconcile must be a function when provided.`);if(t.wakeChannels!==void 0){if(!Array.isArray(t.wakeChannels))throw Error(`Background-work provider '${r}' wakeChannels must be an array when provided.`);let e=t.wakeChannels.map((e,t)=>c(e,`Background-work provider '${r}' wakeChannels[${t}]`,256));if(new Set(e).size!==e.length)throw Error(`Background-work provider '${r}' wakeChannels must not contain duplicates.`)}return e}function u(e,t,n){if(!t||typeof t!=`object`||Array.isArray(t))throw Error(`Background-work provider '${e}' item ${n} must be an object.`);let r=t,i=Object.keys(r).filter(e=>e!==`id`&&e!==`sessionId`);if(i.length>0)throw Error(`Background-work provider '${e}' item ${n} has unknown fields: ${i.join(`, `)}.`);return{id:c(r.id,`Background-work provider '${e}' item ${n} id`,256),sessionId:c(r.sessionId,`Background-work provider '${e}' item ${n} sessionId`,256)}}function d(e,t){let n=l(e),r=s(t);if(!r.providers.has(n.name)&&r.providers.size>=100)throw Error(`Background-work registry supports at most 100 providers.`);return r.providers.set(n.name,n),()=>{r.providers.get(n.name)===n&&r.providers.delete(n.name)}}function f(e){let t=s(e);if(t.providers.size>100)throw Error(`Background-work registry contains more than 100 providers.`);let n=[];for(let[e,r]of t.providers){let t=l(r);if(e!==t.name)throw Error(`Background-work registry key '${e}' does not match provider name '${t.name}'.`);n.push(t)}return n}function p(e){let t=new Set;for(let n of f(e))for(let e of n.wakeChannels??[])t.add(e);return[...t]}function m(e,t=Date.now(),n){c(e,`Background-work snapshot sessionId`,256);let i=f(n),a=[],o=new Set;for(let n of i){try{n.reconcile?.({sessionId:e,nowMs:t})}catch(e){throw Error(`Background-work provider '${n.name}' reconcile failed: ${e instanceof Error?e.message:String(e)}`,{cause:e})}let i;try{i=n.listActiveWork()}catch(e){throw Error(`Background-work provider '${n.name}' listActiveWork failed: ${e instanceof Error?e.message:String(e)}`,{cause:e})}if(!Array.isArray(i))throw Error(`Background-work provider '${n.name}' listActiveWork() must return an array.`);if(i.length>r)throw Error(`Background-work provider '${n.name}' returned ${i.length} items; maximum is ${r}.`);i.forEach((t,r)=>{let i=u(n.name,t,r),s=`${n.name}\0${i.sessionId}\0${i.id}`;if(o.has(s))throw Error(`Background-work provider '${n.name}' returned duplicate item '${i.id}' for session '${i.sessionId}'.`);o.add(s),i.sessionId===e&&a.push({provider:n.name,...i})})}return{providers:i.map(e=>e.name),items:a}}function h(e){a(e).claimProtocolSlot(n,d)}exports.BACKGROUND_WORK_PROTOCOL_VERSION=1,exports.BACKGROUND_WORK_REGISTRATION_BRIDGE_KEY=n,exports.BACKGROUND_WORK_REGISTRY_KEY=t,exports.installBackgroundWorkRegistrationBridge=h,exports.listBackgroundWorkProviders=f,exports.listBackgroundWorkWakeChannels=p,exports.registerBackgroundWorkProvider=d,exports.snapshotBackgroundWork=m;
|
|
2
|
+
//# sourceMappingURL=backgroundWork.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backgroundWork.cjs","names":["GlobalStateBridge"],"sources":["../../src/protocol/backgroundWork.ts"],"sourcesContent":["/**\n * Background-work registry - a cross-bundle protocol.\n *\n * DESIGN PATTERNS:\n * - Process-global registry keyed by `Symbol.for`, so every bundle that speaks\n * the protocol shares one map regardless of how many copies are loaded\n * - Validate on read as well as on write: entries can be authored by a foreign\n * bundle of any version\n *\n * PROTOCOL COMPATIBILITY (do not change):\n * The key strings and version number below are shared with doom-pi-subagents\n * and with consumers built against it. During cutover both packages can be\n * loaded at once and must attach to the same registry, so these literals are\n * reproduced verbatim and pinned by contract tests.\n *\n * AVOID:\n * - Renaming the keys to a doom-team namespace; that would silently split the\n * registry in two and hide half the in-flight work from the parent\n */\n\nimport { GlobalStateBridge } from '../container/globalBridge.ts';\nimport type { IGlobalStateBridge } from '../types.ts';\n\nexport const BACKGROUND_WORK_PROTOCOL_VERSION = 1;\nexport const BACKGROUND_WORK_REGISTRY_KEY = 'pi-subagents.background-work.v1';\n\n/**\n * Backward-compatible cross-package registration bridge. Its shape is separate\n * from the v1 registry so existing hosts can keep validating that registry.\n */\nexport const BACKGROUND_WORK_REGISTRATION_BRIDGE_KEY = 'pi-subagents.background-work.registration.v1';\nexport type BackgroundWorkRegistrationBridge = (provider: BackgroundWorkProvider) => () => void;\n\nconst MAX_PROVIDER_NAME_LENGTH = 128;\nconst MAX_PROVIDERS = 100;\nconst MAX_ITEM_ID_LENGTH = 256;\nconst MAX_SESSION_ID_LENGTH = 256;\nconst MAX_WAKE_CHANNEL_LENGTH = 256;\nconst MAX_ITEMS_PER_PROVIDER = 10_000;\nconst PROVIDER_FIELDS = ['name', 'listActiveWork', 'wakeChannels', 'reconcile'];\n\nexport interface BackgroundWorkItem {\n id: string;\n sessionId: string;\n}\n\nexport interface BackgroundWorkReconcileContext {\n sessionId: string;\n nowMs: number;\n}\n\nexport interface BackgroundWorkProvider {\n name: string;\n listActiveWork(): readonly BackgroundWorkItem[];\n wakeChannels?: readonly string[];\n reconcile?(context: BackgroundWorkReconcileContext): void;\n}\n\nexport interface RegisteredBackgroundWorkItem extends BackgroundWorkItem {\n provider: string;\n}\n\nexport interface BackgroundWorkSnapshot {\n providers: readonly string[];\n items: readonly RegisteredBackgroundWorkItem[];\n}\n\ninterface BackgroundWorkRegistry {\n version: typeof BACKGROUND_WORK_PROTOCOL_VERSION;\n providers: Map<string, BackgroundWorkProvider>;\n}\n\n/**\n * The public API functions are called by extensions that hold no container, so\n * they fall back to a standalone bridge. The bridge is a stateless accessor,\n * which makes this safe.\n */\nfunction bridgeFor(bridge?: IGlobalStateBridge): IGlobalStateBridge {\n return bridge ?? new GlobalStateBridge();\n}\n\nfunction adoptRegistry(existing: unknown): BackgroundWorkRegistry {\n if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {\n throw new Error(`Malformed background-work registry at Symbol.for(\"${BACKGROUND_WORK_REGISTRY_KEY}\").`);\n }\n const candidate = existing as Partial<BackgroundWorkRegistry>;\n if (candidate.version !== BACKGROUND_WORK_PROTOCOL_VERSION || !(candidate.providers instanceof Map)) {\n throw new Error(`Unsupported background-work registry at Symbol.for(\"${BACKGROUND_WORK_REGISTRY_KEY}\").`);\n }\n return candidate as BackgroundWorkRegistry;\n}\n\nfunction registry(bridge?: IGlobalStateBridge): BackgroundWorkRegistry {\n return bridgeFor(bridge).protocolSlot<BackgroundWorkRegistry>(\n BACKGROUND_WORK_REGISTRY_KEY,\n () => ({ version: BACKGROUND_WORK_PROTOCOL_VERSION, providers: new Map() }),\n adoptRegistry,\n );\n}\n\nfunction validateString(value: unknown, field: string, maxLength: number): string {\n if (typeof value !== 'string' || value.length === 0 || value.trim() !== value) {\n throw new Error(`${field} must be a non-empty string without leading or trailing whitespace.`);\n }\n if (value.length > maxLength) throw new Error(`${field} must be at most ${maxLength} characters.`);\n if (value.includes('\\0')) throw new Error(`${field} must not contain NUL characters.`);\n return value;\n}\n\nfunction validateProvider(value: unknown): BackgroundWorkProvider {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('Background-work provider must be an object.');\n }\n const provider = value as Record<string, unknown>;\n const unknownFields = Object.keys(provider).filter((key) => !PROVIDER_FIELDS.includes(key));\n if (unknownFields.length > 0) {\n throw new Error(`Background-work provider has unknown fields: ${unknownFields.join(', ')}.`);\n }\n const name = validateString(provider.name, 'Background-work provider name', MAX_PROVIDER_NAME_LENGTH);\n if (typeof provider.listActiveWork !== 'function') {\n throw new Error(`Background-work provider '${name}' must expose listActiveWork().`);\n }\n if (provider.reconcile !== undefined && typeof provider.reconcile !== 'function') {\n throw new Error(`Background-work provider '${name}' reconcile must be a function when provided.`);\n }\n if (provider.wakeChannels !== undefined) {\n if (!Array.isArray(provider.wakeChannels)) {\n throw new Error(`Background-work provider '${name}' wakeChannels must be an array when provided.`);\n }\n const channels = provider.wakeChannels.map((channel, index) =>\n validateString(channel, `Background-work provider '${name}' wakeChannels[${index}]`, MAX_WAKE_CHANNEL_LENGTH),\n );\n if (new Set(channels).size !== channels.length) {\n throw new Error(`Background-work provider '${name}' wakeChannels must not contain duplicates.`);\n }\n }\n return value as BackgroundWorkProvider;\n}\n\nfunction validateItem(provider: string, value: unknown, index: number): BackgroundWorkItem {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`Background-work provider '${provider}' item ${index} must be an object.`);\n }\n const item = value as Record<string, unknown>;\n const unknownFields = Object.keys(item).filter((key) => key !== 'id' && key !== 'sessionId');\n if (unknownFields.length > 0) {\n throw new Error(\n `Background-work provider '${provider}' item ${index} has unknown fields: ${unknownFields.join(', ')}.`,\n );\n }\n return {\n id: validateString(item.id, `Background-work provider '${provider}' item ${index} id`, MAX_ITEM_ID_LENGTH),\n sessionId: validateString(\n item.sessionId,\n `Background-work provider '${provider}' item ${index} sessionId`,\n MAX_SESSION_ID_LENGTH,\n ),\n };\n}\n\n/**\n * Register or replace one process-local background-work provider. The returned\n * disposer only removes this exact registration, so an old extension reload\n * cannot unregister its replacement.\n */\nexport function registerBackgroundWorkProvider(\n provider: BackgroundWorkProvider,\n bridge?: IGlobalStateBridge,\n): () => void {\n const validated = validateProvider(provider);\n const current = registry(bridge);\n if (!current.providers.has(validated.name) && current.providers.size >= MAX_PROVIDERS) {\n throw new Error(`Background-work registry supports at most ${MAX_PROVIDERS} providers.`);\n }\n current.providers.set(validated.name, validated);\n return () => {\n if (current.providers.get(validated.name) === validated) current.providers.delete(validated.name);\n };\n}\n\nexport function listBackgroundWorkProviders(bridge?: IGlobalStateBridge): readonly BackgroundWorkProvider[] {\n const current = registry(bridge);\n if (current.providers.size > MAX_PROVIDERS) {\n throw new Error(`Background-work registry contains more than ${MAX_PROVIDERS} providers.`);\n }\n const providers: BackgroundWorkProvider[] = [];\n for (const [key, value] of current.providers) {\n const provider = validateProvider(value);\n if (key !== provider.name) {\n throw new Error(`Background-work registry key '${key}' does not match provider name '${provider.name}'.`);\n }\n providers.push(provider);\n }\n return providers;\n}\n\n/** Read validated provider wake channels without reconciling or listing work. */\nexport function listBackgroundWorkWakeChannels(bridge?: IGlobalStateBridge): readonly string[] {\n const channels = new Set<string>();\n for (const provider of listBackgroundWorkProviders(bridge)) {\n for (const channel of provider.wakeChannels ?? []) channels.add(channel);\n }\n return [...channels];\n}\n\n/** Reconcile and snapshot active provider work owned by one exact Pi session. */\nexport function snapshotBackgroundWork(\n sessionId: string,\n nowMs = Date.now(),\n bridge?: IGlobalStateBridge,\n): BackgroundWorkSnapshot {\n validateString(sessionId, 'Background-work snapshot sessionId', MAX_SESSION_ID_LENGTH);\n const providers = listBackgroundWorkProviders(bridge);\n const items: RegisteredBackgroundWorkItem[] = [];\n const identities = new Set<string>();\n for (const provider of providers) {\n try {\n provider.reconcile?.({ sessionId, nowMs });\n } catch (error) {\n throw new Error(\n `Background-work provider '${provider.name}' reconcile failed: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n let active: readonly BackgroundWorkItem[];\n try {\n active = provider.listActiveWork();\n } catch (error) {\n throw new Error(\n `Background-work provider '${provider.name}' listActiveWork failed: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n if (!Array.isArray(active)) {\n throw new Error(`Background-work provider '${provider.name}' listActiveWork() must return an array.`);\n }\n if (active.length > MAX_ITEMS_PER_PROVIDER) {\n throw new Error(\n `Background-work provider '${provider.name}' returned ${active.length} items; maximum is ${MAX_ITEMS_PER_PROVIDER}.`,\n );\n }\n active.forEach((value, index) => {\n const item = validateItem(provider.name, value, index);\n const identity = `${provider.name}\\0${item.sessionId}\\0${item.id}`;\n if (identities.has(identity)) {\n throw new Error(\n `Background-work provider '${provider.name}' returned duplicate item '${item.id}' for session '${item.sessionId}'.`,\n );\n }\n identities.add(identity);\n if (item.sessionId === sessionId) items.push({ provider: provider.name, ...item });\n });\n }\n return { providers: providers.map((provider) => provider.name), items };\n}\n\n/**\n * Publish the registration bridge for hosts that discover it by symbol rather\n * than by import. First writer wins, so a bundle loaded alongside this one keeps\n * whichever function it already installed.\n */\nexport function installBackgroundWorkRegistrationBridge(bridge?: IGlobalStateBridge): void {\n bridgeFor(bridge).claimProtocolSlot(\n BACKGROUND_WORK_REGISTRATION_BRIDGE_KEY,\n registerBackgroundWorkProvider as BackgroundWorkRegistrationBridge,\n );\n}\n"],"mappings":"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"}
|