@agimon-ai/doompi-team 0.0.1-alpha.2 → 0.0.1-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -17
- package/dist/adapters/asyncJobTracker.cjs +2 -0
- package/dist/adapters/asyncJobTracker.cjs.map +1 -0
- package/dist/adapters/asyncJobTracker.mjs +2 -0
- package/dist/adapters/asyncJobTracker.mjs.map +1 -0
- package/dist/adapters/atomicJson.cjs +2 -0
- package/dist/adapters/atomicJson.cjs.map +1 -0
- package/dist/adapters/atomicJson.mjs +2 -0
- package/dist/adapters/atomicJson.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pollScheduler.cjs +2 -0
- package/dist/adapters/pollScheduler.cjs.map +1 -0
- package/dist/adapters/pollScheduler.mjs +2 -0
- package/dist/adapters/pollScheduler.mjs.map +1 -0
- package/dist/adapters/process/childTranscript.cjs +5 -0
- package/dist/adapters/process/childTranscript.cjs.map +1 -0
- package/dist/adapters/process/childTranscript.mjs +5 -0
- package/dist/adapters/process/childTranscript.mjs.map +1 -0
- package/dist/adapters/process/env.cjs +2 -0
- package/dist/adapters/process/env.cjs.map +1 -0
- package/dist/adapters/process/env.d.cts +69 -0
- package/dist/adapters/process/env.d.cts.map +1 -0
- package/dist/adapters/process/env.d.mts +69 -0
- package/dist/adapters/process/env.d.mts.map +1 -0
- package/dist/adapters/process/env.mjs +2 -0
- package/dist/adapters/process/env.mjs.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.cjs +2 -0
- package/dist/adapters/process/sdkRunnerEntry.cjs.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.d.cts +5 -0
- package/dist/adapters/process/sdkRunnerEntry.d.cts.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.d.mts +5 -0
- package/dist/adapters/process/sdkRunnerEntry.d.mts.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.mjs +2 -0
- package/dist/adapters/process/sdkRunnerEntry.mjs.map +1 -0
- package/dist/adapters/processTerminal.cjs +2 -0
- package/dist/adapters/processTerminal.cjs.map +1 -0
- package/dist/adapters/processTerminal.mjs +2 -0
- package/dist/adapters/processTerminal.mjs.map +1 -0
- package/dist/adapters/resultWatcher.cjs +2 -0
- package/dist/adapters/resultWatcher.cjs.map +1 -0
- package/dist/adapters/resultWatcher.mjs +2 -0
- package/dist/adapters/resultWatcher.mjs.map +1 -0
- package/dist/adapters/runIdResolver.cjs +2 -0
- package/dist/adapters/runIdResolver.cjs.map +1 -0
- package/dist/adapters/runIdResolver.mjs +2 -0
- package/dist/adapters/runIdResolver.mjs.map +1 -0
- package/dist/adapters/runRegistry.cjs +2 -0
- package/dist/adapters/runRegistry.cjs.map +1 -0
- package/dist/adapters/runRegistry.mjs +2 -0
- package/dist/adapters/runRegistry.mjs.map +1 -0
- package/dist/adapters/scopeOwner.cjs +2 -0
- package/dist/adapters/scopeOwner.cjs.map +1 -0
- package/dist/adapters/scopeOwner.mjs +2 -0
- package/dist/adapters/scopeOwner.mjs.map +1 -0
- package/dist/adapters/staleRunReconciler.cjs +2 -0
- package/dist/adapters/staleRunReconciler.cjs.map +1 -0
- package/dist/adapters/staleRunReconciler.mjs +2 -0
- package/dist/adapters/staleRunReconciler.mjs.map +1 -0
- package/dist/adapters/statusReader.cjs +2 -0
- package/dist/adapters/statusReader.cjs.map +1 -0
- package/dist/adapters/statusReader.mjs +2 -0
- package/dist/adapters/statusReader.mjs.map +1 -0
- package/dist/adapters/suspendedRuns.cjs +6 -0
- package/dist/adapters/suspendedRuns.cjs.map +1 -0
- package/dist/adapters/suspendedRuns.mjs +6 -0
- package/dist/adapters/suspendedRuns.mjs.map +1 -0
- package/dist/api/backgroundWork.cjs +1 -1
- package/dist/api/backgroundWork.d.cts +2 -2
- package/dist/api/backgroundWork.d.mts +2 -2
- package/dist/api/backgroundWork.mjs +1 -1
- package/dist/api/capabilityCeiling.cjs +1 -1
- package/dist/api/capabilityCeiling.d.cts +3 -3
- package/dist/api/capabilityCeiling.d.mts +3 -3
- package/dist/api/capabilityCeiling.mjs +1 -2
- package/dist/api/delegation.cjs +1 -1
- package/dist/api/delegation.d.cts +1 -1
- package/dist/api/delegation.d.mts +1 -1
- package/dist/api/delegation.mjs +1 -1
- package/dist/api/teamSnapshot.cjs +1 -1
- package/dist/api/teamSnapshot.d.cts +2 -2
- package/dist/api/teamSnapshot.d.mts +2 -2
- package/dist/api/teamSnapshot.mjs +1 -2
- package/dist/commands/slash/chainExpression.cjs.map +1 -0
- package/dist/commands/slash/chainExpression.mjs.map +1 -0
- package/dist/commands/slash/doctor.cjs +5 -0
- package/dist/commands/slash/doctor.cjs.map +1 -0
- package/dist/commands/slash/doctor.mjs +5 -0
- package/dist/{slash/doctor.cjs.map → commands/slash/doctor.mjs.map} +1 -1
- package/dist/{slash → commands/slash}/slashCommands.cjs +1 -1
- package/dist/commands/slash/slashCommands.cjs.map +1 -0
- package/dist/{slash → commands/slash}/slashCommands.mjs +2 -2
- package/dist/commands/slash/slashCommands.mjs.map +1 -0
- package/dist/commands/slash/spawnRequestMapping.cjs.map +1 -0
- package/dist/commands/slash/spawnRequestMapping.mjs.map +1 -0
- package/dist/commands/slash/subagentLaunch.cjs +2 -0
- package/dist/commands/slash/subagentLaunch.cjs.map +1 -0
- package/dist/commands/slash/subagentLaunch.mjs +2 -0
- package/dist/commands/slash/subagentLaunch.mjs.map +1 -0
- package/dist/container/globalBridge.cjs.map +1 -1
- package/dist/container/globalBridge.d.cts +1 -1
- package/dist/container/globalBridge.d.mts +1 -1
- package/dist/container/globalBridge.mjs.map +1 -1
- package/dist/container/index.cjs +1 -1
- package/dist/container/index.cjs.map +1 -1
- package/dist/container/index.d.cts +2 -1
- package/dist/container/index.d.cts.map +1 -1
- package/dist/container/index.d.mts +2 -1
- package/dist/container/index.d.mts.map +1 -1
- package/dist/container/index.mjs +1 -1
- package/dist/container/index.mjs.map +1 -1
- package/dist/container/modules/agents.cjs +1 -1
- package/dist/container/modules/agents.cjs.map +1 -1
- package/dist/container/modules/agents.mjs +1 -1
- package/dist/container/modules/agents.mjs.map +1 -1
- package/dist/container/modules/core.cjs +1 -1
- package/dist/container/modules/core.cjs.map +1 -1
- package/dist/container/modules/core.mjs +1 -1
- package/dist/container/modules/core.mjs.map +1 -1
- package/dist/container/modules/extensions.cjs +1 -1
- package/dist/container/modules/extensions.cjs.map +1 -1
- package/dist/container/modules/extensions.mjs +1 -1
- package/dist/container/modules/extensions.mjs.map +1 -1
- package/dist/container/modules/intercom.cjs +1 -1
- package/dist/container/modules/intercom.cjs.map +1 -1
- package/dist/container/modules/intercom.mjs +1 -1
- package/dist/container/modules/intercom.mjs.map +1 -1
- package/dist/container/modules/runs.cjs +1 -1
- package/dist/container/modules/runs.cjs.map +1 -1
- package/dist/container/modules/runs.mjs +1 -1
- package/dist/container/modules/runs.mjs.map +1 -1
- package/dist/container/types.cjs +1 -1
- package/dist/container/types.cjs.map +1 -1
- package/dist/container/types.d.cts +1 -0
- package/dist/container/types.d.cts.map +1 -1
- package/dist/container/types.d.mts +1 -0
- package/dist/container/types.d.mts.map +1 -1
- package/dist/container/types.mjs +1 -1
- package/dist/container/types.mjs.map +1 -1
- package/dist/env.cjs +1 -2
- package/dist/env.d.cts +2 -65
- package/dist/env.d.mts +2 -65
- package/dist/env.mjs +1 -1
- package/dist/env.mjs.map +1 -1
- package/dist/extensions/config.cjs.map +1 -1
- package/dist/extensions/config.mjs.map +1 -1
- package/dist/extensions/delegationBridge.cjs.map +1 -1
- package/dist/extensions/delegationBridge.mjs.map +1 -1
- package/dist/extensions/doom.cjs +1 -1
- package/dist/extensions/doom.cjs.map +1 -1
- package/dist/extensions/doom.mjs +1 -1
- package/dist/extensions/doom.mjs.map +1 -1
- package/dist/extensions/fablePlanBridge.cjs +1 -1
- package/dist/extensions/fablePlanBridge.cjs.map +1 -1
- package/dist/extensions/fablePlanBridge.mjs +1 -1
- package/dist/extensions/fablePlanBridge.mjs.map +1 -1
- package/dist/extensions/managementActions.cjs +1 -1
- package/dist/extensions/managementActions.cjs.map +1 -1
- package/dist/extensions/managementActions.mjs +1 -1
- package/dist/extensions/managementActions.mjs.map +1 -1
- package/dist/extensions/operationJournal.cjs +1 -1
- package/dist/extensions/operationJournal.cjs.map +1 -1
- package/dist/extensions/operationJournal.mjs +1 -1
- package/dist/extensions/operationJournal.mjs.map +1 -1
- package/dist/extensions/orchestratorPrompt.cjs +5 -6
- package/dist/extensions/orchestratorPrompt.cjs.map +1 -1
- package/dist/extensions/orchestratorPrompt.mjs +5 -6
- package/dist/extensions/orchestratorPrompt.mjs.map +1 -1
- package/dist/extensions/schemas.mjs.map +1 -1
- package/dist/extensions/spawnPlan.cjs +3 -2
- package/dist/extensions/spawnPlan.cjs.map +1 -1
- package/dist/extensions/spawnPlan.mjs +3 -2
- package/dist/extensions/spawnPlan.mjs.map +1 -1
- package/dist/extensions/standard.cjs +1 -1
- package/dist/extensions/standard.cjs.map +1 -1
- package/dist/extensions/standard.mjs +1 -1
- package/dist/extensions/standard.mjs.map +1 -1
- package/dist/extensions/statusViews.cjs +1 -1
- package/dist/extensions/statusViews.cjs.map +1 -1
- package/dist/extensions/statusViews.mjs +2 -2
- package/dist/extensions/statusViews.mjs.map +1 -1
- package/dist/extensions/subagentPromptRuntime.cjs +11 -11
- package/dist/extensions/subagentPromptRuntime.cjs.map +1 -1
- package/dist/extensions/subagentPromptRuntime.d.cts.map +1 -1
- package/dist/extensions/subagentPromptRuntime.d.mts.map +1 -1
- package/dist/extensions/subagentPromptRuntime.mjs +5 -5
- package/dist/extensions/subagentPromptRuntime.mjs.map +1 -1
- package/dist/extensions/subagentTool.cjs +5 -6
- package/dist/extensions/subagentTool.cjs.map +1 -1
- package/dist/extensions/subagentTool.mjs +5 -6
- package/dist/extensions/subagentTool.mjs.map +1 -1
- package/dist/extensions/toolDescription.cjs +1 -1
- package/dist/extensions/toolDescription.cjs.map +1 -1
- package/dist/extensions/toolDescription.mjs +1 -1
- package/dist/extensions/toolDescription.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/runs/background/cliRunnerEntry.cjs +1 -2
- package/dist/runs/background/cliRunnerEntry.mjs +1 -2
- package/dist/runs/sdkRunnerEntry.cjs +1 -2
- package/dist/runs/sdkRunnerEntry.d.cts +2 -1
- package/dist/runs/sdkRunnerEntry.d.mts +2 -1
- package/dist/runs/sdkRunnerEntry.mjs +1 -2
- package/dist/schemas/team/backgroundWork.cjs +2 -0
- package/dist/schemas/team/backgroundWork.cjs.map +1 -0
- package/dist/{protocol → schemas/team}/backgroundWork.d.cts +2 -2
- package/dist/schemas/team/backgroundWork.d.cts.map +1 -0
- package/dist/{protocol → schemas/team}/backgroundWork.d.mts +2 -2
- package/dist/schemas/team/backgroundWork.d.mts.map +1 -0
- package/dist/schemas/team/backgroundWork.mjs +2 -0
- package/dist/{protocol/backgroundWork.cjs.map → schemas/team/backgroundWork.mjs.map} +1 -1
- package/dist/schemas/team/capabilityCeiling.cjs +2 -0
- package/dist/schemas/team/capabilityCeiling.cjs.map +1 -0
- package/dist/{protocol → schemas/team}/capabilityCeiling.d.cts +3 -3
- package/dist/schemas/team/capabilityCeiling.d.cts.map +1 -0
- package/dist/{protocol → schemas/team}/capabilityCeiling.d.mts +3 -3
- package/dist/schemas/team/capabilityCeiling.d.mts.map +1 -0
- package/dist/schemas/team/capabilityCeiling.mjs +2 -0
- package/dist/schemas/team/capabilityCeiling.mjs.map +1 -0
- package/dist/schemas/team/delegation.cjs.map +1 -0
- package/dist/{protocol → schemas/team}/delegation.d.cts +1 -1
- package/dist/schemas/team/delegation.d.cts.map +1 -0
- package/dist/{protocol → schemas/team}/delegation.d.mts +1 -1
- package/dist/schemas/team/delegation.d.mts.map +1 -0
- package/dist/schemas/team/delegation.mjs.map +1 -0
- package/dist/schemas/team/packageConfig.cjs +2 -0
- package/dist/schemas/team/packageConfig.cjs.map +1 -0
- package/dist/schemas/team/packageConfig.mjs +2 -0
- package/dist/schemas/team/packageConfig.mjs.map +1 -0
- package/dist/services/agents/discovery.cjs +2 -0
- package/dist/services/agents/discovery.cjs.map +1 -0
- package/dist/services/agents/discovery.mjs +2 -0
- package/dist/services/agents/discovery.mjs.map +1 -0
- package/dist/services/agents/frontmatter.cjs.map +1 -0
- package/dist/{agents → services/agents}/frontmatter.mjs +1 -1
- package/dist/services/agents/frontmatter.mjs.map +1 -0
- package/dist/services/agents/loader.cjs +2 -0
- package/dist/services/agents/loader.cjs.map +1 -0
- package/dist/services/agents/loader.mjs +2 -0
- package/dist/services/agents/loader.mjs.map +1 -0
- package/dist/services/agents/memory.cjs +3 -0
- package/dist/services/agents/memory.cjs.map +1 -0
- package/dist/{agents → services/agents}/memory.mjs +1 -1
- package/dist/services/agents/memory.mjs.map +1 -0
- package/dist/services/agents/projectRoot.cjs +2 -0
- package/dist/services/agents/projectRoot.cjs.map +1 -0
- package/dist/services/agents/projectRoot.mjs +2 -0
- package/dist/services/agents/projectRoot.mjs.map +1 -0
- package/dist/services/agents/settings.cjs +2 -0
- package/dist/services/agents/settings.cjs.map +1 -0
- package/dist/services/agents/settings.mjs +2 -0
- package/dist/services/agents/settings.mjs.map +1 -0
- package/dist/services/agents/skills.cjs +0 -0
- package/dist/services/agents/skills.cjs.map +1 -0
- package/dist/services/agents/skills.mjs +5 -0
- package/dist/services/agents/skills.mjs.map +1 -0
- package/dist/{agents → services/agents}/types.d.cts +4 -4
- package/dist/services/agents/types.d.cts.map +1 -0
- package/dist/{agents → services/agents}/types.d.mts +4 -4
- package/dist/services/agents/types.d.mts.map +1 -0
- package/dist/services/api/backgroundWork.cjs +1 -0
- package/dist/services/api/backgroundWork.d.cts +1 -0
- package/dist/services/api/backgroundWork.d.mts +1 -0
- package/dist/services/api/backgroundWork.mjs +1 -0
- package/dist/services/api/capabilityCeiling.cjs +1 -0
- package/dist/services/api/capabilityCeiling.d.cts +2 -0
- package/dist/services/api/capabilityCeiling.d.mts +2 -0
- package/dist/services/api/capabilityCeiling.mjs +2 -0
- package/dist/services/api/capabilityCeiling.mjs.map +1 -0
- package/dist/services/api/teamSnapshot.cjs +1 -0
- package/dist/services/api/teamSnapshot.d.cts +1 -0
- package/dist/services/api/teamSnapshot.d.mts +1 -0
- package/dist/services/api/teamSnapshot.mjs +2 -0
- package/dist/services/api/teamSnapshot.mjs.map +1 -0
- package/dist/services/intercom/intercomBridge.cjs.map +1 -0
- package/dist/services/intercom/intercomBridge.mjs.map +1 -0
- package/dist/services/intercom/nativeTeamChannel.cjs +5 -0
- package/dist/services/intercom/nativeTeamChannel.cjs.map +1 -0
- package/dist/{intercom → services/intercom}/nativeTeamChannel.d.cts +1 -1
- package/dist/services/intercom/nativeTeamChannel.d.cts.map +1 -0
- package/dist/{intercom → services/intercom}/nativeTeamChannel.d.mts +1 -1
- package/dist/services/intercom/nativeTeamChannel.d.mts.map +1 -0
- package/dist/services/intercom/nativeTeamChannel.mjs +5 -0
- package/dist/services/intercom/nativeTeamChannel.mjs.map +1 -0
- package/dist/services/intercom/supervisorControlChannel.cjs +2 -0
- package/dist/services/intercom/supervisorControlChannel.cjs.map +1 -0
- package/dist/{intercom → services/intercom}/supervisorControlChannel.mjs +1 -1
- package/dist/services/intercom/supervisorControlChannel.mjs.map +1 -0
- package/dist/services/runs/background/asyncExecution.cjs +2 -0
- package/dist/services/runs/background/asyncExecution.cjs.map +1 -0
- package/dist/services/runs/background/asyncExecution.mjs +2 -0
- package/dist/services/runs/background/asyncExecution.mjs.map +1 -0
- package/dist/services/runs/background/asyncJobTracker.cjs +1 -0
- package/dist/services/runs/background/asyncJobTracker.mjs +2 -0
- package/dist/services/runs/background/asyncJobTracker.mjs.map +1 -0
- package/dist/services/runs/background/cliRunnerEntry.cjs +2 -0
- package/dist/services/runs/background/cliRunnerEntry.cjs.map +1 -0
- package/dist/services/runs/background/cliRunnerEntry.mjs +2 -0
- package/dist/services/runs/background/cliRunnerEntry.mjs.map +1 -0
- package/dist/services/runs/background/completionBatcher.cjs.map +1 -0
- package/dist/services/runs/background/completionBatcher.mjs +2 -0
- package/dist/services/runs/background/completionBatcher.mjs.map +1 -0
- package/dist/{runs → services/runs}/background/notify.cjs +1 -1
- package/dist/services/runs/background/notify.cjs.map +1 -0
- package/dist/{runs → services/runs}/background/notify.mjs +1 -1
- package/dist/services/runs/background/notify.mjs.map +1 -0
- package/dist/services/runs/background/parentWatchdog.cjs.map +1 -0
- package/dist/services/runs/background/parentWatchdog.mjs.map +1 -0
- package/dist/services/runs/background/processTerminal.cjs +1 -0
- package/dist/services/runs/background/processTerminal.mjs +2 -0
- package/dist/services/runs/background/processTerminal.mjs.map +1 -0
- package/dist/services/runs/background/resultWatcher.cjs +1 -0
- package/dist/services/runs/background/resultWatcher.mjs +2 -0
- package/dist/services/runs/background/resultWatcher.mjs.map +1 -0
- package/dist/services/runs/background/runIdResolver.cjs +1 -0
- package/dist/services/runs/background/runIdResolver.mjs +2 -0
- package/dist/services/runs/background/runIdResolver.mjs.map +1 -0
- package/dist/services/runs/background/runnerBootstrap.cjs +2 -0
- package/dist/services/runs/background/runnerBootstrap.cjs.map +1 -0
- package/dist/services/runs/background/runnerBootstrap.mjs +2 -0
- package/dist/services/runs/background/runnerBootstrap.mjs.map +1 -0
- package/dist/services/runs/background/runnerExecution.cjs +2 -0
- package/dist/services/runs/background/runnerExecution.cjs.map +1 -0
- package/dist/services/runs/background/runnerExecution.mjs +2 -0
- package/dist/services/runs/background/runnerExecution.mjs.map +1 -0
- package/dist/{runs → services/runs}/background/runnerReporting.cjs +2 -2
- package/dist/services/runs/background/runnerReporting.cjs.map +1 -0
- package/dist/{runs → services/runs}/background/runnerReporting.mjs +2 -2
- package/dist/services/runs/background/runnerReporting.mjs.map +1 -0
- package/dist/services/runs/background/runnerTeamMembership.cjs +2 -0
- package/dist/services/runs/background/runnerTeamMembership.cjs.map +1 -0
- package/dist/services/runs/background/runnerTeamMembership.mjs +2 -0
- package/dist/services/runs/background/runnerTeamMembership.mjs.map +1 -0
- package/dist/services/runs/background/spawnHandshake.cjs +2 -0
- package/dist/services/runs/background/spawnHandshake.cjs.map +1 -0
- package/dist/services/runs/background/spawnHandshake.mjs +2 -0
- package/dist/services/runs/background/spawnHandshake.mjs.map +1 -0
- package/dist/services/runs/background/staleRunReconciler.cjs +1 -0
- package/dist/services/runs/background/staleRunReconciler.mjs +2 -0
- package/dist/services/runs/background/staleRunReconciler.mjs.map +1 -0
- package/dist/services/runs/background/statusReader.cjs +1 -0
- package/dist/services/runs/background/statusReader.mjs +2 -0
- package/dist/services/runs/background/statusReader.mjs.map +1 -0
- package/dist/services/runs/background/statusWriter.cjs +2 -0
- package/dist/services/runs/background/statusWriter.cjs.map +1 -0
- package/dist/services/runs/background/statusWriter.mjs +2 -0
- package/dist/services/runs/background/statusWriter.mjs.map +1 -0
- package/dist/services/runs/background/subagentWait.cjs +2 -0
- package/dist/services/runs/background/subagentWait.cjs.map +1 -0
- package/dist/services/runs/background/subagentWait.mjs +2 -0
- package/dist/services/runs/background/subagentWait.mjs.map +1 -0
- package/dist/{runs/background/terminalPersistence.mjs → services/runs/background/terminalPersistence.cjs} +2 -2
- package/dist/services/runs/background/terminalPersistence.cjs.map +1 -0
- package/dist/services/runs/background/terminalPersistence.mjs +2 -0
- package/dist/services/runs/background/terminalPersistence.mjs.map +1 -0
- package/dist/services/runs/extensionErrorTelemetry.cjs +2 -0
- package/dist/services/runs/extensionErrorTelemetry.cjs.map +1 -0
- package/dist/services/runs/extensionErrorTelemetry.mjs +2 -0
- package/dist/services/runs/extensionErrorTelemetry.mjs.map +1 -0
- package/dist/services/runs/registry/processGroup.cjs.map +1 -0
- package/dist/services/runs/registry/processGroup.mjs.map +1 -0
- package/dist/services/runs/registry/runRegistry.cjs +1 -0
- package/dist/services/runs/registry/runRegistry.mjs +2 -0
- package/dist/services/runs/registry/runRegistry.mjs.map +1 -0
- package/dist/services/runs/registry/sessionLifecycle.cjs +2 -0
- package/dist/services/runs/registry/sessionLifecycle.cjs.map +1 -0
- package/dist/services/runs/registry/sessionLifecycle.mjs +2 -0
- package/dist/services/runs/registry/sessionLifecycle.mjs.map +1 -0
- package/dist/services/runs/registry/suspendedRuns.cjs +1 -0
- package/dist/services/runs/registry/suspendedRuns.mjs +2 -0
- package/dist/services/runs/registry/suspendedRuns.mjs.map +1 -0
- package/dist/services/runs/sdkRunnerEntry.cjs +1 -0
- package/dist/services/runs/sdkRunnerEntry.mjs +1 -0
- package/dist/services/runs/shared/claudeFableProfile.cjs +4 -0
- package/dist/services/runs/shared/claudeFableProfile.cjs.map +1 -0
- package/dist/services/runs/shared/claudeFableProfile.mjs +4 -0
- package/dist/services/runs/shared/claudeFableProfile.mjs.map +1 -0
- package/dist/services/runs/shared/depthGuard.cjs +2 -0
- package/dist/services/runs/shared/depthGuard.cjs.map +1 -0
- package/dist/services/runs/shared/depthGuard.mjs +2 -0
- package/dist/services/runs/shared/depthGuard.mjs.map +1 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.cjs +2 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.cjs.map +1 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.mjs +2 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.mjs.map +1 -0
- package/dist/services/runs/shared/modelFallback.cjs +2 -0
- package/dist/services/runs/shared/modelFallback.cjs.map +1 -0
- package/dist/services/runs/shared/modelFallback.mjs +2 -0
- package/dist/services/runs/shared/modelFallback.mjs.map +1 -0
- package/dist/services/runs/shared/modelScope.cjs +2 -0
- package/dist/services/runs/shared/modelScope.cjs.map +1 -0
- package/dist/services/runs/shared/modelScope.mjs +2 -0
- package/dist/services/runs/shared/modelScope.mjs.map +1 -0
- package/dist/services/runs/shared/nestedPath.cjs +2 -0
- package/dist/services/runs/shared/nestedPath.cjs.map +1 -0
- package/dist/services/runs/shared/nestedPath.mjs +2 -0
- package/dist/services/runs/shared/nestedPath.mjs.map +1 -0
- package/dist/services/runs/shared/piArgs.cjs +2 -0
- package/dist/services/runs/shared/piArgs.cjs.map +1 -0
- package/dist/services/runs/shared/piArgs.mjs +2 -0
- package/dist/services/runs/shared/piArgs.mjs.map +1 -0
- package/dist/services/runs/shared/piSpawn.cjs +2 -0
- package/dist/services/runs/shared/piSpawn.cjs.map +1 -0
- package/dist/services/runs/shared/piSpawn.mjs +2 -0
- package/dist/services/runs/shared/piSpawn.mjs.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.cjs +2 -0
- package/dist/services/runs/shared/runWithConcurrency.cjs.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.d.cts +25 -0
- package/dist/services/runs/shared/runWithConcurrency.d.cts.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.d.mts +25 -0
- package/dist/services/runs/shared/runWithConcurrency.d.mts.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.mjs +2 -0
- package/dist/services/runs/shared/runWithConcurrency.mjs.map +1 -0
- package/dist/services/runs/shared/runtimeRegistry.cjs +2 -0
- package/dist/services/runs/shared/runtimeRegistry.cjs.map +1 -0
- package/dist/services/runs/shared/runtimeRegistry.mjs +2 -0
- package/dist/services/runs/shared/runtimeRegistry.mjs.map +1 -0
- package/dist/services/runs/shared/structuredOutput.cjs +2 -0
- package/dist/{runs → services/runs}/shared/structuredOutput.cjs.map +1 -1
- package/dist/services/runs/shared/structuredOutput.mjs +2 -0
- package/dist/{runs → services/runs}/shared/structuredOutput.mjs.map +1 -1
- package/dist/services/runs/shared/toolAvailability.cjs +2 -0
- package/dist/services/runs/shared/toolAvailability.cjs.map +1 -0
- package/dist/services/runs/shared/toolAvailability.mjs +2 -0
- package/dist/services/runs/shared/toolAvailability.mjs.map +1 -0
- package/dist/services/runs/shared/toolBudget.cjs +2 -0
- package/dist/services/runs/shared/toolBudget.cjs.map +1 -0
- package/dist/services/runs/shared/toolBudget.mjs +2 -0
- package/dist/services/runs/shared/toolBudget.mjs.map +1 -0
- package/dist/shared/artifacts.cjs.map +1 -1
- package/dist/shared/artifacts.mjs.map +1 -1
- package/dist/shared/atomicJson.cjs +1 -2
- package/dist/shared/atomicJson.mjs +1 -1
- package/dist/shared/atomicJson.mjs.map +1 -1
- package/dist/shared/childTranscript.cjs +1 -5
- package/dist/shared/childTranscript.mjs +1 -4
- package/dist/shared/childTranscript.mjs.map +1 -1
- package/dist/shared/fileSystemRetry.cjs +1 -1
- package/dist/shared/fileSystemRetry.cjs.map +1 -1
- package/dist/shared/fileSystemRetry.mjs +1 -1
- package/dist/shared/fileSystemRetry.mjs.map +1 -1
- package/dist/shared/formatters.cjs.map +1 -1
- package/dist/shared/formatters.mjs.map +1 -1
- package/dist/shared/paths.cjs +1 -1
- package/dist/shared/paths.cjs.map +1 -1
- package/dist/shared/paths.mjs +1 -1
- package/dist/shared/paths.mjs.map +1 -1
- package/dist/shared/pollScheduler.cjs +1 -2
- package/dist/shared/pollScheduler.mjs +1 -1
- package/dist/shared/pollScheduler.mjs.map +1 -1
- package/dist/shared/scopeOwner.cjs +1 -2
- package/dist/shared/scopeOwner.mjs +1 -1
- package/dist/shared/scopeOwner.mjs.map +1 -1
- package/dist/tui/agentCatalog.cjs +1 -1
- package/dist/tui/agentCatalog.cjs.map +1 -1
- package/dist/tui/agentCatalog.mjs +1 -1
- package/dist/tui/agentCatalog.mjs.map +1 -1
- package/dist/tui/agentResourceProjection.cjs +1 -1
- package/dist/tui/agentResourceProjection.cjs.map +1 -1
- package/dist/tui/agentResourceProjection.mjs +1 -1
- package/dist/tui/agentResourceProjection.mjs.map +1 -1
- package/dist/tui/completionNotice.cjs +1 -1
- package/dist/tui/completionNotice.cjs.map +1 -1
- package/dist/tui/completionNotice.mjs +1 -1
- package/dist/tui/completionNotice.mjs.map +1 -1
- package/dist/tui/fleet.cjs +2 -2
- package/dist/tui/fleet.cjs.map +1 -1
- package/dist/tui/fleet.mjs +2 -2
- package/dist/tui/fleet.mjs.map +1 -1
- package/dist/tui/fleetAgentView.cjs.map +1 -1
- package/dist/tui/fleetAgentView.mjs.map +1 -1
- package/dist/tui/fleetStatus.cjs +1 -1
- package/dist/tui/fleetStatus.cjs.map +1 -1
- package/dist/tui/fleetStatus.mjs +1 -1
- package/dist/tui/fleetStatus.mjs.map +1 -1
- package/dist/tui/fleetTranscript.cjs +1 -1
- package/dist/tui/fleetTranscript.cjs.map +1 -1
- package/dist/tui/fleetTranscript.mjs +1 -1
- package/dist/tui/fleetTranscript.mjs.map +1 -1
- package/dist/tui/register.cjs +1 -1
- package/dist/tui/register.cjs.map +1 -1
- package/dist/tui/register.mjs +1 -1
- package/dist/tui/register.mjs.map +1 -1
- package/dist/tui/subagentToolRender.cjs +3 -0
- package/dist/tui/subagentToolRender.cjs.map +1 -0
- package/dist/tui/subagentToolRender.mjs +3 -0
- package/dist/tui/subagentToolRender.mjs.map +1 -0
- package/dist/{types.d.cts → types/index.d.cts} +2 -2
- package/dist/types/index.d.cts.map +1 -0
- package/dist/{types.d.mts → types/index.d.mts} +2 -2
- package/dist/types/index.d.mts.map +1 -0
- package/package.json +23 -16
- package/dist/agents/discovery.cjs +0 -2
- package/dist/agents/discovery.cjs.map +0 -1
- package/dist/agents/discovery.mjs +0 -2
- package/dist/agents/discovery.mjs.map +0 -1
- package/dist/agents/frontmatter.cjs.map +0 -1
- package/dist/agents/frontmatter.mjs.map +0 -1
- package/dist/agents/loader.cjs +0 -2
- package/dist/agents/loader.cjs.map +0 -1
- package/dist/agents/loader.mjs +0 -2
- package/dist/agents/loader.mjs.map +0 -1
- package/dist/agents/memory.cjs +0 -3
- package/dist/agents/memory.cjs.map +0 -1
- package/dist/agents/memory.mjs.map +0 -1
- package/dist/agents/projectRoot.cjs +0 -2
- package/dist/agents/projectRoot.cjs.map +0 -1
- package/dist/agents/projectRoot.mjs +0 -2
- package/dist/agents/projectRoot.mjs.map +0 -1
- package/dist/agents/settings.cjs +0 -2
- package/dist/agents/settings.cjs.map +0 -1
- package/dist/agents/settings.mjs +0 -2
- package/dist/agents/settings.mjs.map +0 -1
- package/dist/agents/skills.cjs +0 -0
- package/dist/agents/skills.cjs.map +0 -1
- package/dist/agents/skills.mjs +0 -5
- package/dist/agents/skills.mjs.map +0 -1
- package/dist/agents/types.d.cts.map +0 -1
- package/dist/agents/types.d.mts.map +0 -1
- package/dist/api/capabilityCeiling.mjs.map +0 -1
- package/dist/api/teamSnapshot.mjs.map +0 -1
- package/dist/env.cjs.map +0 -1
- package/dist/env.d.cts.map +0 -1
- package/dist/env.d.mts.map +0 -1
- package/dist/extension.cjs +0 -2
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.mjs +0 -2
- package/dist/extension.mjs.map +0 -1
- package/dist/intercom/intercomBridge.cjs.map +0 -1
- package/dist/intercom/intercomBridge.mjs.map +0 -1
- package/dist/intercom/nativeTeamChannel.cjs +0 -5
- package/dist/intercom/nativeTeamChannel.cjs.map +0 -1
- package/dist/intercom/nativeTeamChannel.d.cts.map +0 -1
- package/dist/intercom/nativeTeamChannel.d.mts.map +0 -1
- package/dist/intercom/nativeTeamChannel.mjs +0 -5
- package/dist/intercom/nativeTeamChannel.mjs.map +0 -1
- package/dist/intercom/supervisorControlChannel.cjs +0 -2
- package/dist/intercom/supervisorControlChannel.cjs.map +0 -1
- package/dist/intercom/supervisorControlChannel.mjs.map +0 -1
- package/dist/protocol/backgroundWork.cjs +0 -2
- package/dist/protocol/backgroundWork.d.cts.map +0 -1
- package/dist/protocol/backgroundWork.d.mts.map +0 -1
- package/dist/protocol/backgroundWork.mjs +0 -2
- package/dist/protocol/backgroundWork.mjs.map +0 -1
- package/dist/protocol/capabilityCeiling.cjs +0 -2
- package/dist/protocol/capabilityCeiling.cjs.map +0 -1
- package/dist/protocol/capabilityCeiling.d.cts.map +0 -1
- package/dist/protocol/capabilityCeiling.d.mts.map +0 -1
- package/dist/protocol/capabilityCeiling.mjs +0 -2
- package/dist/protocol/capabilityCeiling.mjs.map +0 -1
- package/dist/protocol/delegation.cjs.map +0 -1
- package/dist/protocol/delegation.d.cts.map +0 -1
- package/dist/protocol/delegation.d.mts.map +0 -1
- package/dist/protocol/delegation.mjs.map +0 -1
- package/dist/runs/background/asyncExecution.cjs +0 -2
- package/dist/runs/background/asyncExecution.cjs.map +0 -1
- package/dist/runs/background/asyncExecution.mjs +0 -2
- package/dist/runs/background/asyncExecution.mjs.map +0 -1
- package/dist/runs/background/asyncJobTracker.cjs +0 -2
- package/dist/runs/background/asyncJobTracker.cjs.map +0 -1
- package/dist/runs/background/asyncJobTracker.mjs +0 -2
- package/dist/runs/background/asyncJobTracker.mjs.map +0 -1
- package/dist/runs/background/cliRunnerEntry.cjs.map +0 -1
- package/dist/runs/background/cliRunnerEntry.mjs.map +0 -1
- package/dist/runs/background/completionBatcher.cjs.map +0 -1
- package/dist/runs/background/completionBatcher.mjs +0 -2
- package/dist/runs/background/completionBatcher.mjs.map +0 -1
- package/dist/runs/background/notify.cjs.map +0 -1
- package/dist/runs/background/notify.mjs.map +0 -1
- package/dist/runs/background/parentWatchdog.cjs.map +0 -1
- package/dist/runs/background/parentWatchdog.mjs.map +0 -1
- package/dist/runs/background/processTerminal.cjs +0 -2
- package/dist/runs/background/processTerminal.cjs.map +0 -1
- package/dist/runs/background/processTerminal.mjs +0 -2
- package/dist/runs/background/processTerminal.mjs.map +0 -1
- package/dist/runs/background/resultWatcher.cjs +0 -2
- package/dist/runs/background/resultWatcher.cjs.map +0 -1
- package/dist/runs/background/resultWatcher.mjs +0 -2
- package/dist/runs/background/resultWatcher.mjs.map +0 -1
- package/dist/runs/background/runIdResolver.cjs +0 -2
- package/dist/runs/background/runIdResolver.cjs.map +0 -1
- package/dist/runs/background/runIdResolver.mjs +0 -2
- package/dist/runs/background/runIdResolver.mjs.map +0 -1
- package/dist/runs/background/runnerBootstrap.cjs +0 -2
- package/dist/runs/background/runnerBootstrap.cjs.map +0 -1
- package/dist/runs/background/runnerBootstrap.mjs +0 -2
- package/dist/runs/background/runnerBootstrap.mjs.map +0 -1
- package/dist/runs/background/runnerExecution.cjs +0 -2
- package/dist/runs/background/runnerExecution.cjs.map +0 -1
- package/dist/runs/background/runnerExecution.mjs +0 -2
- package/dist/runs/background/runnerExecution.mjs.map +0 -1
- package/dist/runs/background/runnerReporting.cjs.map +0 -1
- package/dist/runs/background/runnerReporting.mjs.map +0 -1
- package/dist/runs/background/runnerTeamMembership.cjs +0 -2
- package/dist/runs/background/runnerTeamMembership.cjs.map +0 -1
- package/dist/runs/background/runnerTeamMembership.mjs +0 -2
- package/dist/runs/background/runnerTeamMembership.mjs.map +0 -1
- package/dist/runs/background/spawnHandshake.cjs +0 -2
- package/dist/runs/background/spawnHandshake.cjs.map +0 -1
- package/dist/runs/background/spawnHandshake.mjs +0 -2
- package/dist/runs/background/spawnHandshake.mjs.map +0 -1
- package/dist/runs/background/staleRunReconciler.cjs +0 -2
- package/dist/runs/background/staleRunReconciler.cjs.map +0 -1
- package/dist/runs/background/staleRunReconciler.mjs +0 -2
- package/dist/runs/background/staleRunReconciler.mjs.map +0 -1
- package/dist/runs/background/statusReader.cjs +0 -2
- package/dist/runs/background/statusReader.cjs.map +0 -1
- package/dist/runs/background/statusReader.mjs +0 -2
- package/dist/runs/background/statusReader.mjs.map +0 -1
- package/dist/runs/background/statusWriter.cjs +0 -2
- package/dist/runs/background/statusWriter.cjs.map +0 -1
- package/dist/runs/background/statusWriter.mjs +0 -2
- package/dist/runs/background/statusWriter.mjs.map +0 -1
- package/dist/runs/background/subagentWait.cjs +0 -2
- package/dist/runs/background/subagentWait.cjs.map +0 -1
- package/dist/runs/background/subagentWait.mjs +0 -2
- package/dist/runs/background/subagentWait.mjs.map +0 -1
- package/dist/runs/background/terminalPersistence.cjs +0 -2
- package/dist/runs/background/terminalPersistence.cjs.map +0 -1
- package/dist/runs/background/terminalPersistence.mjs.map +0 -1
- package/dist/runs/registry/processGroup.cjs.map +0 -1
- package/dist/runs/registry/processGroup.mjs.map +0 -1
- package/dist/runs/registry/runRegistry.cjs +0 -2
- package/dist/runs/registry/runRegistry.cjs.map +0 -1
- package/dist/runs/registry/runRegistry.mjs +0 -2
- package/dist/runs/registry/runRegistry.mjs.map +0 -1
- package/dist/runs/registry/sessionLifecycle.cjs +0 -2
- package/dist/runs/registry/sessionLifecycle.cjs.map +0 -1
- package/dist/runs/registry/sessionLifecycle.mjs +0 -2
- package/dist/runs/registry/sessionLifecycle.mjs.map +0 -1
- package/dist/runs/registry/suspendedRuns.cjs +0 -4
- package/dist/runs/registry/suspendedRuns.cjs.map +0 -1
- package/dist/runs/registry/suspendedRuns.mjs +0 -4
- package/dist/runs/registry/suspendedRuns.mjs.map +0 -1
- package/dist/runs/sdkRunnerEntry.cjs.map +0 -1
- package/dist/runs/sdkRunnerEntry.mjs.map +0 -1
- package/dist/runs/shared/claudeFableProfile.cjs +0 -4
- package/dist/runs/shared/claudeFableProfile.cjs.map +0 -1
- package/dist/runs/shared/claudeFableProfile.mjs +0 -4
- package/dist/runs/shared/claudeFableProfile.mjs.map +0 -1
- package/dist/runs/shared/depthGuard.cjs +0 -2
- package/dist/runs/shared/depthGuard.cjs.map +0 -1
- package/dist/runs/shared/depthGuard.mjs +0 -2
- package/dist/runs/shared/depthGuard.mjs.map +0 -1
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs +0 -2
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs.map +0 -1
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs +0 -2
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs.map +0 -1
- package/dist/runs/shared/modelFallback.cjs +0 -2
- package/dist/runs/shared/modelFallback.cjs.map +0 -1
- package/dist/runs/shared/modelFallback.mjs +0 -2
- package/dist/runs/shared/modelFallback.mjs.map +0 -1
- package/dist/runs/shared/modelScope.cjs +0 -2
- package/dist/runs/shared/modelScope.cjs.map +0 -1
- package/dist/runs/shared/modelScope.mjs +0 -2
- package/dist/runs/shared/modelScope.mjs.map +0 -1
- package/dist/runs/shared/nestedPath.cjs +0 -2
- package/dist/runs/shared/nestedPath.cjs.map +0 -1
- package/dist/runs/shared/nestedPath.mjs +0 -2
- package/dist/runs/shared/nestedPath.mjs.map +0 -1
- package/dist/runs/shared/piArgs.cjs +0 -2
- package/dist/runs/shared/piArgs.cjs.map +0 -1
- package/dist/runs/shared/piArgs.mjs +0 -2
- package/dist/runs/shared/piArgs.mjs.map +0 -1
- package/dist/runs/shared/piSpawn.cjs +0 -2
- package/dist/runs/shared/piSpawn.cjs.map +0 -1
- package/dist/runs/shared/piSpawn.mjs +0 -2
- package/dist/runs/shared/piSpawn.mjs.map +0 -1
- package/dist/runs/shared/runWithConcurrency.cjs +0 -2
- package/dist/runs/shared/runWithConcurrency.cjs.map +0 -1
- package/dist/runs/shared/runWithConcurrency.mjs +0 -2
- package/dist/runs/shared/runWithConcurrency.mjs.map +0 -1
- package/dist/runs/shared/runtimeRegistry.cjs +0 -2
- package/dist/runs/shared/runtimeRegistry.cjs.map +0 -1
- package/dist/runs/shared/runtimeRegistry.mjs +0 -2
- package/dist/runs/shared/runtimeRegistry.mjs.map +0 -1
- package/dist/runs/shared/structuredOutput.cjs +0 -2
- package/dist/runs/shared/structuredOutput.mjs +0 -2
- package/dist/runs/shared/toolAvailability.cjs +0 -2
- package/dist/runs/shared/toolAvailability.cjs.map +0 -1
- package/dist/runs/shared/toolAvailability.mjs +0 -2
- package/dist/runs/shared/toolAvailability.mjs.map +0 -1
- package/dist/runs/shared/toolBudget.cjs +0 -2
- package/dist/runs/shared/toolBudget.cjs.map +0 -1
- package/dist/runs/shared/toolBudget.mjs +0 -2
- package/dist/runs/shared/toolBudget.mjs.map +0 -1
- package/dist/shared/atomicJson.cjs.map +0 -1
- package/dist/shared/childTranscript.cjs.map +0 -1
- package/dist/shared/pollScheduler.cjs.map +0 -1
- package/dist/shared/scopeOwner.cjs.map +0 -1
- package/dist/slash/chainExpression.cjs.map +0 -1
- package/dist/slash/chainExpression.mjs.map +0 -1
- package/dist/slash/doctor.cjs +0 -5
- package/dist/slash/doctor.mjs +0 -5
- package/dist/slash/doctor.mjs.map +0 -1
- package/dist/slash/slashCommands.cjs.map +0 -1
- package/dist/slash/slashCommands.mjs.map +0 -1
- package/dist/slash/spawnRequestMapping.cjs.map +0 -1
- package/dist/slash/spawnRequestMapping.mjs.map +0 -1
- package/dist/slash/subagentLaunch.cjs +0 -2
- package/dist/slash/subagentLaunch.cjs.map +0 -1
- package/dist/slash/subagentLaunch.mjs +0 -2
- package/dist/slash/subagentLaunch.mjs.map +0 -1
- package/dist/types.d.cts.map +0 -1
- package/dist/types.d.mts.map +0 -1
- /package/dist/{slash → commands/slash}/chainExpression.cjs +0 -0
- /package/dist/{slash → commands/slash}/chainExpression.mjs +0 -0
- /package/dist/{slash → commands/slash}/spawnRequestMapping.cjs +0 -0
- /package/dist/{slash → commands/slash}/spawnRequestMapping.mjs +0 -0
- /package/dist/{protocol → schemas/team}/delegation.cjs +0 -0
- /package/dist/{protocol → schemas/team}/delegation.mjs +0 -0
- /package/dist/{agents → services/agents}/frontmatter.cjs +0 -0
- /package/dist/{intercom → services/intercom}/intercomBridge.cjs +0 -0
- /package/dist/{intercom → services/intercom}/intercomBridge.mjs +0 -0
- /package/dist/{runs → services/runs}/background/completionBatcher.cjs +0 -0
- /package/dist/{runs → services/runs}/background/parentWatchdog.cjs +0 -0
- /package/dist/{runs → services/runs}/background/parentWatchdog.mjs +0 -0
- /package/dist/{runs → services/runs}/registry/processGroup.cjs +0 -0
- /package/dist/{runs → services/runs}/registry/processGroup.mjs +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resultWatcher.mjs","names":[],"sources":["../../../src/runs/background/resultWatcher.ts"],"sourcesContent":["/**\n * Watches `currentResultsDir()` for a run's terminal result and delivers each one to\n * a consumer exactly once.\n *\n * WHY THIS EXISTS:\n * The predecessor's watcher\n * (`doom-pi-subagents/src/runs/background/resultWatcher.ts`) had four\n * separate, verified bugs. This is a redesign around the fixes, not a line\n * port - the fixes below are structural, not patches on the old control flow.\n *\n * FIX 1 - WATCH BOTH 'rename' AND 'change':\n * The predecessor only reacted to `event === 'rename'` (`resultWatcher.ts:398-399`),\n * so a result file rewritten in place - same path, new contents, no\n * create/delete/rename - was never noticed until the poll safety net happened\n * to run. `watchResultsDir()` below reacts to any `fs.watch` callback whose\n * filename matches, regardless of `eventType`, the same way\n * `SpawnHandshake.watchDirectory` already does in this package.\n *\n * FIX 2 - TTL DEDUPE:\n * The predecessor re-implemented \"is this a duplicate\" inline at its call\n * site (`resultWatcher.ts:180-195`) *and* separately inside\n * `completion-dedupe.ts`'s `markSeenWithTtl`, so the check and the record used\n * two independently maintained copies of the same expiry logic. Every check\n * and every record here goes through `hasRecentlyDelivered()` and\n * `markDelivered()` and nothing else - one implementation of the TTL policy,\n * used the same way on both sides.\n *\n * FIX 3 - TIMER TYPES:\n * The predecessor stored `setTimeout`'s return value in fields typed (or\n * used) as if they were numbers (`resultWatcher.ts:361,371,425`), which only\n * happens to work under Node's `setTimeout` and breaks the moment the return\n * value is compared, serialized, or ported somewhere `setTimeout` returns a\n * number (browsers, some polyfills). This module does not own a timer at all:\n * `fs.watch` is the fast path and `PollScheduler` is the bounded safety net\n * (see fix 5), so there is no `Timeout` handle here for the compiler to get\n * wrong in the first place.\n *\n * FIX 4 - ATOMIC CONSUME (TOCTOU):\n * The predecessor checked a result file's existence, then unlinked it, as two\n * separate steps (`resultWatcher.ts:184-194,324-332`), racing any other\n * reader of the same file between the two. Here, the *first* thing that\n * happens to a candidate result is an attempted rename into\n * `currentRunsDir()/<runId>/claimed-result.json` (`claimResult()`). A successful\n * rename is exclusive ownership: nothing else can also be holding that\n * result, because there is only ever one file at the source path and only one\n * caller's `renameSync` can move it. A failed rename (source already gone)\n * means another claimer got there first, which is `claimResult()`'s normal,\n * expected return of `false` - not a caught error.\n *\n * FIX 5 - WATCH-FIRST, POLL AS A BOUNDED SAFETY NET:\n * `start()` registers with `PollScheduler` instead of owning a timer, the\n * same pattern as every other long-lived background subscriber in this\n * directory. `fs.watch` gives low latency; the scheduler's tick is the bound\n * on how late a check can ever be on a platform or mount where watches are\n * unreliable. Neither path is trusted alone.\n *\n * DESIGN PATTERNS:\n * - A claimed result lives at `currentRunsDir()/<runId>/claimed-result.json`, inside\n * the run's own working directory rather than a subdirectory of\n * `currentResultsDir()`. Putting it under `currentResultsDir()` would make our own claims\n * visible to the very `fs.watch` this module registers, and would risk\n * colliding with `currentResultsDir()/<runId>.json` naming. `currentRunsDir()/<runId>/` is\n * already this run's own directory (see `statusWriter.ts`,\n * `terminalPersistence.ts`), so this reuses existing per-run space instead\n * of inventing a second one\n * - `run()` is synchronous, matching `PollScheduler`'s contract, but delivery\n * is not: `deliverClaim()` is fired without being awaited, and a claim's\n * `inFlight` flag stops it from being re-fired on the next tick while the\n * previous attempt is still outstanding\n * - `run()` reports \"work happened\" only when it claimed a new file, not when\n * it merely retried a still-pending delivery. A consumer that keeps\n * rejecting the same result forever would otherwise pin the scheduler's\n * backoff at the floor indefinitely\n * - A crash between claiming a result and delivering it leaves\n * `claimed-result.json` on disk with nothing tracking it in memory.\n * `start()` scans for these once, so a fresh process picks up an\n * interrupted delivery instead of leaving it stranded forever\n *\n * AVOID:\n * - Reading or unlinking anything at `currentResultsDir()/<runId>.json` once\n * `claimResult()` has returned true for it; ownership has moved to the\n * claimed copy, and the source name may already belong to an unrelated,\n * later result\n * - Treating a rename failure from `claimResult()` as noteworthy; the\n * `onProcessingError` path is for delivery failures and parse failures, not\n * for the ordinary case of losing a claim race\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { resolveWatchPath } from '../../shared/configDir.ts';\nimport { currentResultsDir, currentRunsDir } from '../../shared/paths.ts';\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport { SUBAGENT_RUN_ID_ENV } from '../../env.ts';\nimport type { IPollScheduler } from '../../shared/pollScheduler.ts';\n\n/** Suffix a candidate result file must have in `currentResultsDir()`. */\nexport const RESULT_FILE_SUFFIX = '.json';\n/** Leaf name of a claimed result inside `currentRunsDir()/<runId>/`. */\nexport const CLAIMED_RESULT_FILE_NAME = 'claimed-result.json';\n\n/** Poll safety-net cadence, generously looser than `fs.watch`'s latency (see `PollScheduler`'s header). */\nconst DEFAULT_POLL_INTERVAL_MS = 4_000;\n/** How long a delivered run id is remembered, so a re-appearing result for it is dropped instead of redelivered. */\nconst DEFAULT_DEDUPE_TTL_MS = 60_000;\n/** Cap on tracked delivered ids, so a very long session cannot grow this without bound. */\nconst MAX_TRACKED_DELIVERIES = 256;\n\nconst POLL_SUBSCRIBER_ID = 'result-watcher';\n\n/** The parsed contents of a result file, with `runId` guaranteed present. */\nexport interface RunResultFile {\n runId: string;\n [key: string]: unknown;\n}\n\n/**\n * Handles one delivered result. Returns (or resolves to) `true` once it has\n * been durably accepted, which is what lets the claimed copy be removed;\n * `false` means \"not yet\", and the same claim is retried on a later tick.\n */\nexport type ResultConsumer = (result: RunResultFile) => boolean | Promise<boolean>;\n\nexport interface IResultWatcher {\n /** Begin watching. Calling this again resets whatever was previously tracked, same as `CoalescedStatusWriter.open`. */\n start(consumer: ResultConsumer): void;\n /** Stop watching, unregister from the scheduler, and drop all in-memory state. */\n stop(): void;\n}\n\ninterface ClaimEntry {\n /** True while `deliverClaim()` is awaiting this claim's consumer call, so `run()` does not fire it twice. */\n inFlight: boolean;\n}\n\nfunction resultPathFor(file: string): string {\n return path.join(currentResultsDir(), file);\n}\n\nfunction runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\nfunction claimPathFor(runId: string): string {\n return path.join(runDirFor(runId), CLAIMED_RESULT_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\n@injectable()\nexport class ResultWatcher implements IResultWatcher {\n constructor(@inject(DOOM_TEAM_TYPES.PollScheduler) private readonly scheduler: IPollScheduler) {}\n\n /**\n * Seams for tests, as protected members rather than constructor arguments:\n * inversify cannot resolve a primitive, so a test subclass overrides these.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly dedupeTtlMs: number = DEFAULT_DEDUPE_TTL_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /** Records a claim or delivery failure. Overridable; never a stdio write (see package rules). */\n protected onProcessingError(id: string, error: unknown): void {\n this.lastProcessingError = { id, error, at: this.now() };\n this.processingErrorCount += 1;\n }\n\n /** The most recent processing failure, for diagnostics. */\n lastProcessingError: { id: string; error: unknown; at: number } | undefined;\n /** How many processing failures this watcher has absorbed. */\n processingErrorCount = 0;\n\n private consumer: ResultConsumer | undefined;\n private watcher: fs.FSWatcher | undefined;\n private unregisterPoll: (() => void) | undefined;\n private readonly claims = new Map<string, ClaimEntry>();\n private readonly deliveredAt = new Map<string, number>();\n\n // ---------------------------------------------------------------------\n // Filesystem seams. ESM module namespaces are frozen (`vi.spyOn(fs, ...)`\n // throws), so every `node:fs` call a test needs to redirect is wrapped here.\n // ---------------------------------------------------------------------\n\n protected listResultFiles(): string[] {\n try {\n return fs.readdirSync(currentResultsDir()).filter((entry) => entry.endsWith(RESULT_FILE_SUFFIX));\n } catch (error) {\n if (isNotFound(error)) return [];\n throw error;\n }\n }\n\n /** Run ids with a lingering claimed copy, found by scanning `currentRunsDir()` once at `start()`. */\n protected listClaimedRunIds(): string[] {\n try {\n return fs\n .readdirSync(currentRunsDir())\n .filter((entry) => this.fileExists(path.join(runDirFor(entry), CLAIMED_RESULT_FILE_NAME)));\n } catch (error) {\n if (isNotFound(error)) return [];\n throw error;\n }\n }\n\n protected fileExists(filePath: string): boolean {\n return fs.existsSync(filePath);\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n protected ensureDir(dirPath: string): void {\n fs.mkdirSync(dirPath, { recursive: true });\n }\n\n /**\n * The atomic claim (fix 4). Returns `false`, without throwing, when the\n * source is already gone - another claimer won the race, which is the\n * expected outcome of two watchers noticing the same file.\n */\n protected renameFile(fromPath: string, toPath: string): boolean {\n try {\n fs.renameSync(fromPath, toPath);\n return true;\n } catch (error) {\n if (isNotFound(error)) return false;\n throw error;\n }\n }\n\n protected unlinkFile(filePath: string): void {\n try {\n fs.unlinkSync(filePath);\n } catch (error) {\n if (!isNotFound(error)) this.onProcessingError(filePath, error);\n }\n }\n\n /**\n * The `fs.watch` callback's logic, factored out so it can be exercised\n * deterministically (fix 1's actual test coverage): calling this directly\n * with synthetic `eventType`/`filename` values proves the filter reacts to\n * 'change' exactly like 'rename', without depending on whether - or when -\n * the OS actually delivers a real filesystem event. `fs.watch` gives no\n * delivery guarantee at all, so a test that waits on a real one to arrive\n * is testing the OS's scheduler, not this code; see `watchResultsDir()`'s\n * only caller of this, and the poll safety net (fix 5) that exists\n * precisely because that guarantee does not hold.\n */\n protected handleWatchEvent(_eventType: fs.WatchEventType, filename: string | Buffer | null): void {\n if (!filename) return;\n if (!filename.toString().endsWith(RESULT_FILE_SUFFIX)) return;\n this.scheduler.wake();\n }\n\n /**\n * A watch that has emitted 'error' is treated as gone: `run()` checks\n * `getWatcher()` on its next tick and re-establishes it, falling back to\n * the poll safety net (fix 5) in the meantime. Factored out, like\n * `handleWatchEvent`, so a test can trigger it directly rather than by\n * provoking a real watch failure.\n */\n protected handleWatchError(): void {\n this.watcher = undefined;\n }\n\n /** The currently-held watch, if any. Exposed for tests; `run()` reads the field directly. */\n protected getWatcher(): fs.FSWatcher | undefined {\n return this.watcher;\n }\n\n /**\n * Watch `currentResultsDir()` for candidate result files. Reacts to every `fs.watch`\n * callback regardless of `eventType` (fix 1, see `handleWatchEvent`).\n * Returns `undefined`, without throwing, when the watch could not be\n * established at all - `run()` retries this on its next tick, and the poll\n * safety net (fix 5) covers the gap in the meantime.\n */\n protected watchResultsDir(): fs.FSWatcher | undefined {\n try {\n this.ensureDir(currentResultsDir());\n const watchPath = resolveWatchPath(currentResultsDir(), fs.realpathSync.native);\n const watcher = fs.watch(watchPath, (eventType, filename) => this.handleWatchEvent(eventType, filename));\n watcher.on('error', () => this.handleWatchError());\n watcher.unref?.();\n return watcher;\n } catch {\n return undefined;\n }\n }\n\n // ---------------------------------------------------------------------\n // TTL dedupe (fix 2). One implementation, used by both the check and the\n // record, so they cannot drift the way the predecessor's two copies did.\n // ---------------------------------------------------------------------\n\n private pruneDeliveredAt(now: number): void {\n for (const [key, at] of this.deliveredAt) {\n if (now - at > this.dedupeTtlMs) this.deliveredAt.delete(key);\n }\n }\n\n private hasRecentlyDelivered(runId: string): boolean {\n const now = this.now();\n this.pruneDeliveredAt(now);\n return this.deliveredAt.has(runId);\n }\n\n private markDelivered(runId: string): void {\n const now = this.now();\n this.pruneDeliveredAt(now);\n this.deliveredAt.set(runId, now);\n if (this.deliveredAt.size > MAX_TRACKED_DELIVERIES) {\n const oldest = this.deliveredAt.keys().next();\n if (!oldest.done) this.deliveredAt.delete(oldest.value);\n }\n }\n\n // ---------------------------------------------------------------------\n // Claim + deliver\n // ---------------------------------------------------------------------\n\n /** Attempt the atomic claim for one candidate file. `true` only when this call won the race. */\n protected claimResult(runId: string, file: string): boolean {\n this.ensureDir(runDirFor(runId));\n return this.renameFile(resultPathFor(file), claimPathFor(runId));\n }\n\n /** Claim every new candidate in `currentResultsDir()`. Returns how many were newly claimed. */\n private discoverAndClaim(): number {\n let claimedCount = 0;\n const ownRunId = process.env[SUBAGENT_RUN_ID_ENV]?.trim();\n for (const file of this.listResultFiles()) {\n const runId = file.slice(0, -RESULT_FILE_SUFFIX.length);\n // A nested-capable child loads the same Doom Team extension as its\n // parent. It must not claim the result for its own one-shot run.\n if (!runId || runId === ownRunId || this.claims.has(runId)) continue;\n if (this.claimResult(runId, file)) {\n this.claims.set(runId, { inFlight: false });\n claimedCount += 1;\n }\n // A `false` result means another claimer already took this file; that\n // is the normal outcome of a race, not an error (fix 4).\n }\n return claimedCount;\n }\n\n private discardClaim(runId: string): void {\n this.unlinkFile(claimPathFor(runId));\n this.claims.delete(runId);\n }\n\n private async deliverClaim(runId: string, entry: ClaimEntry): Promise<void> {\n const claimPath = claimPathFor(runId);\n try {\n const raw = this.readFile(claimPath);\n if (raw === undefined) {\n // Claimed file vanished from under us (manual cleanup, disk issue);\n // nothing left to deliver.\n this.claims.delete(runId);\n return;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n this.onProcessingError(runId, error);\n this.discardClaim(runId);\n return;\n }\n if (typeof parsed !== 'object' || parsed === null) {\n this.discardClaim(runId);\n return;\n }\n\n const parsedRecord = parsed as Record<string, unknown>;\n const resolvedRunId = typeof parsedRecord.runId === 'string' && parsedRecord.runId ? parsedRecord.runId : runId;\n if (this.hasRecentlyDelivered(resolvedRunId)) {\n // A duplicate write for a run we already delivered inside the TTL\n // window: dropped, not redelivered (fix 2).\n this.discardClaim(runId);\n return;\n }\n\n const consumer = this.consumer;\n if (!consumer) {\n // No subscriber yet (start() raced ahead of the caller wiring one\n // in); retry once one is registered.\n entry.inFlight = false;\n return;\n }\n\n const record: RunResultFile = { ...parsedRecord, runId: resolvedRunId };\n const accepted = await consumer(record);\n if (accepted) {\n this.markDelivered(resolvedRunId);\n this.discardClaim(runId);\n } else {\n entry.inFlight = false;\n }\n } catch (error) {\n this.onProcessingError(runId, error);\n entry.inFlight = false;\n }\n }\n\n /**\n * One scheduler tick. Synchronous, per `PollScheduler`'s contract: it kicks\n * off delivery attempts without awaiting them (`deliverClaim` runs to\n * completion on its own). Returns `true` only when a new result was\n * claimed this tick, so a consumer that keeps saying \"not yet\" cannot pin\n * the scheduler's backoff at the floor forever.\n */\n protected run(): boolean {\n if (!this.watcher) this.watcher = this.watchResultsDir();\n\n const claimedCount = this.discoverAndClaim();\n\n for (const [runId, entry] of this.claims) {\n if (entry.inFlight) continue;\n entry.inFlight = true;\n void this.deliverClaim(runId, entry);\n }\n\n return claimedCount > 0;\n }\n\n // ---------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------\n\n start(consumer: ResultConsumer): void {\n // A second start() is a reset, the same idiom as CoalescedStatusWriter.open().\n this.stop();\n this.consumer = consumer;\n for (const runId of this.listClaimedRunIds()) this.claims.set(runId, { inFlight: false });\n this.watcher = this.watchResultsDir();\n this.unregisterPoll = this.scheduler.register({\n id: POLL_SUBSCRIBER_ID,\n intervalMs: this.pollIntervalMs,\n run: () => this.run(),\n });\n }\n\n stop(): void {\n this.consumer = undefined;\n this.watcher?.close();\n this.watcher = undefined;\n this.unregisterPoll?.();\n this.unregisterPoll = undefined;\n this.claims.clear();\n this.deliveredAt.clear();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require(`../../_virtual/_rolldown/runtime.cjs`),t=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),n=require(`../../shared/paths.cjs`),r=require(`./resultWatcher.cjs`);let i=require(`node:fs`);i=e.__toESM(i,1);let a=require(`node:path`);a=e.__toESM(a,1);let o=require(`inversify`);function s(e){return a.join(n.currentRunsDir(),e)}function c(e){return a.join(n.currentResultsDir(),`${e}${r.RESULT_FILE_SUFFIX}`)}function l(e){return a.join(s(e),r.CLAIMED_RESULT_FILE_NAME)}function u(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function d(e){return u(e)===`ENOENT`}let f=class{pathExists(e){return i.existsSync(e)}runsDir(){return n.currentRunsDir()}resultsDir(){return n.currentResultsDir()}listRunDirNames(){try{return i.readdirSync(this.runsDir(),{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)}catch(e){if(d(e))return[];throw e}}listPendingResultIds(){try{return i.readdirSync(this.resultsDir()).filter(e=>e.endsWith(r.RESULT_FILE_SUFFIX)).map(e=>e.slice(0,-r.RESULT_FILE_SUFFIX.length))}catch(e){if(d(e))return[];throw e}}resolve(e){let t=this.locate(e);if(t)return t;let n=this.candidateRunIds().filter(t=>t.startsWith(e));if(n.length>1)throw Error(`Ambiguous run id prefix '${e}' matched: ${n.join(`, `)}. Provide a longer id.`);let[r]=n;return r?this.locate(r):void 0}candidateRunIds(){return[...new Set([...this.listRunDirNames(),...this.listPendingResultIds()])]}locate(e){let t=this.pathExists(s(e))?s(e):void 0,n=l(e);if(this.pathExists(n))return{runId:e,runDir:t,resultPath:n,claimed:!0};let r=c(e);if(this.pathExists(r))return{runId:e,runDir:t,resultPath:r,claimed:!1};if(t)return{runId:e,runDir:t,resultPath:void 0,claimed:!1}}};f=t.__decorate([(0,o.injectable)()],f),Object.defineProperty(exports,`RunIdResolver`,{enumerable:!0,get:function(){return f}});
|
|
2
|
-
//# sourceMappingURL=runIdResolver.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runIdResolver.cjs","names":["path","currentRunsDir","currentResultsDir","RESULT_FILE_SUFFIX","CLAIMED_RESULT_FILE_NAME","fs"],"sources":["../../../src/runs/background/runIdResolver.ts"],"sourcesContent":["/**\n * Resolve a possibly-abbreviated subagent run id to where its files live.\n *\n * WHY THIS IS SMALLER THAN THE PREDECESSOR:\n * `doom-pi-subagents/src/runs/background/runIdResolver.ts` disambiguates\n * three kinds of run - foreground, async, nested - because that package\n * tracks all three in `SubagentState`. This package is async-only (see\n * `spawnHandshake.ts`'s header) and has not yet ported foreground or nested\n * run tracking (that is later work; see `SubagentState` in the predecessor).\n * This resolver covers the one kind of run this package currently has: a run\n * whose state lives under `currentRunsDir()/<runId>/` and whose terminal result lives\n * in `currentResultsDir()/<runId>.json` until `ResultWatcher` claims it. It is\n * designed to grow, not to be replaced, once the other kinds land.\n *\n * DESIGN PATTERNS:\n * - Aware of `ResultWatcher`'s claim convention\n * (`currentRunsDir()/<runId>/claimed-result.json`): a run whose result has already\n * been claimed for delivery must still resolve, and to the claimed copy,\n * not to a `currentResultsDir()` entry that no longer exists\n * - Prefix matching mirrors the predecessor's ambiguity handling: an\n * unambiguous prefix resolves like an exact id, an ambiguous one throws\n * naming every match, and a prefix matching nothing returns `undefined`\n * rather than throwing - only ambiguity is exceptional, not \"not found\"\n *\n * AVOID:\n * - Reading `currentResultsDir()/<runId>.json` for a run that `pathExists` shows has\n * a claimed copy; the source file may already belong to an unrelated, later\n * run by the time this runs\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\nimport { currentResultsDir, currentRunsDir } from '../../shared/paths.ts';\nimport { CLAIMED_RESULT_FILE_NAME, RESULT_FILE_SUFFIX } from './resultWatcher.ts';\n\nexport interface ResolvedRunLocation {\n runId: string;\n /** `currentRunsDir()/<runId>`, when it exists. */\n runDir: string | undefined;\n /** Wherever the run's result currently lives - pending or already claimed. */\n resultPath: string | undefined;\n /** True when `resultPath` is a claimed copy awaiting delivery, not the original. */\n claimed: boolean;\n}\n\nexport interface IRunIdResolver {\n /**\n * Resolve an exact id or an unambiguous prefix. `undefined` when nothing\n * matches. Throws when a prefix matches more than one run id.\n */\n resolve(id: string): ResolvedRunLocation | undefined;\n}\n\nfunction runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\nfunction pendingResultPathFor(runId: string): string {\n return path.join(currentResultsDir(), `${runId}${RESULT_FILE_SUFFIX}`);\n}\n\nfunction claimedResultPathFor(runId: string): string {\n return path.join(runDirFor(runId), CLAIMED_RESULT_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\n@injectable()\nexport class RunIdResolver implements IRunIdResolver {\n // ---------------------------------------------------------------------\n // Filesystem seams. ESM module namespaces are frozen (`vi.spyOn(fs, ...)`\n // throws), so every `node:fs` call a test needs to redirect is wrapped here.\n // ---------------------------------------------------------------------\n\n protected pathExists(filePath: string): boolean {\n return fs.existsSync(filePath);\n }\n\n /**\n * Seams for the two directory-listing methods below, as protected methods\n * rather than constructor arguments (inversify cannot resolve a primitive\n * like a path, and a directory is no different from `AgentDiscoveryService`'s\n * `now()`/`load()` seams in that respect). A test subclass points these at\n * a directory that does not exist, which exercises the `isNotFound` branch\n * for real without touching the actual shared `currentRunsDir()`/`currentResultsDir()` -\n * closing the only reason this class's tests would otherwise need to.\n */\n protected runsDir(): string {\n return currentRunsDir();\n }\n\n protected resultsDir(): string {\n return currentResultsDir();\n }\n\n protected listRunDirNames(): string[] {\n try {\n return fs\n .readdirSync(this.runsDir(), { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n } catch (error) {\n if (isNotFound(error)) return [];\n throw error;\n }\n }\n\n protected listPendingResultIds(): string[] {\n try {\n return fs\n .readdirSync(this.resultsDir())\n .filter((entry) => entry.endsWith(RESULT_FILE_SUFFIX))\n .map((entry) => entry.slice(0, -RESULT_FILE_SUFFIX.length));\n } catch (error) {\n if (isNotFound(error)) return [];\n throw error;\n }\n }\n\n resolve(id: string): ResolvedRunLocation | undefined {\n const exact = this.locate(id);\n if (exact) return exact;\n\n const candidates = this.candidateRunIds().filter((candidate) => candidate.startsWith(id));\n if (candidates.length > 1) {\n throw new Error(`Ambiguous run id prefix '${id}' matched: ${candidates.join(', ')}. Provide a longer id.`);\n }\n const [onlyMatch] = candidates;\n return onlyMatch ? this.locate(onlyMatch) : undefined;\n }\n\n private candidateRunIds(): string[] {\n return [...new Set([...this.listRunDirNames(), ...this.listPendingResultIds()])];\n }\n\n private locate(runId: string): ResolvedRunLocation | undefined {\n const runDir = this.pathExists(runDirFor(runId)) ? runDirFor(runId) : undefined;\n const claimedPath = claimedResultPathFor(runId);\n if (this.pathExists(claimedPath)) {\n return { runId, runDir, resultPath: claimedPath, claimed: true };\n }\n const pendingPath = pendingResultPathFor(runId);\n if (this.pathExists(pendingPath)) {\n return { runId, runDir, resultPath: pendingPath, claimed: false };\n }\n if (runDir) return { runId, runDir, resultPath: undefined, claimed: false };\n return undefined;\n }\n}\n"],"mappings":"6TAuDA,SAAS,EAAU,EAAuB,CACxC,OAAOA,EAAK,KAAKC,EAAAA,gBAAgB,CAAE,EAAM,CAG3C,SAAS,EAAqB,EAAuB,CACnD,OAAOD,EAAK,KAAKE,EAAAA,mBAAmB,CAAE,GAAG,IAAQC,EAAAA,qBAAqB,CAGxE,SAAS,EAAqB,EAAuB,CACnD,OAAOH,EAAK,KAAK,EAAU,EAAM,CAAEI,EAAAA,yBAAyB,CAG9D,SAAS,EAAU,EAAoC,CACrD,OAAO,OAAO,GAAU,UAAY,GAAkB,SAAU,EAC3D,EAAgC,KACjC,IAAA,GAGN,SAAS,EAAW,EAAyB,CAC3C,OAAO,EAAU,EAAM,GAAK,SAIvB,IAAA,EAAA,KAA8C,CAMnD,WAAqB,EAA2B,CAC9C,OAAOC,EAAG,WAAW,EAAS,CAYhC,SAA4B,CAC1B,OAAOJ,EAAAA,gBAAgB,CAGzB,YAA+B,CAC7B,OAAOC,EAAAA,mBAAmB,CAG5B,iBAAsC,CACpC,GAAI,CACF,OAAOG,EACJ,YAAY,KAAK,SAAS,CAAE,CAAE,cAAe,GAAM,CAAC,CACpD,OAAQ,GAAU,EAAM,aAAa,CAAC,CACtC,IAAK,GAAU,EAAM,KAAK,OACtB,EAAO,CACd,GAAI,EAAW,EAAM,CAAE,MAAO,EAAE,CAChC,MAAM,GAIV,sBAA2C,CACzC,GAAI,CACF,OAAOA,EACJ,YAAY,KAAK,YAAY,CAAC,CAC9B,OAAQ,GAAU,EAAM,SAASF,EAAAA,mBAAmB,CAAC,CACrD,IAAK,GAAU,EAAM,MAAM,EAAG,CAACA,EAAAA,mBAAmB,OAAO,CAAC,OACtD,EAAO,CACd,GAAI,EAAW,EAAM,CAAE,MAAO,EAAE,CAChC,MAAM,GAIV,QAAQ,EAA6C,CACnD,IAAM,EAAQ,KAAK,OAAO,EAAG,CAC7B,GAAI,EAAO,OAAO,EAElB,IAAM,EAAa,KAAK,iBAAiB,CAAC,OAAQ,GAAc,EAAU,WAAW,EAAG,CAAC,CACzF,GAAI,EAAW,OAAS,EACtB,MAAU,MAAM,4BAA4B,EAAG,aAAa,EAAW,KAAK,KAAK,CAAC,wBAAwB,CAE5G,GAAM,CAAC,GAAa,EACpB,OAAO,EAAY,KAAK,OAAO,EAAU,CAAG,IAAA,GAG9C,iBAAoC,CAClC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,iBAAiB,CAAE,GAAG,KAAK,sBAAsB,CAAC,CAAC,CAAC,CAGlF,OAAe,EAAgD,CAC7D,IAAM,EAAS,KAAK,WAAW,EAAU,EAAM,CAAC,CAAG,EAAU,EAAM,CAAG,IAAA,GAChE,EAAc,EAAqB,EAAM,CAC/C,GAAI,KAAK,WAAW,EAAY,CAC9B,MAAO,CAAE,QAAO,SAAQ,WAAY,EAAa,QAAS,GAAM,CAElE,IAAM,EAAc,EAAqB,EAAM,CAC/C,GAAI,KAAK,WAAW,EAAY,CAC9B,MAAO,CAAE,QAAO,SAAQ,WAAY,EAAa,QAAS,GAAO,CAEnE,GAAI,EAAQ,MAAO,CAAE,QAAO,SAAQ,WAAY,IAAA,GAAW,QAAS,GAAO,qCA9ElE,CAAA,CAAA,EAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{currentResultsDir as r,currentRunsDir as i,init_paths as a}from"../../shared/paths.mjs";import{CLAIMED_RESULT_FILE_NAME as o,RESULT_FILE_SUFFIX as s,init_resultWatcher as c}from"./resultWatcher.mjs";import*as l from"node:fs";import*as u from"node:path";import{injectable as d}from"inversify";function f(e){return u.join(i(),e)}function p(e){return u.join(r(),`${e}${s}`)}function m(e){return u.join(f(e),o)}function h(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function g(e){return h(e)===`ENOENT`}var _,v=e((()=>{a(),c(),n(),_=class{pathExists(e){return l.existsSync(e)}runsDir(){return i()}resultsDir(){return r()}listRunDirNames(){try{return l.readdirSync(this.runsDir(),{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)}catch(e){if(g(e))return[];throw e}}listPendingResultIds(){try{return l.readdirSync(this.resultsDir()).filter(e=>e.endsWith(s)).map(e=>e.slice(0,-s.length))}catch(e){if(g(e))return[];throw e}}resolve(e){let t=this.locate(e);if(t)return t;let n=this.candidateRunIds().filter(t=>t.startsWith(e));if(n.length>1)throw Error(`Ambiguous run id prefix '${e}' matched: ${n.join(`, `)}. Provide a longer id.`);let[r]=n;return r?this.locate(r):void 0}candidateRunIds(){return[...new Set([...this.listRunDirNames(),...this.listPendingResultIds()])]}locate(e){let t=this.pathExists(f(e))?f(e):void 0,n=m(e);if(this.pathExists(n))return{runId:e,runDir:t,resultPath:n,claimed:!0};let r=p(e);if(this.pathExists(r))return{runId:e,runDir:t,resultPath:r,claimed:!1};if(t)return{runId:e,runDir:t,resultPath:void 0,claimed:!1}}},_=t([d()],_)}));v();export{_ as RunIdResolver,v as init_runIdResolver};
|
|
2
|
-
//# sourceMappingURL=runIdResolver.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runIdResolver.mjs","names":[],"sources":["../../../src/runs/background/runIdResolver.ts"],"sourcesContent":["/**\n * Resolve a possibly-abbreviated subagent run id to where its files live.\n *\n * WHY THIS IS SMALLER THAN THE PREDECESSOR:\n * `doom-pi-subagents/src/runs/background/runIdResolver.ts` disambiguates\n * three kinds of run - foreground, async, nested - because that package\n * tracks all three in `SubagentState`. This package is async-only (see\n * `spawnHandshake.ts`'s header) and has not yet ported foreground or nested\n * run tracking (that is later work; see `SubagentState` in the predecessor).\n * This resolver covers the one kind of run this package currently has: a run\n * whose state lives under `currentRunsDir()/<runId>/` and whose terminal result lives\n * in `currentResultsDir()/<runId>.json` until `ResultWatcher` claims it. It is\n * designed to grow, not to be replaced, once the other kinds land.\n *\n * DESIGN PATTERNS:\n * - Aware of `ResultWatcher`'s claim convention\n * (`currentRunsDir()/<runId>/claimed-result.json`): a run whose result has already\n * been claimed for delivery must still resolve, and to the claimed copy,\n * not to a `currentResultsDir()` entry that no longer exists\n * - Prefix matching mirrors the predecessor's ambiguity handling: an\n * unambiguous prefix resolves like an exact id, an ambiguous one throws\n * naming every match, and a prefix matching nothing returns `undefined`\n * rather than throwing - only ambiguity is exceptional, not \"not found\"\n *\n * AVOID:\n * - Reading `currentResultsDir()/<runId>.json` for a run that `pathExists` shows has\n * a claimed copy; the source file may already belong to an unrelated, later\n * run by the time this runs\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\nimport { currentResultsDir, currentRunsDir } from '../../shared/paths.ts';\nimport { CLAIMED_RESULT_FILE_NAME, RESULT_FILE_SUFFIX } from './resultWatcher.ts';\n\nexport interface ResolvedRunLocation {\n runId: string;\n /** `currentRunsDir()/<runId>`, when it exists. */\n runDir: string | undefined;\n /** Wherever the run's result currently lives - pending or already claimed. */\n resultPath: string | undefined;\n /** True when `resultPath` is a claimed copy awaiting delivery, not the original. */\n claimed: boolean;\n}\n\nexport interface IRunIdResolver {\n /**\n * Resolve an exact id or an unambiguous prefix. `undefined` when nothing\n * matches. Throws when a prefix matches more than one run id.\n */\n resolve(id: string): ResolvedRunLocation | undefined;\n}\n\nfunction runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\nfunction pendingResultPathFor(runId: string): string {\n return path.join(currentResultsDir(), `${runId}${RESULT_FILE_SUFFIX}`);\n}\n\nfunction claimedResultPathFor(runId: string): string {\n return path.join(runDirFor(runId), CLAIMED_RESULT_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\n@injectable()\nexport class RunIdResolver implements IRunIdResolver {\n // ---------------------------------------------------------------------\n // Filesystem seams. ESM module namespaces are frozen (`vi.spyOn(fs, ...)`\n // throws), so every `node:fs` call a test needs to redirect is wrapped here.\n // ---------------------------------------------------------------------\n\n protected pathExists(filePath: string): boolean {\n return fs.existsSync(filePath);\n }\n\n /**\n * Seams for the two directory-listing methods below, as protected methods\n * rather than constructor arguments (inversify cannot resolve a primitive\n * like a path, and a directory is no different from `AgentDiscoveryService`'s\n * `now()`/`load()` seams in that respect). A test subclass points these at\n * a directory that does not exist, which exercises the `isNotFound` branch\n * for real without touching the actual shared `currentRunsDir()`/`currentResultsDir()` -\n * closing the only reason this class's tests would otherwise need to.\n */\n protected runsDir(): string {\n return currentRunsDir();\n }\n\n protected resultsDir(): string {\n return currentResultsDir();\n }\n\n protected listRunDirNames(): string[] {\n try {\n return fs\n .readdirSync(this.runsDir(), { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name);\n } catch (error) {\n if (isNotFound(error)) return [];\n throw error;\n }\n }\n\n protected listPendingResultIds(): string[] {\n try {\n return fs\n .readdirSync(this.resultsDir())\n .filter((entry) => entry.endsWith(RESULT_FILE_SUFFIX))\n .map((entry) => entry.slice(0, -RESULT_FILE_SUFFIX.length));\n } catch (error) {\n if (isNotFound(error)) return [];\n throw error;\n }\n }\n\n resolve(id: string): ResolvedRunLocation | undefined {\n const exact = this.locate(id);\n if (exact) return exact;\n\n const candidates = this.candidateRunIds().filter((candidate) => candidate.startsWith(id));\n if (candidates.length > 1) {\n throw new Error(`Ambiguous run id prefix '${id}' matched: ${candidates.join(', ')}. Provide a longer id.`);\n }\n const [onlyMatch] = candidates;\n return onlyMatch ? this.locate(onlyMatch) : undefined;\n }\n\n private candidateRunIds(): string[] {\n return [...new Set([...this.listRunDirNames(), ...this.listPendingResultIds()])];\n }\n\n private locate(runId: string): ResolvedRunLocation | undefined {\n const runDir = this.pathExists(runDirFor(runId)) ? runDirFor(runId) : undefined;\n const claimedPath = claimedResultPathFor(runId);\n if (this.pathExists(claimedPath)) {\n return { runId, runDir, resultPath: claimedPath, claimed: true };\n }\n const pendingPath = pendingResultPathFor(runId);\n if (this.pathExists(pendingPath)) {\n return { runId, runDir, resultPath: pendingPath, claimed: false };\n }\n if (runDir) return { runId, runDir, resultPath: undefined, claimed: false };\n return undefined;\n }\n}\n"],"mappings":"6dAuDA,SAAS,EAAU,EAAuB,CACxC,OAAO,EAAK,KAAK,GAAgB,CAAE,EAAM,CAG3C,SAAS,EAAqB,EAAuB,CACnD,OAAO,EAAK,KAAK,GAAmB,CAAE,GAAG,IAAQ,IAAqB,CAGxE,SAAS,EAAqB,EAAuB,CACnD,OAAO,EAAK,KAAK,EAAU,EAAM,CAAE,EAAyB,CAG9D,SAAS,EAAU,EAAoC,CACrD,OAAO,OAAO,GAAU,UAAY,GAAkB,SAAU,EAC3D,EAAgC,KACjC,IAAA,GAGN,SAAS,EAAW,EAAyB,CAC3C,OAAO,EAAU,EAAM,GAAK,4BAxC4C,IACQ,OA2C3E,KAA8C,CAMnD,WAAqB,EAA2B,CAC9C,OAAO,EAAG,WAAW,EAAS,CAYhC,SAA4B,CAC1B,OAAO,GAAgB,CAGzB,YAA+B,CAC7B,OAAO,GAAmB,CAG5B,iBAAsC,CACpC,GAAI,CACF,OAAO,EACJ,YAAY,KAAK,SAAS,CAAE,CAAE,cAAe,GAAM,CAAC,CACpD,OAAQ,GAAU,EAAM,aAAa,CAAC,CACtC,IAAK,GAAU,EAAM,KAAK,OACtB,EAAO,CACd,GAAI,EAAW,EAAM,CAAE,MAAO,EAAE,CAChC,MAAM,GAIV,sBAA2C,CACzC,GAAI,CACF,OAAO,EACJ,YAAY,KAAK,YAAY,CAAC,CAC9B,OAAQ,GAAU,EAAM,SAAS,EAAmB,CAAC,CACrD,IAAK,GAAU,EAAM,MAAM,EAAG,CAAC,EAAmB,OAAO,CAAC,OACtD,EAAO,CACd,GAAI,EAAW,EAAM,CAAE,MAAO,EAAE,CAChC,MAAM,GAIV,QAAQ,EAA6C,CACnD,IAAM,EAAQ,KAAK,OAAO,EAAG,CAC7B,GAAI,EAAO,OAAO,EAElB,IAAM,EAAa,KAAK,iBAAiB,CAAC,OAAQ,GAAc,EAAU,WAAW,EAAG,CAAC,CACzF,GAAI,EAAW,OAAS,EACtB,MAAU,MAAM,4BAA4B,EAAG,aAAa,EAAW,KAAK,KAAK,CAAC,wBAAwB,CAE5G,GAAM,CAAC,GAAa,EACpB,OAAO,EAAY,KAAK,OAAO,EAAU,CAAG,IAAA,GAG9C,iBAAoC,CAClC,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,iBAAiB,CAAE,GAAG,KAAK,sBAAsB,CAAC,CAAC,CAAC,CAGlF,OAAe,EAAgD,CAC7D,IAAM,EAAS,KAAK,WAAW,EAAU,EAAM,CAAC,CAAG,EAAU,EAAM,CAAG,IAAA,GAChE,EAAc,EAAqB,EAAM,CAC/C,GAAI,KAAK,WAAW,EAAY,CAC9B,MAAO,CAAE,QAAO,SAAQ,WAAY,EAAa,QAAS,GAAM,CAElE,IAAM,EAAc,EAAqB,EAAM,CAC/C,GAAI,KAAK,WAAW,EAAY,CAC9B,MAAO,CAAE,QAAO,SAAQ,WAAY,EAAa,QAAS,GAAO,CAEnE,GAAI,EAAQ,MAAO,CAAE,QAAO,SAAQ,WAAY,IAAA,GAAW,QAAS,GAAO,QA9E9E,GAAY,CAAA,CAAA,EAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require(`../../_virtual/_rolldown/runtime.cjs`),t=require(`../../shared/atomicJson.cjs`),n=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),r=require(`../../env.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`),s=require(`../../shared/paths.cjs`),c=require(`./asyncExecution.cjs`);let l=require(`node:fs`);l=e.__toESM(l,1);let u=require(`node:path`);u=e.__toESM(u,1);let d=require(`inversify`);function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}let m=class{constructor(e,t,n,r){this.statusWriter=e,this.terminalPersistence=t,this.reporting=n,this.teamMembership=r}now(){return Date.now()}resolveRunIdFromEnv(){let e=process.env[r.SUBAGENT_RUN_ID_ENV];return e?.trim()?e:void 0}readFile(e){try{return l.readFileSync(e,`utf-8`)}catch(e){if(p(e)===`ENOENT`)return;throw e}}writeHandshake(e,n){t.writeAtomicJson(e,n)}lastTeamRegistrationError;onTeamRegistrationFailed(e,t){this.lastTeamRegistrationError={runId:e,error:t,at:this.now()}}registerTeamMembership(e,t){let n=this.teamMembership.readRoot();if(n)return this.teamMembership.register({root:n,role:`subagent`,agent:e.agent,runId:e.runId,childIndex:e.childIndex,fanoutIndex:t,task:{id:e.runId,subject:e.task},pid:process.pid})}readExistingStatus(e){let t=this.readFile(u.join(s.currentRunsDir(),e,`status.json`));if(t!==void 0)try{let e=JSON.parse(t);return f(e)?e:void 0}catch{return}}readLaunchConfig(e){let t=s.currentRunConfigPath(e),n=this.readFile(t);if(n===void 0)throw Error(`No launch config found for run '${e}' at '${t}'.`);let r;try{r=JSON.parse(n)}catch(t){throw Error(`Launch config for run '${e}' is not valid JSON: ${t.message}`)}if(!f(r)||typeof r.runId!=`string`||r.runId!==e)throw Error(`Launch config for run '${e}' is missing or has a mismatched 'runId'.`);return r}bootstrap(e,t){let n=e??this.resolveRunIdFromEnv();if(!n)throw Error(`No run id available: '${r.SUBAGENT_RUN_ID_ENV}' is not set and none was supplied.`);let i;try{i=this.readLaunchConfig(n)}catch(e){throw this.writeHandshake(c.handshakePathFor(n),{state:`error`,error:e instanceof Error?e.message:String(e)}),e}try{let e=this.now(),r=this.readExistingStatus(n),a=r?.fanoutIndex;this.statusWriter.open(n,{...r,version:1,runId:n,agent:i.agent,state:`running`,startedAt:r?.startedAt??e,lastUpdate:e,...t?{sessionFile:t}:{},...a===void 0?{}:{fanoutIndex:a}});let o;try{o=this.registerTeamMembership(i,a)}catch(e){this.onTeamRegistrationFailed(n,e)}return this.terminalPersistence.begin(n,(e,t)=>{this.reporting.mutateTerminalStatus(e,t),o?.dispose()}),this.writeHandshake(i.handshakePath,{state:`ready`}),{runId:n,launchConfig:i,teamContext:o?.context}}catch(e){throw this.writeHandshake(i.handshakePath,{state:`error`,error:e instanceof Error?e.message:String(e)}),e}}};m=n.__decorate([(0,d.injectable)(),o.__decorateParam(0,(0,d.inject)(i.DOOM_TEAM_TYPES.CoalescedStatusWriter)),o.__decorateParam(1,(0,d.inject)(i.DOOM_TEAM_TYPES.TerminalPersistenceService)),o.__decorateParam(2,(0,d.inject)(i.DOOM_TEAM_TYPES.RunnerReporting)),o.__decorateParam(3,(0,d.inject)(i.DOOM_TEAM_TYPES.RunnerTeamMembership)),a.__decorateMetadata(`design:paramtypes`,[Object,Object,Object,Object])],m),Object.defineProperty(exports,`RunnerBootstrap`,{enumerable:!0,get:function(){return m}});
|
|
2
|
-
//# sourceMappingURL=runnerBootstrap.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerBootstrap.cjs","names":["SUBAGENT_RUN_ID_ENV","fs","path","currentRunsDir","currentRunConfigPath","handshakePathFor","DOOM_TEAM_TYPES"],"sources":["../../../src/runs/background/runnerBootstrap.ts"],"sourcesContent":["/**\n * The child-side half of the spawn handshake: reads the launch config the\n * parent (`AsyncSubagentSpawner`) wrote, opens this run's status, wires\n * `RunnerReporting`'s finalize callback into `TerminalPersistenceService`,\n * and signals the parent that this process is ready to proceed.\n *\n * \"Nothing works until this exists\" (per the task brief): `SpawnHandshake`\n * on the parent side blocks on exactly the file this class writes at the\n * end of `bootstrap()`.\n *\n * WHY THE RUN ID COMES FROM THE ENVIRONMENT, NOT AN ARGUMENT:\n * `SUBAGENT_RUN_ID_ENV` is already the wire contract `buildPiArgs` encodes a\n * run's id into (see `env.ts`); this process discovers its own identity the\n * same way every other piece of this package's child-side machinery would\n * (`SUBAGENT_CHILD_INDEX_ENV`, etc.), rather than requiring whatever calls\n * `bootstrap()` to already know it and pass it in redundantly. An explicit\n * override is still accepted for tests and for a caller that has a genuine\n * reason to know better.\n *\n * WHY A BOOTSTRAP FAILURE REPORTS THROUGH THE HANDSHAKE'S `error` FIELD\n * ONLY:\n * The predecessor invented a second file (`runner-startup.json`, `state:\n * 'error'`) for exactly this case, which is part of why a reader had to\n * check three separate files to know whether a run was actually finished\n * (see `asyncExecution.ts`'s finalization diagnosis). `SpawnHandshake`\n * already has a vocabulary for this - `{state:'error', error}` at the SAME\n * path it watches for `{state:'ready'}` - so a bootstrap failure uses that,\n * not a parallel channel.\n *\n * WHAT REMAINS UNPROVEN UNTIL `subagentPromptRuntime.ts` LANDS:\n * This class is complete and independently testable, but nothing in this\n * package yet calls it for a real spawned child - that entry point is a\n * separate, concurrent port (see `asyncExecution.ts`'s header). Until then,\n * `bootstrap()`'s only proof of correctness is its own unit tests against\n * the documented contract, not an end-to-end run.\n *\n * TEAM MEMBERSHIP IS ADDITIVE, NEVER FATAL:\n * `bootstrap()` also joins this run's `runId` to the native team as a new\n * member (`RunnerTeamMembership`), when the parent forwarded a team root\n * (see `asyncExecution.ts`'s TEAM MEMBERSHIP ENV FORWARDING section) - a\n * child that cannot be messaged, asked, or seen on the shared task board by\n * its siblings is a real gap, but it is not a reason to fail the run itself.\n * A registration failure is recorded (`onTeamRegistrationFailed`), not\n * thrown, and everything else proceeds exactly as if no team root had been\n * present at all.\n *\n * DISPOSAL RIDES THE ONE FINALIZE PATH, NOT A SECOND ONE:\n * The registration's `dispose()` is called from inside the SAME callback\n * passed to `terminalPersistence.begin()` that `RunnerReporting` already\n * owns, immediately after `mutateTerminalStatus` - not from a second,\n * independent cleanup site. `TerminalPersistenceService`'s own `finalized`\n * boolean is still the only thing deciding whether that callback runs at\n * all, so a child that finalizes for any reason (completion, signal,\n * uncaught exception) also leaves the team exactly once, for the same\n * reason there is exactly one finalize.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { currentRunConfigPath, currentRunsDir } from '../../shared/paths.ts';\nimport { writeAtomicJson } from '../../shared/atomicJson.ts';\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport { SUBAGENT_RUN_ID_ENV } from '../../env.ts';\nimport type { TeamMemberContext } from '../../intercom/nativeTeamChannel.ts';\nimport { handshakePathFor, type AsyncRunStatus, type LaunchConfig } from './asyncExecution.ts';\nimport type { IRunnerReporting } from './runnerReporting.ts';\nimport type { IRunnerTeamMembership, RunnerTeamRegistration } from './runnerTeamMembership.ts';\nimport { type ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\nconst STATUS_FILE_NAME = 'status.json';\n\nexport type RunnerLaunchConfig = LaunchConfig;\n\nexport interface RunnerBootstrapResult {\n runId: string;\n launchConfig: RunnerLaunchConfig;\n /** This run's own bound team membership, when it joined one. Same instance `registerTeamMembership` already holds. */\n teamContext: TeamMemberContext | undefined;\n}\n\nexport interface IRunnerBootstrap {\n /**\n * Read the launch config, open status, wire finalize, and signal ready.\n * Throws (after writing an error handshake) on any failure to reach that\n * point - a bootstrap that cannot even establish this run's identity has\n * nothing else it can safely do.\n */\n bootstrap(runId?: string, sessionFile?: string): RunnerBootstrapResult;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\n@injectable()\nexport class RunnerBootstrap implements IRunnerBootstrap {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService<AsyncRunStatus>,\n @inject(DOOM_TEAM_TYPES.RunnerReporting) private readonly reporting: IRunnerReporting,\n @inject(DOOM_TEAM_TYPES.RunnerTeamMembership) private readonly teamMembership: IRunnerTeamMembership,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n protected resolveRunIdFromEnv(): string | undefined {\n const value = process.env[SUBAGENT_RUN_ID_ENV];\n return value?.trim() ? value : undefined;\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (errorCode(error) === 'ENOENT') return undefined;\n throw error;\n }\n }\n\n protected writeHandshake(filePath: string, payload: { state: 'ready' } | { state: 'error'; error: string }): void {\n writeAtomicJson(filePath, payload);\n }\n\n /** Recorded, not thrown - see the module header's TEAM MEMBERSHIP IS ADDITIVE section. */\n lastTeamRegistrationError: { runId: string; error: unknown; at: number } | undefined;\n\n protected onTeamRegistrationFailed(runId: string, error: unknown): void {\n this.lastTeamRegistrationError = { runId, error, at: this.now() };\n }\n\n /**\n * Join the native team as a new member for this run, if the parent\n * forwarded a team root. `undefined` when this run is not part of a team\n * - a silent no-op, not a failure (see `asyncExecution.ts`'s TEAM\n * MEMBERSHIP ENV FORWARDING section).\n */\n protected registerTeamMembership(\n launchConfig: RunnerLaunchConfig,\n fanoutIndex: number | undefined,\n ): RunnerTeamRegistration | undefined {\n const root = this.teamMembership.readRoot();\n if (!root) return undefined;\n return this.teamMembership.register({\n root,\n role: 'subagent',\n agent: launchConfig.agent,\n runId: launchConfig.runId,\n childIndex: launchConfig.childIndex,\n // Only a fan-out child gets a `-N` suffix on its member name; a\n // standalone run has no sibling to disambiguate against.\n fanoutIndex,\n task: { id: launchConfig.runId, subject: launchConfig.task },\n pid: process.pid,\n });\n }\n\n protected readExistingStatus(runId: string): AsyncRunStatus | undefined {\n // Best-effort: the parent already wrote an initial status.json for this\n // run (`AsyncSubagentSpawner.spawn()`); reading it back preserves\n // parent-decided fields (`fanoutIndex`) rather than this process\n // reconstructing them from nothing.\n const raw = this.readFile(path.join(currentRunsDir(), runId, STATUS_FILE_NAME));\n if (raw === undefined) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? (parsed as unknown as AsyncRunStatus) : undefined;\n } catch {\n return undefined;\n }\n }\n\n private readLaunchConfig(runId: string): RunnerLaunchConfig {\n const configPath = currentRunConfigPath(runId);\n const raw = this.readFile(configPath);\n if (raw === undefined) {\n throw new Error(`No launch config found for run '${runId}' at '${configPath}'.`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw new Error(`Launch config for run '${runId}' is not valid JSON: ${(error as Error).message}`);\n }\n if (!isRecord(parsed) || typeof parsed.runId !== 'string' || parsed.runId !== runId) {\n throw new Error(`Launch config for run '${runId}' is missing or has a mismatched 'runId'.`);\n }\n return parsed as unknown as RunnerLaunchConfig;\n }\n\n bootstrap(runId?: string, sessionFile?: string): RunnerBootstrapResult {\n const resolvedRunId = runId ?? this.resolveRunIdFromEnv();\n if (!resolvedRunId) {\n // Nothing identifies this run yet, so there is no handshake path to\n // write an error to either; this can only surface as a thrown error.\n throw new Error(`No run id available: '${SUBAGENT_RUN_ID_ENV}' is not set and none was supplied.`);\n }\n\n let launchConfig: RunnerLaunchConfig;\n try {\n launchConfig = this.readLaunchConfig(resolvedRunId);\n } catch (error) {\n this.writeHandshake(handshakePathFor(resolvedRunId), {\n state: 'error',\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n\n try {\n const now = this.now();\n const existing = this.readExistingStatus(resolvedRunId);\n const fanoutIndex = existing?.fanoutIndex;\n this.statusWriter.open(resolvedRunId, {\n ...existing,\n version: 1,\n runId: resolvedRunId,\n agent: launchConfig.agent,\n state: 'running',\n startedAt: existing?.startedAt ?? now,\n lastUpdate: now,\n ...(sessionFile ? { sessionFile } : {}),\n ...(fanoutIndex === undefined ? {} : { fanoutIndex }),\n });\n\n let teamRegistration: RunnerTeamRegistration | undefined;\n try {\n teamRegistration = this.registerTeamMembership(launchConfig, fanoutIndex);\n } catch (error) {\n // Additive, never fatal - see the module header's TEAM MEMBERSHIP\n // IS ADDITIVE section.\n this.onTeamRegistrationFailed(resolvedRunId, error);\n }\n\n this.terminalPersistence.begin(resolvedRunId, (status, trigger) => {\n this.reporting.mutateTerminalStatus(status, trigger);\n // Disposal rides this SAME callback, not a second cleanup site - see\n // the module header's DISPOSAL RIDES THE ONE FINALIZE PATH section.\n teamRegistration?.dispose();\n });\n\n this.writeHandshake(launchConfig.handshakePath, { state: 'ready' });\n\n return {\n runId: resolvedRunId,\n launchConfig,\n teamContext: teamRegistration?.context,\n };\n } catch (error) {\n this.writeHandshake(launchConfig.handshakePath, {\n state: 'error',\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{init_atomicJson as t,writeAtomicJson as n}from"../../shared/atomicJson.mjs";import{__decorate as r,init_decorate as i}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{SUBAGENT_RUN_ID_ENV as a,init_env as o}from"../../env.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 f}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{currentRunConfigPath as p,currentRunsDir as m,init_paths as h}from"../../shared/paths.mjs";import{handshakePathFor as g,init_asyncExecution as _}from"./asyncExecution.mjs";import*as v from"node:fs";import*as y from"node:path";import{inject as b,injectable as x}from"inversify";function S(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function C(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}var w,T,E=e((()=>{h(),t(),c(),o(),_(),u(),f(),i(),w=`status.json`,T=class{constructor(e,t,n,r){this.statusWriter=e,this.terminalPersistence=t,this.reporting=n,this.teamMembership=r}now(){return Date.now()}resolveRunIdFromEnv(){let e=process.env[a];return e?.trim()?e:void 0}readFile(e){try{return v.readFileSync(e,`utf-8`)}catch(e){if(C(e)===`ENOENT`)return;throw e}}writeHandshake(e,t){n(e,t)}lastTeamRegistrationError;onTeamRegistrationFailed(e,t){this.lastTeamRegistrationError={runId:e,error:t,at:this.now()}}registerTeamMembership(e,t){let n=this.teamMembership.readRoot();if(n)return this.teamMembership.register({root:n,role:`subagent`,agent:e.agent,runId:e.runId,childIndex:e.childIndex,fanoutIndex:t,task:{id:e.runId,subject:e.task},pid:process.pid})}readExistingStatus(e){let t=this.readFile(y.join(m(),e,w));if(t!==void 0)try{let e=JSON.parse(t);return S(e)?e:void 0}catch{return}}readLaunchConfig(e){let t=p(e),n=this.readFile(t);if(n===void 0)throw Error(`No launch config found for run '${e}' at '${t}'.`);let r;try{r=JSON.parse(n)}catch(t){throw Error(`Launch config for run '${e}' is not valid JSON: ${t.message}`)}if(!S(r)||typeof r.runId!=`string`||r.runId!==e)throw Error(`Launch config for run '${e}' is missing or has a mismatched 'runId'.`);return r}bootstrap(e,t){let n=e??this.resolveRunIdFromEnv();if(!n)throw Error(`No run id available: '${a}' is not set and none was supplied.`);let r;try{r=this.readLaunchConfig(n)}catch(e){throw this.writeHandshake(g(n),{state:`error`,error:e instanceof Error?e.message:String(e)}),e}try{let e=this.now(),i=this.readExistingStatus(n),a=i?.fanoutIndex;this.statusWriter.open(n,{...i,version:1,runId:n,agent:r.agent,state:`running`,startedAt:i?.startedAt??e,lastUpdate:e,...t?{sessionFile:t}:{},...a===void 0?{}:{fanoutIndex:a}});let o;try{o=this.registerTeamMembership(r,a)}catch(e){this.onTeamRegistrationFailed(n,e)}return this.terminalPersistence.begin(n,(e,t)=>{this.reporting.mutateTerminalStatus(e,t),o?.dispose()}),this.writeHandshake(r.handshakePath,{state:`ready`}),{runId:n,launchConfig:r,teamContext:o?.context}}catch(e){throw this.writeHandshake(r.handshakePath,{state:`error`,error:e instanceof Error?e.message:String(e)}),e}}},T=r([x(),d(0,b(s.CoalescedStatusWriter)),d(1,b(s.TerminalPersistenceService)),d(2,b(s.RunnerReporting)),d(3,b(s.RunnerTeamMembership)),l(`design:paramtypes`,[Object,Object,Object,Object])],T)}));E();export{T as RunnerBootstrap,E as init_runnerBootstrap};
|
|
2
|
-
//# sourceMappingURL=runnerBootstrap.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerBootstrap.mjs","names":[],"sources":["../../../src/runs/background/runnerBootstrap.ts"],"sourcesContent":["/**\n * The child-side half of the spawn handshake: reads the launch config the\n * parent (`AsyncSubagentSpawner`) wrote, opens this run's status, wires\n * `RunnerReporting`'s finalize callback into `TerminalPersistenceService`,\n * and signals the parent that this process is ready to proceed.\n *\n * \"Nothing works until this exists\" (per the task brief): `SpawnHandshake`\n * on the parent side blocks on exactly the file this class writes at the\n * end of `bootstrap()`.\n *\n * WHY THE RUN ID COMES FROM THE ENVIRONMENT, NOT AN ARGUMENT:\n * `SUBAGENT_RUN_ID_ENV` is already the wire contract `buildPiArgs` encodes a\n * run's id into (see `env.ts`); this process discovers its own identity the\n * same way every other piece of this package's child-side machinery would\n * (`SUBAGENT_CHILD_INDEX_ENV`, etc.), rather than requiring whatever calls\n * `bootstrap()` to already know it and pass it in redundantly. An explicit\n * override is still accepted for tests and for a caller that has a genuine\n * reason to know better.\n *\n * WHY A BOOTSTRAP FAILURE REPORTS THROUGH THE HANDSHAKE'S `error` FIELD\n * ONLY:\n * The predecessor invented a second file (`runner-startup.json`, `state:\n * 'error'`) for exactly this case, which is part of why a reader had to\n * check three separate files to know whether a run was actually finished\n * (see `asyncExecution.ts`'s finalization diagnosis). `SpawnHandshake`\n * already has a vocabulary for this - `{state:'error', error}` at the SAME\n * path it watches for `{state:'ready'}` - so a bootstrap failure uses that,\n * not a parallel channel.\n *\n * WHAT REMAINS UNPROVEN UNTIL `subagentPromptRuntime.ts` LANDS:\n * This class is complete and independently testable, but nothing in this\n * package yet calls it for a real spawned child - that entry point is a\n * separate, concurrent port (see `asyncExecution.ts`'s header). Until then,\n * `bootstrap()`'s only proof of correctness is its own unit tests against\n * the documented contract, not an end-to-end run.\n *\n * TEAM MEMBERSHIP IS ADDITIVE, NEVER FATAL:\n * `bootstrap()` also joins this run's `runId` to the native team as a new\n * member (`RunnerTeamMembership`), when the parent forwarded a team root\n * (see `asyncExecution.ts`'s TEAM MEMBERSHIP ENV FORWARDING section) - a\n * child that cannot be messaged, asked, or seen on the shared task board by\n * its siblings is a real gap, but it is not a reason to fail the run itself.\n * A registration failure is recorded (`onTeamRegistrationFailed`), not\n * thrown, and everything else proceeds exactly as if no team root had been\n * present at all.\n *\n * DISPOSAL RIDES THE ONE FINALIZE PATH, NOT A SECOND ONE:\n * The registration's `dispose()` is called from inside the SAME callback\n * passed to `terminalPersistence.begin()` that `RunnerReporting` already\n * owns, immediately after `mutateTerminalStatus` - not from a second,\n * independent cleanup site. `TerminalPersistenceService`'s own `finalized`\n * boolean is still the only thing deciding whether that callback runs at\n * all, so a child that finalizes for any reason (completion, signal,\n * uncaught exception) also leaves the team exactly once, for the same\n * reason there is exactly one finalize.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { currentRunConfigPath, currentRunsDir } from '../../shared/paths.ts';\nimport { writeAtomicJson } from '../../shared/atomicJson.ts';\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport { SUBAGENT_RUN_ID_ENV } from '../../env.ts';\nimport type { TeamMemberContext } from '../../intercom/nativeTeamChannel.ts';\nimport { handshakePathFor, type AsyncRunStatus, type LaunchConfig } from './asyncExecution.ts';\nimport type { IRunnerReporting } from './runnerReporting.ts';\nimport type { IRunnerTeamMembership, RunnerTeamRegistration } from './runnerTeamMembership.ts';\nimport { type ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\nconst STATUS_FILE_NAME = 'status.json';\n\nexport type RunnerLaunchConfig = LaunchConfig;\n\nexport interface RunnerBootstrapResult {\n runId: string;\n launchConfig: RunnerLaunchConfig;\n /** This run's own bound team membership, when it joined one. Same instance `registerTeamMembership` already holds. */\n teamContext: TeamMemberContext | undefined;\n}\n\nexport interface IRunnerBootstrap {\n /**\n * Read the launch config, open status, wire finalize, and signal ready.\n * Throws (after writing an error handshake) on any failure to reach that\n * point - a bootstrap that cannot even establish this run's identity has\n * nothing else it can safely do.\n */\n bootstrap(runId?: string, sessionFile?: string): RunnerBootstrapResult;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\n@injectable()\nexport class RunnerBootstrap implements IRunnerBootstrap {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService<AsyncRunStatus>,\n @inject(DOOM_TEAM_TYPES.RunnerReporting) private readonly reporting: IRunnerReporting,\n @inject(DOOM_TEAM_TYPES.RunnerTeamMembership) private readonly teamMembership: IRunnerTeamMembership,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n protected resolveRunIdFromEnv(): string | undefined {\n const value = process.env[SUBAGENT_RUN_ID_ENV];\n return value?.trim() ? value : undefined;\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (errorCode(error) === 'ENOENT') return undefined;\n throw error;\n }\n }\n\n protected writeHandshake(filePath: string, payload: { state: 'ready' } | { state: 'error'; error: string }): void {\n writeAtomicJson(filePath, payload);\n }\n\n /** Recorded, not thrown - see the module header's TEAM MEMBERSHIP IS ADDITIVE section. */\n lastTeamRegistrationError: { runId: string; error: unknown; at: number } | undefined;\n\n protected onTeamRegistrationFailed(runId: string, error: unknown): void {\n this.lastTeamRegistrationError = { runId, error, at: this.now() };\n }\n\n /**\n * Join the native team as a new member for this run, if the parent\n * forwarded a team root. `undefined` when this run is not part of a team\n * - a silent no-op, not a failure (see `asyncExecution.ts`'s TEAM\n * MEMBERSHIP ENV FORWARDING section).\n */\n protected registerTeamMembership(\n launchConfig: RunnerLaunchConfig,\n fanoutIndex: number | undefined,\n ): RunnerTeamRegistration | undefined {\n const root = this.teamMembership.readRoot();\n if (!root) return undefined;\n return this.teamMembership.register({\n root,\n role: 'subagent',\n agent: launchConfig.agent,\n runId: launchConfig.runId,\n childIndex: launchConfig.childIndex,\n // Only a fan-out child gets a `-N` suffix on its member name; a\n // standalone run has no sibling to disambiguate against.\n fanoutIndex,\n task: { id: launchConfig.runId, subject: launchConfig.task },\n pid: process.pid,\n });\n }\n\n protected readExistingStatus(runId: string): AsyncRunStatus | undefined {\n // Best-effort: the parent already wrote an initial status.json for this\n // run (`AsyncSubagentSpawner.spawn()`); reading it back preserves\n // parent-decided fields (`fanoutIndex`) rather than this process\n // reconstructing them from nothing.\n const raw = this.readFile(path.join(currentRunsDir(), runId, STATUS_FILE_NAME));\n if (raw === undefined) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? (parsed as unknown as AsyncRunStatus) : undefined;\n } catch {\n return undefined;\n }\n }\n\n private readLaunchConfig(runId: string): RunnerLaunchConfig {\n const configPath = currentRunConfigPath(runId);\n const raw = this.readFile(configPath);\n if (raw === undefined) {\n throw new Error(`No launch config found for run '${runId}' at '${configPath}'.`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw new Error(`Launch config for run '${runId}' is not valid JSON: ${(error as Error).message}`);\n }\n if (!isRecord(parsed) || typeof parsed.runId !== 'string' || parsed.runId !== runId) {\n throw new Error(`Launch config for run '${runId}' is missing or has a mismatched 'runId'.`);\n }\n return parsed as unknown as RunnerLaunchConfig;\n }\n\n bootstrap(runId?: string, sessionFile?: string): RunnerBootstrapResult {\n const resolvedRunId = runId ?? this.resolveRunIdFromEnv();\n if (!resolvedRunId) {\n // Nothing identifies this run yet, so there is no handshake path to\n // write an error to either; this can only surface as a thrown error.\n throw new Error(`No run id available: '${SUBAGENT_RUN_ID_ENV}' is not set and none was supplied.`);\n }\n\n let launchConfig: RunnerLaunchConfig;\n try {\n launchConfig = this.readLaunchConfig(resolvedRunId);\n } catch (error) {\n this.writeHandshake(handshakePathFor(resolvedRunId), {\n state: 'error',\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n\n try {\n const now = this.now();\n const existing = this.readExistingStatus(resolvedRunId);\n const fanoutIndex = existing?.fanoutIndex;\n this.statusWriter.open(resolvedRunId, {\n ...existing,\n version: 1,\n runId: resolvedRunId,\n agent: launchConfig.agent,\n state: 'running',\n startedAt: existing?.startedAt ?? now,\n lastUpdate: now,\n ...(sessionFile ? { sessionFile } : {}),\n ...(fanoutIndex === undefined ? {} : { fanoutIndex }),\n });\n\n let teamRegistration: RunnerTeamRegistration | undefined;\n try {\n teamRegistration = this.registerTeamMembership(launchConfig, fanoutIndex);\n } catch (error) {\n // Additive, never fatal - see the module header's TEAM MEMBERSHIP\n // IS ADDITIVE section.\n this.onTeamRegistrationFailed(resolvedRunId, error);\n }\n\n this.terminalPersistence.begin(resolvedRunId, (status, trigger) => {\n this.reporting.mutateTerminalStatus(status, trigger);\n // Disposal rides this SAME callback, not a second cleanup site - see\n // the module header's DISPOSAL RIDES THE ONE FINALIZE PATH section.\n teamRegistration?.dispose();\n });\n\n this.writeHandshake(launchConfig.handshakePath, { state: 'ready' });\n\n return {\n runId: resolvedRunId,\n launchConfig,\n teamContext: teamRegistration?.context,\n };\n } catch (error) {\n this.writeHandshake(launchConfig.handshakePath, {\n state: 'error',\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),t=require(`../../container/types.cjs`),n=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),r=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`),i=require(`./asyncExecution.cjs`);let a=require(`inversify`),o=class{constructor(e,t,n,r){this.controlChannel=e,this.terminalPersistence=t,this.reporting=n,this.statusWriter=r}exitProcess(e){process.exit(e)}start(e,t={}){let n={onInterrupt:()=>this.interrupt(),onStop:()=>this.stop(),onTimeout:()=>this.timeout(),...t.onSteer?{onSteer:t.onSteer}:{}};return this.controlChannel.watch(i.runDirFor(e),n)}setActivity(e){this.statusWriter.update(t=>{t.activityState=e,t.lastUpdate=Date.now()})}setProgress(e){e.tokens===void 0&&e.currentTool===void 0&&e.toolCount===void 0||this.statusWriter.update(t=>{e.tokens!==void 0&&(t.tokens=e.tokens),e.currentTool!==void 0&&(t.currentTool=e.currentTool),e.toolCount!==void 0&&(t.toolCount=e.toolCount),t.lastUpdate=Date.now()})}complete(e,t){this.reporting.prepareResult({success:e,summary:t}),this.terminalPersistence.finalize()}interrupt(){this.reporting.prepareResult({success:!1,summary:`Interrupted before completion.`}),this.terminalPersistence.finalize()}stop(){this.reporting.prepareResult({success:!1,state:`stopped`,summary:`Stopped before completion.`});try{this.terminalPersistence.finalize()}finally{this.exitProcess(0)}}timeout(){this.reporting.prepareResult({success:!1,summary:`Timed out before completion.`}),this.terminalPersistence.finalize()}};o=e.__decorate([(0,a.injectable)(),r.__decorateParam(0,(0,a.inject)(t.DOOM_TEAM_TYPES.ControlChannelWatcher)),r.__decorateParam(1,(0,a.inject)(t.DOOM_TEAM_TYPES.TerminalPersistenceService)),r.__decorateParam(2,(0,a.inject)(t.DOOM_TEAM_TYPES.RunnerReporting)),r.__decorateParam(3,(0,a.inject)(t.DOOM_TEAM_TYPES.CoalescedStatusWriter)),n.__decorateMetadata(`design:paramtypes`,[Object,Object,Object,Object])],o),Object.defineProperty(exports,`RunnerExecution`,{enumerable:!0,get:function(){return o}});
|
|
2
|
-
//# sourceMappingURL=runnerExecution.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerExecution.cjs","names":["runDirFor","DOOM_TEAM_TYPES"],"sources":["../../../src/runs/background/runnerExecution.ts"],"sourcesContent":["/**\n * The \"while running\" glue: consumes steer/interrupt/timeout/stop requests\n * via `ControlChannelWatcher`, and is the ONE place every terminal trigger\n * (interrupt/stop/timeout, and normal completion) funnels through\n * `TerminalPersistenceService.finalize()`.\n *\n * WHY EVERY TRIGGER CALLS THE SAME `finalize()`, INSTEAD OF EACH DOING ITS\n * OWN THING:\n * The predecessor's `interruptRunner`/`stopRunner`/`timeoutRunner` and its\n * post-loop completion tail each independently computed terminal state and\n * independently wrote it - four triggers, four write sites, each guarded\n * only by a LOCAL boolean that stopped re-entry of THAT SAME handler, none\n * of them aware of the others (see `asyncExecution.ts`'s header for the\n * full diagnosis, including why that made a stray control event able to\n * race the completion tail). Here, `interrupt()`/`stop()`/`timeout()`/\n * `complete()` each do exactly two things: record the outcome via\n * `reporting.prepareResult()`, then call `terminalPersistence.finalize()`.\n * `TerminalPersistenceService`'s own `finalized` boolean, checked and set\n * before anything else runs, is the ONLY thing that decides whether a given\n * call actually does anything. No new idempotency logic is added here -\n * every trigger is made to go through the one that already exists, rather\n * than rolling its own.\n *\n * WHY INTERRUPT/STOP/TIMEOUT EACH STILL CALL `prepareResult()`:\n * `RunnerReporting.mutateTerminalStatus`'s fallback derives a correct\n * outcome from `trigger` for real OS signals/exceptions - but interrupt/\n * stop/timeout are NOT `TerminalTrigger` values (those are this run's own\n * request-driven terminal reasons, not the signals/exceptions\n * `TerminalPersistenceService` itself watches for), and `finalize()` always\n * calls `mutateTerminalStatus` with `trigger === undefined` regardless of\n * WHY this class decided to call it. So each of `interrupt()`/`stop()`/\n * `timeout()` prepares its own result before calling `finalize()`, the same\n * as `complete()` does - `mutateTerminalStatus` cannot otherwise tell\n * interrupt apart from stop apart from normal completion.\n *\n */\n\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport type { ActivityState } from '../../types.ts';\nimport type { AsyncRunStatus } from './asyncExecution.ts';\nimport { runDirFor } from './asyncExecution.ts';\nimport type {\n ControlChannelWatchHandlers,\n IControlChannelWatcher,\n SteerRequest,\n} from '../../intercom/supervisorControlChannel.ts';\nimport type { IRunnerReporting } from './runnerReporting.ts';\nimport type { ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\nexport interface RunnerExecutionHandlers {\n /** Forwarded steer requests, for whatever delivers them to the running step (not this class's job). */\n onSteer?: (request: SteerRequest) => void;\n}\n\nexport interface RunnerProgress {\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport interface IRunnerExecution {\n /**\n * Start watching this run's control inbox and consuming any chain-append\n * requests already waiting. Returns a disposer that stops watching\n * without finalizing (used on a clean, already-finalized exit).\n */\n start(runId: string, handlers?: RunnerExecutionHandlers): () => void;\n /** Record a material, event-driven activity transition. */\n setActivity(activity: ActivityState): void;\n /** Record live child metrics in one coalesced status update. */\n setProgress(progress: RunnerProgress): void;\n /** This run finished normally. Idempotent via `TerminalPersistenceService`. */\n complete(success: boolean, summary: string): void;\n}\n\n@injectable()\nexport class RunnerExecution implements IRunnerExecution {\n constructor(\n @inject(DOOM_TEAM_TYPES.ControlChannelWatcher) private readonly controlChannel: IControlChannelWatcher,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService<AsyncRunStatus>,\n @inject(DOOM_TEAM_TYPES.RunnerReporting) private readonly reporting: IRunnerReporting,\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n ) {}\n\n /** Dedicated runner processes own exactly one session, so a stop may exit after synchronous persistence. */\n protected exitProcess(code: number): void {\n process.exit(code);\n }\n\n start(runId: string, handlers: RunnerExecutionHandlers = {}): () => void {\n const controlHandlers: ControlChannelWatchHandlers = {\n onInterrupt: () => this.interrupt(),\n onStop: () => this.stop(),\n onTimeout: () => this.timeout(),\n ...(handlers.onSteer ? { onSteer: handlers.onSteer } : {}),\n };\n return this.controlChannel.watch(runDirFor(runId), controlHandlers);\n }\n\n setActivity(activity: ActivityState): void {\n this.statusWriter.update((status) => {\n status.activityState = activity;\n status.lastUpdate = Date.now();\n });\n }\n\n setProgress(progress: RunnerProgress): void {\n if (progress.tokens === undefined && progress.currentTool === undefined && progress.toolCount === undefined) {\n return;\n }\n this.statusWriter.update((status) => {\n if (progress.tokens !== undefined) status.tokens = progress.tokens;\n if (progress.currentTool !== undefined) status.currentTool = progress.currentTool;\n if (progress.toolCount !== undefined) status.toolCount = progress.toolCount;\n status.lastUpdate = Date.now();\n });\n }\n\n complete(success: boolean, summary: string): void {\n this.reporting.prepareResult({ success, summary });\n this.terminalPersistence.finalize();\n }\n\n interrupt(): void {\n this.reporting.prepareResult({ success: false, summary: 'Interrupted before completion.' });\n this.terminalPersistence.finalize();\n }\n\n stop(): void {\n this.reporting.prepareResult({ success: false, state: 'stopped', summary: 'Stopped before completion.' });\n try {\n this.terminalPersistence.finalize();\n } finally {\n this.exitProcess(0);\n }\n }\n\n timeout(): void {\n this.reporting.prepareResult({ success: false, summary: 'Timed out before completion.' });\n this.terminalPersistence.finalize();\n }\n}\n"],"mappings":"oZA+EO,EAAA,KAAkD,CACvD,YACE,EACA,EAEA,EACA,EAEA,CANgE,KAAA,eAAA,EAE/C,KAAA,oBAAA,EACyC,KAAA,UAAA,EAEzC,KAAA,aAAA,EAInB,YAAsB,EAAoB,CACxC,QAAQ,KAAK,EAAK,CAGpB,MAAM,EAAe,EAAoC,EAAE,CAAc,CACvE,IAAM,EAA+C,CACnD,gBAAmB,KAAK,WAAW,CACnC,WAAc,KAAK,MAAM,CACzB,cAAiB,KAAK,SAAS,CAC/B,GAAI,EAAS,QAAU,CAAE,QAAS,EAAS,QAAS,CAAG,EAAE,CAC1D,CACD,OAAO,KAAK,eAAe,MAAMA,EAAAA,UAAU,EAAM,CAAE,EAAgB,CAGrE,YAAY,EAA+B,CACzC,KAAK,aAAa,OAAQ,GAAW,CACnC,EAAO,cAAgB,EACvB,EAAO,WAAa,KAAK,KAAK,EAC9B,CAGJ,YAAY,EAAgC,CACtC,EAAS,SAAW,IAAA,IAAa,EAAS,cAAgB,IAAA,IAAa,EAAS,YAAc,IAAA,IAGlG,KAAK,aAAa,OAAQ,GAAW,CAC/B,EAAS,SAAW,IAAA,KAAW,EAAO,OAAS,EAAS,QACxD,EAAS,cAAgB,IAAA,KAAW,EAAO,YAAc,EAAS,aAClE,EAAS,YAAc,IAAA,KAAW,EAAO,UAAY,EAAS,WAClE,EAAO,WAAa,KAAK,KAAK,EAC9B,CAGJ,SAAS,EAAkB,EAAuB,CAChD,KAAK,UAAU,cAAc,CAAE,UAAS,UAAS,CAAC,CAClD,KAAK,oBAAoB,UAAU,CAGrC,WAAkB,CAChB,KAAK,UAAU,cAAc,CAAE,QAAS,GAAO,QAAS,iCAAkC,CAAC,CAC3F,KAAK,oBAAoB,UAAU,CAGrC,MAAa,CACX,KAAK,UAAU,cAAc,CAAE,QAAS,GAAO,MAAO,UAAW,QAAS,6BAA8B,CAAC,CACzG,GAAI,CACF,KAAK,oBAAoB,UAAU,QAC3B,CACR,KAAK,YAAY,EAAE,EAIvB,SAAgB,CACd,KAAK,UAAU,cAAc,CAAE,QAAS,GAAO,QAAS,+BAAgC,CAAC,CACzF,KAAK,oBAAoB,UAAU,qCAlE1B,kCAGDC,EAAAA,gBAAgB,sBAAsB,CAAA,kCACtCA,EAAAA,gBAAgB,2BAA2B,CAAA,kCAE3CA,EAAAA,gBAAgB,gBAAgB,CAAA,kCAChCA,EAAAA,gBAAgB,sBAAsB,CAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as r,init_types as i}from"../../container/types.mjs";import{__decorateMetadata as a,init_decorateMetadata as o}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as s,init_decorateParam as c}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{init_asyncExecution as l,runDirFor as u}from"./asyncExecution.mjs";import{inject as d,injectable as f}from"inversify";var p,m=e((()=>{i(),l(),o(),c(),n(),p=class{constructor(e,t,n,r){this.controlChannel=e,this.terminalPersistence=t,this.reporting=n,this.statusWriter=r}exitProcess(e){process.exit(e)}start(e,t={}){let n={onInterrupt:()=>this.interrupt(),onStop:()=>this.stop(),onTimeout:()=>this.timeout(),...t.onSteer?{onSteer:t.onSteer}:{}};return this.controlChannel.watch(u(e),n)}setActivity(e){this.statusWriter.update(t=>{t.activityState=e,t.lastUpdate=Date.now()})}setProgress(e){e.tokens===void 0&&e.currentTool===void 0&&e.toolCount===void 0||this.statusWriter.update(t=>{e.tokens!==void 0&&(t.tokens=e.tokens),e.currentTool!==void 0&&(t.currentTool=e.currentTool),e.toolCount!==void 0&&(t.toolCount=e.toolCount),t.lastUpdate=Date.now()})}complete(e,t){this.reporting.prepareResult({success:e,summary:t}),this.terminalPersistence.finalize()}interrupt(){this.reporting.prepareResult({success:!1,summary:`Interrupted before completion.`}),this.terminalPersistence.finalize()}stop(){this.reporting.prepareResult({success:!1,state:`stopped`,summary:`Stopped before completion.`});try{this.terminalPersistence.finalize()}finally{this.exitProcess(0)}}timeout(){this.reporting.prepareResult({success:!1,summary:`Timed out before completion.`}),this.terminalPersistence.finalize()}},p=t([f(),s(0,d(r.ControlChannelWatcher)),s(1,d(r.TerminalPersistenceService)),s(2,d(r.RunnerReporting)),s(3,d(r.CoalescedStatusWriter)),a(`design:paramtypes`,[Object,Object,Object,Object])],p)}));m();export{p as RunnerExecution,m as init_runnerExecution};
|
|
2
|
-
//# sourceMappingURL=runnerExecution.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerExecution.mjs","names":[],"sources":["../../../src/runs/background/runnerExecution.ts"],"sourcesContent":["/**\n * The \"while running\" glue: consumes steer/interrupt/timeout/stop requests\n * via `ControlChannelWatcher`, and is the ONE place every terminal trigger\n * (interrupt/stop/timeout, and normal completion) funnels through\n * `TerminalPersistenceService.finalize()`.\n *\n * WHY EVERY TRIGGER CALLS THE SAME `finalize()`, INSTEAD OF EACH DOING ITS\n * OWN THING:\n * The predecessor's `interruptRunner`/`stopRunner`/`timeoutRunner` and its\n * post-loop completion tail each independently computed terminal state and\n * independently wrote it - four triggers, four write sites, each guarded\n * only by a LOCAL boolean that stopped re-entry of THAT SAME handler, none\n * of them aware of the others (see `asyncExecution.ts`'s header for the\n * full diagnosis, including why that made a stray control event able to\n * race the completion tail). Here, `interrupt()`/`stop()`/`timeout()`/\n * `complete()` each do exactly two things: record the outcome via\n * `reporting.prepareResult()`, then call `terminalPersistence.finalize()`.\n * `TerminalPersistenceService`'s own `finalized` boolean, checked and set\n * before anything else runs, is the ONLY thing that decides whether a given\n * call actually does anything. No new idempotency logic is added here -\n * every trigger is made to go through the one that already exists, rather\n * than rolling its own.\n *\n * WHY INTERRUPT/STOP/TIMEOUT EACH STILL CALL `prepareResult()`:\n * `RunnerReporting.mutateTerminalStatus`'s fallback derives a correct\n * outcome from `trigger` for real OS signals/exceptions - but interrupt/\n * stop/timeout are NOT `TerminalTrigger` values (those are this run's own\n * request-driven terminal reasons, not the signals/exceptions\n * `TerminalPersistenceService` itself watches for), and `finalize()` always\n * calls `mutateTerminalStatus` with `trigger === undefined` regardless of\n * WHY this class decided to call it. So each of `interrupt()`/`stop()`/\n * `timeout()` prepares its own result before calling `finalize()`, the same\n * as `complete()` does - `mutateTerminalStatus` cannot otherwise tell\n * interrupt apart from stop apart from normal completion.\n *\n */\n\nimport { inject, injectable } from 'inversify';\n\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport type { ActivityState } from '../../types.ts';\nimport type { AsyncRunStatus } from './asyncExecution.ts';\nimport { runDirFor } from './asyncExecution.ts';\nimport type {\n ControlChannelWatchHandlers,\n IControlChannelWatcher,\n SteerRequest,\n} from '../../intercom/supervisorControlChannel.ts';\nimport type { IRunnerReporting } from './runnerReporting.ts';\nimport type { ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\nexport interface RunnerExecutionHandlers {\n /** Forwarded steer requests, for whatever delivers them to the running step (not this class's job). */\n onSteer?: (request: SteerRequest) => void;\n}\n\nexport interface RunnerProgress {\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport interface IRunnerExecution {\n /**\n * Start watching this run's control inbox and consuming any chain-append\n * requests already waiting. Returns a disposer that stops watching\n * without finalizing (used on a clean, already-finalized exit).\n */\n start(runId: string, handlers?: RunnerExecutionHandlers): () => void;\n /** Record a material, event-driven activity transition. */\n setActivity(activity: ActivityState): void;\n /** Record live child metrics in one coalesced status update. */\n setProgress(progress: RunnerProgress): void;\n /** This run finished normally. Idempotent via `TerminalPersistenceService`. */\n complete(success: boolean, summary: string): void;\n}\n\n@injectable()\nexport class RunnerExecution implements IRunnerExecution {\n constructor(\n @inject(DOOM_TEAM_TYPES.ControlChannelWatcher) private readonly controlChannel: IControlChannelWatcher,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService<AsyncRunStatus>,\n @inject(DOOM_TEAM_TYPES.RunnerReporting) private readonly reporting: IRunnerReporting,\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n ) {}\n\n /** Dedicated runner processes own exactly one session, so a stop may exit after synchronous persistence. */\n protected exitProcess(code: number): void {\n process.exit(code);\n }\n\n start(runId: string, handlers: RunnerExecutionHandlers = {}): () => void {\n const controlHandlers: ControlChannelWatchHandlers = {\n onInterrupt: () => this.interrupt(),\n onStop: () => this.stop(),\n onTimeout: () => this.timeout(),\n ...(handlers.onSteer ? { onSteer: handlers.onSteer } : {}),\n };\n return this.controlChannel.watch(runDirFor(runId), controlHandlers);\n }\n\n setActivity(activity: ActivityState): void {\n this.statusWriter.update((status) => {\n status.activityState = activity;\n status.lastUpdate = Date.now();\n });\n }\n\n setProgress(progress: RunnerProgress): void {\n if (progress.tokens === undefined && progress.currentTool === undefined && progress.toolCount === undefined) {\n return;\n }\n this.statusWriter.update((status) => {\n if (progress.tokens !== undefined) status.tokens = progress.tokens;\n if (progress.currentTool !== undefined) status.currentTool = progress.currentTool;\n if (progress.toolCount !== undefined) status.toolCount = progress.toolCount;\n status.lastUpdate = Date.now();\n });\n }\n\n complete(success: boolean, summary: string): void {\n this.reporting.prepareResult({ success, summary });\n this.terminalPersistence.finalize();\n }\n\n interrupt(): void {\n this.reporting.prepareResult({ success: false, summary: 'Interrupted before completion.' });\n this.terminalPersistence.finalize();\n }\n\n stop(): void {\n this.reporting.prepareResult({ success: false, state: 'stopped', summary: 'Stopped before completion.' });\n try {\n this.terminalPersistence.finalize();\n } finally {\n this.exitProcess(0);\n }\n }\n\n timeout(): void {\n this.reporting.prepareResult({ success: false, summary: 'Timed out before completion.' });\n this.terminalPersistence.finalize();\n }\n}\n"],"mappings":"ypBAuC2D,IAGX,eAqCzC,KAAkD,CACvD,YACE,EACA,EAEA,EACA,EAEA,CANgE,KAAA,eAAA,EAE/C,KAAA,oBAAA,EACyC,KAAA,UAAA,EAEzC,KAAA,aAAA,EAInB,YAAsB,EAAoB,CACxC,QAAQ,KAAK,EAAK,CAGpB,MAAM,EAAe,EAAoC,EAAE,CAAc,CACvE,IAAM,EAA+C,CACnD,gBAAmB,KAAK,WAAW,CACnC,WAAc,KAAK,MAAM,CACzB,cAAiB,KAAK,SAAS,CAC/B,GAAI,EAAS,QAAU,CAAE,QAAS,EAAS,QAAS,CAAG,EAAE,CAC1D,CACD,OAAO,KAAK,eAAe,MAAM,EAAU,EAAM,CAAE,EAAgB,CAGrE,YAAY,EAA+B,CACzC,KAAK,aAAa,OAAQ,GAAW,CACnC,EAAO,cAAgB,EACvB,EAAO,WAAa,KAAK,KAAK,EAC9B,CAGJ,YAAY,EAAgC,CACtC,EAAS,SAAW,IAAA,IAAa,EAAS,cAAgB,IAAA,IAAa,EAAS,YAAc,IAAA,IAGlG,KAAK,aAAa,OAAQ,GAAW,CAC/B,EAAS,SAAW,IAAA,KAAW,EAAO,OAAS,EAAS,QACxD,EAAS,cAAgB,IAAA,KAAW,EAAO,YAAc,EAAS,aAClE,EAAS,YAAc,IAAA,KAAW,EAAO,UAAY,EAAS,WAClE,EAAO,WAAa,KAAK,KAAK,EAC9B,CAGJ,SAAS,EAAkB,EAAuB,CAChD,KAAK,UAAU,cAAc,CAAE,UAAS,UAAS,CAAC,CAClD,KAAK,oBAAoB,UAAU,CAGrC,WAAkB,CAChB,KAAK,UAAU,cAAc,CAAE,QAAS,GAAO,QAAS,iCAAkC,CAAC,CAC3F,KAAK,oBAAoB,UAAU,CAGrC,MAAa,CACX,KAAK,UAAU,cAAc,CAAE,QAAS,GAAO,MAAO,UAAW,QAAS,6BAA8B,CAAC,CACzG,GAAI,CACF,KAAK,oBAAoB,UAAU,QAC3B,CACR,KAAK,YAAY,EAAE,EAIvB,SAAgB,CACd,KAAK,UAAU,cAAc,CAAE,QAAS,GAAO,QAAS,+BAAgC,CAAC,CACzF,KAAK,oBAAoB,UAAU,QAlEtC,GAAY,KAGR,EAAO,EAAgB,sBAAsB,CAAA,KAC7C,EAAO,EAAgB,2BAA2B,CAAA,KAElD,EAAO,EAAgB,gBAAgB,CAAA,KACvC,EAAO,EAAgB,sBAAsB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerReporting.cjs","names":["path","currentResultsDir","RESULT_FILE_SUFFIX","DOOM_TEAM_TYPES"],"sources":["../../../src/runs/background/runnerReporting.ts"],"sourcesContent":["/**\n * Owns the single, synchronous callback wired into\n * `TerminalPersistenceService.begin()`: it both finalizes the run's status\n * and writes the terminal result file `ResultWatcher` claims by rename.\n *\n * WHY BOTH WRITES LIVE IN ONE CALLBACK:\n * `TerminalPersistenceService`'s idempotency guarantee only covers what\n * happens inside that one callback - a second, independent write site for\n * the result file would be exactly the \"fourth, unguarded terminal write\"\n * shape that made the predecessor's finalization the worst bug in its\n * review (see `asyncExecution.ts`'s header for the full diagnosis). Putting\n * the result-file write here means it inherits the same at-most-once\n * guarantee the status write already has, from the same source, rather than\n * needing a second one invented for it.\n *\n * WRITE ORDER, DECIDED DELIBERATELY (status, then result):\n * - status terminal / result missing: `ResultWatcher` delivers nothing, and\n * `StaleRunReconciler` sees no result plus a dead process, so it repairs.\n * RECOVERABLE\n * - result present / status not terminal: `ResultWatcher` delivers, but\n * `StaleRunReconciler` sees a result exists and correctly leaves the run\n * alone (see that class's header), so the stale status persists forever.\n * NOT RECOVERABLE\n * So status must land on disk first. The subtlety: `mutateTerminalStatus` is\n * the MUTATOR `TerminalPersistenceService` passes to\n * `CoalescedStatusWriter.updateSync(mutator)`, and that writer's own flush\n * only happens AFTER the mutator returns - so mutating `status` in place and\n * then writing the result file, in that order, inside this one function,\n * would put the result file on disk BEFORE the status flush, exactly\n * backwards. The fix: call `this.statusWriter.updateSync(() => {})` - a\n * second, no-op-mutator call to the SAME writer, re-entered from inside the\n * first one - immediately after mutating `status`. `updateSync` increments\n * its dirty counter and flushes unconditionally on every call regardless of\n * what its own mutator did, so this forces the just-mutated `status` onto\n * disk right there, before this function does anything else. The one\n * (harmless) cost is that `TerminalPersistenceService`'s own flush, once\n * this callback returns, then flushes again - a redundant but correct\n * second write of the same, already-correct status.\n *\n * WHY A THROW FROM THE RESULT WRITE CANNOT UNDO THE STATUS FLUSH:\n * The forced flush above happens, completes, and returns before\n * `writeResultFile` is even called - not \"before it succeeds\", before it is\n * INVOKED at all. A throw from that call propagates up through\n * `TerminalPersistenceService.triggerFinalize`'s `try` (skipping\n * `clearCrashMarker()`, per that class's own documented \"leave the marker in\n * place\" behavior on a mutator throw) into its `finally` (which still kills\n * tracked children) - but the status write already happened and cannot be\n * un-happened by anything that runs after it.\n *\n * WHY THE RESULT SHAPE IS MINIMAL, WITH ONE DELIBERATE EXCEPTION:\n * Most of the predecessor's ~40 result fields (structured output, artifacts,\n * workflow graph, capability audit) depend on subsystems not fully composed\n * here yet; shipping a correct minimal contract now beats an unverifiable\n * rich one. The exception: `acceptance` (an `AcceptanceLedger`) IS included\n * when `prepareResult()` was given one, because `acceptance.ts` is fully\n * ported and tested in this package today - an agent that declared\n * acceptance criteria and had them evaluated would otherwise produce a\n * result that silently omits the verdict, which is the same failure shape\n * as `DeliverableGuard`'s policy being unobservable (see\n * `asyncExecution.ts`'s header on that).\n *\n * WHY `summary` IS ALSO WRITTEN TO `status.json`, TRUNCATED:\n * Chain mode (`extensions/spawnPlan.ts`) needs a completed step's summary\n * to resolve the next step's `{previous}`/`{outputs.name}` template, but the\n * only thing that ever reads a delivered `RunResultFile` is whoever is\n * registered as `ResultWatcher`'s ONE consumer slot (`CompletionNotifier`,\n * by design). A second reader competing for that single claim-by-rename\n * would either displace the registered consumer or race it - both wrong.\n * `status.json` has no such problem: it has exactly one writer (this\n * callback, via `CoalescedStatusWriter`), is never claimed or unlinked, and\n * `AsyncJobTracker`/`SubagentWaiter` already read it to learn a run\n * finished - chain mode reads a step's summary through the path it already\n * uses. The copy in `status.json` is TRUNCATED (`MAX_STATUS_SUMMARY_CHARS`):\n * status is a coalesced, rewritten-in-full state file, not a log, and an\n * unbounded field there is a size problem waiting to happen. The full,\n * untruncated text still goes to the result file, for anything that reads\n * that instead. `acceptance` is deliberately NOT duplicated into status:\n * nothing this package builds yet gates chain advancement on an acceptance\n * verdict (only on execution success - see `spawnPlan.ts`'s header), so\n * there is no reader for a second copy of it yet. Add it if and when one\n * exists, the same reasoning as everything else flagged \"not wired yet\" in\n * this package.\n *\n * WHY `sessionFile` IS RECORDED HERE TOO, AND BOUNDED BY OMISSION, NOT\n * TRUNCATION:\n * `action='resume'` needs to know which Pi session transcript a completed\n * run's own child was writing to, and the CHILD is the only party that ever\n * knows that - `childTranscript.ts`'s header says the PARENT does not learn\n * a child's session id until the run ends, which already rules out the\n * parent recording it. `recordSessionFile()` is `prepareResult()`'s sibling:\n * a value set from outside, read back inside this same terminal-write\n * callback, on the SAME `CoalescedStatusWriter`, so it lands in the SAME\n * flush as everything else `mutateTerminalStatus` computes - no second\n * writer, no second flush. Where it is called from is\n * `subagentPromptRuntime.ts`'s `session_start` handler, the one place in\n * the child process that receives `ExtensionContext.sessionManager` at all.\n * `summary`'s bound is a truncation with a marker, because a shortened\n * summary is still a readable, honest summary. A shortened PATH is not a\n * smaller path to the same file - it is a WRONG path to some other file, or\n * no file at all, and `resume` acting on it would silently target the wrong\n * transcript. So this bound is \"drop entirely past `MAX_SESSION_FILE_CHARS`,\n * never truncate\" - the field goes missing rather than becoming a plausible-\n * looking lie an eventual `resume` implementation would trust.\n */\n\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { writeAtomicJson } from '../../shared/atomicJson.ts';\nimport { currentResultsDir } from '../../shared/paths.ts';\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport type { AsyncRunStatus } from './asyncExecution.ts';\nimport { RESULT_FILE_SUFFIX, type RunResultFile } from './resultWatcher.ts';\nimport { type ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { TerminalTrigger } from './terminalPersistence.ts';\nimport type { AcceptanceLedger } from '../types.ts';\n\nfunction resultPathFor(runId: string): string {\n return path.join(currentResultsDir(), `${runId}${RESULT_FILE_SUFFIX}`);\n}\n\n/** Generous but bounded - see the module header's summary-in-status section. */\nconst MAX_STATUS_SUMMARY_CHARS = 4_000;\nconst TRUNCATION_MARKER = '\\n... [truncated for status.json; see the result file for the full text]';\n\nfunction truncateForStatus(summary: string): string {\n if (summary.length <= MAX_STATUS_SUMMARY_CHARS) return summary;\n return summary.slice(0, MAX_STATUS_SUMMARY_CHARS - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;\n}\n\n/** Generous for any real filesystem path; see the module header's `sessionFile` section for why exceeding this drops the field instead of truncating it. */\nconst MAX_SESSION_FILE_CHARS = 4_096;\n\n/** What the run itself knows about its own outcome, recorded before a trigger fires. */\nexport interface RunnerResultInput {\n success: boolean;\n summary: string;\n /** Explicit terminal state for controlled outcomes such as an operator stop. */\n state?: 'completed' | 'failed' | 'stopped';\n acceptance?: AcceptanceLedger;\n}\n\nexport interface IRunnerReporting {\n /**\n * Record what to report if/when this run reaches a terminal state. Call\n * this before triggering finalize (normal completion, or right before an\n * interrupt/stop/timeout handler calls `terminalPersistence.finalize()`).\n * A trigger that fires with nothing prepared (a signal or exception mid-run)\n * still produces a result - see `mutateTerminalStatus`'s fallback.\n */\n prepareResult(input: RunnerResultInput): void;\n /**\n * Record this run's own Pi session transcript path, if/when the child\n * learns it. Call this as soon as it is known (`subagentPromptRuntime.ts`'s\n * `session_start` handler, today). The accepted value is returned for the\n * bootstrap status write, then retained here for the terminal status write.\n * See the module header's `sessionFile` section for why a too-long value is\n * dropped, not truncated.\n */\n recordSessionFile(sessionFile: string): string | undefined;\n /** The callback to wire into `TerminalPersistenceService.begin()`. See the module header for what it does and why, in this order. */\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void;\n}\n\nfunction describeReason(reason: unknown): string {\n if (reason instanceof Error) return reason.message;\n return typeof reason === 'string' ? reason : 'Unhandled rejection with no error message.';\n}\n\ninterface TerminalOutcome {\n state: AsyncRunStatus['state'];\n success: boolean;\n error?: string;\n}\n\n@injectable()\nexport class RunnerReporting implements IRunnerReporting {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n protected writeResultFile(runId: string, result: RunResultFile): void {\n writeAtomicJson(resultPathFor(runId), result);\n }\n\n private prepared: RunnerResultInput | undefined;\n private recordedSessionFile: string | undefined;\n\n prepareResult(input: RunnerResultInput): void {\n this.prepared = input;\n }\n\n recordSessionFile(sessionFile: string): string | undefined {\n // Drop, don't truncate, past the bound - see the module header's\n // `sessionFile` section for why a shortened path is worse than no path.\n this.recordedSessionFile = sessionFile.length <= MAX_SESSION_FILE_CHARS ? sessionFile : undefined;\n return this.recordedSessionFile;\n }\n\n private resolveOutcome(trigger: TerminalTrigger | undefined): TerminalOutcome {\n if (trigger?.kind === 'signal') {\n return { state: 'stopped', success: false, error: `Terminated by signal ${trigger.signal}.` };\n }\n if (trigger?.kind === 'uncaughtException') {\n return { state: 'failed', success: false, error: trigger.error.message };\n }\n if (trigger?.kind === 'unhandledRejection') {\n return { state: 'failed', success: false, error: describeReason(trigger.reason) };\n }\n // trigger === undefined: the run's own explicit finalize() call.\n if (this.prepared) {\n return {\n state: this.prepared.state ?? (this.prepared.success ? 'completed' : 'failed'),\n success: this.prepared.success,\n };\n }\n return { state: 'failed', success: false, error: 'Run finalized without ever calling prepareResult().' };\n }\n\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void {\n const now = this.now();\n const outcome = this.resolveOutcome(trigger);\n\n const summary = this.prepared?.summary ?? outcome.error ?? '(no summary)';\n\n status.state = outcome.state;\n status.endedAt = now;\n status.lastUpdate = now;\n status.summary = truncateForStatus(summary);\n if (outcome.error) status.error = outcome.error;\n if (this.recordedSessionFile) status.sessionFile = this.recordedSessionFile;\n\n // Forces the mutation above onto disk before anything else in this\n // function runs. See the module header's WRITE ORDER section for why\n // this specific call, at this specific point, is what achieves that.\n this.statusWriter.updateSync(() => {});\n\n const result: RunResultFile = {\n runId: status.runId,\n agent: status.agent,\n success: outcome.success,\n state: outcome.state,\n summary,\n startedAt: status.startedAt,\n endedAt: now,\n ...(outcome.error ? { error: outcome.error } : {}),\n ...(this.prepared?.acceptance ? { acceptance: this.prepared.acceptance } : {}),\n };\n this.writeResultFile(status.runId, result);\n }\n}\n"],"mappings":"8gBAqHA,SAAS,EAAc,EAAuB,CAC5C,OAAOA,EAAK,KAAKC,EAAAA,mBAAmB,CAAE,GAAG,IAAQC,EAAAA,qBAAqB,CAIxE,MAAM,EAA2B,IAGjC,SAAS,EAAkB,EAAyB,CAElD,OADI,EAAQ,QAAU,EAAiC,EAChD,EAAQ,MAAM,EAAG,EAA2B,GAAyB,CAAG;wEAqCjF,SAAS,EAAe,EAAyB,CAE/C,OADI,aAAkB,MAAc,EAAO,QACpC,OAAO,GAAW,SAAW,EAAS,6CAUxC,IAAA,EAAA,KAAkD,CACvD,YACE,EAEA,CADiB,KAAA,aAAA,EAGnB,KAAwB,CACtB,OAAO,KAAK,KAAK,CAGnB,gBAA0B,EAAe,EAA6B,CACpE,EAAA,gBAAgB,EAAc,EAAM,CAAE,EAAO,CAG/C,SACA,oBAEA,cAAc,EAAgC,CAC5C,KAAK,SAAW,EAGlB,kBAAkB,EAAyC,CAIzD,MADA,MAAK,oBAAsB,EAAY,QAAU,KAAyB,EAAc,IAAA,GACjF,KAAK,oBAGd,eAAuB,EAAuD,CAiB5E,OAhBI,GAAS,OAAS,SACb,CAAE,MAAO,UAAW,QAAS,GAAO,MAAO,wBAAwB,EAAQ,OAAO,GAAI,CAE3F,GAAS,OAAS,oBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAQ,MAAM,QAAS,CAEtE,GAAS,OAAS,qBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAe,EAAQ,OAAO,CAAE,CAG/E,KAAK,SACA,CACL,MAAO,KAAK,SAAS,QAAU,KAAK,SAAS,QAAU,YAAc,UACrE,QAAS,KAAK,SAAS,QACxB,CAEI,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,sDAAuD,CAG1G,qBAAqB,EAAwB,EAA4C,CACvF,IAAM,EAAM,KAAK,KAAK,CAChB,EAAU,KAAK,eAAe,EAAQ,CAEtC,EAAU,KAAK,UAAU,SAAW,EAAQ,OAAS,eAE3D,EAAO,MAAQ,EAAQ,MACvB,EAAO,QAAU,EACjB,EAAO,WAAa,EACpB,EAAO,QAAU,EAAkB,EAAQ,CACvC,EAAQ,QAAO,EAAO,MAAQ,EAAQ,OACtC,KAAK,sBAAqB,EAAO,YAAc,KAAK,qBAKxD,KAAK,aAAa,eAAiB,GAAG,CAEtC,IAAM,EAAwB,CAC5B,MAAO,EAAO,MACd,MAAO,EAAO,MACd,QAAS,EAAQ,QACjB,MAAO,EAAQ,MACf,UACA,UAAW,EAAO,UAClB,QAAS,EACT,GAAI,EAAQ,MAAQ,CAAE,MAAO,EAAQ,MAAO,CAAG,EAAE,CACjD,GAAI,KAAK,UAAU,WAAa,CAAE,WAAY,KAAK,SAAS,WAAY,CAAG,EAAE,CAC9E,CACD,KAAK,gBAAgB,EAAO,MAAO,EAAO,qCA9EjC,kCAGDC,EAAAA,gBAAgB,sBAAsB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerReporting.mjs","names":[],"sources":["../../../src/runs/background/runnerReporting.ts"],"sourcesContent":["/**\n * Owns the single, synchronous callback wired into\n * `TerminalPersistenceService.begin()`: it both finalizes the run's status\n * and writes the terminal result file `ResultWatcher` claims by rename.\n *\n * WHY BOTH WRITES LIVE IN ONE CALLBACK:\n * `TerminalPersistenceService`'s idempotency guarantee only covers what\n * happens inside that one callback - a second, independent write site for\n * the result file would be exactly the \"fourth, unguarded terminal write\"\n * shape that made the predecessor's finalization the worst bug in its\n * review (see `asyncExecution.ts`'s header for the full diagnosis). Putting\n * the result-file write here means it inherits the same at-most-once\n * guarantee the status write already has, from the same source, rather than\n * needing a second one invented for it.\n *\n * WRITE ORDER, DECIDED DELIBERATELY (status, then result):\n * - status terminal / result missing: `ResultWatcher` delivers nothing, and\n * `StaleRunReconciler` sees no result plus a dead process, so it repairs.\n * RECOVERABLE\n * - result present / status not terminal: `ResultWatcher` delivers, but\n * `StaleRunReconciler` sees a result exists and correctly leaves the run\n * alone (see that class's header), so the stale status persists forever.\n * NOT RECOVERABLE\n * So status must land on disk first. The subtlety: `mutateTerminalStatus` is\n * the MUTATOR `TerminalPersistenceService` passes to\n * `CoalescedStatusWriter.updateSync(mutator)`, and that writer's own flush\n * only happens AFTER the mutator returns - so mutating `status` in place and\n * then writing the result file, in that order, inside this one function,\n * would put the result file on disk BEFORE the status flush, exactly\n * backwards. The fix: call `this.statusWriter.updateSync(() => {})` - a\n * second, no-op-mutator call to the SAME writer, re-entered from inside the\n * first one - immediately after mutating `status`. `updateSync` increments\n * its dirty counter and flushes unconditionally on every call regardless of\n * what its own mutator did, so this forces the just-mutated `status` onto\n * disk right there, before this function does anything else. The one\n * (harmless) cost is that `TerminalPersistenceService`'s own flush, once\n * this callback returns, then flushes again - a redundant but correct\n * second write of the same, already-correct status.\n *\n * WHY A THROW FROM THE RESULT WRITE CANNOT UNDO THE STATUS FLUSH:\n * The forced flush above happens, completes, and returns before\n * `writeResultFile` is even called - not \"before it succeeds\", before it is\n * INVOKED at all. A throw from that call propagates up through\n * `TerminalPersistenceService.triggerFinalize`'s `try` (skipping\n * `clearCrashMarker()`, per that class's own documented \"leave the marker in\n * place\" behavior on a mutator throw) into its `finally` (which still kills\n * tracked children) - but the status write already happened and cannot be\n * un-happened by anything that runs after it.\n *\n * WHY THE RESULT SHAPE IS MINIMAL, WITH ONE DELIBERATE EXCEPTION:\n * Most of the predecessor's ~40 result fields (structured output, artifacts,\n * workflow graph, capability audit) depend on subsystems not fully composed\n * here yet; shipping a correct minimal contract now beats an unverifiable\n * rich one. The exception: `acceptance` (an `AcceptanceLedger`) IS included\n * when `prepareResult()` was given one, because `acceptance.ts` is fully\n * ported and tested in this package today - an agent that declared\n * acceptance criteria and had them evaluated would otherwise produce a\n * result that silently omits the verdict, which is the same failure shape\n * as `DeliverableGuard`'s policy being unobservable (see\n * `asyncExecution.ts`'s header on that).\n *\n * WHY `summary` IS ALSO WRITTEN TO `status.json`, TRUNCATED:\n * Chain mode (`extensions/spawnPlan.ts`) needs a completed step's summary\n * to resolve the next step's `{previous}`/`{outputs.name}` template, but the\n * only thing that ever reads a delivered `RunResultFile` is whoever is\n * registered as `ResultWatcher`'s ONE consumer slot (`CompletionNotifier`,\n * by design). A second reader competing for that single claim-by-rename\n * would either displace the registered consumer or race it - both wrong.\n * `status.json` has no such problem: it has exactly one writer (this\n * callback, via `CoalescedStatusWriter`), is never claimed or unlinked, and\n * `AsyncJobTracker`/`SubagentWaiter` already read it to learn a run\n * finished - chain mode reads a step's summary through the path it already\n * uses. The copy in `status.json` is TRUNCATED (`MAX_STATUS_SUMMARY_CHARS`):\n * status is a coalesced, rewritten-in-full state file, not a log, and an\n * unbounded field there is a size problem waiting to happen. The full,\n * untruncated text still goes to the result file, for anything that reads\n * that instead. `acceptance` is deliberately NOT duplicated into status:\n * nothing this package builds yet gates chain advancement on an acceptance\n * verdict (only on execution success - see `spawnPlan.ts`'s header), so\n * there is no reader for a second copy of it yet. Add it if and when one\n * exists, the same reasoning as everything else flagged \"not wired yet\" in\n * this package.\n *\n * WHY `sessionFile` IS RECORDED HERE TOO, AND BOUNDED BY OMISSION, NOT\n * TRUNCATION:\n * `action='resume'` needs to know which Pi session transcript a completed\n * run's own child was writing to, and the CHILD is the only party that ever\n * knows that - `childTranscript.ts`'s header says the PARENT does not learn\n * a child's session id until the run ends, which already rules out the\n * parent recording it. `recordSessionFile()` is `prepareResult()`'s sibling:\n * a value set from outside, read back inside this same terminal-write\n * callback, on the SAME `CoalescedStatusWriter`, so it lands in the SAME\n * flush as everything else `mutateTerminalStatus` computes - no second\n * writer, no second flush. Where it is called from is\n * `subagentPromptRuntime.ts`'s `session_start` handler, the one place in\n * the child process that receives `ExtensionContext.sessionManager` at all.\n * `summary`'s bound is a truncation with a marker, because a shortened\n * summary is still a readable, honest summary. A shortened PATH is not a\n * smaller path to the same file - it is a WRONG path to some other file, or\n * no file at all, and `resume` acting on it would silently target the wrong\n * transcript. So this bound is \"drop entirely past `MAX_SESSION_FILE_CHARS`,\n * never truncate\" - the field goes missing rather than becoming a plausible-\n * looking lie an eventual `resume` implementation would trust.\n */\n\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { writeAtomicJson } from '../../shared/atomicJson.ts';\nimport { currentResultsDir } from '../../shared/paths.ts';\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport type { AsyncRunStatus } from './asyncExecution.ts';\nimport { RESULT_FILE_SUFFIX, type RunResultFile } from './resultWatcher.ts';\nimport { type ICoalescedStatusWriter } from './statusWriter.ts';\nimport type { TerminalTrigger } from './terminalPersistence.ts';\nimport type { AcceptanceLedger } from '../types.ts';\n\nfunction resultPathFor(runId: string): string {\n return path.join(currentResultsDir(), `${runId}${RESULT_FILE_SUFFIX}`);\n}\n\n/** Generous but bounded - see the module header's summary-in-status section. */\nconst MAX_STATUS_SUMMARY_CHARS = 4_000;\nconst TRUNCATION_MARKER = '\\n... [truncated for status.json; see the result file for the full text]';\n\nfunction truncateForStatus(summary: string): string {\n if (summary.length <= MAX_STATUS_SUMMARY_CHARS) return summary;\n return summary.slice(0, MAX_STATUS_SUMMARY_CHARS - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;\n}\n\n/** Generous for any real filesystem path; see the module header's `sessionFile` section for why exceeding this drops the field instead of truncating it. */\nconst MAX_SESSION_FILE_CHARS = 4_096;\n\n/** What the run itself knows about its own outcome, recorded before a trigger fires. */\nexport interface RunnerResultInput {\n success: boolean;\n summary: string;\n /** Explicit terminal state for controlled outcomes such as an operator stop. */\n state?: 'completed' | 'failed' | 'stopped';\n acceptance?: AcceptanceLedger;\n}\n\nexport interface IRunnerReporting {\n /**\n * Record what to report if/when this run reaches a terminal state. Call\n * this before triggering finalize (normal completion, or right before an\n * interrupt/stop/timeout handler calls `terminalPersistence.finalize()`).\n * A trigger that fires with nothing prepared (a signal or exception mid-run)\n * still produces a result - see `mutateTerminalStatus`'s fallback.\n */\n prepareResult(input: RunnerResultInput): void;\n /**\n * Record this run's own Pi session transcript path, if/when the child\n * learns it. Call this as soon as it is known (`subagentPromptRuntime.ts`'s\n * `session_start` handler, today). The accepted value is returned for the\n * bootstrap status write, then retained here for the terminal status write.\n * See the module header's `sessionFile` section for why a too-long value is\n * dropped, not truncated.\n */\n recordSessionFile(sessionFile: string): string | undefined;\n /** The callback to wire into `TerminalPersistenceService.begin()`. See the module header for what it does and why, in this order. */\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void;\n}\n\nfunction describeReason(reason: unknown): string {\n if (reason instanceof Error) return reason.message;\n return typeof reason === 'string' ? reason : 'Unhandled rejection with no error message.';\n}\n\ninterface TerminalOutcome {\n state: AsyncRunStatus['state'];\n success: boolean;\n error?: string;\n}\n\n@injectable()\nexport class RunnerReporting implements IRunnerReporting {\n constructor(\n @inject(DOOM_TEAM_TYPES.CoalescedStatusWriter)\n private readonly statusWriter: ICoalescedStatusWriter<AsyncRunStatus>,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n protected writeResultFile(runId: string, result: RunResultFile): void {\n writeAtomicJson(resultPathFor(runId), result);\n }\n\n private prepared: RunnerResultInput | undefined;\n private recordedSessionFile: string | undefined;\n\n prepareResult(input: RunnerResultInput): void {\n this.prepared = input;\n }\n\n recordSessionFile(sessionFile: string): string | undefined {\n // Drop, don't truncate, past the bound - see the module header's\n // `sessionFile` section for why a shortened path is worse than no path.\n this.recordedSessionFile = sessionFile.length <= MAX_SESSION_FILE_CHARS ? sessionFile : undefined;\n return this.recordedSessionFile;\n }\n\n private resolveOutcome(trigger: TerminalTrigger | undefined): TerminalOutcome {\n if (trigger?.kind === 'signal') {\n return { state: 'stopped', success: false, error: `Terminated by signal ${trigger.signal}.` };\n }\n if (trigger?.kind === 'uncaughtException') {\n return { state: 'failed', success: false, error: trigger.error.message };\n }\n if (trigger?.kind === 'unhandledRejection') {\n return { state: 'failed', success: false, error: describeReason(trigger.reason) };\n }\n // trigger === undefined: the run's own explicit finalize() call.\n if (this.prepared) {\n return {\n state: this.prepared.state ?? (this.prepared.success ? 'completed' : 'failed'),\n success: this.prepared.success,\n };\n }\n return { state: 'failed', success: false, error: 'Run finalized without ever calling prepareResult().' };\n }\n\n mutateTerminalStatus(status: AsyncRunStatus, trigger: TerminalTrigger | undefined): void {\n const now = this.now();\n const outcome = this.resolveOutcome(trigger);\n\n const summary = this.prepared?.summary ?? outcome.error ?? '(no summary)';\n\n status.state = outcome.state;\n status.endedAt = now;\n status.lastUpdate = now;\n status.summary = truncateForStatus(summary);\n if (outcome.error) status.error = outcome.error;\n if (this.recordedSessionFile) status.sessionFile = this.recordedSessionFile;\n\n // Forces the mutation above onto disk before anything else in this\n // function runs. See the module header's WRITE ORDER section for why\n // this specific call, at this specific point, is what achieves that.\n this.statusWriter.updateSync(() => {});\n\n const result: RunResultFile = {\n runId: status.runId,\n agent: status.agent,\n success: outcome.success,\n state: outcome.state,\n summary,\n startedAt: status.startedAt,\n endedAt: now,\n ...(outcome.error ? { error: outcome.error } : {}),\n ...(this.prepared?.acceptance ? { acceptance: this.prepared.acceptance } : {}),\n };\n this.writeResultFile(status.runId, result);\n }\n}\n"],"mappings":"u0BAqHA,SAAS,EAAc,EAAuB,CAC5C,OAAO,EAAK,KAAK,GAAmB,CAAE,GAAG,IAAQ,IAAqB,CAOxE,SAAS,EAAkB,EAAyB,CAElD,OADI,EAAQ,QAAU,EAAiC,EAChD,EAAQ,MAAM,EAAG,EAA2B,GAAyB,CAAG,EAqCjF,SAAS,EAAe,EAAyB,CAE/C,OADI,aAAkB,MAAc,EAAO,QACpC,OAAO,GAAW,SAAW,EAAS,sEA1Dc,IACH,IACC,IAEiB,aAUtE,EAA2B,IAC3B,EAAoB;wEAQpB,EAAyB,OA6CxB,KAAkD,CACvD,YACE,EAEA,CADiB,KAAA,aAAA,EAGnB,KAAwB,CACtB,OAAO,KAAK,KAAK,CAGnB,gBAA0B,EAAe,EAA6B,CACpE,EAAgB,EAAc,EAAM,CAAE,EAAO,CAG/C,SACA,oBAEA,cAAc,EAAgC,CAC5C,KAAK,SAAW,EAGlB,kBAAkB,EAAyC,CAIzD,MADA,MAAK,oBAAsB,EAAY,QAAU,EAAyB,EAAc,IAAA,GACjF,KAAK,oBAGd,eAAuB,EAAuD,CAiB5E,OAhBI,GAAS,OAAS,SACb,CAAE,MAAO,UAAW,QAAS,GAAO,MAAO,wBAAwB,EAAQ,OAAO,GAAI,CAE3F,GAAS,OAAS,oBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAQ,MAAM,QAAS,CAEtE,GAAS,OAAS,qBACb,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,EAAe,EAAQ,OAAO,CAAE,CAG/E,KAAK,SACA,CACL,MAAO,KAAK,SAAS,QAAU,KAAK,SAAS,QAAU,YAAc,UACrE,QAAS,KAAK,SAAS,QACxB,CAEI,CAAE,MAAO,SAAU,QAAS,GAAO,MAAO,sDAAuD,CAG1G,qBAAqB,EAAwB,EAA4C,CACvF,IAAM,EAAM,KAAK,KAAK,CAChB,EAAU,KAAK,eAAe,EAAQ,CAEtC,EAAU,KAAK,UAAU,SAAW,EAAQ,OAAS,eAE3D,EAAO,MAAQ,EAAQ,MACvB,EAAO,QAAU,EACjB,EAAO,WAAa,EACpB,EAAO,QAAU,EAAkB,EAAQ,CACvC,EAAQ,QAAO,EAAO,MAAQ,EAAQ,OACtC,KAAK,sBAAqB,EAAO,YAAc,KAAK,qBAKxD,KAAK,aAAa,eAAiB,GAAG,CAEtC,IAAM,EAAwB,CAC5B,MAAO,EAAO,MACd,MAAO,EAAO,MACd,QAAS,EAAQ,QACjB,MAAO,EAAQ,MACf,UACA,UAAW,EAAO,UAClB,QAAS,EACT,GAAI,EAAQ,MAAQ,CAAE,MAAO,EAAQ,MAAO,CAAG,EAAE,CACjD,GAAI,KAAK,UAAU,WAAa,CAAE,WAAY,KAAK,SAAS,WAAY,CAAG,EAAE,CAC9E,CACD,KAAK,gBAAgB,EAAO,MAAO,EAAO,QA9E7C,GAAY,KAGR,EAAO,EAAgB,sBAAsB,CAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
require(`../../_virtual/_rolldown/runtime.cjs`);const e=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),t=require(`../../intercom/nativeTeamChannel.cjs`);let n=require(`inversify`),r=class{readRoot(){return t.readNativeTeamRootFromEnvironment()}register(e){return t.registerNativeTeamMember(e)}};r=e.__decorate([(0,n.injectable)()],r),Object.defineProperty(exports,`RunnerTeamMembership`,{enumerable:!0,get:function(){return r}});
|
|
2
|
-
//# sourceMappingURL=runnerTeamMembership.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerTeamMembership.cjs","names":["readNativeTeamRootFromEnvironment","registerNativeTeamMember"],"sources":["../../../src/runs/background/runnerTeamMembership.ts"],"sourcesContent":["/**\n * Thin composition seam over `nativeTeamChannel.ts`'s registration\n * functions, the same shape as `RunnerRevivalLease` over `session-lease.ts`:\n * no logic of its own, just an injectable wrapper so `RunnerBootstrap` can be\n * tested without writing real team files to disk.\n *\n * ERROR POLICY LIVES IN THE CALLER, NOT HERE:\n * `register()` throws exactly what `registerNativeTeamMember()` throws;\n * whether that is fatal to the run is `RunnerBootstrap`'s decision (it is\n * not - see that class's header for why team membership is additive), not\n * this wrapper's.\n */\n\nimport { injectable } from 'inversify';\nimport {\n readNativeTeamRootFromEnvironment,\n registerNativeTeamMember,\n type RegisterNativeTeamMemberInput,\n type TeamMemberContext,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\n\nexport interface RunnerTeamRegistration {\n context: TeamMemberContext;\n dispose: () => void;\n}\n\nexport interface IRunnerTeamMembership {\n /** This process's own team root context, if one was forwarded by whatever spawned it. `undefined` when this run is not part of a team. */\n readRoot(): TeamRootContext | undefined;\n /** Join the team as a new member. Throws on failure - see `RunnerBootstrap` for what it does with that. */\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration;\n}\n\n@injectable()\nexport class RunnerTeamMembership implements IRunnerTeamMembership {\n readRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration {\n return registerNativeTeamMember(input);\n }\n}\n"],"mappings":"kNAmCO,EAAA,KAA4D,CACjE,UAAwC,CACtC,OAAOA,EAAAA,mCAAmC,CAG5C,SAAS,EAA8D,CACrE,OAAOC,EAAAA,yBAAyB,EAAM,qCAP7B,CAAA,CAAA,EAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{init_nativeTeamChannel as r,readNativeTeamRootFromEnvironment as i,registerNativeTeamMember as a}from"../../intercom/nativeTeamChannel.mjs";import{injectable as o}from"inversify";var s,c=e((()=>{r(),n(),s=class{readRoot(){return i()}register(e){return a(e)}},s=t([o()],s)}));c();export{s as RunnerTeamMembership,c as init_runnerTeamMembership};
|
|
2
|
-
//# sourceMappingURL=runnerTeamMembership.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runnerTeamMembership.mjs","names":[],"sources":["../../../src/runs/background/runnerTeamMembership.ts"],"sourcesContent":["/**\n * Thin composition seam over `nativeTeamChannel.ts`'s registration\n * functions, the same shape as `RunnerRevivalLease` over `session-lease.ts`:\n * no logic of its own, just an injectable wrapper so `RunnerBootstrap` can be\n * tested without writing real team files to disk.\n *\n * ERROR POLICY LIVES IN THE CALLER, NOT HERE:\n * `register()` throws exactly what `registerNativeTeamMember()` throws;\n * whether that is fatal to the run is `RunnerBootstrap`'s decision (it is\n * not - see that class's header for why team membership is additive), not\n * this wrapper's.\n */\n\nimport { injectable } from 'inversify';\nimport {\n readNativeTeamRootFromEnvironment,\n registerNativeTeamMember,\n type RegisterNativeTeamMemberInput,\n type TeamMemberContext,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\n\nexport interface RunnerTeamRegistration {\n context: TeamMemberContext;\n dispose: () => void;\n}\n\nexport interface IRunnerTeamMembership {\n /** This process's own team root context, if one was forwarded by whatever spawned it. `undefined` when this run is not part of a team. */\n readRoot(): TeamRootContext | undefined;\n /** Join the team as a new member. Throws on failure - see `RunnerBootstrap` for what it does with that. */\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration;\n}\n\n@injectable()\nexport class RunnerTeamMembership implements IRunnerTeamMembership {\n readRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n register(input: RegisterNativeTeamMemberInput): RunnerTeamRegistration {\n return registerNativeTeamMember(input);\n }\n}\n"],"mappings":"+XAoB6C,OAetC,KAA4D,CACjE,UAAwC,CACtC,OAAO,GAAmC,CAG5C,SAAS,EAA8D,CACrE,OAAO,EAAyB,EAAM,QAPzC,GAAY,CAAA,CAAA,EAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require(`../../_virtual/_rolldown/runtime.cjs`),t=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`);let n=require(`node:fs`);n=e.__toESM(n,1);let r=require(`node:path`);r=e.__toESM(r,1);let i=require(`inversify`),a=class{pollIntervalMs=20;defaultTimeoutMs=1e4;readFile(e){try{return n.readFileSync(e,`utf-8`)}catch{return}}watchDirectory(e,t){try{let r=n.watch(e,()=>t());return r.on(`error`,()=>t()),r}catch{return}}readOutcome(e){let t=this.readFile(e);if(t===void 0)return;let n;try{n=JSON.parse(t)}catch{return}if(typeof n!=`object`||!n)return;let r=n.state;if(r===`ready`)return{status:`signalled`};if(r===`error`){let e=n.error;return{status:`failed`,error:typeof e==`string`?e:`Async runner reported an error.`}}}waitForHandshake(e){let t=e.timeoutMs??this.defaultTimeoutMs,n=!1,i,a,o,s,c=()=>{i&&clearInterval(i),a&&clearTimeout(a),o?.close(),i=void 0,a=void 0,o=void 0};return{promise:new Promise((l,u)=>{s=u;let d=e=>{n||(n=!0,c(),l(e))},f=()=>{if(n)return;let t=this.readOutcome(e.path);t&&d(t)};a=setTimeout(()=>d({status:`timed-out`}),t),a.unref?.(),o=this.watchDirectory(r.dirname(e.path),f),i=setInterval(f,this.pollIntervalMs),i.unref?.(),f()}),cancel:e=>{n||(n=!0,c(),s?.(Error(e)))}}}};a=t.__decorate([(0,i.injectable)()],a),Object.defineProperty(exports,`SpawnHandshake`,{enumerable:!0,get:function(){return a}});
|
|
2
|
-
//# sourceMappingURL=spawnHandshake.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spawnHandshake.cjs","names":["fs","path"],"sources":["../../../src/runs/background/spawnHandshake.ts"],"sourcesContent":["/**\n * Promise-based wait for a spawned child to signal that it has started.\n *\n * WHY THIS EXISTS:\n * The predecessor package waits for this same signal with `Atomics.wait`\n * (`doom-pi-subagents/src/runs/background/asyncExecution.ts:376-380`), which\n * blocks the parent's event loop for up to 10 seconds. That froze the whole TUI.\n * It was survivable when background spawns were opt-in; this package is\n * async-only, so every spawn would pay it. This module reads the same\n * readiness signal (a JSON file the child writes, `{ state: 'ready' }` or\n * `{ state: 'error', error }`) but never blocks the event loop to wait for it.\n *\n * WATCH-FIRST, POLL AS A SAFETY NET:\n * Same pattern as `PollScheduler`'s header, for the same reason: `fs.watch` is\n * the fast path, a ~20ms poll is the safety net for the platforms and mount\n * types where watches are unreliable or do not fire, and neither path is\n * trusted alone.\n *\n * WHY THIS DOES NOT REGISTER WITH `PollScheduler`:\n * `PollScheduler` exists to let background housekeeping (a handful of\n * long-lived subscribers: channel polling, status writing) back off toward\n * multi-second intervals while idle. A handshake wait is the opposite shape: a\n * per-spawn, short-lived (bounded by `timeoutMs`, default 10s), low-latency\n * wait that specifically must NOT inherit the scheduler's idle backoff. Sharing\n * the master tick would force a choice between two bad options: either every\n * concurrent handshake wait keeps the whole scheduler pinned near its floor\n * (so unrelated housekeeping never gets to back off while any spawn is\n * starting), or the handshake is checked only as often as the scheduler's\n * current backed-off interval allows (which defeats the ~20ms safety net this\n * requirement exists for). A dedicated, self-terminating timer avoids coupling\n * spawn concurrency to the cadence of unrelated background work.\n *\n * DESIGN PATTERNS:\n * - `waitForHandshake` returns `{ promise, cancel }` rather than only a promise.\n * `cancel` is a real exit path, not a convenience: an orchestrator that sees\n * the child process itself exit before signalling should be able to reject\n * immediately rather than waiting out the remaining timeout doing nothing\n * - The three terminal outcomes a caller actually wants to distinguish\n * (`signalled`, `failed`, `timed-out`) are all resolutions, not rejections,\n * so a plain timeout does not force every caller into a try/catch. `cancel`\n * is the one path that rejects, because it is the caller abandoning the wait\n * for a reason of its own, not an outcome of the handshake itself\n * - One `cleanup` function is the only place that clears the timers and closes\n * the watcher, and every terminal path (signal, failure, timeout, cancel)\n * goes through it, so there is exactly one place a leak could be introduced\n * - Reading the handshake file behind protected seams rather than importing\n * `node:fs` calls directly into tests: ESM module namespaces are frozen, so\n * `vi.spyOn(fs, ...)` throws, and a seam is the documented way around that\n *\n * AVOID:\n * - Treating a JSON parse failure on the handshake file as a definite outcome;\n * the caller writes it with a rename-based atomic write, so a failure here\n * means this check landed mid-write, not that the child failed. It must be\n * treated as \"still pending\" and retried on the next check\n * - Letting `watchDirectory` throwing propagate; a platform or mount that\n * cannot support `fs.watch` is exactly the case the poll safety net exists\n * for, so watch setup failing must fall through to poll-only, not abort\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\n/** Poll safety net cadence. Watches are the fast path; this bounds the worst case. */\nconst DEFAULT_POLL_INTERVAL_MS = 20;\n/** Matches the predecessor's `RUNNER_STARTUP_TIMEOUT_MS`: this is the wire protocol's known-good bound. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport interface SpawnHandshakeOptions {\n /** Absolute path of the file the child writes when it starts (or fails to). */\n path: string;\n /** Upper bound on how long to wait before resolving `timed-out`. Defaults to 10s. */\n timeoutMs?: number;\n}\n\nexport type SpawnHandshakeOutcome =\n | { status: 'signalled' }\n | { status: 'failed'; error: string }\n | { status: 'timed-out' };\n\nexport interface SpawnHandshakeWait {\n promise: Promise<SpawnHandshakeOutcome>;\n /** Reject early and tear down. For a caller that already knows waiting further is pointless. */\n cancel: (reason: string) => void;\n}\n\nexport interface ISpawnHandshake {\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait;\n}\n\n/** Minimal surface this module needs from a `fs.FSWatcher`, so a test seam does not need the real type. */\ninterface DirectoryWatch {\n close: () => void;\n}\n\n@injectable()\nexport class SpawnHandshake implements ISpawnHandshake {\n /**\n * Seams for tests, as protected members rather than constructor arguments.\n * The container resolves this service by type, and inversify cannot supply a\n * primitive like a poll interval, so a test subclass overrides these instead.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly defaultTimeoutMs: number = DEFAULT_TIMEOUT_MS;\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n // Missing (not yet written) or transiently unreadable: keep waiting.\n return undefined;\n }\n }\n\n /**\n * Watches the handshake file's directory, not the file itself: the file does\n * not exist yet when the wait starts, and `fs.watch` on a path that does not\n * exist throws on most platforms. Returns `undefined` when watching could not\n * be set up at all, which the caller treats as \"poll-only for this wait\".\n */\n protected watchDirectory(dirPath: string, onEvent: () => void): DirectoryWatch | undefined {\n try {\n const watcher = fs.watch(dirPath, () => onEvent());\n // An unhandled 'error' event on an FSWatcher crashes the process; this\n // wait has a poll safety net regardless, so a watch error just means\n // falling back to it rather than losing the wait entirely.\n watcher.on('error', () => onEvent());\n return watcher;\n } catch {\n return undefined;\n }\n }\n\n /** Interpret the handshake file's contents, or `undefined` when the outcome is not decided yet. */\n private readOutcome(filePath: string): SpawnHandshakeOutcome | undefined {\n const raw = this.readFile(filePath);\n if (raw === undefined) return undefined;\n let payload: unknown;\n try {\n payload = JSON.parse(raw);\n } catch {\n // A rename-based atomic write should never be readable mid-write, but a\n // torn read is cheaper to tolerate than to rule out; the next check retries.\n return undefined;\n }\n if (typeof payload !== 'object' || payload === null) return undefined;\n const state = (payload as { state?: unknown }).state;\n if (state === 'ready') return { status: 'signalled' };\n if (state === 'error') {\n const error = (payload as { error?: unknown }).error;\n return { status: 'failed', error: typeof error === 'string' ? error : 'Async runner reported an error.' };\n }\n return undefined;\n }\n\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait {\n const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;\n let settled = false;\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n let watcher: DirectoryWatch | undefined;\n let rejectWait: ((reason: unknown) => void) | undefined;\n\n const cleanup = (): void => {\n if (pollTimer) clearInterval(pollTimer);\n if (timeoutTimer) clearTimeout(timeoutTimer);\n watcher?.close();\n pollTimer = undefined;\n timeoutTimer = undefined;\n watcher = undefined;\n };\n\n const promise = new Promise<SpawnHandshakeOutcome>((resolve, reject) => {\n rejectWait = reject;\n\n const settle = (outcome: SpawnHandshakeOutcome): void => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(outcome);\n };\n\n const checkNow = (): void => {\n if (settled) return;\n const outcome = this.readOutcome(options.path);\n if (outcome) settle(outcome);\n };\n\n timeoutTimer = setTimeout(() => settle({ status: 'timed-out' }), timeoutMs);\n timeoutTimer.unref?.();\n\n watcher = this.watchDirectory(path.dirname(options.path), checkNow);\n\n pollTimer = setInterval(checkNow, this.pollIntervalMs);\n pollTimer.unref?.();\n\n // The file may already exist (a very fast child, or a wait that started\n // late), so the first check happens immediately rather than waiting out\n // either the poll interval or the first watch event.\n checkNow();\n });\n\n const cancel = (reason: string): void => {\n if (settled) return;\n settled = true;\n cleanup();\n rejectWait?.(new Error(reason));\n };\n\n return { promise, cancel };\n }\n}\n"],"mappings":"wPAgGO,EAAA,KAAgD,CAMrD,eAA4C,GAC5C,iBAA8C,IAE9C,SAAmB,EAAsC,CACvD,GAAI,CACF,OAAOA,EAAG,aAAa,EAAU,QAAQ,MACnC,CAEN,QAUJ,eAAyB,EAAiB,EAAiD,CACzF,GAAI,CACF,IAAM,EAAUA,EAAG,MAAM,MAAe,GAAS,CAAC,CAKlD,OADA,EAAQ,GAAG,YAAe,GAAS,CAAC,CAC7B,OACD,CACN,QAKJ,YAAoB,EAAqD,CACvE,IAAM,EAAM,KAAK,SAAS,EAAS,CACnC,GAAI,IAAQ,IAAA,GAAW,OACvB,IAAI,EACJ,GAAI,CACF,EAAU,KAAK,MAAM,EAAI,MACnB,CAGN,OAEF,GAAI,OAAO,GAAY,WAAY,EAAkB,OACrD,IAAM,EAAS,EAAgC,MAC/C,GAAI,IAAU,QAAS,MAAO,CAAE,OAAQ,YAAa,CACrD,GAAI,IAAU,QAAS,CACrB,IAAM,EAAS,EAAgC,MAC/C,MAAO,CAAE,OAAQ,SAAU,MAAO,OAAO,GAAU,SAAW,EAAQ,kCAAmC,EAK7G,iBAAiB,EAAoD,CACnE,IAAM,EAAY,EAAQ,WAAa,KAAK,iBACxC,EAAU,GACV,EACA,EACA,EACA,EAEE,MAAsB,CACtB,GAAW,cAAc,EAAU,CACnC,GAAc,aAAa,EAAa,CAC5C,GAAS,OAAO,CAChB,EAAY,IAAA,GACZ,EAAe,IAAA,GACf,EAAU,IAAA,IAwCZ,MAAO,CAAE,YArCW,SAAgC,EAAS,IAAW,CACtE,EAAa,EAEb,IAAM,EAAU,GAAyC,CACnD,IACJ,EAAU,GACV,GAAS,CACT,EAAQ,EAAQ,GAGZ,MAAuB,CAC3B,GAAI,EAAS,OACb,IAAM,EAAU,KAAK,YAAY,EAAQ,KAAK,CAC1C,GAAS,EAAO,EAAQ,EAG9B,EAAe,eAAiB,EAAO,CAAE,OAAQ,YAAa,CAAC,CAAE,EAAU,CAC3E,EAAa,SAAS,CAEtB,EAAU,KAAK,eAAeC,EAAK,QAAQ,EAAQ,KAAK,CAAE,EAAS,CAEnE,EAAY,YAAY,EAAU,KAAK,eAAe,CACtD,EAAU,SAAS,CAKnB,GAAU,EAUH,CAAS,OAPF,GAAyB,CACnC,IACJ,EAAU,GACV,GAAS,CACT,IAAiB,MAAM,EAAO,CAAC,GAGP,qCAlHjB,CAAA,CAAA,EAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import*as r from"node:fs";import*as i from"node:path";import{injectable as a}from"inversify";var o,s,c,l=e((()=>{n(),o=20,s=1e4,c=class{pollIntervalMs=o;defaultTimeoutMs=s;readFile(e){try{return r.readFileSync(e,`utf-8`)}catch{return}}watchDirectory(e,t){try{let n=r.watch(e,()=>t());return n.on(`error`,()=>t()),n}catch{return}}readOutcome(e){let t=this.readFile(e);if(t===void 0)return;let n;try{n=JSON.parse(t)}catch{return}if(typeof n!=`object`||!n)return;let r=n.state;if(r===`ready`)return{status:`signalled`};if(r===`error`){let e=n.error;return{status:`failed`,error:typeof e==`string`?e:`Async runner reported an error.`}}}waitForHandshake(e){let t=e.timeoutMs??this.defaultTimeoutMs,n=!1,r,a,o,s,c=()=>{r&&clearInterval(r),a&&clearTimeout(a),o?.close(),r=void 0,a=void 0,o=void 0};return{promise:new Promise((l,u)=>{s=u;let d=e=>{n||(n=!0,c(),l(e))},f=()=>{if(n)return;let t=this.readOutcome(e.path);t&&d(t)};a=setTimeout(()=>d({status:`timed-out`}),t),a.unref?.(),o=this.watchDirectory(i.dirname(e.path),f),r=setInterval(f,this.pollIntervalMs),r.unref?.(),f()}),cancel:e=>{n||(n=!0,c(),s?.(Error(e)))}}}},c=t([a()],c)}));l();export{c as SpawnHandshake,l as init_spawnHandshake};
|
|
2
|
-
//# sourceMappingURL=spawnHandshake.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"spawnHandshake.mjs","names":[],"sources":["../../../src/runs/background/spawnHandshake.ts"],"sourcesContent":["/**\n * Promise-based wait for a spawned child to signal that it has started.\n *\n * WHY THIS EXISTS:\n * The predecessor package waits for this same signal with `Atomics.wait`\n * (`doom-pi-subagents/src/runs/background/asyncExecution.ts:376-380`), which\n * blocks the parent's event loop for up to 10 seconds. That froze the whole TUI.\n * It was survivable when background spawns were opt-in; this package is\n * async-only, so every spawn would pay it. This module reads the same\n * readiness signal (a JSON file the child writes, `{ state: 'ready' }` or\n * `{ state: 'error', error }`) but never blocks the event loop to wait for it.\n *\n * WATCH-FIRST, POLL AS A SAFETY NET:\n * Same pattern as `PollScheduler`'s header, for the same reason: `fs.watch` is\n * the fast path, a ~20ms poll is the safety net for the platforms and mount\n * types where watches are unreliable or do not fire, and neither path is\n * trusted alone.\n *\n * WHY THIS DOES NOT REGISTER WITH `PollScheduler`:\n * `PollScheduler` exists to let background housekeeping (a handful of\n * long-lived subscribers: channel polling, status writing) back off toward\n * multi-second intervals while idle. A handshake wait is the opposite shape: a\n * per-spawn, short-lived (bounded by `timeoutMs`, default 10s), low-latency\n * wait that specifically must NOT inherit the scheduler's idle backoff. Sharing\n * the master tick would force a choice between two bad options: either every\n * concurrent handshake wait keeps the whole scheduler pinned near its floor\n * (so unrelated housekeeping never gets to back off while any spawn is\n * starting), or the handshake is checked only as often as the scheduler's\n * current backed-off interval allows (which defeats the ~20ms safety net this\n * requirement exists for). A dedicated, self-terminating timer avoids coupling\n * spawn concurrency to the cadence of unrelated background work.\n *\n * DESIGN PATTERNS:\n * - `waitForHandshake` returns `{ promise, cancel }` rather than only a promise.\n * `cancel` is a real exit path, not a convenience: an orchestrator that sees\n * the child process itself exit before signalling should be able to reject\n * immediately rather than waiting out the remaining timeout doing nothing\n * - The three terminal outcomes a caller actually wants to distinguish\n * (`signalled`, `failed`, `timed-out`) are all resolutions, not rejections,\n * so a plain timeout does not force every caller into a try/catch. `cancel`\n * is the one path that rejects, because it is the caller abandoning the wait\n * for a reason of its own, not an outcome of the handshake itself\n * - One `cleanup` function is the only place that clears the timers and closes\n * the watcher, and every terminal path (signal, failure, timeout, cancel)\n * goes through it, so there is exactly one place a leak could be introduced\n * - Reading the handshake file behind protected seams rather than importing\n * `node:fs` calls directly into tests: ESM module namespaces are frozen, so\n * `vi.spyOn(fs, ...)` throws, and a seam is the documented way around that\n *\n * AVOID:\n * - Treating a JSON parse failure on the handshake file as a definite outcome;\n * the caller writes it with a rename-based atomic write, so a failure here\n * means this check landed mid-write, not that the child failed. It must be\n * treated as \"still pending\" and retried on the next check\n * - Letting `watchDirectory` throwing propagate; a platform or mount that\n * cannot support `fs.watch` is exactly the case the poll safety net exists\n * for, so watch setup failing must fall through to poll-only, not abort\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { injectable } from 'inversify';\n\n/** Poll safety net cadence. Watches are the fast path; this bounds the worst case. */\nconst DEFAULT_POLL_INTERVAL_MS = 20;\n/** Matches the predecessor's `RUNNER_STARTUP_TIMEOUT_MS`: this is the wire protocol's known-good bound. */\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\nexport interface SpawnHandshakeOptions {\n /** Absolute path of the file the child writes when it starts (or fails to). */\n path: string;\n /** Upper bound on how long to wait before resolving `timed-out`. Defaults to 10s. */\n timeoutMs?: number;\n}\n\nexport type SpawnHandshakeOutcome =\n | { status: 'signalled' }\n | { status: 'failed'; error: string }\n | { status: 'timed-out' };\n\nexport interface SpawnHandshakeWait {\n promise: Promise<SpawnHandshakeOutcome>;\n /** Reject early and tear down. For a caller that already knows waiting further is pointless. */\n cancel: (reason: string) => void;\n}\n\nexport interface ISpawnHandshake {\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait;\n}\n\n/** Minimal surface this module needs from a `fs.FSWatcher`, so a test seam does not need the real type. */\ninterface DirectoryWatch {\n close: () => void;\n}\n\n@injectable()\nexport class SpawnHandshake implements ISpawnHandshake {\n /**\n * Seams for tests, as protected members rather than constructor arguments.\n * The container resolves this service by type, and inversify cannot supply a\n * primitive like a poll interval, so a test subclass overrides these instead.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly defaultTimeoutMs: number = DEFAULT_TIMEOUT_MS;\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n // Missing (not yet written) or transiently unreadable: keep waiting.\n return undefined;\n }\n }\n\n /**\n * Watches the handshake file's directory, not the file itself: the file does\n * not exist yet when the wait starts, and `fs.watch` on a path that does not\n * exist throws on most platforms. Returns `undefined` when watching could not\n * be set up at all, which the caller treats as \"poll-only for this wait\".\n */\n protected watchDirectory(dirPath: string, onEvent: () => void): DirectoryWatch | undefined {\n try {\n const watcher = fs.watch(dirPath, () => onEvent());\n // An unhandled 'error' event on an FSWatcher crashes the process; this\n // wait has a poll safety net regardless, so a watch error just means\n // falling back to it rather than losing the wait entirely.\n watcher.on('error', () => onEvent());\n return watcher;\n } catch {\n return undefined;\n }\n }\n\n /** Interpret the handshake file's contents, or `undefined` when the outcome is not decided yet. */\n private readOutcome(filePath: string): SpawnHandshakeOutcome | undefined {\n const raw = this.readFile(filePath);\n if (raw === undefined) return undefined;\n let payload: unknown;\n try {\n payload = JSON.parse(raw);\n } catch {\n // A rename-based atomic write should never be readable mid-write, but a\n // torn read is cheaper to tolerate than to rule out; the next check retries.\n return undefined;\n }\n if (typeof payload !== 'object' || payload === null) return undefined;\n const state = (payload as { state?: unknown }).state;\n if (state === 'ready') return { status: 'signalled' };\n if (state === 'error') {\n const error = (payload as { error?: unknown }).error;\n return { status: 'failed', error: typeof error === 'string' ? error : 'Async runner reported an error.' };\n }\n return undefined;\n }\n\n waitForHandshake(options: SpawnHandshakeOptions): SpawnHandshakeWait {\n const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;\n let settled = false;\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n let watcher: DirectoryWatch | undefined;\n let rejectWait: ((reason: unknown) => void) | undefined;\n\n const cleanup = (): void => {\n if (pollTimer) clearInterval(pollTimer);\n if (timeoutTimer) clearTimeout(timeoutTimer);\n watcher?.close();\n pollTimer = undefined;\n timeoutTimer = undefined;\n watcher = undefined;\n };\n\n const promise = new Promise<SpawnHandshakeOutcome>((resolve, reject) => {\n rejectWait = reject;\n\n const settle = (outcome: SpawnHandshakeOutcome): void => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(outcome);\n };\n\n const checkNow = (): void => {\n if (settled) return;\n const outcome = this.readOutcome(options.path);\n if (outcome) settle(outcome);\n };\n\n timeoutTimer = setTimeout(() => settle({ status: 'timed-out' }), timeoutMs);\n timeoutTimer.unref?.();\n\n watcher = this.watchDirectory(path.dirname(options.path), checkNow);\n\n pollTimer = setInterval(checkNow, this.pollIntervalMs);\n pollTimer.unref?.();\n\n // The file may already exist (a very fast child, or a wait that started\n // late), so the first check happens immediately rather than waiting out\n // either the poll interval or the first watch event.\n checkNow();\n });\n\n const cancel = (reason: string): void => {\n if (settled) return;\n settled = true;\n cleanup();\n rejectWait?.(new Error(reason));\n };\n\n return { promise, cancel };\n }\n}\n"],"mappings":"uSAgEM,EAA2B,GAE3B,EAAqB,MA8BpB,KAAgD,CAMrD,eAA4C,EAC5C,iBAA8C,EAE9C,SAAmB,EAAsC,CACvD,GAAI,CACF,OAAO,EAAG,aAAa,EAAU,QAAQ,MACnC,CAEN,QAUJ,eAAyB,EAAiB,EAAiD,CACzF,GAAI,CACF,IAAM,EAAU,EAAG,MAAM,MAAe,GAAS,CAAC,CAKlD,OADA,EAAQ,GAAG,YAAe,GAAS,CAAC,CAC7B,OACD,CACN,QAKJ,YAAoB,EAAqD,CACvE,IAAM,EAAM,KAAK,SAAS,EAAS,CACnC,GAAI,IAAQ,IAAA,GAAW,OACvB,IAAI,EACJ,GAAI,CACF,EAAU,KAAK,MAAM,EAAI,MACnB,CAGN,OAEF,GAAI,OAAO,GAAY,WAAY,EAAkB,OACrD,IAAM,EAAS,EAAgC,MAC/C,GAAI,IAAU,QAAS,MAAO,CAAE,OAAQ,YAAa,CACrD,GAAI,IAAU,QAAS,CACrB,IAAM,EAAS,EAAgC,MAC/C,MAAO,CAAE,OAAQ,SAAU,MAAO,OAAO,GAAU,SAAW,EAAQ,kCAAmC,EAK7G,iBAAiB,EAAoD,CACnE,IAAM,EAAY,EAAQ,WAAa,KAAK,iBACxC,EAAU,GACV,EACA,EACA,EACA,EAEE,MAAsB,CACtB,GAAW,cAAc,EAAU,CACnC,GAAc,aAAa,EAAa,CAC5C,GAAS,OAAO,CAChB,EAAY,IAAA,GACZ,EAAe,IAAA,GACf,EAAU,IAAA,IAwCZ,MAAO,CAAE,YArCW,SAAgC,EAAS,IAAW,CACtE,EAAa,EAEb,IAAM,EAAU,GAAyC,CACnD,IACJ,EAAU,GACV,GAAS,CACT,EAAQ,EAAQ,GAGZ,MAAuB,CAC3B,GAAI,EAAS,OACb,IAAM,EAAU,KAAK,YAAY,EAAQ,KAAK,CAC1C,GAAS,EAAO,EAAQ,EAG9B,EAAe,eAAiB,EAAO,CAAE,OAAQ,YAAa,CAAC,CAAE,EAAU,CAC3E,EAAa,SAAS,CAEtB,EAAU,KAAK,eAAe,EAAK,QAAQ,EAAQ,KAAK,CAAE,EAAS,CAEnE,EAAY,YAAY,EAAU,KAAK,eAAe,CACtD,EAAU,SAAS,CAKnB,GAAU,EAUH,CAAS,OAPF,GAAyB,CACnC,IACJ,EAAU,GACV,GAAS,CACT,IAAiB,MAAM,EAAO,CAAC,GAGP,QAlH7B,GAAY,CAAA,CAAA,EAAA"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require(`../../_virtual/_rolldown/runtime.cjs`),t=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),n=require(`../../container/types.cjs`),r=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),i=require(`../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`),a=require(`../../shared/paths.cjs`),o=require(`./statusWriter.cjs`),s=require(`./asyncJobTracker.cjs`),c=require(`../../shared/artifacts.cjs`);let l=require(`node:fs`);l=e.__toESM(l,1);let u=require(`node:path`);u=e.__toESM(u,1);let d=require(`inversify`);const f=`.claim-`;function p(e){return u.join(a.currentRunsDir(),e,o.STATUS_FILE_NAME)}function m(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function h(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}let g=class{constructor(e,t){this.processTerminal=e,this.runIdResolver=t}now(){return Date.now()}createStatusWriter(){return new o.CoalescedStatusWriter}readFile(e){try{return l.readFileSync(e,`utf-8`)}catch(e){if(m(e)===`ENOENT`)return;throw e}}readStatus(e){let t=this.readFile(p(e));if(t===void 0)return;let n;try{n=JSON.parse(t)}catch{return}return h(n)?n:void 0}appendEvent(e,t){try{c.appendJsonl(u.join(a.currentRunsDir(),e,`events.jsonl`),JSON.stringify(t))}catch{}}walkForClaims(e,t=0){let n;try{n=l.readdirSync(e,{withFileTypes:!0})}catch(e){if(m(e)===`ENOENT`)return[];throw e}let r=[];for(let i of n)i.name.includes(f)?r.push({dir:e,name:i.name}):i.isDirectory()&&t<8&&r.push(...this.walkForClaims(u.join(e,i.name),t+1));return r}recoverClaim(e,t){try{return l.renameSync(e,t),!0}catch{try{l.unlinkSync(e)}catch{}return!1}}inspectOnce(e){let t;return()=>t??=this.processTerminal.inspect(e)}reconcile(e){return this.reconcileWith(e,this.inspectOnce(e))}sweepOrphanedClaims(e){return this.sweepWith(e,this.inspectOnce(e))}reconcileAndSweep(e){let t=this.inspectOnce(e);return{reconcile:this.reconcileWith(e,t),sweep:this.sweepWith(e,t)}}reconcileWith(e,t){let n=this.readStatus(e);if(!n)return{repaired:!1,reason:`No status file for this run.`};let r=n.state;if(r!==void 0&&s.TERMINAL_ASYNC_JOB_STATES.has(r))return{repaired:!1,reason:`Run already terminal (${r}).`};if(this.runIdResolver.resolve(e)?.resultPath)return{repaired:!1,reason:`A result already exists; delivery is ResultWatcher's job, not a repair.`};let i=t();return i.state===`crashed`?this.repair(e,n,i.marker.pid):{repaired:!1,reason:`Owning process liveness is '${i.state}', which does not prove death; leaving the run as is.`}}sweepWith(e,t){let n=t();if(n.state!==`crashed`)return{recovered:[]};let r=u.join(a.currentRunsDir(),e),i=[];for(let{dir:t,name:a}of this.walkForClaims(r)){let o=a.lastIndexOf(f);if(o===-1)continue;let s=a.slice(0,o);if(this.recoverClaim(u.join(t,a),u.join(t,s))){let a=u.relative(r,t).split(u.sep).join(`/`);i.push({queue:a,fileName:s}),this.appendEvent(e,{type:`subagent.run.claim_recovered`,ts:this.now(),runId:e,pid:n.marker.pid,queue:a,recovered:s})}}return{recovered:i}}repair(e,t,n){let r=this.now(),i=`Runner process ${n} exited without finalizing this run. Marked failed by stale-run reconciliation.`,a=this.createStatusWriter();return a.open(e,t),a.updateSync(e=>{e.state=`failed`,e.error=e.error??i,e.lastUpdate=r,e.endedAt=e.endedAt??r}),this.appendEvent(e,{type:`subagent.run.repaired_stale`,ts:r,runId:e,pid:n,message:i}),{repaired:!0,reason:i}}};g=t.__decorate([(0,d.injectable)(),i.__decorateParam(0,(0,d.inject)(n.DOOM_TEAM_TYPES.ProcessTerminalInspector)),i.__decorateParam(1,(0,d.inject)(n.DOOM_TEAM_TYPES.RunIdResolver)),r.__decorateMetadata(`design:paramtypes`,[Object,Object])],g),Object.defineProperty(exports,`StaleRunReconciler`,{enumerable:!0,get:function(){return g}});
|
|
2
|
-
//# sourceMappingURL=staleRunReconciler.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"staleRunReconciler.cjs","names":["path","currentRunsDir","STATUS_FILE_NAME","CoalescedStatusWriter","fs","TERMINAL_ASYNC_JOB_STATES","DOOM_TEAM_TYPES"],"sources":["../../../src/runs/background/staleRunReconciler.ts"],"sourcesContent":["/**\n * Repairs a run's `status.json` from `running` to `failed` when, and only\n * when, its owning process is provably dead.\n *\n * WHY THIS EXISTS:\n * A runner process can die without ever reaching\n * `TerminalPersistenceService.finalize()` - `SIGKILL`, a host crash, an `OOM`\n * kill. `status.json` is then stuck reporting `running` forever: nothing else\n * in this package ever revisits it, because nothing else has a reason to.\n * This is that reason. It is read-only reconnaissance until it has actual\n * proof, and exactly one narrow write when it does.\n *\n * WHY THIS DOES NOT TOUCH THE RESULT FILE (the TOCTOU the predecessor had):\n * `doom-pi-subagents/src/runs/background/staleRunReconciler.ts:436-450`\n * checks `fs.existsSync(resultPath)`, then separately reads and parses that\n * same path to decide how to repair `status.json` from the result's content.\n * Between those two steps, `ResultWatcher.claimResult()` on the other side of\n * this same package can rename that exact path away as part of its own\n * atomic claim (`resultWatcher.ts`'s fix 4) - so the existence check and the\n * read do not necessarily observe the same file. This module never reads or\n * interprets a result file at all. It asks `RunIdResolver`, which already\n * knows about both the pending and the claimed location, only ONE question:\n * \"does a result exist anywhere for this run yet, in either form?\" If the\n * answer is yes, this module does nothing - a result existing at all means\n * the run finished, which is `ResultWatcher` and whatever consumes its\n * delivery's job to turn into a status update, not a staleness this module\n * needs to repair. This module only ever acts when NO result exists anywhere\n * and the owning process is provably gone, which is a state `ResultWatcher`\n * has no reason to ever contend for.\n *\n * WHY THIS NEVER CALLS `writeAtomicJson(statusPath, ...)`:\n * `CoalescedStatusWriter` is the only thing in this package permitted to write\n * `status.json` (see that module's header). This class is not exempt: a\n * repair opens its OWN, dedicated `CoalescedStatusWriter` instance - never the\n * process-wide singleton, which may simultaneously be the live write path for\n * a run this process is actually running - seeds it from the on-disk status\n * that was just read, and applies the repair through `updateSync()`, the same\n * as any other terminal transition in this package. The write is still, in\n * every observable way, `CoalescedStatusWriter` writing `status.json`; only\n * the instance's *lifetime* is scoped to this one repair.\n *\n * WHAT \"PROVABLE\" MEANS HERE:\n * Only `ProcessTerminalInspector.inspect()` returning `'crashed'` - an\n * OS-confirmed `ESRCH` on the crash marker's pid - authorizes a repair. The\n * predecessor also repaired a run whose pid was still alive (or unverifiable)\n * purely because `status.json` had not updated in 24 hours\n * (`staleAlivePidMs`). That branch is deliberately NOT ported: an\n * unresponsive-but-alive pid is still alive, and treating a long silence as\n * death is a guess, not proof. A run that is merely slow survives this module\n * untouched, by design.\n *\n * ORPHANED CLAIMS (steer-requests, append-requests, and any future queue):\n * `control-channel.ts` claims a queued steer request by renaming it to\n * `<name>.claim-<claimant>` before dispatch, then `commit()`s (deletes) it on\n * success or `release()`s (renames it back) on a throw. `chain-append.ts`\n * does the identical thing for append-requests. A hard kill between claim and\n * release leaves the `.claim-<claimant>` file behind with neither outcome\n * ever happening - the request just sits there, invisible, forever.\n * `sweepOrphanedClaims()` is the same kind of repair as `reconcile()`, for a\n * different piece of state: it puts every orphaned claim back under its\n * original queued name so the next scan retries it, on the same \"provable\n * death, never a guess\" standard.\n *\n * Per `port-steering`, confirmed against the actual code for both queues\n * separately rather than assumed to carry over from one to the other: the\n * two queues do NOT construct their queued filenames the same way (steer\n * zero-pads a timestamp and base64url-encodes an id; append uses a raw\n * `Date.now()` integer and a raw `randomUUID()`), but both still guarantee no\n * literal `.` before the trailing `.json`, so a last-occurrence split on\n * `.claim-` is unambiguous for either. For claimant identity: steer's only\n * production claimant is `` `${process.pid}-${randomUUID()}` ``, built by the\n * run's own runner process. Append-requests has no production claimant yet -\n * nothing in this package claims from it in production, only tests - so its\n * eventual format is undecided. What DOES hold for append-requests today,\n * independent of whatever claimant format is eventually chosen, is that\n * `append-requests/` lives under `currentRunsDir()/<runId>/`, scoped to exactly one\n * run, and nothing in this package's architecture has a reason for any\n * process other than that run's own runner to ever claim from its own queue.\n * So for every queue this module knows about, \"is this claim orphaned\" and\n * \"is this run's owning process provably dead\" are the same question,\n * answered by the same evidence - for steer-requests because of what its one\n * real claimant is, for append-requests because of what the directory can\n * structurally ever contain - which is why this method takes no claimant and\n * gates purely on `ProcessTerminalInspector.inspect(runId)` the same as\n * `reconcile()` does - once that says `'crashed'`, every claim under every\n * one of the run's claim-queue directories is orphaned, full stop; there is\n * nothing left to correlate a specific claim against.\n *\n * STRUCTURAL DISCOVERY, NOT A REGISTRY (revised from an earlier version of\n * this module that kept a `CLAIM_QUEUES` list of known queue directories):\n * A registry means a third queue added later is swept only if whoever adds it\n * remembers to add an entry here too - the exact \"someone has to remember\"\n * failure mode this whole feature exists to close for orphaned claims\n * themselves. So `sweepOrphanedClaims()` does not enumerate queues at all: it\n * walks every directory under the run's own folder (`currentRunsDir()/<runId>/`,\n * bounded by `MAX_CLAIM_SCAN_DEPTH` as insurance, not because any real queue\n * nests that deep) and recovers whatever it finds containing `.claim-`. A\n * queue added anywhere under a run's directory is covered from the day it\n * exists, with no change to this file.\n *\n * WHAT THIS GIVES UP, DELIBERATELY: there is no longer a single list a reader\n * can check to see \"which queues does this module know about\" - the queues\n * ARE whatever is on disk, discovered structurally. It also makes the\n * `.claim-<claimant>` suffix a load-bearing, package-wide contract rather\n * than an internal detail of `control-channel.ts` and `chain-append.ts`: any\n * future file under a run's directory whose name happens to contain that\n * substring is treated as an orphaned claim and unconditionally recovered by\n * stripping the suffix, on the assumption that anything shaped like this\n * package's one established claim-by-rename convention behaves like it. That\n * assumption held for both queues that exist today (see the `port-steering`\n * paragraph above) and is judged worth the coverage guarantee in exchange.\n *\n * `sweepOrphanedClaims()` is a separate public method rather than a step\n * folded into `reconcile()`, because it has an independent gating condition\n * (it is not related to `status.json`'s state, or to whether a result\n * exists) and an independent, testable outcome.\n *\n * AVOID:\n * - Synthesizing a status for a run whose `status.json` does not exist yet.\n * `CoalescedStatusWriter.open()` writes it synchronously before returning\n * (see that module's header), so a genuinely started run already has one by\n * the time anything could observe it as missing; a missing file is either a\n * run that has not started yet or an id that does not exist, and this\n * module cannot tell those apart, so it reports nothing for either\n * - Treating `'unknown'` liveness the same as `'crashed'`; see\n * `processTerminal.ts` for why they are kept distinct\n * - Parsing a pid out of a claimant string. It is `ControlChannelWatcher`'s\n * implementation detail, not a documented wire format (`port-steering`'s\n * own words); the crash marker is already the load-bearing evidence\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { inject, injectable } from 'inversify';\n\nimport { currentRunsDir } from '../../shared/paths.ts';\nimport { appendJsonl } from '../../shared/artifacts.ts';\nimport { DOOM_TEAM_TYPES } from '../../container/types.ts';\nimport { TERMINAL_ASYNC_JOB_STATES } from './asyncJobTracker.ts';\nimport type { IProcessTerminalInspector } from './processTerminal.ts';\nimport type { IRunIdResolver } from './runIdResolver.ts';\nimport {\n CoalescedStatusWriter,\n STATUS_FILE_NAME,\n type ICoalescedStatusWriter,\n type StatusWithRecentEntries,\n} from './statusWriter.ts';\n\nconst EVENTS_FILE_NAME = 'events.jsonl';\n\n/**\n * Matches `control-channel.ts`'s and `chain-append.ts`'s private\n * `CLAIM_SUFFIX_PREFIX` exactly (the two are identical to each other); not\n * exported from either module, and now the one thing that makes a directory\n * entry \"a claim\" for the purposes of the structural scan below.\n */\nconst CLAIM_SUFFIX_PREFIX = '.claim-';\n\n/**\n * Insurance, not a modeled limit: no real claim queue nests anywhere near\n * this deep under a run's directory. Bounds `walkForClaims()` so a pathological\n * or symlink-cycle directory tree cannot make the scan unbounded.\n */\nconst MAX_CLAIM_SCAN_DEPTH = 8;\n\n/**\n * The minimum shape this module reads and repairs. A run's actual status\n * carries far more than this - this package has not settled a shared status\n * type yet - so everything else on disk is preserved verbatim by round\n * tripping the whole parsed object through `open()`, never reconstructed here.\n */\nexport interface ReconcilableStatus extends StatusWithRecentEntries {\n runId?: string;\n state?: string;\n error?: string;\n startedAt?: number;\n lastUpdate?: number;\n endedAt?: number;\n [key: string]: unknown;\n}\n\nexport interface ReconcileOutcome {\n repaired: boolean;\n /** Why nothing happened, or what was repaired - for logging, never parsed. */\n reason: string;\n}\n\n/** One orphaned claim recovered back to its original queued name. */\nexport interface RecoveredClaim {\n /**\n * The queue's directory, relative to the run's own directory and\n * forward-slash-normalized (e.g. `'append-requests'`, `'control/steer-requests'`).\n * Discovered structurally by `walkForClaims()`, not looked up in a registry.\n */\n queue: string;\n /** The original (recovered) filename, in that queue. */\n fileName: string;\n}\n\nexport interface SweepOutcome {\n recovered: RecoveredClaim[];\n}\n\n/** Both repairs from one pass over a run. See `IStaleRunReconciler.reconcileAndSweep`. */\nexport interface ReconcileSweepOutcome {\n reconcile: ReconcileOutcome;\n sweep: SweepOutcome;\n}\n\nexport interface IStaleRunReconciler {\n /** Repair `runId`'s status if, and only if, its process is provably dead. */\n reconcile(runId: string): ReconcileOutcome;\n /** Recover every one of `runId`'s orphaned claims, discovered structurally under its run directory, if and only if its process is provably dead. */\n sweepOrphanedClaims(runId: string): SweepOutcome;\n /**\n * Both repairs, sharing ONE `ProcessTerminalInspector.inspect()` call.\n *\n * Calling `reconcile()` and `sweepOrphanedClaims()` separately - which is\n * what the parent's poll subscriber used to do - inspects the same run\n * twice per tick, and each inspect is a crash-marker read, a JSON parse and\n * a `kill(pid, 0)`. Both gate on the identical question (\"is this run's\n * owning process provably dead?\"), so the second answer is always the first\n * answer; only the cost is new. For a healthy child, which is the\n * overwhelmingly common case, every bit of that work is discarded.\n *\n * The two methods stay public and independently callable: they have\n * genuinely independent gating conditions (see `sweepOrphanedClaims`'s own\n * doc) and independent tests. This is a shared-work wrapper over them, not\n * a replacement.\n *\n * The inspect is lazy and `reconcile`'s cheap file gates run before it, so\n * a terminal or already-delivered run never pays for liveness on the\n * reconcile side. It still pays once on the sweep side: orphaned claims are\n * gated on process death alone, deliberately independent of `status.json`\n * (see `sweepOrphanedClaims`), and folding them into the status gate would\n * quietly stop recovering claims for a run that finished. The guarantee here\n * is exactly one probe per pass, not zero.\n *\n * In practice the parent's subscriber skips terminal runs before calling\n * this at all, so that case costs nothing in the hot path either.\n */\n reconcileAndSweep(runId: string): ReconcileSweepOutcome;\n}\n\nfunction statusPathFor(runId: string): string {\n return path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n@injectable()\nexport class StaleRunReconciler implements IStaleRunReconciler {\n constructor(\n @inject(DOOM_TEAM_TYPES.ProcessTerminalInspector) private readonly processTerminal: IProcessTerminalInspector,\n @inject(DOOM_TEAM_TYPES.RunIdResolver) private readonly runIdResolver: IRunIdResolver,\n ) {}\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * A fresh writer per repair, deliberately never the container's singleton -\n * see the class doc for why sharing it would be unsafe. Protected so a test\n * can substitute a recording fake instead of touching disk.\n */\n protected createStatusWriter(): ICoalescedStatusWriter<ReconcilableStatus> {\n return new CoalescedStatusWriter<ReconcilableStatus>();\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (errorCode(error) === 'ENOENT') return undefined;\n throw error;\n }\n }\n\n protected readStatus(runId: string): ReconcilableStatus | undefined {\n const raw = this.readFile(statusPathFor(runId));\n if (raw === undefined) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n // A status file mid-write (CoalescedStatusWriter's rename not yet\n // visible, or a torn read) is not evidence of anything; retried later.\n return undefined;\n }\n return isRecord(parsed) ? (parsed as ReconcilableStatus) : undefined;\n }\n\n /** Best-effort diagnostic trail; a failing event log must never fail a repair. */\n protected appendEvent(runId: string, payload: object): void {\n try {\n appendJsonl(path.join(currentRunsDir(), runId, EVENTS_FILE_NAME), JSON.stringify(payload));\n } catch {\n // See method doc.\n }\n }\n\n /**\n * Walks `dir` and every subdirectory beneath it (bounded by\n * `MAX_CLAIM_SCAN_DEPTH`), returning every entry whose name contains\n * `.claim-`, wherever it is found. Replaces a flat, single-directory\n * listing keyed off a registry: see the class doc's \"STRUCTURAL DISCOVERY\"\n * section for why. Recursing only on `entry.isDirectory()` means a symlink\n * (`isSymbolicLink()`, not `isDirectory()`) is never followed, so this\n * needs no separate cycle-detection.\n */\n protected walkForClaims(dir: string, depth = 0): Array<{ dir: string; name: string }> {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch (error) {\n if (errorCode(error) === 'ENOENT') return [];\n throw error;\n }\n\n const found: Array<{ dir: string; name: string }> = [];\n for (const entry of entries) {\n if (entry.name.includes(CLAIM_SUFFIX_PREFIX)) {\n found.push({ dir, name: entry.name });\n } else if (entry.isDirectory() && depth < MAX_CLAIM_SCAN_DEPTH) {\n found.push(...this.walkForClaims(path.join(dir, entry.name), depth + 1));\n }\n }\n return found;\n }\n\n /**\n * Put a claimed file back under its original queued name. Returns `true`\n * only when the rename actually happened. Mirrors `control-channel.ts`'s\n * own `release()`: on any failure (original name occupied - astronomically\n * unlikely, the directory gone), there is nothing left to preserve by\n * retrying, so the orphaned claim is dropped rather than left unreachable.\n */\n protected recoverClaim(claimedPath: string, originalPath: string): boolean {\n try {\n fs.renameSync(claimedPath, originalPath);\n return true;\n } catch {\n try {\n fs.unlinkSync(claimedPath);\n } catch {\n // Best effort; see method doc.\n }\n return false;\n }\n }\n\n /**\n * A memoizing wrapper over `inspect()`, so one tick's pair of repairs asks\n * the OS about a pid once instead of twice. Each public entry point makes\n * its own, so a standalone `reconcile()` still inspects exactly when it\n * always did.\n */\n private inspectOnce(runId: string): () => ReturnType<IProcessTerminalInspector['inspect']> {\n let verdict: ReturnType<IProcessTerminalInspector['inspect']> | undefined;\n return () => (verdict ??= this.processTerminal.inspect(runId));\n }\n\n reconcile(runId: string): ReconcileOutcome {\n return this.reconcileWith(runId, this.inspectOnce(runId));\n }\n\n sweepOrphanedClaims(runId: string): SweepOutcome {\n return this.sweepWith(runId, this.inspectOnce(runId));\n }\n\n reconcileAndSweep(runId: string): ReconcileSweepOutcome {\n const inspect = this.inspectOnce(runId);\n // Order matters: `reconcileWith` runs its cheap file gates before ever\n // calling `inspect`, so a terminal or already-delivered run pays nothing\n // extra here. Whichever of the two asks first funds the single call.\n return { reconcile: this.reconcileWith(runId, inspect), sweep: this.sweepWith(runId, inspect) };\n }\n\n private reconcileWith(\n runId: string,\n inspect: () => ReturnType<IProcessTerminalInspector['inspect']>,\n ): ReconcileOutcome {\n const status = this.readStatus(runId);\n if (!status) return { repaired: false, reason: 'No status file for this run.' };\n\n const state = status.state;\n if (state !== undefined && TERMINAL_ASYNC_JOB_STATES.has(state)) {\n return { repaired: false, reason: `Run already terminal (${state}).` };\n }\n\n const location = this.runIdResolver.resolve(runId);\n if (location?.resultPath) {\n return { repaired: false, reason: \"A result already exists; delivery is ResultWatcher's job, not a repair.\" };\n }\n\n const verdict = inspect();\n if (verdict.state !== 'crashed') {\n return {\n repaired: false,\n reason: `Owning process liveness is '${verdict.state}', which does not prove death; leaving the run as is.`,\n };\n }\n\n return this.repair(runId, status, verdict.marker.pid);\n }\n\n private sweepWith(runId: string, inspect: () => ReturnType<IProcessTerminalInspector['inspect']>): SweepOutcome {\n const verdict = inspect();\n if (verdict.state !== 'crashed') return { recovered: [] };\n\n const runDir = path.join(currentRunsDir(), runId);\n const recovered: RecoveredClaim[] = [];\n for (const { dir, name: entry } of this.walkForClaims(runDir)) {\n const claimIndex = entry.lastIndexOf(CLAIM_SUFFIX_PREFIX);\n // Filtered by walkForClaims() already; defensive, not reachable.\n if (claimIndex === -1) continue;\n const fileName = entry.slice(0, claimIndex);\n const wasRecovered = this.recoverClaim(path.join(dir, entry), path.join(dir, fileName));\n if (wasRecovered) {\n const queue = path.relative(runDir, dir).split(path.sep).join('/');\n recovered.push({ queue, fileName });\n this.appendEvent(runId, {\n type: 'subagent.run.claim_recovered',\n ts: this.now(),\n runId,\n pid: verdict.marker.pid,\n queue,\n recovered: fileName,\n });\n }\n }\n return { recovered };\n }\n\n private repair(runId: string, status: ReconcilableStatus, deadPid: number): ReconcileOutcome {\n const now = this.now();\n const message = `Runner process ${deadPid} exited without finalizing this run. Marked failed by stale-run reconciliation.`;\n\n const writer = this.createStatusWriter();\n writer.open(runId, status);\n writer.updateSync((current) => {\n current.state = 'failed';\n current.error = current.error ?? message;\n current.lastUpdate = now;\n current.endedAt = current.endedAt ?? now;\n });\n\n this.appendEvent(runId, { type: 'subagent.run.repaired_stale', ts: now, runId, pid: deadPid, message });\n\n return { repaired: true, reason: message };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{__decorate as t,init_decorate as n}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{DOOM_TEAM_TYPES as r,init_types as i}from"../../container/types.mjs";import{__decorateMetadata as a,init_decorateMetadata as o}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as s,init_decorateParam as c}from"../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{currentRunsDir as l,init_paths as u}from"../../shared/paths.mjs";import{CoalescedStatusWriter as d,STATUS_FILE_NAME as f,init_statusWriter as p}from"./statusWriter.mjs";import{TERMINAL_ASYNC_JOB_STATES as m,init_asyncJobTracker as h}from"./asyncJobTracker.mjs";import{appendJsonl as g,init_artifacts as _}from"../../shared/artifacts.mjs";import*as v from"node:fs";import*as y from"node:path";import{inject as b,injectable as x}from"inversify";function S(e){return y.join(l(),e,f)}function C(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function w(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var T,E,D,O,k=e((()=>{u(),_(),i(),h(),p(),o(),c(),n(),T=`events.jsonl`,E=`.claim-`,D=8,O=class{constructor(e,t){this.processTerminal=e,this.runIdResolver=t}now(){return Date.now()}createStatusWriter(){return new d}readFile(e){try{return v.readFileSync(e,`utf-8`)}catch(e){if(C(e)===`ENOENT`)return;throw e}}readStatus(e){let t=this.readFile(S(e));if(t===void 0)return;let n;try{n=JSON.parse(t)}catch{return}return w(n)?n:void 0}appendEvent(e,t){try{g(y.join(l(),e,T),JSON.stringify(t))}catch{}}walkForClaims(e,t=0){let n;try{n=v.readdirSync(e,{withFileTypes:!0})}catch(e){if(C(e)===`ENOENT`)return[];throw e}let r=[];for(let i of n)i.name.includes(E)?r.push({dir:e,name:i.name}):i.isDirectory()&&t<D&&r.push(...this.walkForClaims(y.join(e,i.name),t+1));return r}recoverClaim(e,t){try{return v.renameSync(e,t),!0}catch{try{v.unlinkSync(e)}catch{}return!1}}inspectOnce(e){let t;return()=>t??=this.processTerminal.inspect(e)}reconcile(e){return this.reconcileWith(e,this.inspectOnce(e))}sweepOrphanedClaims(e){return this.sweepWith(e,this.inspectOnce(e))}reconcileAndSweep(e){let t=this.inspectOnce(e);return{reconcile:this.reconcileWith(e,t),sweep:this.sweepWith(e,t)}}reconcileWith(e,t){let n=this.readStatus(e);if(!n)return{repaired:!1,reason:`No status file for this run.`};let r=n.state;if(r!==void 0&&m.has(r))return{repaired:!1,reason:`Run already terminal (${r}).`};if(this.runIdResolver.resolve(e)?.resultPath)return{repaired:!1,reason:`A result already exists; delivery is ResultWatcher's job, not a repair.`};let i=t();return i.state===`crashed`?this.repair(e,n,i.marker.pid):{repaired:!1,reason:`Owning process liveness is '${i.state}', which does not prove death; leaving the run as is.`}}sweepWith(e,t){let n=t();if(n.state!==`crashed`)return{recovered:[]};let r=y.join(l(),e),i=[];for(let{dir:t,name:a}of this.walkForClaims(r)){let o=a.lastIndexOf(E);if(o===-1)continue;let s=a.slice(0,o);if(this.recoverClaim(y.join(t,a),y.join(t,s))){let a=y.relative(r,t).split(y.sep).join(`/`);i.push({queue:a,fileName:s}),this.appendEvent(e,{type:`subagent.run.claim_recovered`,ts:this.now(),runId:e,pid:n.marker.pid,queue:a,recovered:s})}}return{recovered:i}}repair(e,t,n){let r=this.now(),i=`Runner process ${n} exited without finalizing this run. Marked failed by stale-run reconciliation.`,a=this.createStatusWriter();return a.open(e,t),a.updateSync(e=>{e.state=`failed`,e.error=e.error??i,e.lastUpdate=r,e.endedAt=e.endedAt??r}),this.appendEvent(e,{type:`subagent.run.repaired_stale`,ts:r,runId:e,pid:n,message:i}),{repaired:!0,reason:i}}},O=t([x(),s(0,b(r.ProcessTerminalInspector)),s(1,b(r.RunIdResolver)),a(`design:paramtypes`,[Object,Object])],O)}));k();export{O as StaleRunReconciler,k as init_staleRunReconciler};
|
|
2
|
-
//# sourceMappingURL=staleRunReconciler.mjs.map
|