@agimon-ai/doompi-team 0.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/README.md +46 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs +1 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +1 -0
- package/dist/agents/discovery.cjs +2 -0
- package/dist/agents/discovery.cjs.map +1 -0
- package/dist/agents/discovery.mjs +2 -0
- package/dist/agents/discovery.mjs.map +1 -0
- package/dist/agents/frontmatter.cjs +10 -0
- package/dist/agents/frontmatter.cjs.map +1 -0
- package/dist/agents/frontmatter.mjs +10 -0
- package/dist/agents/frontmatter.mjs.map +1 -0
- package/dist/agents/loader.cjs +2 -0
- package/dist/agents/loader.cjs.map +1 -0
- package/dist/agents/loader.mjs +2 -0
- package/dist/agents/loader.mjs.map +1 -0
- package/dist/agents/memory.cjs +3 -0
- package/dist/agents/memory.cjs.map +1 -0
- package/dist/agents/memory.mjs +3 -0
- package/dist/agents/memory.mjs.map +1 -0
- package/dist/agents/projectRoot.cjs +2 -0
- package/dist/agents/projectRoot.cjs.map +1 -0
- package/dist/agents/projectRoot.mjs +2 -0
- package/dist/agents/projectRoot.mjs.map +1 -0
- package/dist/agents/settings.cjs +2 -0
- package/dist/agents/settings.cjs.map +1 -0
- package/dist/agents/settings.mjs +2 -0
- package/dist/agents/settings.mjs.map +1 -0
- package/dist/agents/skills.cjs +0 -0
- package/dist/agents/skills.cjs.map +1 -0
- package/dist/agents/skills.mjs +5 -0
- package/dist/agents/skills.mjs.map +1 -0
- package/dist/agents/types.d.cts +121 -0
- package/dist/agents/types.d.cts.map +1 -0
- package/dist/agents/types.d.mts +121 -0
- package/dist/agents/types.d.mts.map +1 -0
- package/dist/api/backgroundWork.cjs +1 -0
- package/dist/api/backgroundWork.d.cts +2 -0
- package/dist/api/backgroundWork.d.mts +2 -0
- package/dist/api/backgroundWork.mjs +1 -0
- package/dist/api/capabilityCeiling.cjs +1 -0
- package/dist/api/capabilityCeiling.d.cts +3 -0
- package/dist/api/capabilityCeiling.d.mts +3 -0
- package/dist/api/capabilityCeiling.mjs +2 -0
- package/dist/api/capabilityCeiling.mjs.map +1 -0
- package/dist/api/delegation.cjs +1 -0
- package/dist/api/delegation.d.cts +2 -0
- package/dist/api/delegation.d.mts +2 -0
- package/dist/api/delegation.mjs +1 -0
- package/dist/api/teamSnapshot.cjs +1 -0
- package/dist/api/teamSnapshot.d.cts +2 -0
- package/dist/api/teamSnapshot.d.mts +2 -0
- package/dist/api/teamSnapshot.mjs +2 -0
- package/dist/api/teamSnapshot.mjs.map +1 -0
- package/dist/container/bindOrRebind.cjs +2 -0
- package/dist/container/bindOrRebind.cjs.map +1 -0
- package/dist/container/bindOrRebind.mjs +2 -0
- package/dist/container/bindOrRebind.mjs.map +1 -0
- package/dist/container/globalBridge.cjs +2 -0
- package/dist/container/globalBridge.cjs.map +1 -0
- package/dist/container/globalBridge.d.cts +19 -0
- package/dist/container/globalBridge.d.cts.map +1 -0
- package/dist/container/globalBridge.d.mts +19 -0
- package/dist/container/globalBridge.d.mts.map +1 -0
- package/dist/container/globalBridge.mjs +2 -0
- package/dist/container/globalBridge.mjs.map +1 -0
- package/dist/container/index.cjs +2 -0
- package/dist/container/index.cjs.map +1 -0
- package/dist/container/index.d.cts +38 -0
- package/dist/container/index.d.cts.map +1 -0
- package/dist/container/index.d.mts +38 -0
- package/dist/container/index.d.mts.map +1 -0
- package/dist/container/index.mjs +2 -0
- package/dist/container/index.mjs.map +1 -0
- package/dist/container/modules/agents.cjs +2 -0
- package/dist/container/modules/agents.cjs.map +1 -0
- package/dist/container/modules/agents.d.cts +1 -0
- package/dist/container/modules/agents.d.mts +1 -0
- package/dist/container/modules/agents.mjs +2 -0
- package/dist/container/modules/agents.mjs.map +1 -0
- package/dist/container/modules/core.cjs +2 -0
- package/dist/container/modules/core.cjs.map +1 -0
- package/dist/container/modules/core.d.cts +1 -0
- package/dist/container/modules/core.d.mts +1 -0
- package/dist/container/modules/core.mjs +2 -0
- package/dist/container/modules/core.mjs.map +1 -0
- package/dist/container/modules/extensions.cjs +2 -0
- package/dist/container/modules/extensions.cjs.map +1 -0
- package/dist/container/modules/extensions.mjs +2 -0
- package/dist/container/modules/extensions.mjs.map +1 -0
- package/dist/container/modules/intercom.cjs +2 -0
- package/dist/container/modules/intercom.cjs.map +1 -0
- package/dist/container/modules/intercom.mjs +2 -0
- package/dist/container/modules/intercom.mjs.map +1 -0
- package/dist/container/modules/runs.cjs +2 -0
- package/dist/container/modules/runs.cjs.map +1 -0
- package/dist/container/modules/runs.mjs +2 -0
- package/dist/container/modules/runs.mjs.map +1 -0
- package/dist/container/types.cjs +2 -0
- package/dist/container/types.cjs.map +1 -0
- package/dist/container/types.d.cts +45 -0
- package/dist/container/types.d.cts.map +1 -0
- package/dist/container/types.d.mts +45 -0
- package/dist/container/types.d.mts.map +1 -0
- package/dist/container/types.mjs +2 -0
- package/dist/container/types.mjs.map +1 -0
- package/dist/env.cjs +2 -0
- package/dist/env.cjs.map +1 -0
- package/dist/env.d.cts +65 -0
- package/dist/env.d.cts.map +1 -0
- package/dist/env.d.mts +65 -0
- package/dist/env.d.mts.map +1 -0
- package/dist/env.mjs +2 -0
- package/dist/env.mjs.map +1 -0
- package/dist/extension.cjs +2 -0
- package/dist/extension.cjs.map +1 -0
- package/dist/extension.mjs +2 -0
- package/dist/extension.mjs.map +1 -0
- package/dist/extensions/config.cjs +2 -0
- package/dist/extensions/config.cjs.map +1 -0
- package/dist/extensions/config.mjs +2 -0
- package/dist/extensions/config.mjs.map +1 -0
- package/dist/extensions/delegationBridge.cjs +2 -0
- package/dist/extensions/delegationBridge.cjs.map +1 -0
- package/dist/extensions/delegationBridge.mjs +2 -0
- package/dist/extensions/delegationBridge.mjs.map +1 -0
- package/dist/extensions/doom.cjs +2 -0
- package/dist/extensions/doom.cjs.map +1 -0
- package/dist/extensions/doom.d.cts +8 -0
- package/dist/extensions/doom.d.cts.map +1 -0
- package/dist/extensions/doom.d.mts +8 -0
- package/dist/extensions/doom.d.mts.map +1 -0
- package/dist/extensions/doom.mjs +2 -0
- package/dist/extensions/doom.mjs.map +1 -0
- package/dist/extensions/fablePlanBridge.cjs +2 -0
- package/dist/extensions/fablePlanBridge.cjs.map +1 -0
- package/dist/extensions/fablePlanBridge.mjs +2 -0
- package/dist/extensions/fablePlanBridge.mjs.map +1 -0
- package/dist/extensions/managementActions.cjs +2 -0
- package/dist/extensions/managementActions.cjs.map +1 -0
- package/dist/extensions/managementActions.mjs +2 -0
- package/dist/extensions/managementActions.mjs.map +1 -0
- package/dist/extensions/operationJournal.cjs +2 -0
- package/dist/extensions/operationJournal.cjs.map +1 -0
- package/dist/extensions/operationJournal.mjs +2 -0
- package/dist/extensions/operationJournal.mjs.map +1 -0
- package/dist/extensions/orchestratorPrompt.cjs +35 -0
- package/dist/extensions/orchestratorPrompt.cjs.map +1 -0
- package/dist/extensions/orchestratorPrompt.mjs +35 -0
- package/dist/extensions/orchestratorPrompt.mjs.map +1 -0
- package/dist/extensions/pi.cjs +2 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +2 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/schemas.cjs +1 -0
- package/dist/extensions/schemas.mjs +2 -0
- package/dist/extensions/schemas.mjs.map +1 -0
- package/dist/extensions/spawnPlan.cjs +3 -0
- package/dist/extensions/spawnPlan.cjs.map +1 -0
- package/dist/extensions/spawnPlan.mjs +3 -0
- package/dist/extensions/spawnPlan.mjs.map +1 -0
- package/dist/extensions/standard.cjs +2 -0
- package/dist/extensions/standard.cjs.map +1 -0
- package/dist/extensions/standard.d.cts +8 -0
- package/dist/extensions/standard.d.cts.map +1 -0
- package/dist/extensions/standard.d.mts +8 -0
- package/dist/extensions/standard.d.mts.map +1 -0
- package/dist/extensions/standard.mjs +2 -0
- package/dist/extensions/standard.mjs.map +1 -0
- package/dist/extensions/statusViews.cjs +7 -0
- package/dist/extensions/statusViews.cjs.map +1 -0
- package/dist/extensions/statusViews.mjs +7 -0
- package/dist/extensions/statusViews.mjs.map +1 -0
- package/dist/extensions/subagentPromptRuntime.cjs +18 -0
- package/dist/extensions/subagentPromptRuntime.cjs.map +1 -0
- package/dist/extensions/subagentPromptRuntime.d.cts +58 -0
- package/dist/extensions/subagentPromptRuntime.d.cts.map +1 -0
- package/dist/extensions/subagentPromptRuntime.d.mts +58 -0
- package/dist/extensions/subagentPromptRuntime.d.mts.map +1 -0
- package/dist/extensions/subagentPromptRuntime.mjs +18 -0
- package/dist/extensions/subagentPromptRuntime.mjs.map +1 -0
- package/dist/extensions/subagentPromptRuntimeEntry.cjs +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.cjs.map +1 -0
- package/dist/extensions/subagentPromptRuntimeEntry.d.cts +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.d.mts +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.mjs +2 -0
- package/dist/extensions/subagentPromptRuntimeEntry.mjs.map +1 -0
- package/dist/extensions/subagentTool.cjs +7 -0
- package/dist/extensions/subagentTool.cjs.map +1 -0
- package/dist/extensions/subagentTool.mjs +7 -0
- package/dist/extensions/subagentTool.mjs.map +1 -0
- package/dist/extensions/toolDescription.cjs +2 -0
- package/dist/extensions/toolDescription.cjs.map +1 -0
- package/dist/extensions/toolDescription.mjs +2 -0
- package/dist/extensions/toolDescription.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +34 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/intercom/intercomBridge.cjs +2 -0
- package/dist/intercom/intercomBridge.cjs.map +1 -0
- package/dist/intercom/intercomBridge.mjs +2 -0
- package/dist/intercom/intercomBridge.mjs.map +1 -0
- package/dist/intercom/nativeTeamChannel.cjs +5 -0
- package/dist/intercom/nativeTeamChannel.cjs.map +1 -0
- package/dist/intercom/nativeTeamChannel.d.cts +22 -0
- package/dist/intercom/nativeTeamChannel.d.cts.map +1 -0
- package/dist/intercom/nativeTeamChannel.d.mts +22 -0
- package/dist/intercom/nativeTeamChannel.d.mts.map +1 -0
- package/dist/intercom/nativeTeamChannel.mjs +5 -0
- package/dist/intercom/nativeTeamChannel.mjs.map +1 -0
- package/dist/intercom/supervisorControlChannel.cjs +2 -0
- package/dist/intercom/supervisorControlChannel.cjs.map +1 -0
- package/dist/intercom/supervisorControlChannel.mjs +2 -0
- package/dist/intercom/supervisorControlChannel.mjs.map +1 -0
- package/dist/protocol/backgroundWork.cjs +2 -0
- package/dist/protocol/backgroundWork.cjs.map +1 -0
- package/dist/protocol/backgroundWork.d.cts +52 -0
- package/dist/protocol/backgroundWork.d.cts.map +1 -0
- package/dist/protocol/backgroundWork.d.mts +52 -0
- package/dist/protocol/backgroundWork.d.mts.map +1 -0
- package/dist/protocol/backgroundWork.mjs +2 -0
- package/dist/protocol/backgroundWork.mjs.map +1 -0
- package/dist/protocol/capabilityCeiling.cjs +2 -0
- package/dist/protocol/capabilityCeiling.cjs.map +1 -0
- package/dist/protocol/capabilityCeiling.d.cts +51 -0
- package/dist/protocol/capabilityCeiling.d.cts.map +1 -0
- package/dist/protocol/capabilityCeiling.d.mts +51 -0
- package/dist/protocol/capabilityCeiling.d.mts.map +1 -0
- package/dist/protocol/capabilityCeiling.mjs +2 -0
- package/dist/protocol/capabilityCeiling.mjs.map +1 -0
- package/dist/protocol/delegation.cjs +2 -0
- package/dist/protocol/delegation.cjs.map +1 -0
- package/dist/protocol/delegation.d.cts +269 -0
- package/dist/protocol/delegation.d.cts.map +1 -0
- package/dist/protocol/delegation.d.mts +269 -0
- package/dist/protocol/delegation.d.mts.map +1 -0
- package/dist/protocol/delegation.mjs +2 -0
- package/dist/protocol/delegation.mjs.map +1 -0
- package/dist/runs/background/asyncExecution.cjs +2 -0
- package/dist/runs/background/asyncExecution.cjs.map +1 -0
- package/dist/runs/background/asyncExecution.mjs +2 -0
- package/dist/runs/background/asyncExecution.mjs.map +1 -0
- package/dist/runs/background/asyncJobTracker.cjs +2 -0
- package/dist/runs/background/asyncJobTracker.cjs.map +1 -0
- package/dist/runs/background/asyncJobTracker.mjs +2 -0
- package/dist/runs/background/asyncJobTracker.mjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.cjs +2 -0
- package/dist/runs/background/cliRunnerEntry.cjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.d.cts +1 -0
- package/dist/runs/background/cliRunnerEntry.d.mts +1 -0
- package/dist/runs/background/cliRunnerEntry.mjs +2 -0
- package/dist/runs/background/cliRunnerEntry.mjs.map +1 -0
- package/dist/runs/background/completionBatcher.cjs +2 -0
- package/dist/runs/background/completionBatcher.cjs.map +1 -0
- package/dist/runs/background/completionBatcher.mjs +2 -0
- package/dist/runs/background/completionBatcher.mjs.map +1 -0
- package/dist/runs/background/notify.cjs +4 -0
- package/dist/runs/background/notify.cjs.map +1 -0
- package/dist/runs/background/notify.mjs +4 -0
- package/dist/runs/background/notify.mjs.map +1 -0
- package/dist/runs/background/parentWatchdog.cjs +2 -0
- package/dist/runs/background/parentWatchdog.cjs.map +1 -0
- package/dist/runs/background/parentWatchdog.mjs +2 -0
- package/dist/runs/background/parentWatchdog.mjs.map +1 -0
- package/dist/runs/background/processTerminal.cjs +2 -0
- package/dist/runs/background/processTerminal.cjs.map +1 -0
- package/dist/runs/background/processTerminal.mjs +2 -0
- package/dist/runs/background/processTerminal.mjs.map +1 -0
- package/dist/runs/background/resultWatcher.cjs +2 -0
- package/dist/runs/background/resultWatcher.cjs.map +1 -0
- package/dist/runs/background/resultWatcher.mjs +2 -0
- package/dist/runs/background/resultWatcher.mjs.map +1 -0
- package/dist/runs/background/runIdResolver.cjs +2 -0
- package/dist/runs/background/runIdResolver.cjs.map +1 -0
- package/dist/runs/background/runIdResolver.mjs +2 -0
- package/dist/runs/background/runIdResolver.mjs.map +1 -0
- package/dist/runs/background/runnerBootstrap.cjs +2 -0
- package/dist/runs/background/runnerBootstrap.cjs.map +1 -0
- package/dist/runs/background/runnerBootstrap.mjs +2 -0
- package/dist/runs/background/runnerBootstrap.mjs.map +1 -0
- package/dist/runs/background/runnerExecution.cjs +2 -0
- package/dist/runs/background/runnerExecution.cjs.map +1 -0
- package/dist/runs/background/runnerExecution.mjs +2 -0
- package/dist/runs/background/runnerExecution.mjs.map +1 -0
- package/dist/runs/background/runnerReporting.cjs +3 -0
- package/dist/runs/background/runnerReporting.cjs.map +1 -0
- package/dist/runs/background/runnerReporting.mjs +3 -0
- package/dist/runs/background/runnerReporting.mjs.map +1 -0
- package/dist/runs/background/runnerTeamMembership.cjs +2 -0
- package/dist/runs/background/runnerTeamMembership.cjs.map +1 -0
- package/dist/runs/background/runnerTeamMembership.mjs +2 -0
- package/dist/runs/background/runnerTeamMembership.mjs.map +1 -0
- package/dist/runs/background/spawnHandshake.cjs +2 -0
- package/dist/runs/background/spawnHandshake.cjs.map +1 -0
- package/dist/runs/background/spawnHandshake.mjs +2 -0
- package/dist/runs/background/spawnHandshake.mjs.map +1 -0
- package/dist/runs/background/staleRunReconciler.cjs +2 -0
- package/dist/runs/background/staleRunReconciler.cjs.map +1 -0
- package/dist/runs/background/staleRunReconciler.mjs +2 -0
- package/dist/runs/background/staleRunReconciler.mjs.map +1 -0
- package/dist/runs/background/statusReader.cjs +2 -0
- package/dist/runs/background/statusReader.cjs.map +1 -0
- package/dist/runs/background/statusReader.mjs +2 -0
- package/dist/runs/background/statusReader.mjs.map +1 -0
- package/dist/runs/background/statusWriter.cjs +2 -0
- package/dist/runs/background/statusWriter.cjs.map +1 -0
- package/dist/runs/background/statusWriter.mjs +2 -0
- package/dist/runs/background/statusWriter.mjs.map +1 -0
- package/dist/runs/background/subagentWait.cjs +2 -0
- package/dist/runs/background/subagentWait.cjs.map +1 -0
- package/dist/runs/background/subagentWait.mjs +2 -0
- package/dist/runs/background/subagentWait.mjs.map +1 -0
- package/dist/runs/background/terminalPersistence.cjs +2 -0
- package/dist/runs/background/terminalPersistence.cjs.map +1 -0
- package/dist/runs/background/terminalPersistence.mjs +2 -0
- package/dist/runs/background/terminalPersistence.mjs.map +1 -0
- package/dist/runs/registry/processGroup.cjs +2 -0
- package/dist/runs/registry/processGroup.cjs.map +1 -0
- package/dist/runs/registry/processGroup.mjs +2 -0
- package/dist/runs/registry/processGroup.mjs.map +1 -0
- package/dist/runs/registry/runRegistry.cjs +2 -0
- package/dist/runs/registry/runRegistry.cjs.map +1 -0
- package/dist/runs/registry/runRegistry.mjs +2 -0
- package/dist/runs/registry/runRegistry.mjs.map +1 -0
- package/dist/runs/registry/sessionLifecycle.cjs +2 -0
- package/dist/runs/registry/sessionLifecycle.cjs.map +1 -0
- package/dist/runs/registry/sessionLifecycle.mjs +2 -0
- package/dist/runs/registry/sessionLifecycle.mjs.map +1 -0
- package/dist/runs/registry/suspendedRuns.cjs +4 -0
- package/dist/runs/registry/suspendedRuns.cjs.map +1 -0
- package/dist/runs/registry/suspendedRuns.mjs +4 -0
- package/dist/runs/registry/suspendedRuns.mjs.map +1 -0
- package/dist/runs/sdkRunnerEntry.cjs +2 -0
- package/dist/runs/sdkRunnerEntry.cjs.map +1 -0
- package/dist/runs/sdkRunnerEntry.d.cts +1 -0
- package/dist/runs/sdkRunnerEntry.d.mts +1 -0
- package/dist/runs/sdkRunnerEntry.mjs +2 -0
- package/dist/runs/sdkRunnerEntry.mjs.map +1 -0
- package/dist/runs/shared/claudeFableProfile.cjs +4 -0
- package/dist/runs/shared/claudeFableProfile.cjs.map +1 -0
- package/dist/runs/shared/claudeFableProfile.mjs +4 -0
- package/dist/runs/shared/claudeFableProfile.mjs.map +1 -0
- package/dist/runs/shared/depthGuard.cjs +2 -0
- package/dist/runs/shared/depthGuard.cjs.map +1 -0
- package/dist/runs/shared/depthGuard.mjs +2 -0
- package/dist/runs/shared/depthGuard.mjs.map +1 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs +2 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs.map +1 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs +2 -0
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs.map +1 -0
- package/dist/runs/shared/modelFallback.cjs +2 -0
- package/dist/runs/shared/modelFallback.cjs.map +1 -0
- package/dist/runs/shared/modelFallback.mjs +2 -0
- package/dist/runs/shared/modelFallback.mjs.map +1 -0
- package/dist/runs/shared/modelScope.cjs +2 -0
- package/dist/runs/shared/modelScope.cjs.map +1 -0
- package/dist/runs/shared/modelScope.mjs +2 -0
- package/dist/runs/shared/modelScope.mjs.map +1 -0
- package/dist/runs/shared/nestedPath.cjs +2 -0
- package/dist/runs/shared/nestedPath.cjs.map +1 -0
- package/dist/runs/shared/nestedPath.mjs +2 -0
- package/dist/runs/shared/nestedPath.mjs.map +1 -0
- package/dist/runs/shared/piArgs.cjs +2 -0
- package/dist/runs/shared/piArgs.cjs.map +1 -0
- package/dist/runs/shared/piArgs.mjs +2 -0
- package/dist/runs/shared/piArgs.mjs.map +1 -0
- package/dist/runs/shared/piSpawn.cjs +2 -0
- package/dist/runs/shared/piSpawn.cjs.map +1 -0
- package/dist/runs/shared/piSpawn.mjs +2 -0
- package/dist/runs/shared/piSpawn.mjs.map +1 -0
- package/dist/runs/shared/runWithConcurrency.cjs +2 -0
- package/dist/runs/shared/runWithConcurrency.cjs.map +1 -0
- package/dist/runs/shared/runWithConcurrency.mjs +2 -0
- package/dist/runs/shared/runWithConcurrency.mjs.map +1 -0
- package/dist/runs/shared/runtimeRegistry.cjs +2 -0
- package/dist/runs/shared/runtimeRegistry.cjs.map +1 -0
- package/dist/runs/shared/runtimeRegistry.mjs +2 -0
- package/dist/runs/shared/runtimeRegistry.mjs.map +1 -0
- package/dist/runs/shared/structuredOutput.cjs +2 -0
- package/dist/runs/shared/structuredOutput.cjs.map +1 -0
- package/dist/runs/shared/structuredOutput.mjs +2 -0
- package/dist/runs/shared/structuredOutput.mjs.map +1 -0
- package/dist/runs/shared/toolAvailability.cjs +2 -0
- package/dist/runs/shared/toolAvailability.cjs.map +1 -0
- package/dist/runs/shared/toolAvailability.mjs +2 -0
- package/dist/runs/shared/toolAvailability.mjs.map +1 -0
- package/dist/runs/shared/toolBudget.cjs +2 -0
- package/dist/runs/shared/toolBudget.cjs.map +1 -0
- package/dist/runs/shared/toolBudget.mjs +2 -0
- package/dist/runs/shared/toolBudget.mjs.map +1 -0
- package/dist/shared/artifacts.cjs +2 -0
- package/dist/shared/artifacts.cjs.map +1 -0
- package/dist/shared/artifacts.mjs +2 -0
- package/dist/shared/artifacts.mjs.map +1 -0
- package/dist/shared/atomicJson.cjs +2 -0
- package/dist/shared/atomicJson.cjs.map +1 -0
- package/dist/shared/atomicJson.mjs +2 -0
- package/dist/shared/atomicJson.mjs.map +1 -0
- package/dist/shared/boundedKeySet.cjs +2 -0
- package/dist/shared/boundedKeySet.cjs.map +1 -0
- package/dist/shared/boundedKeySet.mjs +2 -0
- package/dist/shared/boundedKeySet.mjs.map +1 -0
- package/dist/shared/childTranscript.cjs +5 -0
- package/dist/shared/childTranscript.cjs.map +1 -0
- package/dist/shared/childTranscript.mjs +5 -0
- package/dist/shared/childTranscript.mjs.map +1 -0
- package/dist/shared/configDir.cjs +2 -0
- package/dist/shared/configDir.cjs.map +1 -0
- package/dist/shared/configDir.mjs +2 -0
- package/dist/shared/configDir.mjs.map +1 -0
- package/dist/shared/errors.cjs +2 -0
- package/dist/shared/errors.cjs.map +1 -0
- package/dist/shared/errors.mjs +2 -0
- package/dist/shared/errors.mjs.map +1 -0
- package/dist/shared/fileSystemRetry.cjs +2 -0
- package/dist/shared/fileSystemRetry.cjs.map +1 -0
- package/dist/shared/fileSystemRetry.mjs +2 -0
- package/dist/shared/fileSystemRetry.mjs.map +1 -0
- package/dist/shared/formatters.cjs +2 -0
- package/dist/shared/formatters.cjs.map +1 -0
- package/dist/shared/formatters.mjs +2 -0
- package/dist/shared/formatters.mjs.map +1 -0
- package/dist/shared/lruCache.cjs +2 -0
- package/dist/shared/lruCache.cjs.map +1 -0
- package/dist/shared/lruCache.mjs +2 -0
- package/dist/shared/lruCache.mjs.map +1 -0
- package/dist/shared/modelInfo.cjs +2 -0
- package/dist/shared/modelInfo.cjs.map +1 -0
- package/dist/shared/modelInfo.mjs +2 -0
- package/dist/shared/modelInfo.mjs.map +1 -0
- package/dist/shared/modelResolution.cjs +2 -0
- package/dist/shared/modelResolution.cjs.map +1 -0
- package/dist/shared/modelResolution.mjs +2 -0
- package/dist/shared/modelResolution.mjs.map +1 -0
- package/dist/shared/paths.cjs +2 -0
- package/dist/shared/paths.cjs.map +1 -0
- package/dist/shared/paths.mjs +2 -0
- package/dist/shared/paths.mjs.map +1 -0
- package/dist/shared/pollScheduler.cjs +2 -0
- package/dist/shared/pollScheduler.cjs.map +1 -0
- package/dist/shared/pollScheduler.mjs +2 -0
- package/dist/shared/pollScheduler.mjs.map +1 -0
- package/dist/shared/scopeOwner.cjs +2 -0
- package/dist/shared/scopeOwner.cjs.map +1 -0
- package/dist/shared/scopeOwner.mjs +2 -0
- package/dist/shared/scopeOwner.mjs.map +1 -0
- package/dist/shared/versioned.cjs +2 -0
- package/dist/shared/versioned.cjs.map +1 -0
- package/dist/shared/versioned.mjs +2 -0
- package/dist/shared/versioned.mjs.map +1 -0
- package/dist/slash/chainExpression.cjs +2 -0
- package/dist/slash/chainExpression.cjs.map +1 -0
- package/dist/slash/chainExpression.mjs +2 -0
- package/dist/slash/chainExpression.mjs.map +1 -0
- package/dist/slash/doctor.cjs +5 -0
- package/dist/slash/doctor.cjs.map +1 -0
- package/dist/slash/doctor.mjs +5 -0
- package/dist/slash/doctor.mjs.map +1 -0
- package/dist/slash/slashCommands.cjs +4 -0
- package/dist/slash/slashCommands.cjs.map +1 -0
- package/dist/slash/slashCommands.mjs +4 -0
- package/dist/slash/slashCommands.mjs.map +1 -0
- package/dist/slash/spawnRequestMapping.cjs +2 -0
- package/dist/slash/spawnRequestMapping.cjs.map +1 -0
- package/dist/slash/spawnRequestMapping.mjs +2 -0
- package/dist/slash/spawnRequestMapping.mjs.map +1 -0
- package/dist/slash/subagentLaunch.cjs +2 -0
- package/dist/slash/subagentLaunch.cjs.map +1 -0
- package/dist/slash/subagentLaunch.mjs +2 -0
- package/dist/slash/subagentLaunch.mjs.map +1 -0
- package/dist/tui/agentCatalog.cjs +2 -0
- package/dist/tui/agentCatalog.cjs.map +1 -0
- package/dist/tui/agentCatalog.mjs +2 -0
- package/dist/tui/agentCatalog.mjs.map +1 -0
- package/dist/tui/agentResourceProjection.cjs +2 -0
- package/dist/tui/agentResourceProjection.cjs.map +1 -0
- package/dist/tui/agentResourceProjection.mjs +2 -0
- package/dist/tui/agentResourceProjection.mjs.map +1 -0
- package/dist/tui/completionNotice.cjs +5 -0
- package/dist/tui/completionNotice.cjs.map +1 -0
- package/dist/tui/completionNotice.mjs +5 -0
- package/dist/tui/completionNotice.mjs.map +1 -0
- package/dist/tui/fleet.cjs +3 -0
- package/dist/tui/fleet.cjs.map +1 -0
- package/dist/tui/fleet.mjs +3 -0
- package/dist/tui/fleet.mjs.map +1 -0
- package/dist/tui/fleetAgentView.cjs +2 -0
- package/dist/tui/fleetAgentView.cjs.map +1 -0
- package/dist/tui/fleetAgentView.mjs +2 -0
- package/dist/tui/fleetAgentView.mjs.map +1 -0
- package/dist/tui/fleetStatus.cjs +2 -0
- package/dist/tui/fleetStatus.cjs.map +1 -0
- package/dist/tui/fleetStatus.mjs +2 -0
- package/dist/tui/fleetStatus.mjs.map +1 -0
- package/dist/tui/fleetTranscript.cjs +5 -0
- package/dist/tui/fleetTranscript.cjs.map +1 -0
- package/dist/tui/fleetTranscript.mjs +5 -0
- package/dist/tui/fleetTranscript.mjs.map +1 -0
- package/dist/tui/register.cjs +2 -0
- package/dist/tui/register.cjs.map +1 -0
- package/dist/tui/register.mjs +2 -0
- package/dist/tui/register.mjs.map +1 -0
- package/dist/types.d.cts +59 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +59 -0
- package/dist/types.d.mts.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"childTranscript.mjs","names":[],"sources":["../../src/shared/childTranscript.ts"],"sourcesContent":["/**\n * Append-only JSONL transcript of one child run, written by the parent.\n *\n * WHY THE PARENT WRITES ITS OWN:\n * The child's session transcript lives in the host agent's config directory and\n * is keyed by a session id the parent does not learn until the run ends. This\n * file is the parent's independent record: it exists from the first prompt, it\n * survives a child that never started, and it carries the stdout and stderr the\n * child's own transcript has no place for.\n *\n * DESIGN PATTERNS:\n * - Two independent byte budgets. Each tool payload is capped so one huge result\n * cannot consume the file, and the file itself is capped so a runaway child\n * cannot fill the disk\n * - The file budget reserves room for the truncation marker up front, so the\n * record that trips the limit is dropped in favour of a record explaining why\n * - Write failures latch into one reported error instead of throwing. This file\n * is evidence about a run, so losing it must never end the run it describes\n * - Truncation is carried as a flag beside the text. The predecessor rediscovered\n * it by searching the output for the marker substring, which any payload\n * quoting that phrase would have tripped\n *\n * PERFORMANCE:\n * The marker's byte size is measured once. Every field feeding it is fixed for\n * the writer's lifetime and both timestamps are fixed width, so the predecessor's\n * re-serialisation of the marker on every single record bought nothing.\n *\n * AVOID:\n * - Throwing from any writer method\n * - Cutting a payload mid-character; the cut walks back off UTF-8 continuation\n * bytes before slicing\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nexport const CHILD_TRANSCRIPT_ARTIFACT_VERSION = 1;\n\nconst BYTES_PER_KIB = 1024;\nconst BYTES_PER_MIB = 1024 * BYTES_PER_KIB;\nconst MAX_TOOL_PAYLOAD_BYTES = 32 * BYTES_PER_KIB;\nconst DEFAULT_MAX_CHILD_TRANSCRIPT_BYTES = 50 * BYTES_PER_MIB;\nconst TOOL_PAYLOAD_TRUNCATION_MARKER = '\\n\\n… payload truncated';\nconst JSON_INDENT = 2;\n\n/** High bits identifying a UTF-8 continuation byte (10xxxxxx). */\nconst UTF8_CONTINUATION_MASK = 0xc0;\nconst UTF8_CONTINUATION_BITS = 0x80;\n\nconst PREVIEW_ELLIPSIS = '...';\nconst PREVIEW_MAX_LENGTH = 60;\nconst FALLBACK_PREVIEW_MAX_LENGTH = 50;\nconst WORKFLOW_PREVIEW_MAX_LENGTH = 48;\nconst MCP_ARGS_PREVIEW_MAX_LENGTH = 40;\n\n/** Argument keys worth previewing verbatim, in the order they are preferred. */\nconst PREVIEW_KEYS = ['command', 'path', 'file_path', 'pattern', 'query', 'url', 'task', 'describe', 'search'] as const;\n\n/**\n * Which execution path produced the records in a file.\n *\n * Only background runs exist, so this is single-valued today. It is still\n * written into every record because the file is read by tools that must be able\n * to tell what produced it without being told out of band, and because a second\n * variant is cheaper to add than to retrofit onto files already on disk.\n */\nexport type ChildTranscriptSource = 'async';\n\nexport type ChildTranscriptRecordType = 'message' | 'tool_start' | 'tool_end' | 'stdout' | 'stderr' | 'truncated';\n\n/**\n * The message fields this transcript records.\n *\n * Declared structurally rather than imported from the host agent's message type:\n * the writer only reads these fields, and a shared leaf should not take on an\n * optional peer dependency to describe them.\n */\nexport interface ChildTranscriptMessage {\n role: string;\n content?: unknown;\n toolCallId?: string;\n toolName?: string;\n isError?: boolean;\n timestamp?: unknown;\n model?: string;\n errorMessage?: string;\n stopReason?: string;\n usage?: unknown;\n}\n\nexport interface ChildTranscriptEvent {\n type?: string;\n message?: ChildTranscriptMessage;\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n isError?: boolean;\n}\n\nexport interface ChildTranscriptWriterInput {\n transcriptPath: string;\n source: ChildTranscriptSource;\n runId: string;\n agent: string;\n childIndex?: number;\n cwd: string;\n maxBytes?: number;\n}\n\nexport interface ChildTranscriptWriter {\n path: string;\n writeInitialUserMessage(prompt: string): void;\n writeChildEvent(event: ChildTranscriptEvent): void;\n writeStdoutLine(line: string): void;\n writeStderrLine(line: string): void;\n writeStderrText(text: string): void;\n getError(): string | undefined;\n}\n\ninterface BoundedPayload {\n text: string;\n truncated: boolean;\n}\n\ninterface NormalizedUsage {\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n cost: number;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Render `value` as text no larger than `maxBytes`, or undefined if it carries\n * nothing worth recording.\n *\n * A value that cannot be serialised (a cycle, a BigInt) is dropped rather than\n * reported, because a tool payload is context and not part of the run's result.\n */\nfunction boundedPayload(value: unknown, maxBytes = MAX_TOOL_PAYLOAD_BYTES): BoundedPayload | undefined {\n let text: string;\n if (typeof value === 'string') {\n text = value;\n } else {\n try {\n const serialized = JSON.stringify(value, null, JSON_INDENT);\n if (serialized === undefined) return undefined;\n text = serialized;\n } catch {\n // Cyclic or non-serialisable payloads have no textual form to record.\n return undefined;\n }\n }\n if (!text.trim()) return undefined;\n\n const payload = Buffer.from(text, 'utf-8');\n if (payload.length <= maxBytes) return { text, truncated: false };\n\n const markerBytes = Buffer.byteLength(TOOL_PAYLOAD_TRUNCATION_MARKER, 'utf-8');\n let end = Math.max(0, maxBytes - markerBytes);\n // Back off any continuation bytes so the cut lands on a character boundary.\n while (end > 0 && ((payload[end] ?? 0) & UTF8_CONTINUATION_MASK) === UTF8_CONTINUATION_BITS) end--;\n return {\n text: `${payload.subarray(0, end).toString('utf-8')}${TOOL_PAYLOAD_TRUNCATION_MARKER}`,\n truncated: true,\n };\n}\n\n/**\n * Read a child's usage counters, tolerating both field namings the host has used.\n *\n * Cost arrives either as a number or as an object with a `total`, so both are\n * accepted; anything else counts as zero rather than as missing, because a run\n * with unreadable cost still has a real elapsed usage record.\n */\nfunction normalizeUsage(value: unknown): NormalizedUsage | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const raw = value as Record<string, unknown>;\n const rawCost = raw.cost;\n const cost =\n rawCost && typeof rawCost === 'object'\n ? (finiteNumber((rawCost as { total?: unknown }).total) ?? 0)\n : (finiteNumber(rawCost) ?? 0);\n return {\n input: finiteNumber(raw.input) ?? finiteNumber(raw.inputTokens) ?? 0,\n output: finiteNumber(raw.output) ?? finiteNumber(raw.outputTokens) ?? 0,\n cacheRead: finiteNumber(raw.cacheRead) ?? 0,\n cacheWrite: finiteNumber(raw.cacheWrite) ?? 0,\n cost,\n };\n}\n\n/**\n * Return the token total observed on one finalized child message.\n *\n * Missing usage means that this message carried no observation and therefore\n * returns undefined. An actual usage object with empty or zero buckets returns\n * zero, which is a meaningful observation for live progress.\n */\nexport function getMessageUsageTokens(message: ChildTranscriptMessage): number | undefined {\n const usage = normalizeUsage(message.usage);\n if (!usage) return undefined;\n return usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n}\n\nfunction truncatePreview(value: string, maxLength: number): string {\n return value.length > maxLength ? `${value.slice(0, maxLength - PREVIEW_ELLIPSIS.length)}${PREVIEW_ELLIPSIS}` : value;\n}\n\nfunction stringifyPreviewValue(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim().length > 0) return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n return undefined;\n}\n\nfunction previewArray(value: unknown): string | undefined {\n if (!Array.isArray(value) || value.length === 0) return undefined;\n const first = stringifyPreviewValue(value[0]);\n if (!first) return undefined;\n const suffix = value.length > 1 ? ` (+${value.length - 1} more)` : '';\n return `${first}${suffix}`;\n}\n\n/**\n * One short line describing what a tool call was asked to do.\n *\n * The ladder is ordered by how much the key tells a reader, not alphabetically:\n * an MCP call is named by server and tool, a search by its query, and everything\n * else falls back to the first stringable argument so the line is never empty\n * when the call had any arguments at all.\n */\nfunction extractToolArgsPreview(args: Record<string, unknown>): string {\n if (typeof args.tool === 'string' && args.tool) {\n const server = typeof args.server === 'string' && args.server ? `${args.server}/` : '';\n const toolArgs =\n typeof args.args === 'string' && args.args ? ` ${args.args.slice(0, MCP_ARGS_PREVIEW_MAX_LENGTH)}` : '';\n return `${server}${args.tool}${toolArgs}`;\n }\n\n const queriesPreview = previewArray(args.queries);\n if (queriesPreview) return truncatePreview(queriesPreview, PREVIEW_MAX_LENGTH);\n if (typeof args.query === 'string' && args.query.trim().length > 0) {\n return truncatePreview(args.query, PREVIEW_MAX_LENGTH);\n }\n if (typeof args.workflow === 'string' && args.workflow.trim().length > 0) {\n return `workflow=${truncatePreview(args.workflow, WORKFLOW_PREVIEW_MAX_LENGTH)}`;\n }\n if (typeof args.url === 'string' && args.url.trim().length > 0) return truncatePreview(args.url, PREVIEW_MAX_LENGTH);\n const urlsPreview = previewArray(args.urls);\n if (urlsPreview) return truncatePreview(urlsPreview, PREVIEW_MAX_LENGTH);\n if (typeof args.prompt === 'string' && args.prompt.trim().length > 0) {\n return truncatePreview(args.prompt, PREVIEW_MAX_LENGTH);\n }\n\n for (const key of PREVIEW_KEYS) {\n const value = args[key];\n if (typeof value === 'string' && value.length > 0) return truncatePreview(value, PREVIEW_MAX_LENGTH);\n }\n\n for (const [key, value] of Object.entries(args)) {\n const arrayPreview = previewArray(value);\n if (arrayPreview) return `${key}=${truncatePreview(arrayPreview, FALLBACK_PREVIEW_MAX_LENGTH)}`;\n if (typeof value === 'string' && value.length > 0) {\n return `${key}=${truncatePreview(value, FALLBACK_PREVIEW_MAX_LENGTH)}`;\n }\n }\n return '';\n}\n\n/**\n * Flatten a message's content into plain text.\n *\n * Content arrives as unknown JSON in four shapes the host has used over time, so\n * every hop is checked. Anything unrecognised contributes nothing rather than\n * being stringified, which would put `[object Object]` into the transcript.\n */\nfunction extractTextFromContent(content: unknown): string {\n if (!content) return '';\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n\n const texts: string[] = [];\n for (const part of content) {\n if (!part || typeof part !== 'object') continue;\n const record = part as Record<string, unknown>;\n if (record.type === 'text' && 'text' in record) {\n texts.push(String(record.text));\n } else if (record.type === 'tool_result' && 'content' in record) {\n const inner = extractTextFromContent(record.content);\n if (inner) texts.push(inner);\n } else if ('text' in record) {\n texts.push(String(record.text));\n }\n }\n return texts.join('\\n');\n}\n\nfunction eventArgs(event: ChildTranscriptEvent): Record<string, unknown> {\n return event.args && typeof event.args === 'object' && !Array.isArray(event.args)\n ? (event.args as Record<string, unknown>)\n : {};\n}\n\nexport function createChildTranscriptWriter(input: ChildTranscriptWriterInput): ChildTranscriptWriter {\n let bytesWritten = 0;\n let writeError: string | undefined;\n let truncated = false;\n const maxBytes = input.maxBytes ?? DEFAULT_MAX_CHILD_TRANSCRIPT_BYTES;\n\n const baseRecord = (recordType: ChildTranscriptRecordType) => {\n const ts = Date.now();\n return {\n version: CHILD_TRANSCRIPT_ARTIFACT_VERSION,\n recordType,\n source: input.source,\n runId: input.runId,\n agent: input.agent,\n ...(input.childIndex !== undefined ? { childIndex: input.childIndex } : {}),\n cwd: input.cwd,\n ts,\n timestamp: new Date(ts).toISOString(),\n };\n };\n\n const truncationMarkerLine = (): string =>\n `${JSON.stringify({\n ...baseRecord('truncated'),\n maxBytes,\n message: `Child transcript exceeded ${maxBytes} bytes; further records were omitted.`,\n })}\\n`;\n\n // Fixed for this writer: every field is constant except the two timestamps,\n // which are fixed width. Measuring once is why writeRecord stays allocation-free.\n const reservedMarkerBytes = Buffer.byteLength(truncationMarkerLine(), 'utf-8');\n\n const writeTruncatedMarker = () => {\n truncated = true;\n const marker = truncationMarkerLine();\n const markerBytes = Buffer.byteLength(marker, 'utf-8');\n if (bytesWritten + markerBytes > maxBytes) return;\n try {\n fs.appendFileSync(input.transcriptPath, marker, 'utf-8');\n bytesWritten += markerBytes;\n } catch (error) {\n writeError = `Failed to write child transcript '${input.transcriptPath}': ${errorMessage(error)}`;\n }\n };\n\n const writeRecord = (record: Record<string, unknown>) => {\n if (writeError || truncated) return;\n const line = `${JSON.stringify(record)}\\n`;\n const bytes = Buffer.byteLength(line, 'utf-8');\n // The marker's room is reserved, so the file can always state why it ended.\n if (bytesWritten + bytes + reservedMarkerBytes > maxBytes) {\n writeTruncatedMarker();\n return;\n }\n try {\n fs.appendFileSync(input.transcriptPath, line, 'utf-8');\n bytesWritten += bytes;\n } catch (error) {\n writeError = `Failed to write child transcript '${input.transcriptPath}': ${errorMessage(error)}`;\n }\n };\n\n try {\n fs.mkdirSync(path.dirname(input.transcriptPath), { recursive: true });\n fs.writeFileSync(input.transcriptPath, '', 'utf-8');\n } catch (error) {\n writeError = `Failed to initialize child transcript '${input.transcriptPath}': ${errorMessage(error)}`;\n }\n\n const writeMessage = (sourceEventType: string, message: ChildTranscriptMessage) => {\n const text = extractTextFromContent(message.content);\n const usage = normalizeUsage(message.usage);\n if (message.role === 'toolResult') {\n const output = boundedPayload(text);\n writeRecord({\n ...baseRecord('message'),\n sourceEventType,\n role: message.role,\n toolCallId: message.toolCallId,\n toolName: message.toolName,\n isError: message.isError,\n ...(output ? { text: output.text, outputTruncated: output.truncated } : {}),\n message: {\n role: message.role,\n toolCallId: message.toolCallId,\n toolName: message.toolName,\n isError: message.isError,\n content: output ? [{ type: 'text', text: output.text }] : [],\n ...(message.timestamp !== undefined ? { timestamp: message.timestamp } : {}),\n },\n });\n return;\n }\n writeRecord({\n ...baseRecord('message'),\n sourceEventType,\n role: message.role,\n ...(text ? { text } : {}),\n ...(message.model ? { model: message.model } : {}),\n ...(message.stopReason ? { stopReason: message.stopReason } : {}),\n ...(message.errorMessage ? { errorMessage: message.errorMessage } : {}),\n ...(usage ? { usage } : {}),\n message,\n });\n };\n\n const writeStderrLine = (line: string) => {\n if (!line.trim()) return;\n writeRecord({ ...baseRecord('stderr'), text: line });\n };\n\n return {\n path: input.transcriptPath,\n writeInitialUserMessage(prompt: string) {\n writeRecord({\n ...baseRecord('message'),\n sourceEventType: 'initial_prompt',\n role: 'user',\n text: prompt,\n message: { role: 'user', content: [{ type: 'text', text: prompt }] },\n });\n },\n writeChildEvent(event: ChildTranscriptEvent) {\n if ((event.type === 'message_end' || event.type === 'tool_result_end') && event.message) {\n writeMessage(event.type, event.message);\n return;\n }\n if (event.type === 'tool_execution_start' && event.toolName) {\n const args = eventArgs(event);\n const argsPayload = boundedPayload(args);\n writeRecord({\n ...baseRecord('tool_start'),\n sourceEventType: event.type,\n ...(event.toolCallId ? { toolCallId: event.toolCallId } : {}),\n toolName: event.toolName,\n ...(Object.keys(args).length > 0 ? { argsPreview: extractToolArgsPreview(args) } : {}),\n ...(argsPayload ? { argsPayload: argsPayload.text } : {}),\n });\n return;\n }\n if (event.type === 'tool_execution_end') {\n writeRecord({\n ...baseRecord('tool_end'),\n sourceEventType: event.type,\n ...(event.toolCallId ? { toolCallId: event.toolCallId } : {}),\n ...(event.toolName ? { toolName: event.toolName } : {}),\n ...(typeof event.isError === 'boolean' ? { isError: event.isError } : {}),\n });\n }\n },\n writeStdoutLine(line: string) {\n if (!line.trim()) return;\n writeRecord({ ...baseRecord('stdout'), text: line });\n },\n writeStderrLine,\n writeStderrText(text: string) {\n for (const line of text.split(/\\r?\\n/)) writeStderrLine(line);\n },\n getError() {\n return writeError;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../_virtual/_rolldown/runtime.cjs`);let t=require(`node:fs`);t=e.__toESM(t,1);let n=require(`node:path`);n=e.__toESM(n,1);let r=require(`node:os`);r=e.__toESM(r,1);function i(e,n=t.realpathSync.native){try{return n(e)}catch{return e}}function a(e){return typeof e==`string`&&e.trim()?e:void 0}function o(e){if(e)try{let r=JSON.parse(t.readFileSync(n.join(e,`package.json`),`utf-8`));if(typeof r!=`object`||!r)return;let i=r;return i.name===`@earendil-works/pi-coding-agent`?a((typeof i.piConfig==`object`&&i.piConfig!==null?i.piConfig:void 0)?.configDir):void 0}catch{return}}function s(e=process.argv[1],r=process.env.PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT){let i=o(r);if(i)return i;if(e)try{let r=n.dirname(t.realpathSync(e));for(;r!==n.dirname(r);){let e=o(r);if(e)return e;r=n.dirname(r)}}catch{}}function c(e,t,n){return(e&&typeof e==`object`?a(e.CONFIG_DIR_NAME):void 0)??s(t,n)??`.pi`}let l;function u(){return l??=c(),l}function d(e){return n.join(e,u())}function f(e){if(!t.existsSync(e))return{};let n;try{n=t.readFileSync(e,`utf-8`)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to read settings file '${e}': ${n}`,{cause:t})}let r;try{r=JSON.parse(n)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to parse settings file '${e}': ${n}`,{cause:t})}if(!r||typeof r!=`object`||Array.isArray(r))throw Error(`Settings file '${e}' must contain a JSON object.`);return r}function p(){let e=process.env.PI_CODING_AGENT_DIR;return e===`~`?r.homedir():e?.startsWith(`~/`)?n.join(r.homedir(),e.slice(2)):e||n.join(r.homedir(),u(),`agent`)}exports.getAgentDir=p,exports.getProjectConfigDir=d,exports.readSettingsFileStrict=f,exports.resolveWatchPath=i;
|
|
2
|
+
//# sourceMappingURL=configDir.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configDir.cjs","names":["fs","path","os"],"sources":["../../src/shared/configDir.ts"],"sourcesContent":["/**\n * Locating the coding agent's config directory.\n *\n * The directory is normally `.pi`, but a rebranded build of the coding agent\n * can rename it, so the real name is discovered from the running installation\n * rather than assumed. Discovery order runs from most to least authoritative:\n * the loaded module's own export, then an explicitly configured package root,\n * then a walk upward from the process entry point, then the default.\n *\n * DESIGN PATTERNS:\n * - Resolution is a pure function of its inputs; the process-wide convenience\n * wrapper is a separate, memoized entry point\n * - Every discovery step is best-effort. A detached runner must still start\n * when package metadata is unreadable, so failures fall through to the default\n *\n * PERFORMANCE:\n * `getConfigDirName` is memoized because the uncached path walks from the entry\n * point to the filesystem root, reading and parsing a package.json per level.\n * Callers join it into paths, so it was previously re-walked per path built.\n *\n * AVOID:\n * - Hardcoding `.pi` at a call site\n * - Calling the uncached resolver on a hot path\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nconst DEFAULT_CONFIG_DIR_NAME = '.pi';\nconst PI_CODING_AGENT_PACKAGE_NAME = '@earendil-works/pi-coding-agent';\nconst AGENT_DIR_NAME = 'agent';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\n\n/** Overrides the upward package walk when the installation root is known. */\nexport const PI_CODING_AGENT_PACKAGE_ROOT_ENV = 'PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT';\n\n/**\n * Resolve a path through symlinks for use with a filesystem watcher.\n *\n * libuv's Windows watcher cannot mix an 8.3 short path used at registration\n * with the long paths it reports in events, so watchers register the real path.\n * An unresolvable path is returned unchanged; watching it may still work.\n */\nexport function resolveWatchPath(\n watchPath: string,\n nativeRealpath: (filePath: string) => string = fs.realpathSync.native,\n): string {\n try {\n return nativeRealpath(watchPath);\n } catch {\n // The path may not exist yet, or realpath may be unsupported here.\n return watchPath;\n }\n}\n\nfunction validConfigDirName(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\n/** Read the config dir name from a directory, if it is the coding agent's package root. */\nfunction readConfigDirNameFromPackageRoot(packageRoot: string | undefined): string | undefined {\n if (!packageRoot) return undefined;\n try {\n const manifest: unknown = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));\n if (typeof manifest !== 'object' || manifest === null) return undefined;\n\n const record = manifest as { name?: unknown; piConfig?: unknown };\n if (record.name !== PI_CODING_AGENT_PACKAGE_NAME) return undefined;\n\n const piConfig =\n typeof record.piConfig === 'object' && record.piConfig !== null\n ? (record.piConfig as { configDir?: unknown })\n : undefined;\n return validConfigDirName(piConfig?.configDir);\n } catch {\n // Absent or malformed metadata simply means this is not the package root.\n return undefined;\n }\n}\n\nfunction resolveConfigDirNameFromPackageJson(\n entryPoint = process.argv[1],\n packageRoot = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV],\n): string | undefined {\n const configuredRootValue = readConfigDirNameFromPackageRoot(packageRoot);\n if (configuredRootValue) return configuredRootValue;\n if (!entryPoint) return undefined;\n\n try {\n let dir = path.dirname(fs.realpathSync(entryPoint));\n while (dir !== path.dirname(dir)) {\n const value = readConfigDirNameFromPackageRoot(dir);\n if (value) return value;\n dir = path.dirname(dir);\n }\n } catch {\n // Package metadata lookup is best-effort; a detached runner must not fail here.\n }\n return undefined;\n}\n\n/**\n * Resolve the config directory name from explicit inputs.\n *\n * Pure with respect to its arguments, which is what makes it testable; the\n * process-wide answer comes from `getConfigDirName`.\n */\nexport function resolveConfigDirName(codingAgentModule?: unknown, entryPoint?: string, packageRoot?: string): string {\n const moduleValue =\n codingAgentModule && typeof codingAgentModule === 'object'\n ? validConfigDirName((codingAgentModule as { CONFIG_DIR_NAME?: unknown }).CONFIG_DIR_NAME)\n : undefined;\n return moduleValue ?? resolveConfigDirNameFromPackageJson(entryPoint, packageRoot) ?? DEFAULT_CONFIG_DIR_NAME;\n}\n\nlet memoizedConfigDirName: string | undefined;\n\n/** The config directory name for this process. Resolved once, then reused. */\nexport function getConfigDirName(): string {\n memoizedConfigDirName ??= resolveConfigDirName();\n return memoizedConfigDirName;\n}\n\n/**\n * Drop the memoized name.\n *\n * The installation cannot change under a running process, so this exists for\n * tests that vary the environment between cases.\n */\nexport function resetConfigDirNameCache(): void {\n memoizedConfigDirName = undefined;\n}\n\n/** The project-local config directory, for example `<projectRoot>/.pi`. */\nexport function getProjectConfigDir(projectRoot: string): string {\n return path.join(projectRoot, getConfigDirName());\n}\n\n/** Read a JSON settings file as an object, retaining strict path-specific errors. */\nexport function readSettingsFileStrict(filePath: string): Record<string, unknown> {\n if (!fs.existsSync(filePath)) return {};\n\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`Settings file '${filePath}' must contain a JSON object.`);\n }\n return parsed as Record<string, unknown>;\n}\n\n/** The user-level agent directory, honouring `~` expansion in the override. */\nexport function getAgentDir(): string {\n const configured = process.env.PI_CODING_AGENT_DIR;\n if (configured === HOME_ALIAS) return os.homedir();\n if (configured?.startsWith(HOME_ALIAS_PREFIX)) {\n return path.join(os.homedir(), configured.slice(HOME_ALIAS_PREFIX.length));\n }\n return configured || path.join(os.homedir(), getConfigDirName(), AGENT_DIR_NAME);\n}\n\n/** Resolve a child's working directory, which may be relative to the parent's. */\nexport function resolveChildCwd(baseCwd: string, childCwd: string | undefined): string {\n if (!childCwd) return baseCwd;\n return path.isAbsolute(childCwd) ? childCwd : path.resolve(baseCwd, childCwd);\n}\n"],"mappings":"qLA6CA,SAAgB,EACd,EACA,EAA+CA,EAAG,aAAa,OACvD,CACR,GAAI,CACF,OAAO,EAAe,EAAU,MAC1B,CAEN,OAAO,GAIX,SAAS,EAAmB,EAAoC,CAC9D,OAAO,OAAO,GAAU,UAAY,EAAM,MAAM,CAAG,EAAQ,IAAA,GAI7D,SAAS,EAAiC,EAAqD,CACxF,KACL,GAAI,CACF,IAAM,EAAoB,KAAK,MAAMA,EAAG,aAAaC,EAAK,KAAK,EAAa,eAAe,CAAE,QAAQ,CAAC,CACtG,GAAI,OAAO,GAAa,WAAY,EAAmB,OAEvD,IAAM,EAAS,EAOf,OANI,EAAO,OAAS,kCAMb,GAHL,OAAO,EAAO,UAAa,UAAY,EAAO,WAAa,KACtD,EAAO,SACR,IAAA,KAC8B,UAAU,CANI,YAO5C,CAEN,QAIJ,SAAS,EACP,EAAa,QAAQ,KAAK,GAC1B,EAAc,QAAQ,IAAI,0CACN,CACpB,IAAM,EAAsB,EAAiC,EAAY,CACzE,GAAI,EAAqB,OAAO,EAC3B,KAEL,GAAI,CACF,IAAI,EAAMA,EAAK,QAAQD,EAAG,aAAa,EAAW,CAAC,CACnD,KAAO,IAAQC,EAAK,QAAQ,EAAI,EAAE,CAChC,IAAM,EAAQ,EAAiC,EAAI,CACnD,GAAI,EAAO,OAAO,EAClB,EAAMA,EAAK,QAAQ,EAAI,OAEnB,GAYV,SAAgB,EAAqB,EAA6B,EAAqB,EAA8B,CAKnH,OAHE,GAAqB,OAAO,GAAsB,SAC9C,EAAoB,EAAoD,gBAAgB,CACxF,IAAA,KACgB,EAAoC,EAAY,EAAY,EAAI,MAGxF,IAAI,EAGJ,SAAgB,GAA2B,CAEzC,MADA,KAA0B,GAAsB,CACzC,EAcT,SAAgB,EAAoB,EAA6B,CAC/D,OAAOA,EAAK,KAAK,EAAa,GAAkB,CAAC,CAInD,SAAgB,EAAuB,EAA2C,CAChF,GAAI,CAACD,EAAG,WAAW,EAAS,CAAE,MAAO,EAAE,CAEvC,IAAI,EACJ,GAAI,CACF,EAAMA,EAAG,aAAa,EAAU,QAAQ,OACjC,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CACtE,MAAU,MAAM,iCAAiC,EAAS,KAAK,IAAW,CAAE,MAAO,EAAO,CAAC,CAG7F,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,EAAI,OACjB,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CACtE,MAAU,MAAM,kCAAkC,EAAS,KAAK,IAAW,CAAE,MAAO,EAAO,CAAC,CAE9F,GAAI,CAAC,GAAU,OAAO,GAAW,UAAY,MAAM,QAAQ,EAAO,CAChE,MAAU,MAAM,kBAAkB,EAAS,+BAA+B,CAE5E,OAAO,EAIT,SAAgB,GAAsB,CACpC,IAAM,EAAa,QAAQ,IAAI,oBAK/B,OAJI,IAAe,IAAmBE,EAAG,SAAS,CAC9C,GAAY,WAAW,KAAkB,CACpCD,EAAK,KAAKC,EAAG,SAAS,CAAE,EAAW,MAAM,EAAyB,CAAC,CAErE,GAAcD,EAAK,KAAKC,EAAG,SAAS,CAAE,GAAkB,CAAE,QAAe"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import*as t from"node:fs";import*as n from"node:path";import*as r from"node:os";function i(e,n=t.realpathSync.native){try{return n(e)}catch{return e}}function a(e){return typeof e==`string`&&e.trim()?e:void 0}function o(e){if(e)try{let r=JSON.parse(t.readFileSync(n.join(e,`package.json`),`utf-8`));if(typeof r!=`object`||!r)return;let i=r;return i.name===m?a((typeof i.piConfig==`object`&&i.piConfig!==null?i.piConfig:void 0)?.configDir):void 0}catch{return}}function s(e=process.argv[1],r=process.env[v]){let i=o(r);if(i)return i;if(e)try{let r=n.dirname(t.realpathSync(e));for(;r!==n.dirname(r);){let e=o(r);if(e)return e;r=n.dirname(r)}}catch{}}function c(e,t,n){return(e&&typeof e==`object`?a(e.CONFIG_DIR_NAME):void 0)??s(t,n)??p}function l(){return y??=c(),y}function u(e){return n.join(e,l())}function d(e){if(!t.existsSync(e))return{};let n;try{n=t.readFileSync(e,`utf-8`)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to read settings file '${e}': ${n}`,{cause:t})}let r;try{r=JSON.parse(n)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to parse settings file '${e}': ${n}`,{cause:t})}if(!r||typeof r!=`object`||Array.isArray(r))throw Error(`Settings file '${e}' must contain a JSON object.`);return r}function f(){let e=process.env.PI_CODING_AGENT_DIR;return e===g?r.homedir():e?.startsWith(_)?n.join(r.homedir(),e.slice(2)):e||n.join(r.homedir(),l(),h)}var p,m,h,g,_,v,y,b=e((()=>{p=`.pi`,m=`@earendil-works/pi-coding-agent`,h=`agent`,g=`~`,_=`~/`,v=`PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT`}));b();export{f as getAgentDir,u as getProjectConfigDir,b as init_configDir,d as readSettingsFileStrict,i as resolveWatchPath};
|
|
2
|
+
//# sourceMappingURL=configDir.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configDir.mjs","names":[],"sources":["../../src/shared/configDir.ts"],"sourcesContent":["/**\n * Locating the coding agent's config directory.\n *\n * The directory is normally `.pi`, but a rebranded build of the coding agent\n * can rename it, so the real name is discovered from the running installation\n * rather than assumed. Discovery order runs from most to least authoritative:\n * the loaded module's own export, then an explicitly configured package root,\n * then a walk upward from the process entry point, then the default.\n *\n * DESIGN PATTERNS:\n * - Resolution is a pure function of its inputs; the process-wide convenience\n * wrapper is a separate, memoized entry point\n * - Every discovery step is best-effort. A detached runner must still start\n * when package metadata is unreadable, so failures fall through to the default\n *\n * PERFORMANCE:\n * `getConfigDirName` is memoized because the uncached path walks from the entry\n * point to the filesystem root, reading and parsing a package.json per level.\n * Callers join it into paths, so it was previously re-walked per path built.\n *\n * AVOID:\n * - Hardcoding `.pi` at a call site\n * - Calling the uncached resolver on a hot path\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nconst DEFAULT_CONFIG_DIR_NAME = '.pi';\nconst PI_CODING_AGENT_PACKAGE_NAME = '@earendil-works/pi-coding-agent';\nconst AGENT_DIR_NAME = 'agent';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\n\n/** Overrides the upward package walk when the installation root is known. */\nexport const PI_CODING_AGENT_PACKAGE_ROOT_ENV = 'PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT';\n\n/**\n * Resolve a path through symlinks for use with a filesystem watcher.\n *\n * libuv's Windows watcher cannot mix an 8.3 short path used at registration\n * with the long paths it reports in events, so watchers register the real path.\n * An unresolvable path is returned unchanged; watching it may still work.\n */\nexport function resolveWatchPath(\n watchPath: string,\n nativeRealpath: (filePath: string) => string = fs.realpathSync.native,\n): string {\n try {\n return nativeRealpath(watchPath);\n } catch {\n // The path may not exist yet, or realpath may be unsupported here.\n return watchPath;\n }\n}\n\nfunction validConfigDirName(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\n/** Read the config dir name from a directory, if it is the coding agent's package root. */\nfunction readConfigDirNameFromPackageRoot(packageRoot: string | undefined): string | undefined {\n if (!packageRoot) return undefined;\n try {\n const manifest: unknown = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));\n if (typeof manifest !== 'object' || manifest === null) return undefined;\n\n const record = manifest as { name?: unknown; piConfig?: unknown };\n if (record.name !== PI_CODING_AGENT_PACKAGE_NAME) return undefined;\n\n const piConfig =\n typeof record.piConfig === 'object' && record.piConfig !== null\n ? (record.piConfig as { configDir?: unknown })\n : undefined;\n return validConfigDirName(piConfig?.configDir);\n } catch {\n // Absent or malformed metadata simply means this is not the package root.\n return undefined;\n }\n}\n\nfunction resolveConfigDirNameFromPackageJson(\n entryPoint = process.argv[1],\n packageRoot = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV],\n): string | undefined {\n const configuredRootValue = readConfigDirNameFromPackageRoot(packageRoot);\n if (configuredRootValue) return configuredRootValue;\n if (!entryPoint) return undefined;\n\n try {\n let dir = path.dirname(fs.realpathSync(entryPoint));\n while (dir !== path.dirname(dir)) {\n const value = readConfigDirNameFromPackageRoot(dir);\n if (value) return value;\n dir = path.dirname(dir);\n }\n } catch {\n // Package metadata lookup is best-effort; a detached runner must not fail here.\n }\n return undefined;\n}\n\n/**\n * Resolve the config directory name from explicit inputs.\n *\n * Pure with respect to its arguments, which is what makes it testable; the\n * process-wide answer comes from `getConfigDirName`.\n */\nexport function resolveConfigDirName(codingAgentModule?: unknown, entryPoint?: string, packageRoot?: string): string {\n const moduleValue =\n codingAgentModule && typeof codingAgentModule === 'object'\n ? validConfigDirName((codingAgentModule as { CONFIG_DIR_NAME?: unknown }).CONFIG_DIR_NAME)\n : undefined;\n return moduleValue ?? resolveConfigDirNameFromPackageJson(entryPoint, packageRoot) ?? DEFAULT_CONFIG_DIR_NAME;\n}\n\nlet memoizedConfigDirName: string | undefined;\n\n/** The config directory name for this process. Resolved once, then reused. */\nexport function getConfigDirName(): string {\n memoizedConfigDirName ??= resolveConfigDirName();\n return memoizedConfigDirName;\n}\n\n/**\n * Drop the memoized name.\n *\n * The installation cannot change under a running process, so this exists for\n * tests that vary the environment between cases.\n */\nexport function resetConfigDirNameCache(): void {\n memoizedConfigDirName = undefined;\n}\n\n/** The project-local config directory, for example `<projectRoot>/.pi`. */\nexport function getProjectConfigDir(projectRoot: string): string {\n return path.join(projectRoot, getConfigDirName());\n}\n\n/** Read a JSON settings file as an object, retaining strict path-specific errors. */\nexport function readSettingsFileStrict(filePath: string): Record<string, unknown> {\n if (!fs.existsSync(filePath)) return {};\n\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`Settings file '${filePath}' must contain a JSON object.`);\n }\n return parsed as Record<string, unknown>;\n}\n\n/** The user-level agent directory, honouring `~` expansion in the override. */\nexport function getAgentDir(): string {\n const configured = process.env.PI_CODING_AGENT_DIR;\n if (configured === HOME_ALIAS) return os.homedir();\n if (configured?.startsWith(HOME_ALIAS_PREFIX)) {\n return path.join(os.homedir(), configured.slice(HOME_ALIAS_PREFIX.length));\n }\n return configured || path.join(os.homedir(), getConfigDirName(), AGENT_DIR_NAME);\n}\n\n/** Resolve a child's working directory, which may be relative to the parent's. */\nexport function resolveChildCwd(baseCwd: string, childCwd: string | undefined): string {\n if (!childCwd) return baseCwd;\n return path.isAbsolute(childCwd) ? childCwd : path.resolve(baseCwd, childCwd);\n}\n"],"mappings":"6IA6CA,SAAgB,EACd,EACA,EAA+C,EAAG,aAAa,OACvD,CACR,GAAI,CACF,OAAO,EAAe,EAAU,MAC1B,CAEN,OAAO,GAIX,SAAS,EAAmB,EAAoC,CAC9D,OAAO,OAAO,GAAU,UAAY,EAAM,MAAM,CAAG,EAAQ,IAAA,GAI7D,SAAS,EAAiC,EAAqD,CACxF,KACL,GAAI,CACF,IAAM,EAAoB,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAa,eAAe,CAAE,QAAQ,CAAC,CACtG,GAAI,OAAO,GAAa,WAAY,EAAmB,OAEvD,IAAM,EAAS,EAOf,OANI,EAAO,OAAS,EAMb,GAHL,OAAO,EAAO,UAAa,UAAY,EAAO,WAAa,KACtD,EAAO,SACR,IAAA,KAC8B,UAAU,CANI,YAO5C,CAEN,QAIJ,SAAS,EACP,EAAa,QAAQ,KAAK,GAC1B,EAAc,QAAQ,IAAI,GACN,CACpB,IAAM,EAAsB,EAAiC,EAAY,CACzE,GAAI,EAAqB,OAAO,EAC3B,KAEL,GAAI,CACF,IAAI,EAAM,EAAK,QAAQ,EAAG,aAAa,EAAW,CAAC,CACnD,KAAO,IAAQ,EAAK,QAAQ,EAAI,EAAE,CAChC,IAAM,EAAQ,EAAiC,EAAI,CACnD,GAAI,EAAO,OAAO,EAClB,EAAM,EAAK,QAAQ,EAAI,OAEnB,GAYV,SAAgB,EAAqB,EAA6B,EAAqB,EAA8B,CAKnH,OAHE,GAAqB,OAAO,GAAsB,SAC9C,EAAoB,EAAoD,gBAAgB,CACxF,IAAA,KACgB,EAAoC,EAAY,EAAY,EAAI,EAMxF,SAAgB,GAA2B,CAEzC,MADA,KAA0B,GAAsB,CACzC,EAcT,SAAgB,EAAoB,EAA6B,CAC/D,OAAO,EAAK,KAAK,EAAa,GAAkB,CAAC,CAInD,SAAgB,EAAuB,EAA2C,CAChF,GAAI,CAAC,EAAG,WAAW,EAAS,CAAE,MAAO,EAAE,CAEvC,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,EAAU,QAAQ,OACjC,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CACtE,MAAU,MAAM,iCAAiC,EAAS,KAAK,IAAW,CAAE,MAAO,EAAO,CAAC,CAG7F,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,EAAI,OACjB,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CACtE,MAAU,MAAM,kCAAkC,EAAS,KAAK,IAAW,CAAE,MAAO,EAAO,CAAC,CAE9F,GAAI,CAAC,GAAU,OAAO,GAAW,UAAY,MAAM,QAAQ,EAAO,CAChE,MAAU,MAAM,kBAAkB,EAAS,+BAA+B,CAE5E,OAAO,EAIT,SAAgB,GAAsB,CACpC,IAAM,EAAa,QAAQ,IAAI,oBAK/B,OAJI,IAAe,EAAmB,EAAG,SAAS,CAC9C,GAAY,WAAW,EAAkB,CACpC,EAAK,KAAK,EAAG,SAAS,CAAE,EAAW,MAAM,EAAyB,CAAC,CAErE,GAAc,EAAK,KAAK,EAAG,SAAS,CAAE,GAAkB,CAAE,EAAe,6BA/I5E,EAA0B,MAC1B,EAA+B,kCAC/B,EAAiB,QACjB,EAAa,IACb,EAAoB,KAGb,EAAmC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class extends Error{constructor(e,t,n,r){super(`[${e}] ${t}\nRecovery: ${r}`),this.code=e,this.retryable=n,this.recovery=r,this.name=`DoomTeamExpectedError`}};function t(t,n){return new e(`invalid_request`,t,!1,n)}exports.DoomTeamExpectedError=e,exports.invalidRequest=t;
|
|
2
|
+
//# sourceMappingURL=errors.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.cjs","names":[],"sources":["../../src/shared/errors.ts"],"sourcesContent":["export type DoomTeamErrorCode =\n | 'invalid_request'\n | 'unsupported_operation'\n | 'unsupported_context'\n | 'agent_not_found'\n | 'runtime_unavailable'\n | 'model_unavailable'\n | 'run_not_found'\n | 'ambiguous_run_id'\n | 'run_terminal'\n | 'not_resumable'\n | 'communication_unavailable'\n | 'recipient_not_found'\n | 'recipient_ambiguous'\n | 'delivery_unconfirmed'\n | 'reply_timeout'\n | 'status_corrupt'\n | 'operation_conflict'\n | 'tool_conflict';\n\nexport class DoomTeamExpectedError extends Error {\n constructor(\n readonly code: DoomTeamErrorCode,\n message: string,\n readonly retryable: boolean,\n readonly recovery: string,\n ) {\n super(`[${code}] ${message}\\nRecovery: ${recovery}`);\n this.name = 'DoomTeamExpectedError';\n }\n}\n\nexport function invalidRequest(message: string, recovery: string): DoomTeamExpectedError {\n return new DoomTeamExpectedError('invalid_request', message, false, recovery);\n}\n"],"mappings":"AAoBA,IAAa,EAAb,cAA2C,KAAM,CAC/C,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,IAAI,EAAK,IAAI,EAAQ,cAAc,IAAW,CAL3C,KAAA,KAAA,EAEA,KAAA,UAAA,EACA,KAAA,SAAA,EAGT,KAAK,KAAO,0BAIhB,SAAgB,EAAe,EAAiB,EAAyC,CACvF,OAAO,IAAI,EAAsB,kBAAmB,EAAS,GAAO,EAAS"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";function t(e,t){return new n(`invalid_request`,e,!1,t)}var n,r=e((()=>{n=class extends Error{constructor(e,t,n,r){super(`[${e}] ${t}\nRecovery: ${r}`),this.code=e,this.retryable=n,this.recovery=r,this.name=`DoomTeamExpectedError`}}}));r();export{n as DoomTeamExpectedError,r as init_errors,t as invalidRequest};
|
|
2
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/shared/errors.ts"],"sourcesContent":["export type DoomTeamErrorCode =\n | 'invalid_request'\n | 'unsupported_operation'\n | 'unsupported_context'\n | 'agent_not_found'\n | 'runtime_unavailable'\n | 'model_unavailable'\n | 'run_not_found'\n | 'ambiguous_run_id'\n | 'run_terminal'\n | 'not_resumable'\n | 'communication_unavailable'\n | 'recipient_not_found'\n | 'recipient_ambiguous'\n | 'delivery_unconfirmed'\n | 'reply_timeout'\n | 'status_corrupt'\n | 'operation_conflict'\n | 'tool_conflict';\n\nexport class DoomTeamExpectedError extends Error {\n constructor(\n readonly code: DoomTeamErrorCode,\n message: string,\n readonly retryable: boolean,\n readonly recovery: string,\n ) {\n super(`[${code}] ${message}\\nRecovery: ${recovery}`);\n this.name = 'DoomTeamExpectedError';\n }\n}\n\nexport function invalidRequest(message: string, recovery: string): DoomTeamExpectedError {\n return new DoomTeamExpectedError('invalid_request', message, false, recovery);\n}\n"],"mappings":"6DAgCA,SAAgB,EAAe,EAAiB,EAAyC,CACvF,OAAO,IAAI,EAAsB,kBAAmB,EAAS,GAAO,EAAS,iBAblE,EAAb,cAA2C,KAAM,CAC/C,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,IAAI,EAAK,IAAI,EAAQ,cAAc,IAAW,CAL3C,KAAA,KAAA,EAEA,KAAA,UAAA,EACA,KAAA,SAAA,EAGT,KAAK,KAAO"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=typeof SharedArrayBuffer<`u`?new SharedArrayBuffer(4):void 0,t=e?new Int32Array(e):void 0,n=new Set([`EACCES`,`EBUSY`,`EPERM`]),r=[10,25,50,100,200,500,1e3,2e3,4e3];function i(e){if(e<=0)return;if(t)try{Atomics.wait(t,0,0,e);return}catch{}let n=Date.now()+e;for(;Date.now()<n;);}function a(e){let t=e?.code;return typeof t==`string`&&n.has(t)}function o(e,t={}){let n=t.retryDelaysMs??r,o=t.wait??i;for(let t=0;;t++)try{return e()}catch(e){let r=n[t];if(r===void 0||!a(e))throw e;o(r)}}exports.DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS=r,exports.runFileSystemOperationWithRetry=o,exports.waitForFileSystemRetry=i;
|
|
2
|
+
//# sourceMappingURL=fileSystemRetry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileSystemRetry.cjs","names":[],"sources":["../../src/shared/fileSystemRetry.ts"],"sourcesContent":["/**\n * Retry helpers for transient filesystem failures.\n *\n * Windows holds brief directory and rename locks (antivirus, indexers, another\n * process reading the file), surfacing as EACCES, EBUSY or EPERM. Those clear on\n * their own, so the operation is retried on a backoff instead of failing a run.\n *\n * DESIGN PATTERNS:\n * - Two variants with the same policy: a synchronous one for the atomic writers\n * that must not yield mid-write, and an async one for everything else\n *\n * AVOID:\n * - Reaching for the synchronous variant on a hot path; it blocks the event\n * loop, and every millisecond of backoff is a millisecond the parent cannot\n * service a child. Prefer `runFileSystemOperationWithRetryAsync`\n * - Retrying ENOENT or ENOTDIR; those are real errors, not contention\n */\n\nconst WAIT_BUFFER = typeof SharedArrayBuffer !== 'undefined' ? new SharedArrayBuffer(4) : undefined;\nconst WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : undefined;\nconst RETRYABLE_FILE_SYSTEM_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);\n\nexport const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 500, 1000, 2000, 4000] as const;\n\nexport type FileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => void;\n};\n\nexport type AsyncFileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => Promise<void>;\n};\n\n/**\n * Block the current thread for `delayMs`.\n *\n * Only for synchronous callers that cannot yield. `Atomics.wait` parks the\n * thread rather than burning CPU; the loop below is a portable fallback for\n * runtimes without SharedArrayBuffer and does spin, which is why async callers\n * must use `delayForFileSystemRetry` instead.\n */\nexport function waitForFileSystemRetry(delayMs: number): void {\n if (delayMs <= 0) return;\n if (WAIT_VIEW) {\n try {\n Atomics.wait(WAIT_VIEW, 0, 0, delayMs);\n return;\n } catch {\n // Fall through to the portable busy wait below.\n }\n }\n const end = Date.now() + delayMs;\n while (Date.now() < end) {\n // Portable fallback for runtimes where Atomics.wait is unavailable.\n }\n}\n\n/** Yield for `delayMs` without blocking the event loop. */\nexport function delayForFileSystemRetry(delayMs: number): Promise<void> {\n if (delayMs <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\nexport function isRetryableFileSystemError(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n return typeof code === 'string' && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);\n}\n\nexport function runFileSystemOperationWithRetry<T>(operation: () => T, options: FileSystemRetryOptions = {}): T {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? waitForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n wait(delayMs);\n }\n }\n}\n\nexport async function runFileSystemOperationWithRetryAsync<T>(\n operation: () => T | Promise<T>,\n options: AsyncFileSystemRetryOptions = {},\n): Promise<T> {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? delayForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n await wait(delayMs);\n }\n }\n}\n"],"mappings":"AAkBA,MAAM,EAAc,OAAO,kBAAsB,IAAc,IAAI,kBAAkB,EAAE,CAAG,IAAA,GACpF,EAAY,EAAc,IAAI,WAAW,EAAY,CAAG,IAAA,GACxD,EAAoC,IAAI,IAAI,CAAC,SAAU,QAAS,QAAQ,CAAC,CAElE,EAAsC,CAAC,GAAI,GAAI,GAAI,IAAK,IAAK,IAAK,IAAM,IAAM,IAAK,CAoBhG,SAAgB,EAAuB,EAAuB,CAC5D,GAAI,GAAW,EAAG,OAClB,GAAI,EACF,GAAI,CACF,QAAQ,KAAK,EAAW,EAAG,EAAG,EAAQ,CACtC,YACM,EAIV,IAAM,EAAM,KAAK,KAAK,CAAG,EACzB,KAAO,KAAK,KAAK,CAAG,KAatB,SAAgB,EAA2B,EAAyB,CAClE,IAAM,EAAQ,GAA6C,KAC3D,OAAO,OAAO,GAAS,UAAY,EAAkC,IAAI,EAAK,CAGhF,SAAgB,EAAmC,EAAoB,EAAkC,EAAE,CAAK,CAC9G,IAAM,EAAgB,EAAQ,eAAiB,EACzC,EAAO,EAAQ,MAAQ,EAC7B,IAAK,IAAI,EAAU,GAAK,IACtB,GAAI,CACF,OAAO,GAAW,OACX,EAAO,CACd,IAAM,EAAU,EAAc,GAC9B,GAAI,IAAY,IAAA,IAAa,CAAC,EAA2B,EAAM,CAAE,MAAM,EACvE,EAAK,EAAQ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";function t(e){if(e<=0)return;if(a)try{Atomics.wait(a,0,0,e);return}catch{}let t=Date.now()+e;for(;Date.now()<t;);}function n(e){let t=e?.code;return typeof t==`string`&&o.has(t)}function r(e,r={}){let i=r.retryDelaysMs??s,a=r.wait??t;for(let t=0;;t++)try{return e()}catch(e){let r=i[t];if(r===void 0||!n(e))throw e;a(r)}}var i,a,o,s,c=e((()=>{i=typeof SharedArrayBuffer<`u`?new SharedArrayBuffer(4):void 0,a=i?new Int32Array(i):void 0,o=new Set([`EACCES`,`EBUSY`,`EPERM`]),s=[10,25,50,100,200,500,1e3,2e3,4e3]}));c();export{s as DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS,c as init_fileSystemRetry,r as runFileSystemOperationWithRetry,t as waitForFileSystemRetry};
|
|
2
|
+
//# sourceMappingURL=fileSystemRetry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileSystemRetry.mjs","names":[],"sources":["../../src/shared/fileSystemRetry.ts"],"sourcesContent":["/**\n * Retry helpers for transient filesystem failures.\n *\n * Windows holds brief directory and rename locks (antivirus, indexers, another\n * process reading the file), surfacing as EACCES, EBUSY or EPERM. Those clear on\n * their own, so the operation is retried on a backoff instead of failing a run.\n *\n * DESIGN PATTERNS:\n * - Two variants with the same policy: a synchronous one for the atomic writers\n * that must not yield mid-write, and an async one for everything else\n *\n * AVOID:\n * - Reaching for the synchronous variant on a hot path; it blocks the event\n * loop, and every millisecond of backoff is a millisecond the parent cannot\n * service a child. Prefer `runFileSystemOperationWithRetryAsync`\n * - Retrying ENOENT or ENOTDIR; those are real errors, not contention\n */\n\nconst WAIT_BUFFER = typeof SharedArrayBuffer !== 'undefined' ? new SharedArrayBuffer(4) : undefined;\nconst WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : undefined;\nconst RETRYABLE_FILE_SYSTEM_ERROR_CODES = new Set(['EACCES', 'EBUSY', 'EPERM']);\n\nexport const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 500, 1000, 2000, 4000] as const;\n\nexport type FileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => void;\n};\n\nexport type AsyncFileSystemRetryOptions = {\n retryDelaysMs?: readonly number[];\n wait?: (delayMs: number) => Promise<void>;\n};\n\n/**\n * Block the current thread for `delayMs`.\n *\n * Only for synchronous callers that cannot yield. `Atomics.wait` parks the\n * thread rather than burning CPU; the loop below is a portable fallback for\n * runtimes without SharedArrayBuffer and does spin, which is why async callers\n * must use `delayForFileSystemRetry` instead.\n */\nexport function waitForFileSystemRetry(delayMs: number): void {\n if (delayMs <= 0) return;\n if (WAIT_VIEW) {\n try {\n Atomics.wait(WAIT_VIEW, 0, 0, delayMs);\n return;\n } catch {\n // Fall through to the portable busy wait below.\n }\n }\n const end = Date.now() + delayMs;\n while (Date.now() < end) {\n // Portable fallback for runtimes where Atomics.wait is unavailable.\n }\n}\n\n/** Yield for `delayMs` without blocking the event loop. */\nexport function delayForFileSystemRetry(delayMs: number): Promise<void> {\n if (delayMs <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeout(resolve, delayMs);\n });\n}\n\nexport function isRetryableFileSystemError(error: unknown): boolean {\n const code = (error as NodeJS.ErrnoException | undefined)?.code;\n return typeof code === 'string' && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);\n}\n\nexport function runFileSystemOperationWithRetry<T>(operation: () => T, options: FileSystemRetryOptions = {}): T {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? waitForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n wait(delayMs);\n }\n }\n}\n\nexport async function runFileSystemOperationWithRetryAsync<T>(\n operation: () => T | Promise<T>,\n options: AsyncFileSystemRetryOptions = {},\n): Promise<T> {\n const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;\n const wait = options.wait ?? delayForFileSystemRetry;\n for (let attempt = 0; ; attempt++) {\n try {\n return await operation();\n } catch (error) {\n const delayMs = retryDelaysMs[attempt];\n if (delayMs === undefined || !isRetryableFileSystemError(error)) throw error;\n await wait(delayMs);\n }\n }\n}\n"],"mappings":"6DA0CA,SAAgB,EAAuB,EAAuB,CAC5D,GAAI,GAAW,EAAG,OAClB,GAAI,EACF,GAAI,CACF,QAAQ,KAAK,EAAW,EAAG,EAAG,EAAQ,CACtC,YACM,EAIV,IAAM,EAAM,KAAK,KAAK,CAAG,EACzB,KAAO,KAAK,KAAK,CAAG,KAatB,SAAgB,EAA2B,EAAyB,CAClE,IAAM,EAAQ,GAA6C,KAC3D,OAAO,OAAO,GAAS,UAAY,EAAkC,IAAI,EAAK,CAGhF,SAAgB,EAAmC,EAAoB,EAAkC,EAAE,CAAK,CAC9G,IAAM,EAAgB,EAAQ,eAAiB,EACzC,EAAO,EAAQ,MAAQ,EAC7B,IAAK,IAAI,EAAU,GAAK,IACtB,GAAI,CACF,OAAO,GAAW,OACX,EAAO,CACd,IAAM,EAAU,EAAc,GAC9B,GAAI,IAAY,IAAA,IAAa,CAAC,EAA2B,EAAM,CAAE,MAAM,EACvE,EAAK,EAAQ,wBA9Db,EAAc,OAAO,kBAAsB,IAAc,IAAI,kBAAkB,EAAE,CAAG,IAAA,GACpF,EAAY,EAAc,IAAI,WAAW,EAAY,CAAG,IAAA,GACxD,EAAoC,IAAI,IAAI,CAAC,SAAU,QAAS,QAAQ,CAAC,CAElE,EAAsC,CAAC,GAAI,GAAI,GAAI,IAAK,IAAK,IAAK,IAAM,IAAM,IAAK"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`./modelInfo.cjs`),t=1e3,n=1e3,r=6e4;function i(e){return e<t?String(e):e<1e4?`${(e/t).toFixed(1)}k`:`${Math.round(e/t)}k`}function a(t,n){let r=t?e.splitKnownThinkingSuffix(t):void 0,i=r?.baseModel??t,a=e.THINKING_LEVELS.find(e=>e===n?.trim()),o=r?.thinkingSuffix?r.thinkingSuffix.slice(1):a;if(i){let e=i.lastIndexOf(`/`);e!==-1&&(i=i.slice(e+1))}return[i,o?`thinking ${o}`:void 0].filter(Boolean).join(` · `)}function o(e){return e<n?`${e}ms`:e<r?`${(e/n).toFixed(1)}s`:`${Math.floor(e/r)}m${Math.floor(e%r/n)}s`}function s(e){let t=process.env.HOME;return t&&e.startsWith(t)?`~${e.slice(t.length)}`:e}exports.formatDuration=o,exports.formatModelThinking=a,exports.formatTokens=i,exports.shortenPath=s;
|
|
2
|
+
//# sourceMappingURL=formatters.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.cjs","names":["splitKnownThinkingSuffix","THINKING_LEVELS"],"sources":["../../src/shared/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for display output.\n *\n * DESIGN PATTERNS:\n * - Every function here is presentation only. Nothing it returns is parsed back,\n * so rounding and truncation are free to favour readability\n * - Truncation limits are supplied per call site through the `expanded` flag so a\n * detail pane and a one-line status row share one implementation\n *\n * AVOID:\n * - Round-tripping these strings; `formatTokens` and `formatDuration` are lossy\n * - Widening the tool-call preview limits without checking the narrowest surface\n * that renders them\n */\n\nimport type { Usage } from '../types.ts';\nimport { splitKnownThinkingSuffix, THINKING_LEVELS } from './modelInfo.ts';\n\nconst TOKENS_PER_K = 1000;\nconst TOKENS_FRACTIONAL_K_LIMIT = 10000;\nconst MS_PER_SECOND = 1000;\nconst MS_PER_MINUTE = 60000;\nconst COST_DECIMAL_PLACES = 4;\nconst SECONDS_DECIMAL_PLACES = 1;\nconst K_DECIMAL_PLACES = 1;\nconst BASH_PREVIEW_LIMIT = 60;\nconst BASH_PREVIEW_LIMIT_EXPANDED = 240;\nconst ARGS_PREVIEW_LIMIT = 40;\nconst ARGS_PREVIEW_LIMIT_EXPANDED = 160;\nconst TRUNCATION_ELLIPSIS = '...';\nconst HOME_SHORTHAND = '~';\n\n/**\n * Format token count with k suffix for large numbers\n */\nexport function formatTokens(n: number): string {\n if (n < TOKENS_PER_K) return String(n);\n if (n < TOKENS_FRACTIONAL_K_LIMIT) return `${(n / TOKENS_PER_K).toFixed(K_DECIMAL_PLACES)}k`;\n return `${Math.round(n / TOKENS_PER_K)}k`;\n}\n\nexport function formatModelThinking(model?: string, thinking?: string): string {\n const parsed = model ? splitKnownThinkingSuffix(model) : undefined;\n let displayModel = parsed?.baseModel ?? model;\n const explicitThinking = THINKING_LEVELS.find((level) => level === thinking?.trim());\n // A suffix on the model string is the caller's most specific intent, so it wins\n // over the separately configured level.\n const displayThinking = parsed?.thinkingSuffix ? parsed.thinkingSuffix.slice(1) : explicitThinking;\n if (displayModel) {\n const slashIdx = displayModel.lastIndexOf('/');\n if (slashIdx !== -1) displayModel = displayModel.slice(slashIdx + 1);\n }\n return [displayModel, displayThinking ? `thinking ${displayThinking}` : undefined].filter(Boolean).join(' · ');\n}\n\n/**\n * Format usage statistics into a compact string\n */\nexport function formatUsage(u: Usage, model?: string): string {\n const parts: string[] = [];\n if (u.turns) parts.push(`${u.turns} turn${u.turns > 1 ? 's' : ''}`);\n if (u.input) parts.push(`in:${formatTokens(u.input)}`);\n if (u.output) parts.push(`out:${formatTokens(u.output)}`);\n if (u.cacheRead) parts.push(`R${formatTokens(u.cacheRead)}`);\n if (u.cacheWrite) parts.push(`W${formatTokens(u.cacheWrite)}`);\n if (u.cost) parts.push(`$${u.cost.toFixed(COST_DECIMAL_PLACES)}`);\n if (model) parts.push(model);\n return parts.join(' ');\n}\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms < MS_PER_SECOND) return `${ms}ms`;\n if (ms < MS_PER_MINUTE) return `${(ms / MS_PER_SECOND).toFixed(SECONDS_DECIMAL_PLACES)}s`;\n return `${Math.floor(ms / MS_PER_MINUTE)}m${Math.floor((ms % MS_PER_MINUTE) / MS_PER_SECOND)}s`;\n}\n\n/**\n * Format a tool call for display\n */\nexport function formatToolCall(name: string, args: Record<string, unknown>, expanded = false): string {\n switch (name) {\n case 'bash': {\n const command = typeof args.command === 'string' ? args.command : '';\n const maxLength = expanded ? BASH_PREVIEW_LIMIT_EXPANDED : BASH_PREVIEW_LIMIT;\n return `$ ${command.slice(0, maxLength)}${command.length > maxLength ? TRUNCATION_ELLIPSIS : ''}`;\n }\n case 'read':\n case 'write':\n case 'edit': {\n const target =\n typeof args.path === 'string' ? args.path : typeof args.file_path === 'string' ? args.file_path : '';\n return `${name} ${shortenPath(target)}`;\n }\n default: {\n const s = JSON.stringify(args);\n const maxLength = expanded ? ARGS_PREVIEW_LIMIT_EXPANDED : ARGS_PREVIEW_LIMIT;\n return `${name} ${s.slice(0, maxLength)}${s.length > maxLength ? TRUNCATION_ELLIPSIS : ''}`;\n }\n }\n}\n\n/**\n * Shorten a path by replacing home directory with ~\n */\nexport function shortenPath(p: string): string {\n const home = process.env.HOME;\n if (home && p.startsWith(home)) {\n return `${HOME_SHORTHAND}${p.slice(home.length)}`;\n }\n return p;\n}\n"],"mappings":"mCAkBM,EAAe,IAEf,EAAgB,IAChB,EAAgB,IActB,SAAgB,EAAa,EAAmB,CAG9C,OAFI,EAAI,EAAqB,OAAO,EAAE,CAClC,EAAI,IAAkC,IAAI,EAAI,GAAc,QAAQ,EAAiB,CAAC,GACnF,GAAG,KAAK,MAAM,EAAI,EAAa,CAAC,GAGzC,SAAgB,EAAoB,EAAgB,EAA2B,CAC7E,IAAM,EAAS,EAAQA,EAAAA,yBAAyB,EAAM,CAAG,IAAA,GACrD,EAAe,GAAQ,WAAa,EAClC,EAAmBC,EAAAA,gBAAgB,KAAM,GAAU,IAAU,GAAU,MAAM,CAAC,CAG9E,EAAkB,GAAQ,eAAiB,EAAO,eAAe,MAAM,EAAE,CAAG,EAClF,GAAI,EAAc,CAChB,IAAM,EAAW,EAAa,YAAY,IAAI,CAC1C,IAAa,KAAI,EAAe,EAAa,MAAM,EAAW,EAAE,EAEtE,MAAO,CAAC,EAAc,EAAkB,YAAY,IAAoB,IAAA,GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM,CAqBhH,SAAgB,EAAe,EAAoB,CAGjD,OAFI,EAAK,EAAsB,GAAG,EAAG,IACjC,EAAK,EAAsB,IAAI,EAAK,GAAe,QAAQ,EAAuB,CAAC,GAChF,GAAG,KAAK,MAAM,EAAK,EAAc,CAAC,GAAG,KAAK,MAAO,EAAK,EAAiB,EAAc,CAAC,GA+B/F,SAAgB,EAAY,EAAmB,CAC7C,IAAM,EAAO,QAAQ,IAAI,KAIzB,OAHI,GAAQ,EAAE,WAAW,EAAK,CACrB,IAAoB,EAAE,MAAM,EAAK,OAAO,GAE1C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{THINKING_LEVELS as t,init_modelInfo as n,splitKnownThinkingSuffix as r}from"./modelInfo.mjs";function i(e){return e<c?String(e):e<l?`${(e/c).toFixed(p)}k`:`${Math.round(e/c)}k`}function a(e,n){let i=e?r(e):void 0,a=i?.baseModel??e,o=t.find(e=>e===n?.trim()),s=i?.thinkingSuffix?i.thinkingSuffix.slice(1):o;if(a){let e=a.lastIndexOf(`/`);e!==-1&&(a=a.slice(e+1))}return[a,s?`thinking ${s}`:void 0].filter(Boolean).join(` · `)}function o(e){return e<u?`${e}ms`:e<d?`${(e/u).toFixed(f)}s`:`${Math.floor(e/d)}m${Math.floor(e%d/u)}s`}function s(e){let t=process.env.HOME;return t&&e.startsWith(t)?`${m}${e.slice(t.length)}`:e}var c,l,u,d,f,p,m,h=e((()=>{n(),c=1e3,l=1e4,u=1e3,d=6e4,f=1,p=1,m=`~`}));h();export{o as formatDuration,a as formatModelThinking,i as formatTokens,h as init_formatters,s as shortenPath};
|
|
2
|
+
//# sourceMappingURL=formatters.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.mjs","names":[],"sources":["../../src/shared/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for display output.\n *\n * DESIGN PATTERNS:\n * - Every function here is presentation only. Nothing it returns is parsed back,\n * so rounding and truncation are free to favour readability\n * - Truncation limits are supplied per call site through the `expanded` flag so a\n * detail pane and a one-line status row share one implementation\n *\n * AVOID:\n * - Round-tripping these strings; `formatTokens` and `formatDuration` are lossy\n * - Widening the tool-call preview limits without checking the narrowest surface\n * that renders them\n */\n\nimport type { Usage } from '../types.ts';\nimport { splitKnownThinkingSuffix, THINKING_LEVELS } from './modelInfo.ts';\n\nconst TOKENS_PER_K = 1000;\nconst TOKENS_FRACTIONAL_K_LIMIT = 10000;\nconst MS_PER_SECOND = 1000;\nconst MS_PER_MINUTE = 60000;\nconst COST_DECIMAL_PLACES = 4;\nconst SECONDS_DECIMAL_PLACES = 1;\nconst K_DECIMAL_PLACES = 1;\nconst BASH_PREVIEW_LIMIT = 60;\nconst BASH_PREVIEW_LIMIT_EXPANDED = 240;\nconst ARGS_PREVIEW_LIMIT = 40;\nconst ARGS_PREVIEW_LIMIT_EXPANDED = 160;\nconst TRUNCATION_ELLIPSIS = '...';\nconst HOME_SHORTHAND = '~';\n\n/**\n * Format token count with k suffix for large numbers\n */\nexport function formatTokens(n: number): string {\n if (n < TOKENS_PER_K) return String(n);\n if (n < TOKENS_FRACTIONAL_K_LIMIT) return `${(n / TOKENS_PER_K).toFixed(K_DECIMAL_PLACES)}k`;\n return `${Math.round(n / TOKENS_PER_K)}k`;\n}\n\nexport function formatModelThinking(model?: string, thinking?: string): string {\n const parsed = model ? splitKnownThinkingSuffix(model) : undefined;\n let displayModel = parsed?.baseModel ?? model;\n const explicitThinking = THINKING_LEVELS.find((level) => level === thinking?.trim());\n // A suffix on the model string is the caller's most specific intent, so it wins\n // over the separately configured level.\n const displayThinking = parsed?.thinkingSuffix ? parsed.thinkingSuffix.slice(1) : explicitThinking;\n if (displayModel) {\n const slashIdx = displayModel.lastIndexOf('/');\n if (slashIdx !== -1) displayModel = displayModel.slice(slashIdx + 1);\n }\n return [displayModel, displayThinking ? `thinking ${displayThinking}` : undefined].filter(Boolean).join(' · ');\n}\n\n/**\n * Format usage statistics into a compact string\n */\nexport function formatUsage(u: Usage, model?: string): string {\n const parts: string[] = [];\n if (u.turns) parts.push(`${u.turns} turn${u.turns > 1 ? 's' : ''}`);\n if (u.input) parts.push(`in:${formatTokens(u.input)}`);\n if (u.output) parts.push(`out:${formatTokens(u.output)}`);\n if (u.cacheRead) parts.push(`R${formatTokens(u.cacheRead)}`);\n if (u.cacheWrite) parts.push(`W${formatTokens(u.cacheWrite)}`);\n if (u.cost) parts.push(`$${u.cost.toFixed(COST_DECIMAL_PLACES)}`);\n if (model) parts.push(model);\n return parts.join(' ');\n}\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms < MS_PER_SECOND) return `${ms}ms`;\n if (ms < MS_PER_MINUTE) return `${(ms / MS_PER_SECOND).toFixed(SECONDS_DECIMAL_PLACES)}s`;\n return `${Math.floor(ms / MS_PER_MINUTE)}m${Math.floor((ms % MS_PER_MINUTE) / MS_PER_SECOND)}s`;\n}\n\n/**\n * Format a tool call for display\n */\nexport function formatToolCall(name: string, args: Record<string, unknown>, expanded = false): string {\n switch (name) {\n case 'bash': {\n const command = typeof args.command === 'string' ? args.command : '';\n const maxLength = expanded ? BASH_PREVIEW_LIMIT_EXPANDED : BASH_PREVIEW_LIMIT;\n return `$ ${command.slice(0, maxLength)}${command.length > maxLength ? TRUNCATION_ELLIPSIS : ''}`;\n }\n case 'read':\n case 'write':\n case 'edit': {\n const target =\n typeof args.path === 'string' ? args.path : typeof args.file_path === 'string' ? args.file_path : '';\n return `${name} ${shortenPath(target)}`;\n }\n default: {\n const s = JSON.stringify(args);\n const maxLength = expanded ? ARGS_PREVIEW_LIMIT_EXPANDED : ARGS_PREVIEW_LIMIT;\n return `${name} ${s.slice(0, maxLength)}${s.length > maxLength ? TRUNCATION_ELLIPSIS : ''}`;\n }\n }\n}\n\n/**\n * Shorten a path by replacing home directory with ~\n */\nexport function shortenPath(p: string): string {\n const home = process.env.HOME;\n if (home && p.startsWith(home)) {\n return `${HOME_SHORTHAND}${p.slice(home.length)}`;\n }\n return p;\n}\n"],"mappings":"iKAmCA,SAAgB,EAAa,EAAmB,CAG9C,OAFI,EAAI,EAAqB,OAAO,EAAE,CAClC,EAAI,EAAkC,IAAI,EAAI,GAAc,QAAQ,EAAiB,CAAC,GACnF,GAAG,KAAK,MAAM,EAAI,EAAa,CAAC,GAGzC,SAAgB,EAAoB,EAAgB,EAA2B,CAC7E,IAAM,EAAS,EAAQ,EAAyB,EAAM,CAAG,IAAA,GACrD,EAAe,GAAQ,WAAa,EAClC,EAAmB,EAAgB,KAAM,GAAU,IAAU,GAAU,MAAM,CAAC,CAG9E,EAAkB,GAAQ,eAAiB,EAAO,eAAe,MAAM,EAAE,CAAG,EAClF,GAAI,EAAc,CAChB,IAAM,EAAW,EAAa,YAAY,IAAI,CAC1C,IAAa,KAAI,EAAe,EAAa,MAAM,EAAW,EAAE,EAEtE,MAAO,CAAC,EAAc,EAAkB,YAAY,IAAoB,IAAA,GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM,CAqBhH,SAAgB,EAAe,EAAoB,CAGjD,OAFI,EAAK,EAAsB,GAAG,EAAG,IACjC,EAAK,EAAsB,IAAI,EAAK,GAAe,QAAQ,EAAuB,CAAC,GAChF,GAAG,KAAK,MAAM,EAAK,EAAc,CAAC,GAAG,KAAK,MAAO,EAAK,EAAiB,EAAc,CAAC,GA+B/F,SAAgB,EAAY,EAAmB,CAC7C,IAAM,EAAO,QAAQ,IAAI,KAIzB,OAHI,GAAQ,EAAE,WAAW,EAAK,CACrB,GAAG,IAAiB,EAAE,MAAM,EAAK,OAAO,GAE1C,iCAhGkE,CAErE,EAAe,IACf,EAA4B,IAC5B,EAAgB,IAChB,EAAgB,IAEhB,EAAyB,EACzB,EAAmB,EAMnB,EAAiB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class{entries=new Map;capacity;constructor(e){if(!Number.isInteger(e)||e<1)throw Error(`LruCache capacity must be an integer of at least 1.`);this.capacity=e}get size(){return this.entries.size}has(e){return this.entries.has(e)}get(e){if(!this.entries.has(e))return;let t=this.entries.get(e);return this.entries.delete(e),this.entries.set(e,t),t}set(e,t){for(this.entries.has(e)&&this.entries.delete(e),this.entries.set(e,t);this.entries.size>this.capacity;){let e=this.entries.keys().next();if(e.done===!0)break;this.entries.delete(e.value)}}delete(e){return this.entries.delete(e)}clear(){this.entries.clear()}keysFromOldest(){return[...this.entries.keys()]}};exports.LruCache=e;
|
|
2
|
+
//# sourceMappingURL=lruCache.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lruCache.cjs","names":[],"sources":["../../src/shared/lruCache.ts"],"sourcesContent":["/**\n * LruCache - a bounded cache with least-recently-used eviction.\n *\n * DESIGN PATTERNS:\n * - Backed by a Map, which iterates in insertion order. Re-inserting a key on\n * read moves it to the end, so the first key is always the least recently used\n *\n * WHY LRU AND NOT FIFO:\n * The predecessor package evicted in insertion order at a cap of 50. Once more\n * than 50 runs were live it evicted the entry it was about to read next, so the\n * cache degenerated to a permanent miss while still paying for the stat that\n * populated it. Recency-based eviction keeps the hot entries.\n *\n * AVOID:\n * - Reading through `map.get` directly anywhere; that skips the recency update\n */\n\nconst MINIMUM_CAPACITY = 1;\n\nexport class LruCache<K, V> {\n private readonly entries = new Map<K, V>();\n private readonly capacity: number;\n\n constructor(capacity: number) {\n if (!Number.isInteger(capacity) || capacity < MINIMUM_CAPACITY) {\n throw new Error(`LruCache capacity must be an integer of at least ${MINIMUM_CAPACITY}.`);\n }\n this.capacity = capacity;\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n has(key: K): boolean {\n return this.entries.has(key);\n }\n\n /** Read a value, marking it most recently used. */\n get(key: K): V | undefined {\n if (!this.entries.has(key)) return undefined;\n const value = this.entries.get(key) as V;\n this.entries.delete(key);\n this.entries.set(key, value);\n return value;\n }\n\n /** Insert or replace a value, marking it most recently used. */\n set(key: K, value: V): void {\n if (this.entries.has(key)) this.entries.delete(key);\n this.entries.set(key, value);\n while (this.entries.size > this.capacity) {\n const oldest = this.entries.keys().next();\n if (oldest.done === true) break;\n this.entries.delete(oldest.value);\n }\n }\n\n delete(key: K): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n /** Keys from least to most recently used. Intended for tests and diagnostics. */\n keysFromOldest(): K[] {\n return [...this.entries.keys()];\n }\n}\n"],"mappings":"AAmBA,IAAa,EAAb,KAA4B,CAC1B,QAA2B,IAAI,IAC/B,SAEA,YAAY,EAAkB,CAC5B,GAAI,CAAC,OAAO,UAAU,EAAS,EAAI,EAAW,EAC5C,MAAU,MAAM,sDAAwE,CAE1F,KAAK,SAAW,EAGlB,IAAI,MAAe,CACjB,OAAO,KAAK,QAAQ,KAGtB,IAAI,EAAiB,CACnB,OAAO,KAAK,QAAQ,IAAI,EAAI,CAI9B,IAAI,EAAuB,CACzB,GAAI,CAAC,KAAK,QAAQ,IAAI,EAAI,CAAE,OAC5B,IAAM,EAAQ,KAAK,QAAQ,IAAI,EAAI,CAGnC,OAFA,KAAK,QAAQ,OAAO,EAAI,CACxB,KAAK,QAAQ,IAAI,EAAK,EAAM,CACrB,EAIT,IAAI,EAAQ,EAAgB,CAG1B,IAFI,KAAK,QAAQ,IAAI,EAAI,EAAE,KAAK,QAAQ,OAAO,EAAI,CACnD,KAAK,QAAQ,IAAI,EAAK,EAAM,CACrB,KAAK,QAAQ,KAAO,KAAK,UAAU,CACxC,IAAM,EAAS,KAAK,QAAQ,MAAM,CAAC,MAAM,CACzC,GAAI,EAAO,OAAS,GAAM,MAC1B,KAAK,QAAQ,OAAO,EAAO,MAAM,EAIrC,OAAO,EAAiB,CACtB,OAAO,KAAK,QAAQ,OAAO,EAAI,CAGjC,OAAc,CACZ,KAAK,QAAQ,OAAO,CAItB,gBAAsB,CACpB,MAAO,CAAC,GAAG,KAAK,QAAQ,MAAM,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";var t,n,r=e((()=>{t=1,n=class{entries=new Map;capacity;constructor(e){if(!Number.isInteger(e)||e<t)throw Error(`LruCache capacity must be an integer of at least ${t}.`);this.capacity=e}get size(){return this.entries.size}has(e){return this.entries.has(e)}get(e){if(!this.entries.has(e))return;let t=this.entries.get(e);return this.entries.delete(e),this.entries.set(e,t),t}set(e,t){for(this.entries.has(e)&&this.entries.delete(e),this.entries.set(e,t);this.entries.size>this.capacity;){let e=this.entries.keys().next();if(e.done===!0)break;this.entries.delete(e.value)}}delete(e){return this.entries.delete(e)}clear(){this.entries.clear()}keysFromOldest(){return[...this.entries.keys()]}}}));r();export{n as LruCache,r as init_lruCache};
|
|
2
|
+
//# sourceMappingURL=lruCache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lruCache.mjs","names":[],"sources":["../../src/shared/lruCache.ts"],"sourcesContent":["/**\n * LruCache - a bounded cache with least-recently-used eviction.\n *\n * DESIGN PATTERNS:\n * - Backed by a Map, which iterates in insertion order. Re-inserting a key on\n * read moves it to the end, so the first key is always the least recently used\n *\n * WHY LRU AND NOT FIFO:\n * The predecessor package evicted in insertion order at a cap of 50. Once more\n * than 50 runs were live it evicted the entry it was about to read next, so the\n * cache degenerated to a permanent miss while still paying for the stat that\n * populated it. Recency-based eviction keeps the hot entries.\n *\n * AVOID:\n * - Reading through `map.get` directly anywhere; that skips the recency update\n */\n\nconst MINIMUM_CAPACITY = 1;\n\nexport class LruCache<K, V> {\n private readonly entries = new Map<K, V>();\n private readonly capacity: number;\n\n constructor(capacity: number) {\n if (!Number.isInteger(capacity) || capacity < MINIMUM_CAPACITY) {\n throw new Error(`LruCache capacity must be an integer of at least ${MINIMUM_CAPACITY}.`);\n }\n this.capacity = capacity;\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n has(key: K): boolean {\n return this.entries.has(key);\n }\n\n /** Read a value, marking it most recently used. */\n get(key: K): V | undefined {\n if (!this.entries.has(key)) return undefined;\n const value = this.entries.get(key) as V;\n this.entries.delete(key);\n this.entries.set(key, value);\n return value;\n }\n\n /** Insert or replace a value, marking it most recently used. */\n set(key: K, value: V): void {\n if (this.entries.has(key)) this.entries.delete(key);\n this.entries.set(key, value);\n while (this.entries.size > this.capacity) {\n const oldest = this.entries.keys().next();\n if (oldest.done === true) break;\n this.entries.delete(oldest.value);\n }\n }\n\n delete(key: K): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n }\n\n /** Keys from least to most recently used. Intended for tests and diagnostics. */\n keysFromOldest(): K[] {\n return [...this.entries.keys()];\n }\n}\n"],"mappings":"+EAiBM,EAAmB,EAEZ,EAAb,KAA4B,CAC1B,QAA2B,IAAI,IAC/B,SAEA,YAAY,EAAkB,CAC5B,GAAI,CAAC,OAAO,UAAU,EAAS,EAAI,EAAW,EAC5C,MAAU,MAAM,oDAAoD,EAAiB,GAAG,CAE1F,KAAK,SAAW,EAGlB,IAAI,MAAe,CACjB,OAAO,KAAK,QAAQ,KAGtB,IAAI,EAAiB,CACnB,OAAO,KAAK,QAAQ,IAAI,EAAI,CAI9B,IAAI,EAAuB,CACzB,GAAI,CAAC,KAAK,QAAQ,IAAI,EAAI,CAAE,OAC5B,IAAM,EAAQ,KAAK,QAAQ,IAAI,EAAI,CAGnC,OAFA,KAAK,QAAQ,OAAO,EAAI,CACxB,KAAK,QAAQ,IAAI,EAAK,EAAM,CACrB,EAIT,IAAI,EAAQ,EAAgB,CAG1B,IAFI,KAAK,QAAQ,IAAI,EAAI,EAAE,KAAK,QAAQ,OAAO,EAAI,CACnD,KAAK,QAAQ,IAAI,EAAK,EAAM,CACrB,KAAK,QAAQ,KAAO,KAAK,UAAU,CACxC,IAAM,EAAS,KAAK,QAAQ,MAAM,CAAC,MAAM,CACzC,GAAI,EAAO,OAAS,GAAM,MAC1B,KAAK,QAAQ,OAAO,EAAO,MAAM,EAIrC,OAAO,EAAiB,CACtB,OAAO,KAAK,QAAQ,OAAO,EAAI,CAGjC,OAAc,CACZ,KAAK,QAAQ,OAAO,CAItB,gBAAsB,CACpB,MAAO,CAAC,GAAG,KAAK,QAAQ,MAAM,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`];function t(t){let n=t.lastIndexOf(`:`);if(n===-1)return{baseModel:t,thinkingSuffix:``};let r=e.find(e=>e===t.substring(n+1));return r?{baseModel:t.substring(0,n),thinkingSuffix:`:${r}`}:{baseModel:t,thinkingSuffix:``}}exports.THINKING_LEVELS=e,exports.splitKnownThinkingSuffix=t;
|
|
2
|
+
//# sourceMappingURL=modelInfo.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelInfo.cjs","names":[],"sources":["../../src/shared/modelInfo.ts"],"sourcesContent":["/**\n * Model identity and thinking-level resolution.\n *\n * DESIGN PATTERNS:\n * - A model string may carry a thinking level as a `:level` suffix. Only suffixes\n * in THINKING_LEVELS are recognised, so a model id that legitimately contains a\n * colon (`openrouter/vendor:free`) is never mistaken for a thinking request\n * - `max` is excluded from the default supported set because a model only offers\n * it when its thinkingLevelMap says so explicitly\n *\n * AVOID:\n * - Splitting a model string on the first colon; the suffix is at the end\n * - Assuming a missing thinkingLevelMap means \"no thinking\"; it means \"unknown\",\n * which is treated as the standard set\n */\n\nexport const THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport type ThinkingLevel = (typeof THINKING_LEVELS)[number];\nexport type ThinkingLevelMap = Partial<Record<ThinkingLevel, string | null>>;\n\nconst THINKING_SUFFIX_SEPARATOR = ':';\nconst PROVIDER_SEPARATOR = '/';\n\nexport interface ModelInfo {\n provider: string;\n id: string;\n fullId: string;\n api?: string;\n reasoning?: boolean;\n thinkingLevelMap?: ThinkingLevelMap;\n}\n\ninterface RegistryModelLike {\n provider: string;\n id: string;\n api?: string;\n reasoning?: boolean;\n thinkingLevelMap?: ThinkingLevelMap;\n}\n\nexport function toModelInfo(model: RegistryModelLike): ModelInfo {\n return {\n provider: model.provider,\n id: model.id,\n fullId: `${model.provider}${PROVIDER_SEPARATOR}${model.id}`,\n api: model.api,\n reasoning: model.reasoning,\n thinkingLevelMap: model.thinkingLevelMap,\n };\n}\n\n/** Resolve the effective thinking level from a model string (which may contain a known suffix like `:high`)\n * and an explicit thinking config value. Returns `undefined` when no thinking is applicable\n * (e.g. no model was specified, or the model has no suffix and no config was provided). */\nexport function resolveEffectiveThinking(\n model: string | undefined,\n configThinking: string | false | undefined,\n): string | undefined {\n if (!model) return undefined;\n const { thinkingSuffix } = splitKnownThinkingSuffix(model);\n if (thinkingSuffix) return thinkingSuffix.slice(1);\n return THINKING_LEVELS.find((level) => level === configThinking);\n}\n\nexport function splitKnownThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {\n const colonIdx = model.lastIndexOf(THINKING_SUFFIX_SEPARATOR);\n if (colonIdx === -1) return { baseModel: model, thinkingSuffix: '' };\n const suffix = THINKING_LEVELS.find((level) => level === model.substring(colonIdx + 1));\n if (!suffix) return { baseModel: model, thinkingSuffix: '' };\n return {\n baseModel: model.substring(0, colonIdx),\n thinkingSuffix: `${THINKING_SUFFIX_SEPARATOR}${suffix}`,\n };\n}\n\nexport function findModelInfo(\n model: string | undefined,\n availableModels: ModelInfo[] | undefined,\n preferredProvider?: string,\n): ModelInfo | undefined {\n if (!model || !availableModels || availableModels.length === 0) return undefined;\n const { baseModel } = splitKnownThinkingSuffix(model);\n const exact = availableModels.find((entry) => entry.fullId === baseModel);\n if (exact) return exact;\n\n // A bare id can be ambiguous across providers. Prefer the caller's provider,\n // and otherwise resolve only when exactly one provider offers the id.\n const matches = availableModels.filter((entry) => entry.id === baseModel);\n if (preferredProvider) {\n const preferred = matches.find((entry) => entry.provider === preferredProvider);\n if (preferred) return preferred;\n }\n return matches.length === 1 ? matches[0] : undefined;\n}\n\nexport function getSupportedThinkingLevels(model: ModelInfo | undefined): ThinkingLevel[] {\n if (!model) return THINKING_LEVELS.filter((level) => level !== 'max');\n if (model.reasoning === false) return ['off'];\n\n if (!model.thinkingLevelMap) return THINKING_LEVELS.filter((level) => level !== 'max');\n\n const levels = THINKING_LEVELS.filter((level) => {\n const mapped = model.thinkingLevelMap?.[level];\n if (mapped === null) return false;\n // The extended levels are opt-in: they count as supported only when mapped.\n if (level === 'xhigh' || level === 'max') return mapped !== undefined;\n return true;\n });\n return levels;\n}\n"],"mappings":"AAgBA,MAAa,EAAkB,CAAC,MAAO,UAAW,MAAO,SAAU,OAAQ,QAAS,MAAM,CAgD1F,SAAgB,EAAyB,EAA8D,CACrG,IAAM,EAAW,EAAM,YAAY,IAA0B,CAC7D,GAAI,IAAa,GAAI,MAAO,CAAE,UAAW,EAAO,eAAgB,GAAI,CACpE,IAAM,EAAS,EAAgB,KAAM,GAAU,IAAU,EAAM,UAAU,EAAW,EAAE,CAAC,CAEvF,OADK,EACE,CACL,UAAW,EAAM,UAAU,EAAG,EAAS,CACvC,eAAgB,IAA+B,IAChD,CAJmB,CAAE,UAAW,EAAO,eAAgB,GAAI"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";function t(e){let t=e.lastIndexOf(r);if(t===-1)return{baseModel:e,thinkingSuffix:``};let i=n.find(n=>n===e.substring(t+1));return i?{baseModel:e.substring(0,t),thinkingSuffix:`${r}${i}`}:{baseModel:e,thinkingSuffix:``}}var n,r,i=e((()=>{n=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],r=`:`}));i();export{n as THINKING_LEVELS,i as init_modelInfo,t as splitKnownThinkingSuffix};
|
|
2
|
+
//# sourceMappingURL=modelInfo.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelInfo.mjs","names":[],"sources":["../../src/shared/modelInfo.ts"],"sourcesContent":["/**\n * Model identity and thinking-level resolution.\n *\n * DESIGN PATTERNS:\n * - A model string may carry a thinking level as a `:level` suffix. Only suffixes\n * in THINKING_LEVELS are recognised, so a model id that legitimately contains a\n * colon (`openrouter/vendor:free`) is never mistaken for a thinking request\n * - `max` is excluded from the default supported set because a model only offers\n * it when its thinkingLevelMap says so explicitly\n *\n * AVOID:\n * - Splitting a model string on the first colon; the suffix is at the end\n * - Assuming a missing thinkingLevelMap means \"no thinking\"; it means \"unknown\",\n * which is treated as the standard set\n */\n\nexport const THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport type ThinkingLevel = (typeof THINKING_LEVELS)[number];\nexport type ThinkingLevelMap = Partial<Record<ThinkingLevel, string | null>>;\n\nconst THINKING_SUFFIX_SEPARATOR = ':';\nconst PROVIDER_SEPARATOR = '/';\n\nexport interface ModelInfo {\n provider: string;\n id: string;\n fullId: string;\n api?: string;\n reasoning?: boolean;\n thinkingLevelMap?: ThinkingLevelMap;\n}\n\ninterface RegistryModelLike {\n provider: string;\n id: string;\n api?: string;\n reasoning?: boolean;\n thinkingLevelMap?: ThinkingLevelMap;\n}\n\nexport function toModelInfo(model: RegistryModelLike): ModelInfo {\n return {\n provider: model.provider,\n id: model.id,\n fullId: `${model.provider}${PROVIDER_SEPARATOR}${model.id}`,\n api: model.api,\n reasoning: model.reasoning,\n thinkingLevelMap: model.thinkingLevelMap,\n };\n}\n\n/** Resolve the effective thinking level from a model string (which may contain a known suffix like `:high`)\n * and an explicit thinking config value. Returns `undefined` when no thinking is applicable\n * (e.g. no model was specified, or the model has no suffix and no config was provided). */\nexport function resolveEffectiveThinking(\n model: string | undefined,\n configThinking: string | false | undefined,\n): string | undefined {\n if (!model) return undefined;\n const { thinkingSuffix } = splitKnownThinkingSuffix(model);\n if (thinkingSuffix) return thinkingSuffix.slice(1);\n return THINKING_LEVELS.find((level) => level === configThinking);\n}\n\nexport function splitKnownThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {\n const colonIdx = model.lastIndexOf(THINKING_SUFFIX_SEPARATOR);\n if (colonIdx === -1) return { baseModel: model, thinkingSuffix: '' };\n const suffix = THINKING_LEVELS.find((level) => level === model.substring(colonIdx + 1));\n if (!suffix) return { baseModel: model, thinkingSuffix: '' };\n return {\n baseModel: model.substring(0, colonIdx),\n thinkingSuffix: `${THINKING_SUFFIX_SEPARATOR}${suffix}`,\n };\n}\n\nexport function findModelInfo(\n model: string | undefined,\n availableModels: ModelInfo[] | undefined,\n preferredProvider?: string,\n): ModelInfo | undefined {\n if (!model || !availableModels || availableModels.length === 0) return undefined;\n const { baseModel } = splitKnownThinkingSuffix(model);\n const exact = availableModels.find((entry) => entry.fullId === baseModel);\n if (exact) return exact;\n\n // A bare id can be ambiguous across providers. Prefer the caller's provider,\n // and otherwise resolve only when exactly one provider offers the id.\n const matches = availableModels.filter((entry) => entry.id === baseModel);\n if (preferredProvider) {\n const preferred = matches.find((entry) => entry.provider === preferredProvider);\n if (preferred) return preferred;\n }\n return matches.length === 1 ? matches[0] : undefined;\n}\n\nexport function getSupportedThinkingLevels(model: ModelInfo | undefined): ThinkingLevel[] {\n if (!model) return THINKING_LEVELS.filter((level) => level !== 'max');\n if (model.reasoning === false) return ['off'];\n\n if (!model.thinkingLevelMap) return THINKING_LEVELS.filter((level) => level !== 'max');\n\n const levels = THINKING_LEVELS.filter((level) => {\n const mapped = model.thinkingLevelMap?.[level];\n if (mapped === null) return false;\n // The extended levels are opt-in: they count as supported only when mapped.\n if (level === 'xhigh' || level === 'max') return mapped !== undefined;\n return true;\n });\n return levels;\n}\n"],"mappings":"6DAgEA,SAAgB,EAAyB,EAA8D,CACrG,IAAM,EAAW,EAAM,YAAY,EAA0B,CAC7D,GAAI,IAAa,GAAI,MAAO,CAAE,UAAW,EAAO,eAAgB,GAAI,CACpE,IAAM,EAAS,EAAgB,KAAM,GAAU,IAAU,EAAM,UAAU,EAAW,EAAE,CAAC,CAEvF,OADK,EACE,CACL,UAAW,EAAM,UAAU,EAAG,EAAS,CACvC,eAAgB,GAAG,IAA4B,IAChD,CAJmB,CAAE,UAAW,EAAO,eAAgB,GAAI,mBApDjD,EAAkB,CAAC,MAAO,UAAW,MAAO,SAAU,OAAQ,QAAS,MAAM,CAIpF,EAA4B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`./modelInfo.cjs`);function t(e){return e.getAvailable().filter(t=>e.hasConfiguredAuth(t)).map(e=>({provider:e.provider,id:e.id,fullId:`${e.provider}/${e.id}`}))}const n=[`:`,`.`],r=/^(.*)-(\d{4})-(\d{2})-(\d{2})$/,i=/^(.*)-(\d{4})(\d{2})(\d{2})$/;function a(e){return e.toLowerCase().replace(/[._]+/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``)}function o(e,t,n){let r=Number(e),i=Number(t),a=Number(n);return r>=1900&&r<=2099&&i>=1&&i<=12&&a>=1&&a<=31}function s(e){let t=r.exec(e);if(t&&o(t[2],t[3],t[4]))return t[1];let n=i.exec(e);return n&&o(n[2],n[3],n[4])?n[1]:e}function c(e,t,n){if(e.includes(`/`)){let n=t.find(t=>t.fullId===e);if(n)return n.fullId}else{let r=t.filter(t=>t.id===e);if(n){let e=r.find(e=>e.provider===n);if(e)return e.fullId}if(r.length===1)return r[0].fullId}return l(e,t,n)}function l(e,t,r){let i,o=e,c=e.indexOf(`/`);if(c!==-1)i=a(e.slice(0,c)),o=e.slice(c+1);else for(let r of n){let n=e.indexOf(r);if(n<=0)continue;let s=a(e.slice(0,n));if(t.some(e=>a(e.provider)===s)){i=s,o=e.slice(n+1);break}}let l=a(o),u=s(l),d=t.filter(e=>{let t=a(e.id);return!(t!==l&&s(t)!==u||i!==void 0&&a(e.provider)!==i)});if(d.length!==0){if(r){let e=a(r),t=d.find(t=>a(t.provider)===e);if(t)return t.fullId}if(d.length===1)return d[0].fullId}}function u(t,n,r){if(!t)return;if(!n||n.length===0)return t;let i=c(t,n,r);if(i)return i;let{baseModel:a,thinkingSuffix:o}=e.splitKnownThinkingSuffix(t);if(!o)return t;let s=c(a,n,r);return s?`${s}${o}`:t}exports.authenticatedModelInfos=t,exports.fuzzyResolveModel=l,exports.normalizeModelSegment=a,exports.resolveModelCandidate=u;
|
|
2
|
+
//# sourceMappingURL=modelResolution.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelResolution.cjs","names":["splitKnownThinkingSuffix"],"sources":["../../src/shared/modelResolution.ts"],"sourcesContent":["/**\n * Resolving a loosely-spelled model name to a canonical `provider/id`.\n *\n * Users and agent frontmatter name models by hand, so the same string has to\n * resolve the same way wherever it is written. This is a leaf so every domain\n * can share one answer.\n *\n * DESIGN PATTERNS:\n * - Pure functions over a registry passed in, so model policy is testable with\n * no process, no config on disk and no network\n * - Exact matches win; fuzzy matching is only ever a fallback\n * - A qualified `provider/id` query never resolves across providers, and an\n * ambiguous match resolves to nothing rather than guessing. Picking the wrong\n * provider costs real money\n *\n * AVOID:\n * - Splitting a model string on the first colon to find a thinking suffix; ids\n * legitimately contain colons, which is why `splitKnownThinkingSuffix` exists\n * - Adding a second, looser resolver anywhere. Two spellings of \"the same\n * model\" that disagree are worse than one that is occasionally strict\n */\n\nimport { type ModelInfo as AvailableModelInfo, splitKnownThinkingSuffix } from './modelInfo.ts';\n\nexport type { AvailableModelInfo };\n\ninterface ModelRegistryLike<TModel extends { provider: string; id: string }> {\n getAvailable(): TModel[];\n hasConfiguredAuth(model: TModel): boolean;\n}\n\n/** Return only models the live host can authenticate, in registry order. */\nexport function authenticatedModelInfos<TModel extends { provider: string; id: string }>(\n registry: ModelRegistryLike<TModel>,\n): AvailableModelInfo[] {\n return registry\n .getAvailable()\n .filter((model) => registry.hasConfiguredAuth(model))\n .map((model) => ({ provider: model.provider, id: model.id, fullId: `${model.provider}/${model.id}` }));\n}\n\n/** Separates provider from id in a canonical model reference. */\nexport const PROVIDER_SEPARATOR = '/';\n\n/**\n * Separators an unqualified query may use in place of `/`. Only accepted when\n * the leading segment names a provider that actually exists, so a model id\n * containing a dot is not mistaken for `provider.id`.\n */\nconst ALTERNATE_PROVIDER_SEPARATORS = [':', '.'];\n\n/** Bounds for a trailing date stamp, wide enough to be permissive and narrow enough to reject version numbers. */\nconst MIN_DATE_STAMP_YEAR = 1900;\nconst MAX_DATE_STAMP_YEAR = 2099;\nconst MONTHS_PER_YEAR = 12;\nconst MAX_DAYS_PER_MONTH = 31;\n\n/** `-2025-10-01` and `-20251001` respectively, anchored to the end of a segment. */\nconst DASHED_DATE_STAMP = /^(.*)-(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst COMPACT_DATE_STAMP = /^(.*)-(\\d{4})(\\d{2})(\\d{2})$/;\n\n/**\n * Normalize a model id or provider segment for fuzzy comparison: case-fold,\n * treat dots/underscores as dashes (so `4.5` matches `4-5`), and collapse\n * repeated separators. Pure.\n */\nexport function normalizeModelSegment(segment: string): string {\n return segment.toLowerCase().replace(/[._]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\n}\n\nfunction isPlausibleDateStamp(year: string, month: string, day: string): boolean {\n const yyyy = Number(year);\n const mm = Number(month);\n const dd = Number(day);\n return (\n yyyy >= MIN_DATE_STAMP_YEAR &&\n yyyy <= MAX_DATE_STAMP_YEAR &&\n mm >= 1 &&\n mm <= MONTHS_PER_YEAR &&\n dd >= 1 &&\n dd <= MAX_DAYS_PER_MONTH\n );\n}\n\n/** Drop a trailing date stamp (`-20251001` or `-2025-10-01`) so dated and undated ids match. Pure. */\nfunction stripTrailingDateStamp(segment: string): string {\n const dashed = DASHED_DATE_STAMP.exec(segment);\n if (dashed && isPlausibleDateStamp(dashed[2], dashed[3], dashed[4])) return dashed[1];\n const compact = COMPACT_DATE_STAMP.exec(segment);\n if (compact && isPlausibleDateStamp(compact[2], compact[3], compact[4])) return compact[1];\n return segment;\n}\n\nfunction resolveBaseModelCandidate(\n baseModel: string,\n availableModels: AvailableModelInfo[],\n preferredProvider?: string,\n): string | undefined {\n if (baseModel.includes(PROVIDER_SEPARATOR)) {\n const exact = availableModels.find((entry) => entry.fullId === baseModel);\n if (exact) return exact.fullId;\n } else {\n const exactMatches = availableModels.filter((entry) => entry.id === baseModel);\n if (preferredProvider) {\n const preferredMatch = exactMatches.find((entry) => entry.provider === preferredProvider);\n if (preferredMatch) return preferredMatch.fullId;\n }\n if (exactMatches.length === 1) return exactMatches[0].fullId;\n }\n\n return fuzzyResolveModel(baseModel, availableModels, preferredProvider);\n}\n\n/**\n * Fuzzy-resolve a base model id (thinking suffix already stripped) against the\n * registry, tolerating separator, case, and optional date-stamp differences so\n * users do not have to spell provider/model exactly. A qualified `provider/id`\n * query only matches within the named provider, so this never silently switches\n * providers for security/cost-sensitive configs. Returns the matched `fullId`,\n * or `undefined` when there is no match or the match is ambiguous across\n * providers (and no `preferredProvider` disambiguates). Pure.\n */\nexport function fuzzyResolveModel(\n baseModel: string,\n availableModels: AvailableModelInfo[],\n preferredProvider?: string,\n): string | undefined {\n let queryProvider: string | undefined;\n let queryIdRaw = baseModel;\n const slashIdx = baseModel.indexOf(PROVIDER_SEPARATOR);\n if (slashIdx !== -1) {\n queryProvider = normalizeModelSegment(baseModel.slice(0, slashIdx));\n queryIdRaw = baseModel.slice(slashIdx + 1);\n } else {\n for (const separator of ALTERNATE_PROVIDER_SEPARATORS) {\n const separatorIdx = baseModel.indexOf(separator);\n if (separatorIdx <= 0) continue;\n const providerPart = normalizeModelSegment(baseModel.slice(0, separatorIdx));\n if (!availableModels.some((entry) => normalizeModelSegment(entry.provider) === providerPart)) continue;\n queryProvider = providerPart;\n queryIdRaw = baseModel.slice(separatorIdx + 1);\n break;\n }\n }\n const queryId = normalizeModelSegment(queryIdRaw);\n const queryIdNoDate = stripTrailingDateStamp(queryId);\n\n const candidates = availableModels.filter((entry) => {\n const entryId = normalizeModelSegment(entry.id);\n if (entryId !== queryId && stripTrailingDateStamp(entryId) !== queryIdNoDate) return false;\n if (queryProvider !== undefined && normalizeModelSegment(entry.provider) !== queryProvider) return false;\n return true;\n });\n if (candidates.length === 0) return undefined;\n if (preferredProvider) {\n const preferredProviderNorm = normalizeModelSegment(preferredProvider);\n const preferred = candidates.find((entry) => normalizeModelSegment(entry.provider) === preferredProviderNorm);\n if (preferred) return preferred.fullId;\n }\n if (candidates.length === 1) return candidates[0].fullId;\n return undefined;\n}\n\n/**\n * Resolve a possibly-loose model id to a canonical `provider/id` (plus any\n * thinking suffix). Exact registry matches win; fuzzy normalization\n * (separator/case/date-stamp via {@link fuzzyResolveModel}) is a fallback so\n * spelling differences still resolve. Never switches providers for a qualified\n * query. Pure.\n */\nexport function resolveModelCandidate(\n model: string | undefined,\n availableModels: AvailableModelInfo[] | undefined,\n preferredProvider?: string,\n): string | undefined {\n if (!model) return undefined;\n // With no registry to match against, the caller's spelling is all there is.\n if (!availableModels || availableModels.length === 0) return model;\n\n const resolvedWhole = resolveBaseModelCandidate(model, availableModels, preferredProvider);\n if (resolvedWhole) return resolvedWhole;\n\n const { baseModel, thinkingSuffix } = splitKnownThinkingSuffix(model);\n if (!thinkingSuffix) return model;\n const resolvedBase = resolveBaseModelCandidate(baseModel, availableModels, preferredProvider);\n if (resolvedBase) return `${resolvedBase}${thinkingSuffix}`;\n return model;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{init_modelInfo as t,splitKnownThinkingSuffix as n}from"./modelInfo.mjs";function r(e){return e.getAvailable().filter(t=>e.hasConfiguredAuth(t)).map(e=>({provider:e.provider,id:e.id,fullId:`${e.provider}/${e.id}`}))}function i(e){return e.toLowerCase().replace(/[._]+/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``)}function a(e,t,n){let r=Number(e),i=Number(t),a=Number(n);return r>=d&&r<=f&&i>=1&&i<=p&&a>=1&&a<=m}function o(e){let t=h.exec(e);if(t&&a(t[2],t[3],t[4]))return t[1];let n=g.exec(e);return n&&a(n[2],n[3],n[4])?n[1]:e}function s(e,t,n){if(e.includes(`/`)){let n=t.find(t=>t.fullId===e);if(n)return n.fullId}else{let r=t.filter(t=>t.id===e);if(n){let e=r.find(e=>e.provider===n);if(e)return e.fullId}if(r.length===1)return r[0].fullId}return c(e,t,n)}function c(e,t,n){let r,a=e,s=e.indexOf(`/`);if(s!==-1)r=i(e.slice(0,s)),a=e.slice(s+1);else for(let n of u){let o=e.indexOf(n);if(o<=0)continue;let s=i(e.slice(0,o));if(t.some(e=>i(e.provider)===s)){r=s,a=e.slice(o+1);break}}let c=i(a),l=o(c),d=t.filter(e=>{let t=i(e.id);return!(t!==c&&o(t)!==l||r!==void 0&&i(e.provider)!==r)});if(d.length!==0){if(n){let e=i(n),t=d.find(t=>i(t.provider)===e);if(t)return t.fullId}if(d.length===1)return d[0].fullId}}function l(e,t,r){if(!e)return;if(!t||t.length===0)return e;let i=s(e,t,r);if(i)return i;let{baseModel:a,thinkingSuffix:o}=n(e);if(!o)return e;let c=s(a,t,r);return c?`${c}${o}`:e}var u,d,f,p,m,h,g,_=e((()=>{t(),u=[`:`,`.`],d=1900,f=2099,p=12,m=31,h=/^(.*)-(\d{4})-(\d{2})-(\d{2})$/,g=/^(.*)-(\d{4})(\d{2})(\d{2})$/}));_();export{r as authenticatedModelInfos,c as fuzzyResolveModel,_ as init_modelResolution,i as normalizeModelSegment,l as resolveModelCandidate};
|
|
2
|
+
//# sourceMappingURL=modelResolution.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelResolution.mjs","names":[],"sources":["../../src/shared/modelResolution.ts"],"sourcesContent":["/**\n * Resolving a loosely-spelled model name to a canonical `provider/id`.\n *\n * Users and agent frontmatter name models by hand, so the same string has to\n * resolve the same way wherever it is written. This is a leaf so every domain\n * can share one answer.\n *\n * DESIGN PATTERNS:\n * - Pure functions over a registry passed in, so model policy is testable with\n * no process, no config on disk and no network\n * - Exact matches win; fuzzy matching is only ever a fallback\n * - A qualified `provider/id` query never resolves across providers, and an\n * ambiguous match resolves to nothing rather than guessing. Picking the wrong\n * provider costs real money\n *\n * AVOID:\n * - Splitting a model string on the first colon to find a thinking suffix; ids\n * legitimately contain colons, which is why `splitKnownThinkingSuffix` exists\n * - Adding a second, looser resolver anywhere. Two spellings of \"the same\n * model\" that disagree are worse than one that is occasionally strict\n */\n\nimport { type ModelInfo as AvailableModelInfo, splitKnownThinkingSuffix } from './modelInfo.ts';\n\nexport type { AvailableModelInfo };\n\ninterface ModelRegistryLike<TModel extends { provider: string; id: string }> {\n getAvailable(): TModel[];\n hasConfiguredAuth(model: TModel): boolean;\n}\n\n/** Return only models the live host can authenticate, in registry order. */\nexport function authenticatedModelInfos<TModel extends { provider: string; id: string }>(\n registry: ModelRegistryLike<TModel>,\n): AvailableModelInfo[] {\n return registry\n .getAvailable()\n .filter((model) => registry.hasConfiguredAuth(model))\n .map((model) => ({ provider: model.provider, id: model.id, fullId: `${model.provider}/${model.id}` }));\n}\n\n/** Separates provider from id in a canonical model reference. */\nexport const PROVIDER_SEPARATOR = '/';\n\n/**\n * Separators an unqualified query may use in place of `/`. Only accepted when\n * the leading segment names a provider that actually exists, so a model id\n * containing a dot is not mistaken for `provider.id`.\n */\nconst ALTERNATE_PROVIDER_SEPARATORS = [':', '.'];\n\n/** Bounds for a trailing date stamp, wide enough to be permissive and narrow enough to reject version numbers. */\nconst MIN_DATE_STAMP_YEAR = 1900;\nconst MAX_DATE_STAMP_YEAR = 2099;\nconst MONTHS_PER_YEAR = 12;\nconst MAX_DAYS_PER_MONTH = 31;\n\n/** `-2025-10-01` and `-20251001` respectively, anchored to the end of a segment. */\nconst DASHED_DATE_STAMP = /^(.*)-(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst COMPACT_DATE_STAMP = /^(.*)-(\\d{4})(\\d{2})(\\d{2})$/;\n\n/**\n * Normalize a model id or provider segment for fuzzy comparison: case-fold,\n * treat dots/underscores as dashes (so `4.5` matches `4-5`), and collapse\n * repeated separators. Pure.\n */\nexport function normalizeModelSegment(segment: string): string {\n return segment.toLowerCase().replace(/[._]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');\n}\n\nfunction isPlausibleDateStamp(year: string, month: string, day: string): boolean {\n const yyyy = Number(year);\n const mm = Number(month);\n const dd = Number(day);\n return (\n yyyy >= MIN_DATE_STAMP_YEAR &&\n yyyy <= MAX_DATE_STAMP_YEAR &&\n mm >= 1 &&\n mm <= MONTHS_PER_YEAR &&\n dd >= 1 &&\n dd <= MAX_DAYS_PER_MONTH\n );\n}\n\n/** Drop a trailing date stamp (`-20251001` or `-2025-10-01`) so dated and undated ids match. Pure. */\nfunction stripTrailingDateStamp(segment: string): string {\n const dashed = DASHED_DATE_STAMP.exec(segment);\n if (dashed && isPlausibleDateStamp(dashed[2], dashed[3], dashed[4])) return dashed[1];\n const compact = COMPACT_DATE_STAMP.exec(segment);\n if (compact && isPlausibleDateStamp(compact[2], compact[3], compact[4])) return compact[1];\n return segment;\n}\n\nfunction resolveBaseModelCandidate(\n baseModel: string,\n availableModels: AvailableModelInfo[],\n preferredProvider?: string,\n): string | undefined {\n if (baseModel.includes(PROVIDER_SEPARATOR)) {\n const exact = availableModels.find((entry) => entry.fullId === baseModel);\n if (exact) return exact.fullId;\n } else {\n const exactMatches = availableModels.filter((entry) => entry.id === baseModel);\n if (preferredProvider) {\n const preferredMatch = exactMatches.find((entry) => entry.provider === preferredProvider);\n if (preferredMatch) return preferredMatch.fullId;\n }\n if (exactMatches.length === 1) return exactMatches[0].fullId;\n }\n\n return fuzzyResolveModel(baseModel, availableModels, preferredProvider);\n}\n\n/**\n * Fuzzy-resolve a base model id (thinking suffix already stripped) against the\n * registry, tolerating separator, case, and optional date-stamp differences so\n * users do not have to spell provider/model exactly. A qualified `provider/id`\n * query only matches within the named provider, so this never silently switches\n * providers for security/cost-sensitive configs. Returns the matched `fullId`,\n * or `undefined` when there is no match or the match is ambiguous across\n * providers (and no `preferredProvider` disambiguates). Pure.\n */\nexport function fuzzyResolveModel(\n baseModel: string,\n availableModels: AvailableModelInfo[],\n preferredProvider?: string,\n): string | undefined {\n let queryProvider: string | undefined;\n let queryIdRaw = baseModel;\n const slashIdx = baseModel.indexOf(PROVIDER_SEPARATOR);\n if (slashIdx !== -1) {\n queryProvider = normalizeModelSegment(baseModel.slice(0, slashIdx));\n queryIdRaw = baseModel.slice(slashIdx + 1);\n } else {\n for (const separator of ALTERNATE_PROVIDER_SEPARATORS) {\n const separatorIdx = baseModel.indexOf(separator);\n if (separatorIdx <= 0) continue;\n const providerPart = normalizeModelSegment(baseModel.slice(0, separatorIdx));\n if (!availableModels.some((entry) => normalizeModelSegment(entry.provider) === providerPart)) continue;\n queryProvider = providerPart;\n queryIdRaw = baseModel.slice(separatorIdx + 1);\n break;\n }\n }\n const queryId = normalizeModelSegment(queryIdRaw);\n const queryIdNoDate = stripTrailingDateStamp(queryId);\n\n const candidates = availableModels.filter((entry) => {\n const entryId = normalizeModelSegment(entry.id);\n if (entryId !== queryId && stripTrailingDateStamp(entryId) !== queryIdNoDate) return false;\n if (queryProvider !== undefined && normalizeModelSegment(entry.provider) !== queryProvider) return false;\n return true;\n });\n if (candidates.length === 0) return undefined;\n if (preferredProvider) {\n const preferredProviderNorm = normalizeModelSegment(preferredProvider);\n const preferred = candidates.find((entry) => normalizeModelSegment(entry.provider) === preferredProviderNorm);\n if (preferred) return preferred.fullId;\n }\n if (candidates.length === 1) return candidates[0].fullId;\n return undefined;\n}\n\n/**\n * Resolve a possibly-loose model id to a canonical `provider/id` (plus any\n * thinking suffix). Exact registry matches win; fuzzy normalization\n * (separator/case/date-stamp via {@link fuzzyResolveModel}) is a fallback so\n * spelling differences still resolve. Never switches providers for a qualified\n * query. Pure.\n */\nexport function resolveModelCandidate(\n model: string | undefined,\n availableModels: AvailableModelInfo[] | undefined,\n preferredProvider?: string,\n): string | undefined {\n if (!model) return undefined;\n // With no registry to match against, the caller's spelling is all there is.\n if (!availableModels || availableModels.length === 0) return model;\n\n const resolvedWhole = resolveBaseModelCandidate(model, availableModels, preferredProvider);\n if (resolvedWhole) return resolvedWhole;\n\n const { baseModel, thinkingSuffix } = splitKnownThinkingSuffix(model);\n if (!thinkingSuffix) return model;\n const resolvedBase = resolveBaseModelCandidate(baseModel, availableModels, preferredProvider);\n if (resolvedBase) return `${resolvedBase}${thinkingSuffix}`;\n return model;\n}\n"],"mappings":"4IAgCA,SAAgB,EACd,EACsB,CACtB,OAAO,EACJ,cAAc,CACd,OAAQ,GAAU,EAAS,kBAAkB,EAAM,CAAC,CACpD,IAAK,IAAW,CAAE,SAAU,EAAM,SAAU,GAAI,EAAM,GAAI,OAAQ,GAAG,EAAM,SAAS,GAAG,EAAM,KAAM,EAAE,CA4B1G,SAAgB,EAAsB,EAAyB,CAC7D,OAAO,EAAQ,aAAa,CAAC,QAAQ,SAAU,IAAI,CAAC,QAAQ,MAAO,IAAI,CAAC,QAAQ,SAAU,GAAG,CAG/F,SAAS,EAAqB,EAAc,EAAe,EAAsB,CAC/E,IAAM,EAAO,OAAO,EAAK,CACnB,EAAK,OAAO,EAAM,CAClB,EAAK,OAAO,EAAI,CACtB,OACE,GAAQ,GACR,GAAQ,GACR,GAAM,GACN,GAAM,GACN,GAAM,GACN,GAAM,EAKV,SAAS,EAAuB,EAAyB,CACvD,IAAM,EAAS,EAAkB,KAAK,EAAQ,CAC9C,GAAI,GAAU,EAAqB,EAAO,GAAI,EAAO,GAAI,EAAO,GAAG,CAAE,OAAO,EAAO,GACnF,IAAM,EAAU,EAAmB,KAAK,EAAQ,CAEhD,OADI,GAAW,EAAqB,EAAQ,GAAI,EAAQ,GAAI,EAAQ,GAAG,CAAS,EAAQ,GACjF,EAGT,SAAS,EACP,EACA,EACA,EACoB,CACpB,GAAI,EAAU,SAAA,IAA4B,CAAE,CAC1C,IAAM,EAAQ,EAAgB,KAAM,GAAU,EAAM,SAAW,EAAU,CACzE,GAAI,EAAO,OAAO,EAAM,WACnB,CACL,IAAM,EAAe,EAAgB,OAAQ,GAAU,EAAM,KAAO,EAAU,CAC9E,GAAI,EAAmB,CACrB,IAAM,EAAiB,EAAa,KAAM,GAAU,EAAM,WAAa,EAAkB,CACzF,GAAI,EAAgB,OAAO,EAAe,OAE5C,GAAI,EAAa,SAAW,EAAG,OAAO,EAAa,GAAG,OAGxD,OAAO,EAAkB,EAAW,EAAiB,EAAkB,CAYzE,SAAgB,EACd,EACA,EACA,EACoB,CACpB,IAAI,EACA,EAAa,EACX,EAAW,EAAU,QAAA,IAA2B,CACtD,GAAI,IAAa,GACf,EAAgB,EAAsB,EAAU,MAAM,EAAG,EAAS,CAAC,CACnE,EAAa,EAAU,MAAM,EAAW,EAAE,MAE1C,IAAK,IAAM,KAAa,EAA+B,CACrD,IAAM,EAAe,EAAU,QAAQ,EAAU,CACjD,GAAI,GAAgB,EAAG,SACvB,IAAM,EAAe,EAAsB,EAAU,MAAM,EAAG,EAAa,CAAC,CACvE,KAAgB,KAAM,GAAU,EAAsB,EAAM,SAAS,GAAK,EAAa,CAE5F,CADA,EAAgB,EAChB,EAAa,EAAU,MAAM,EAAe,EAAE,CAC9C,OAGJ,IAAM,EAAU,EAAsB,EAAW,CAC3C,EAAgB,EAAuB,EAAQ,CAE/C,EAAa,EAAgB,OAAQ,GAAU,CACnD,IAAM,EAAU,EAAsB,EAAM,GAAG,CAG/C,MADA,EADI,IAAY,GAAW,EAAuB,EAAQ,GAAK,GAC3D,IAAkB,IAAA,IAAa,EAAsB,EAAM,SAAS,GAAK,IAE7E,CACE,KAAW,SAAW,EAC1B,IAAI,EAAmB,CACrB,IAAM,EAAwB,EAAsB,EAAkB,CAChE,EAAY,EAAW,KAAM,GAAU,EAAsB,EAAM,SAAS,GAAK,EAAsB,CAC7G,GAAI,EAAW,OAAO,EAAU,OAElC,GAAI,EAAW,SAAW,EAAG,OAAO,EAAW,GAAG,QAWpD,SAAgB,EACd,EACA,EACA,EACoB,CACpB,GAAI,CAAC,EAAO,OAEZ,GAAI,CAAC,GAAmB,EAAgB,SAAW,EAAG,OAAO,EAE7D,IAAM,EAAgB,EAA0B,EAAO,EAAiB,EAAkB,CAC1F,GAAI,EAAe,OAAO,EAE1B,GAAM,CAAE,YAAW,kBAAmB,EAAyB,EAAM,CACrE,GAAI,CAAC,EAAgB,OAAO,EAC5B,IAAM,EAAe,EAA0B,EAAW,EAAiB,EAAkB,CAE7F,OADI,EAAqB,GAAG,IAAe,IACpC,iCApKuF,CA2B1F,EAAgC,CAAC,IAAK,IAAI,CAG1C,EAAsB,KACtB,EAAsB,KACtB,EAAkB,GAClB,EAAqB,GAGrB,EAAoB,iCACpB,EAAqB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../_virtual/_rolldown/runtime.cjs`);require(`../env.cjs`);let t=require(`node:path`);t=e.__toESM(t,1);let n=require(`node:os`);n=e.__toESM(n,1);let r=require(`node:crypto`),i=require(`@agimon-ai/doompi-extension-contracts/child-process`);const a=[`USERNAME`,`USER`,`LOGNAME`];function o(e){return e.trim().replace(/[^A-Za-z0-9._-]+/gu,`-`).replace(/^-+|-+$/gu,``)||`unknown`}function s(e){let t=e?.env??process.env,r=e&&Object.hasOwn(e,`getuid`)?e.getuid:process.getuid?.bind(process);if(typeof r==`function`)return`uid-${r()}`;for(let e of a){let n=t[e];if(n)return`user-${o(n)}`}let i=e&&Object.hasOwn(e,`userInfo`)?e.userInfo:n.userInfo;try{let e=i?.().username;if(e)return`user-${o(e)}`}catch{}let s=t.USERPROFILE??t.HOME;if(s)return`home-${o(s)}`;let c=e&&Object.hasOwn(e,`homedir`)?e.homedir:n.homedir;try{let e=c?.();if(e)return`home-${o(e)}`}catch{}return`shared`}const c=t.join(n.tmpdir(),`doom-team-${s()}`),l=t.join(c,`artifacts`),u=t.join(c,`sessions`);function d(e){return(0,r.createHash)(`sha256`).update(e).digest(`hex`).slice(0,16)}function f(e){let t=e.trim();if(!t)throw Error(`A session scope requires a non-empty root session id.`);return{rootSessionId:t,scopeKey:d(t)}}function p(e){return t.join(u,e.scopeKey)}function m(e){return t.join(p(e),`owner.json`)}function h(e){return t.join(p(e),`run-results`)}function g(e){return t.join(p(e),`runs`)}function _(e){return t.join(p(e),`team`)}function v(e){return t.join(p(e),`suspended`)}function y(e){return t.join(p(e),`child-launch`)}function b(e,n){return t.join(p(e),`launch`,`${n}.json`)}let x;function S(e){x=e}function C(){return x}function w(){if(!x)throw Error(`No session scope is set. The parent sets it on session_start; a child sets it from its spawn environment.`);return x}function T(){return h(w())}function E(){return g(w())}function D(){return y(w())}function O(e){return b(w(),e)}function k(e){return{[i.SUBAGENT_ROOT_SESSION_ENV]:e.rootSessionId}}function A(e=process.env){let t=e[i.SUBAGENT_ROOT_SESSION_ENV]?.trim();return t?f(t):void 0}function j(e=process.env){let t=A(e);return t?(S(t),!0):!1}exports.SESSIONS_ROOT_DIR=u,exports.TEMP_ARTIFACTS_DIR=l,exports.TEMP_ROOT_DIR=c,exports.adoptSessionScopeFromEnv=j,exports.createSessionScope=f,exports.currentChildLaunchTempDir=D,exports.currentResultsDir=T,exports.currentRunConfigPath=O,exports.currentRunsDir=E,exports.requireCurrentSessionScope=w,exports.scopeOwnerPath=m,exports.scopeSuspendedDir=v,exports.scopeTeamDir=_,exports.sessionScopeDir=p,exports.sessionScopeEnvironment=k,exports.setCurrentSessionScope=S,exports.tryCurrentSessionScope=C;
|
|
2
|
+
//# sourceMappingURL=paths.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.cjs","names":["os","path","SUBAGENT_ROOT_SESSION_ENV"],"sources":["../../src/shared/paths.ts"],"sourcesContent":["/**\n * Filesystem roots for every artifact this package writes outside the project.\n *\n * DESIGN PATTERNS:\n * - One root, derived subdirectories. Nothing else in the package joins\n * `os.tmpdir()` directly, so the whole tree can be relocated from here\n * - The root is scoped per operating-system user. A shared `/tmp` on a\n * multi-user host would otherwise let one user read another's run state, and\n * worse, let two users collide on the same run id\n *\n * The root is named for this package, not for its predecessor. That is what\n * keeps a doom-team parent and a doom-pi-subagents parent from reading each\n * other's channels while both are installed during cutover.\n *\n * AVOID:\n * - Joining `os.tmpdir()` anywhere else\n * - Putting a user-supplied string into a path segment without sanitizing it\n */\n\nimport { createHash } from 'node:crypto';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport { SUBAGENT_ROOT_SESSION_ENV } from '../env.ts';\n\nconst TEMP_ROOT_PREFIX = 'doom-team';\nconst UNKNOWN_SCOPE_SEGMENT = 'unknown';\nconst SHARED_SCOPE_ID = 'shared';\nconst SCOPE_ENV_KEYS = ['USERNAME', 'USER', 'LOGNAME'] as const;\n\n/** Reduce a value to a single safe path segment. */\nfunction sanitizeTempScopeSegment(value: string): string {\n const sanitized = value\n .trim()\n .replace(/[^A-Za-z0-9._-]+/gu, '-')\n .replace(/^-+|-+$/gu, '');\n return sanitized || UNKNOWN_SCOPE_SEGMENT;\n}\n\n/** The identity sources used to scope the temp root, in preference order. */\nexport interface TempScopeSources {\n env?: NodeJS.ProcessEnv;\n getuid?: (() => number) | undefined;\n userInfo?: (() => { username?: string | null }) | undefined;\n homedir?: (() => string) | undefined;\n}\n\n/**\n * Identify the current user for temp-root scoping.\n *\n * The uid is preferred because it cannot be spoofed by the environment. The\n * remaining sources are progressively weaker fallbacks for platforms that do\n * not expose one; `shared` is the last resort and is deliberately reachable, so\n * a run never fails purely because the user could not be identified.\n */\nexport function resolveTempScopeId(options?: TempScopeSources): string {\n const env = options?.env ?? process.env;\n const getuid = options && Object.hasOwn(options, 'getuid') ? options.getuid : process.getuid?.bind(process);\n if (typeof getuid === 'function') return `uid-${getuid()}`;\n\n for (const key of SCOPE_ENV_KEYS) {\n const value = env[key];\n if (value) return `user-${sanitizeTempScopeSegment(value)}`;\n }\n\n const userInfo = options && Object.hasOwn(options, 'userInfo') ? options.userInfo : os.userInfo;\n try {\n const username = userInfo?.().username;\n if (username) return `user-${sanitizeTempScopeSegment(username)}`;\n } catch {\n // os.userInfo throws when the uid has no passwd entry; scope by home instead.\n }\n\n const homedir = env.USERPROFILE ?? env.HOME;\n if (homedir) return `home-${sanitizeTempScopeSegment(homedir)}`;\n\n const resolveHomedir = options && Object.hasOwn(options, 'homedir') ? options.homedir : os.homedir;\n try {\n const fallbackHomedir = resolveHomedir?.();\n if (fallbackHomedir) return `home-${sanitizeTempScopeSegment(fallbackHomedir)}`;\n } catch {\n // Every identity source failed; fall back to the shared scope.\n }\n\n return SHARED_SCOPE_ID;\n}\n\n/** Root of this package's out-of-project state, scoped to the current user. */\nexport const TEMP_ROOT_DIR = path.join(os.tmpdir(), `${TEMP_ROOT_PREFIX}-${resolveTempScopeId()}`);\n\n/** Artifacts produced by runs whose artifact preference resolves to `temp`. */\nexport const TEMP_ARTIFACTS_DIR = path.join(TEMP_ROOT_DIR, 'artifacts');\n\n// ============================================================================\n// Session scope\n// ============================================================================\n\n/**\n * Everything a session owns lives under one directory named for that session.\n *\n * WHY THIS EXISTS:\n * These paths used to be flat constants under `TEMP_ROOT_DIR`, shared by every\n * concurrent session of the same user. That is not merely untidy: with one\n * shared `run-results/`, a session could win `ResultWatcher`'s atomic-rename\n * claim on a run it did not own, and then never deliver it, because the\n * consumer's \"not mine\" answer is the same `false` that means \"retry later\".\n * `tests/unit/sessionIsolation.test.ts` reproduces exactly that. Scoping the\n * read paths removes the race by construction rather than by teaching the\n * consumer to distinguish two meanings of `false`.\n *\n * THE KEY IS THE ROOT SESSION, NOT THIS PROCESS'S SESSION:\n * A spawned child has its own Pi session id, so it cannot derive the tree from\n * `SessionManager.getSessionId()` - it would land in a directory of its own and\n * see none of its parent's state. The ROOT session id travels down by\n * environment instead, which is the same thing `nativeTeamChannel.ts` already\n * does for team membership.\n *\n * AVOID:\n * - Reading another scope's tree for anything except reaping. Cross-scope reads\n * are what this whole layout exists to prevent\n */\nexport interface SessionScope {\n /** Pi's session id for the session at the top of this process tree. */\n rootSessionId: string;\n /** `rootSessionId` reduced to one safe path segment. */\n scopeKey: string;\n}\n\nconst SESSIONS_DIR_NAME = 'sessions';\nconst SCOPE_KEY_HASH_LENGTH = 16;\n\n/** All scopes. Only a reaping sweep may read across this; never a delivery path. */\nexport const SESSIONS_ROOT_DIR = path.join(TEMP_ROOT_DIR, SESSIONS_DIR_NAME);\n\n/**\n * Hashed rather than sanitized: a Pi session id is a uuidv7 today, but the id\n * is also settable by a caller (`SessionManager.newSession({ id })`), and a\n * hash is the only derivation that cannot collide two different ids onto one\n * directory after sanitizing away their differing characters.\n */\nexport function sessionScopeKey(rootSessionId: string): string {\n return createHash('sha256').update(rootSessionId).digest('hex').slice(0, SCOPE_KEY_HASH_LENGTH);\n}\n\nexport function createSessionScope(rootSessionId: string): SessionScope {\n const trimmed = rootSessionId.trim();\n if (!trimmed) throw new Error('A session scope requires a non-empty root session id.');\n return { rootSessionId: trimmed, scopeKey: sessionScopeKey(trimmed) };\n}\n\n/** `sessions/<scopeKey>`. Every other scoped path derives from this one. */\nexport function sessionScopeDir(scope: SessionScope): string {\n return path.join(SESSIONS_ROOT_DIR, scope.scopeKey);\n}\n\n/** Records which session and process own a scope, so a later sweep can reap it. */\nexport function scopeOwnerPath(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'owner.json');\n}\n\n/** Terminal run results, written once by the runner and read by this scope's parent only. */\nexport function scopeResultsDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'run-results');\n}\n\n/** Per-run working state: status.json, transcripts, control inboxes, claimed results. */\nexport function scopeRunsDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'runs');\n}\n\n/** Team member records, heartbeats, inboxes and replies for this scope's team. */\nexport function scopeTeamDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'team');\n}\n\n/** Runs stopped by a session shutdown, awaiting an explicit restore. */\nexport function scopeSuspendedDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'suspended');\n}\n\n/**\n * Scratch space for launching a child: its prompt, task and tool diagnostics.\n *\n * Callers `mkdtemp` inside this rather than in `os.tmpdir()` directly, so the\n * scoping applies to launch scratch too. These files carry the full task text,\n * which is exactly the content that should not be world-readable.\n */\nexport function scopeChildLaunchTempDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'child-launch');\n}\n\n/**\n * Path of the launch config handed to a detached runner.\n *\n * These files carry the full launch contract, so the runner's spawn site writes\n * them 0600 and the parent removes them once the child has read them.\n */\nexport function getRunConfigPath(scope: SessionScope, runId: string): string {\n return path.join(sessionScopeDir(scope), 'launch', `${runId}.json`);\n}\n\n// ============================================================================\n// The process's current scope\n// ============================================================================\n\n/**\n * One process serves exactly one root session at a time, so the scope is\n * process state rather than a parameter on every path call.\n *\n * WHY THIS IS SETTABLE AND NOT A CONSTANT:\n * `TEMP_ROOT_DIR` can be a constant because the OS user is fixed for the life\n * of the process. The root session is not: a parent learns it at\n * `session_start`, and Pi fires that again with a DIFFERENT session id after\n * `/new`, `/resume` and `/fork` (it builds a fresh `SessionManager` each time).\n * A child learns it from the environment its parent spawned it with.\n *\n * WHY AN UNSET SCOPE THROWS RATHER THAN FALLING BACK:\n * A silent fallback to an unscoped directory is exactly the shared-state bug\n * this layout removes, and it would fail invisibly - two sessions quietly\n * sharing a tree again. Every real code path runs after either `session_start`\n * or child bootstrap, so reaching a path helper with no scope is a wiring bug\n * and should say so.\n *\n * AVOID:\n * - Reading this at module scope. It is not set at import time\n */\nlet currentScope: SessionScope | undefined;\n\nexport function setCurrentSessionScope(scope: SessionScope): void {\n currentScope = scope;\n}\n\n/** Test-only, and for a child that legitimately has no team root forwarded. */\nexport function clearCurrentSessionScope(): void {\n currentScope = undefined;\n}\n\nexport function tryCurrentSessionScope(): SessionScope | undefined {\n return currentScope;\n}\n\nexport function requireCurrentSessionScope(): SessionScope {\n if (!currentScope) {\n throw new Error(\n 'No session scope is set. The parent sets it on session_start; a child sets it from its spawn environment.',\n );\n }\n return currentScope;\n}\n\n/** `run-results/` for this process's scope. */\nexport function currentResultsDir(): string {\n return scopeResultsDir(requireCurrentSessionScope());\n}\n\n/** `runs/` for this process's scope. */\nexport function currentRunsDir(): string {\n return scopeRunsDir(requireCurrentSessionScope());\n}\n\n/** `child-launch/` for this process's scope. */\nexport function currentChildLaunchTempDir(): string {\n return scopeChildLaunchTempDir(requireCurrentSessionScope());\n}\n\n/** `suspended/` for this process's scope. */\nexport function currentSuspendedDir(): string {\n return scopeSuspendedDir(requireCurrentSessionScope());\n}\n\n/** Launch config path for a run in this process's scope. */\nexport function currentRunConfigPath(runId: string): string {\n return getRunConfigPath(requireCurrentSessionScope(), runId);\n}\n\n/** Folded into a spawned child's environment so it resolves the same tree. */\nexport function sessionScopeEnvironment(scope: SessionScope): Record<string, string> {\n return { [SUBAGENT_ROOT_SESSION_ENV]: scope.rootSessionId };\n}\n\n/** The scope a spawned child inherits, or undefined in a process nothing spawned. */\nexport function resolveSessionScopeFromEnv(env: NodeJS.ProcessEnv = process.env): SessionScope | undefined {\n const rootSessionId = env[SUBAGENT_ROOT_SESSION_ENV]?.trim();\n return rootSessionId ? createSessionScope(rootSessionId) : undefined;\n}\n\n/**\n * Adopt the inherited scope, if this process was spawned with one.\n *\n * Returns whether a scope was adopted, so a child entry point can fail loudly\n * rather than silently writing into an unscoped tree.\n */\nexport function adoptSessionScopeFromEnv(env: NodeJS.ProcessEnv = process.env): boolean {\n const scope = resolveSessionScopeFromEnv(env);\n if (!scope) return false;\n setCurrentSessionScope(scope);\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{SUBAGENT_ROOT_SESSION_ENV as t,init_env as n}from"../env.mjs";import*as r from"node:path";import*as i from"node:os";import{createHash as a}from"node:crypto";function o(e){return e.trim().replace(/[^A-Za-z0-9._-]+/gu,`-`).replace(/^-+|-+$/gu,``)||k}function s(e){let t=e?.env??process.env,n=e&&Object.hasOwn(e,`getuid`)?e.getuid:process.getuid?.bind(process);if(typeof n==`function`)return`uid-${n()}`;for(let e of j){let n=t[e];if(n)return`user-${o(n)}`}let r=e&&Object.hasOwn(e,`userInfo`)?e.userInfo:i.userInfo;try{let e=r?.().username;if(e)return`user-${o(e)}`}catch{}let a=t.USERPROFILE??t.HOME;if(a)return`home-${o(a)}`;let s=e&&Object.hasOwn(e,`homedir`)?e.homedir:i.homedir;try{let e=s?.();if(e)return`home-${o(e)}`}catch{}return A}function c(e){return a(`sha256`).update(e).digest(`hex`).slice(0,F)}function l(e){let t=e.trim();if(!t)throw Error(`A session scope requires a non-empty root session id.`);return{rootSessionId:t,scopeKey:c(t)}}function u(e){return r.join(I,e.scopeKey)}function d(e){return r.join(u(e),`owner.json`)}function f(e){return r.join(u(e),`run-results`)}function p(e){return r.join(u(e),`runs`)}function m(e){return r.join(u(e),`team`)}function h(e){return r.join(u(e),`suspended`)}function g(e){return r.join(u(e),`child-launch`)}function _(e,t){return r.join(u(e),`launch`,`${t}.json`)}function v(e){L=e}function y(){return L}function b(){if(!L)throw Error(`No session scope is set. The parent sets it on session_start; a child sets it from its spawn environment.`);return L}function x(){return f(b())}function S(){return p(b())}function C(){return g(b())}function w(e){return _(b(),e)}function T(e){return{[t]:e.rootSessionId}}function E(e=process.env){let n=e[t]?.trim();return n?l(n):void 0}function D(e=process.env){let t=E(e);return t?(v(t),!0):!1}var O,k,A,j,M,N,P,F,I,L,R=e((()=>{n(),O=`doom-team`,k=`unknown`,A=`shared`,j=[`USERNAME`,`USER`,`LOGNAME`],M=r.join(i.tmpdir(),`${O}-${s()}`),N=r.join(M,`artifacts`),P=`sessions`,F=16,I=r.join(M,P)}));R();export{I as SESSIONS_ROOT_DIR,N as TEMP_ARTIFACTS_DIR,M as TEMP_ROOT_DIR,D as adoptSessionScopeFromEnv,l as createSessionScope,C as currentChildLaunchTempDir,x as currentResultsDir,w as currentRunConfigPath,S as currentRunsDir,R as init_paths,b as requireCurrentSessionScope,d as scopeOwnerPath,h as scopeSuspendedDir,m as scopeTeamDir,u as sessionScopeDir,T as sessionScopeEnvironment,v as setCurrentSessionScope,y as tryCurrentSessionScope};
|
|
2
|
+
//# sourceMappingURL=paths.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.mjs","names":[],"sources":["../../src/shared/paths.ts"],"sourcesContent":["/**\n * Filesystem roots for every artifact this package writes outside the project.\n *\n * DESIGN PATTERNS:\n * - One root, derived subdirectories. Nothing else in the package joins\n * `os.tmpdir()` directly, so the whole tree can be relocated from here\n * - The root is scoped per operating-system user. A shared `/tmp` on a\n * multi-user host would otherwise let one user read another's run state, and\n * worse, let two users collide on the same run id\n *\n * The root is named for this package, not for its predecessor. That is what\n * keeps a doom-team parent and a doom-pi-subagents parent from reading each\n * other's channels while both are installed during cutover.\n *\n * AVOID:\n * - Joining `os.tmpdir()` anywhere else\n * - Putting a user-supplied string into a path segment without sanitizing it\n */\n\nimport { createHash } from 'node:crypto';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport { SUBAGENT_ROOT_SESSION_ENV } from '../env.ts';\n\nconst TEMP_ROOT_PREFIX = 'doom-team';\nconst UNKNOWN_SCOPE_SEGMENT = 'unknown';\nconst SHARED_SCOPE_ID = 'shared';\nconst SCOPE_ENV_KEYS = ['USERNAME', 'USER', 'LOGNAME'] as const;\n\n/** Reduce a value to a single safe path segment. */\nfunction sanitizeTempScopeSegment(value: string): string {\n const sanitized = value\n .trim()\n .replace(/[^A-Za-z0-9._-]+/gu, '-')\n .replace(/^-+|-+$/gu, '');\n return sanitized || UNKNOWN_SCOPE_SEGMENT;\n}\n\n/** The identity sources used to scope the temp root, in preference order. */\nexport interface TempScopeSources {\n env?: NodeJS.ProcessEnv;\n getuid?: (() => number) | undefined;\n userInfo?: (() => { username?: string | null }) | undefined;\n homedir?: (() => string) | undefined;\n}\n\n/**\n * Identify the current user for temp-root scoping.\n *\n * The uid is preferred because it cannot be spoofed by the environment. The\n * remaining sources are progressively weaker fallbacks for platforms that do\n * not expose one; `shared` is the last resort and is deliberately reachable, so\n * a run never fails purely because the user could not be identified.\n */\nexport function resolveTempScopeId(options?: TempScopeSources): string {\n const env = options?.env ?? process.env;\n const getuid = options && Object.hasOwn(options, 'getuid') ? options.getuid : process.getuid?.bind(process);\n if (typeof getuid === 'function') return `uid-${getuid()}`;\n\n for (const key of SCOPE_ENV_KEYS) {\n const value = env[key];\n if (value) return `user-${sanitizeTempScopeSegment(value)}`;\n }\n\n const userInfo = options && Object.hasOwn(options, 'userInfo') ? options.userInfo : os.userInfo;\n try {\n const username = userInfo?.().username;\n if (username) return `user-${sanitizeTempScopeSegment(username)}`;\n } catch {\n // os.userInfo throws when the uid has no passwd entry; scope by home instead.\n }\n\n const homedir = env.USERPROFILE ?? env.HOME;\n if (homedir) return `home-${sanitizeTempScopeSegment(homedir)}`;\n\n const resolveHomedir = options && Object.hasOwn(options, 'homedir') ? options.homedir : os.homedir;\n try {\n const fallbackHomedir = resolveHomedir?.();\n if (fallbackHomedir) return `home-${sanitizeTempScopeSegment(fallbackHomedir)}`;\n } catch {\n // Every identity source failed; fall back to the shared scope.\n }\n\n return SHARED_SCOPE_ID;\n}\n\n/** Root of this package's out-of-project state, scoped to the current user. */\nexport const TEMP_ROOT_DIR = path.join(os.tmpdir(), `${TEMP_ROOT_PREFIX}-${resolveTempScopeId()}`);\n\n/** Artifacts produced by runs whose artifact preference resolves to `temp`. */\nexport const TEMP_ARTIFACTS_DIR = path.join(TEMP_ROOT_DIR, 'artifacts');\n\n// ============================================================================\n// Session scope\n// ============================================================================\n\n/**\n * Everything a session owns lives under one directory named for that session.\n *\n * WHY THIS EXISTS:\n * These paths used to be flat constants under `TEMP_ROOT_DIR`, shared by every\n * concurrent session of the same user. That is not merely untidy: with one\n * shared `run-results/`, a session could win `ResultWatcher`'s atomic-rename\n * claim on a run it did not own, and then never deliver it, because the\n * consumer's \"not mine\" answer is the same `false` that means \"retry later\".\n * `tests/unit/sessionIsolation.test.ts` reproduces exactly that. Scoping the\n * read paths removes the race by construction rather than by teaching the\n * consumer to distinguish two meanings of `false`.\n *\n * THE KEY IS THE ROOT SESSION, NOT THIS PROCESS'S SESSION:\n * A spawned child has its own Pi session id, so it cannot derive the tree from\n * `SessionManager.getSessionId()` - it would land in a directory of its own and\n * see none of its parent's state. The ROOT session id travels down by\n * environment instead, which is the same thing `nativeTeamChannel.ts` already\n * does for team membership.\n *\n * AVOID:\n * - Reading another scope's tree for anything except reaping. Cross-scope reads\n * are what this whole layout exists to prevent\n */\nexport interface SessionScope {\n /** Pi's session id for the session at the top of this process tree. */\n rootSessionId: string;\n /** `rootSessionId` reduced to one safe path segment. */\n scopeKey: string;\n}\n\nconst SESSIONS_DIR_NAME = 'sessions';\nconst SCOPE_KEY_HASH_LENGTH = 16;\n\n/** All scopes. Only a reaping sweep may read across this; never a delivery path. */\nexport const SESSIONS_ROOT_DIR = path.join(TEMP_ROOT_DIR, SESSIONS_DIR_NAME);\n\n/**\n * Hashed rather than sanitized: a Pi session id is a uuidv7 today, but the id\n * is also settable by a caller (`SessionManager.newSession({ id })`), and a\n * hash is the only derivation that cannot collide two different ids onto one\n * directory after sanitizing away their differing characters.\n */\nexport function sessionScopeKey(rootSessionId: string): string {\n return createHash('sha256').update(rootSessionId).digest('hex').slice(0, SCOPE_KEY_HASH_LENGTH);\n}\n\nexport function createSessionScope(rootSessionId: string): SessionScope {\n const trimmed = rootSessionId.trim();\n if (!trimmed) throw new Error('A session scope requires a non-empty root session id.');\n return { rootSessionId: trimmed, scopeKey: sessionScopeKey(trimmed) };\n}\n\n/** `sessions/<scopeKey>`. Every other scoped path derives from this one. */\nexport function sessionScopeDir(scope: SessionScope): string {\n return path.join(SESSIONS_ROOT_DIR, scope.scopeKey);\n}\n\n/** Records which session and process own a scope, so a later sweep can reap it. */\nexport function scopeOwnerPath(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'owner.json');\n}\n\n/** Terminal run results, written once by the runner and read by this scope's parent only. */\nexport function scopeResultsDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'run-results');\n}\n\n/** Per-run working state: status.json, transcripts, control inboxes, claimed results. */\nexport function scopeRunsDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'runs');\n}\n\n/** Team member records, heartbeats, inboxes and replies for this scope's team. */\nexport function scopeTeamDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'team');\n}\n\n/** Runs stopped by a session shutdown, awaiting an explicit restore. */\nexport function scopeSuspendedDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'suspended');\n}\n\n/**\n * Scratch space for launching a child: its prompt, task and tool diagnostics.\n *\n * Callers `mkdtemp` inside this rather than in `os.tmpdir()` directly, so the\n * scoping applies to launch scratch too. These files carry the full task text,\n * which is exactly the content that should not be world-readable.\n */\nexport function scopeChildLaunchTempDir(scope: SessionScope): string {\n return path.join(sessionScopeDir(scope), 'child-launch');\n}\n\n/**\n * Path of the launch config handed to a detached runner.\n *\n * These files carry the full launch contract, so the runner's spawn site writes\n * them 0600 and the parent removes them once the child has read them.\n */\nexport function getRunConfigPath(scope: SessionScope, runId: string): string {\n return path.join(sessionScopeDir(scope), 'launch', `${runId}.json`);\n}\n\n// ============================================================================\n// The process's current scope\n// ============================================================================\n\n/**\n * One process serves exactly one root session at a time, so the scope is\n * process state rather than a parameter on every path call.\n *\n * WHY THIS IS SETTABLE AND NOT A CONSTANT:\n * `TEMP_ROOT_DIR` can be a constant because the OS user is fixed for the life\n * of the process. The root session is not: a parent learns it at\n * `session_start`, and Pi fires that again with a DIFFERENT session id after\n * `/new`, `/resume` and `/fork` (it builds a fresh `SessionManager` each time).\n * A child learns it from the environment its parent spawned it with.\n *\n * WHY AN UNSET SCOPE THROWS RATHER THAN FALLING BACK:\n * A silent fallback to an unscoped directory is exactly the shared-state bug\n * this layout removes, and it would fail invisibly - two sessions quietly\n * sharing a tree again. Every real code path runs after either `session_start`\n * or child bootstrap, so reaching a path helper with no scope is a wiring bug\n * and should say so.\n *\n * AVOID:\n * - Reading this at module scope. It is not set at import time\n */\nlet currentScope: SessionScope | undefined;\n\nexport function setCurrentSessionScope(scope: SessionScope): void {\n currentScope = scope;\n}\n\n/** Test-only, and for a child that legitimately has no team root forwarded. */\nexport function clearCurrentSessionScope(): void {\n currentScope = undefined;\n}\n\nexport function tryCurrentSessionScope(): SessionScope | undefined {\n return currentScope;\n}\n\nexport function requireCurrentSessionScope(): SessionScope {\n if (!currentScope) {\n throw new Error(\n 'No session scope is set. The parent sets it on session_start; a child sets it from its spawn environment.',\n );\n }\n return currentScope;\n}\n\n/** `run-results/` for this process's scope. */\nexport function currentResultsDir(): string {\n return scopeResultsDir(requireCurrentSessionScope());\n}\n\n/** `runs/` for this process's scope. */\nexport function currentRunsDir(): string {\n return scopeRunsDir(requireCurrentSessionScope());\n}\n\n/** `child-launch/` for this process's scope. */\nexport function currentChildLaunchTempDir(): string {\n return scopeChildLaunchTempDir(requireCurrentSessionScope());\n}\n\n/** `suspended/` for this process's scope. */\nexport function currentSuspendedDir(): string {\n return scopeSuspendedDir(requireCurrentSessionScope());\n}\n\n/** Launch config path for a run in this process's scope. */\nexport function currentRunConfigPath(runId: string): string {\n return getRunConfigPath(requireCurrentSessionScope(), runId);\n}\n\n/** Folded into a spawned child's environment so it resolves the same tree. */\nexport function sessionScopeEnvironment(scope: SessionScope): Record<string, string> {\n return { [SUBAGENT_ROOT_SESSION_ENV]: scope.rootSessionId };\n}\n\n/** The scope a spawned child inherits, or undefined in a process nothing spawned. */\nexport function resolveSessionScopeFromEnv(env: NodeJS.ProcessEnv = process.env): SessionScope | undefined {\n const rootSessionId = env[SUBAGENT_ROOT_SESSION_ENV]?.trim();\n return rootSessionId ? createSessionScope(rootSessionId) : undefined;\n}\n\n/**\n * Adopt the inherited scope, if this process was spawned with one.\n *\n * Returns whether a scope was adopted, so a child entry point can fail loudly\n * rather than silently writing into an unscoped tree.\n */\nexport function adoptSessionScopeFromEnv(env: NodeJS.ProcessEnv = process.env): boolean {\n const scope = resolveSessionScopeFromEnv(env);\n if (!scope) return false;\n setCurrentSessionScope(scope);\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`);let t=require(`inversify`),n=class{floorMs=200;ceilingMs=5e3;backoffMultiplier=2;now(){return Date.now()}onSubscriberError(e,t){this.lastSubscriberError={id:e,error:t,at:this.now()},this.subscriberErrorCount+=1}lastSubscriberError;subscriberErrorCount=0;subscribers=new Map;nextSubscriberKey=0;timer;currentIntervalMs;running=!1;register(e){if(!(e.intervalMs>0))throw Error(`Poll subscriber "${e.id}" must have a positive intervalMs.`);let t=this.nextSubscriberKey++;return this.subscribers.set(t,{...e,lastRunAt:void 0}),()=>{this.subscribers.delete(t)}}start(){this.running||(this.running=!0,this.currentIntervalMs=this.floorMs,this.tick(!1))}stop(){this.running=!1,this.timer&&clearTimeout(this.timer),this.timer=void 0}wake(){this.running&&(this.timer&&clearTimeout(this.timer),this.timer=void 0,this.tick(!0))}tick(e){let t=this.now(),n=!1,r=!1;for(let i of this.subscribers.values())if(e||i.lastRunAt===void 0||t-i.lastRunAt>=i.intervalMs){i.lastRunAt=t,r=!0;try{i.run()&&(n=!0)}catch(e){this.onSubscriberError(i.id,e)}}if(r){let e=this.currentIntervalMs??this.floorMs;this.currentIntervalMs=n?this.floorMs:Math.min(e*this.backoffMultiplier,this.ceilingMs)}this.scheduleNext()}scheduleNext(){if(!this.running)return;let e=this.currentIntervalMs??this.floorMs;this.timer=setTimeout(()=>this.tick(!1),e),this.timer.unref?.()}};n=e.__decorate([(0,t.injectable)()],n),Object.defineProperty(exports,`PollScheduler`,{enumerable:!0,get:function(){return n}});
|
|
2
|
+
//# sourceMappingURL=pollScheduler.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pollScheduler.cjs","names":[],"sources":["../../src/shared/pollScheduler.ts"],"sourcesContent":["/**\n * One master tick that drives every background subscriber in the runner\n * process: channels, watchers, inspectors, anything that used to own its own\n * `setInterval`.\n *\n * WHY ONE TICK AND NOT ONE TIMER PER SUBSCRIBER:\n * The predecessor package (`doom-pi-subagents`) gave every channel, watcher\n * and inspector its own independent interval. None of them coordinated, all of\n * them kept firing when nothing was happening, and the parent process burned\n * CPU while completely idle. A single scheduler that every subscriber reports\n * to can decide, once, whether there is any reason to keep ticking quickly.\n *\n * IDLE BACKOFF:\n * The tick interval starts at `floorMs`. A tick where nothing reported work\n * multiplies the interval by `backoffMultiplier`, capped at `ceilingMs`. A tick\n * where anything reported work resets it straight back to `floorMs`. \"Reported\n * work\" means a subscriber's `run()` returned `true`; returning `false` or\n * `void` means it checked and found nothing to do.\n *\n * WATCH-FIRST, POLL AS A SAFETY NET:\n * The intended pattern is: a subscriber's own `intervalMs` is set generously\n * (roughly 4x looser than how quickly you actually want it checked), and a\n * filesystem watch is the fast path. When the watch fires, its handler calls\n * `wake()`, which runs every subscriber immediately regardless of backoff.\n * Polling on `intervalMs` only exists to cover the platforms and mount types\n * where filesystem watches are unreliable or simply do not fire. Neither path\n * is trusted alone: the watch gives latency, the poll interval gives a bound\n * on how late a check can ever be.\n *\n * DESIGN PATTERNS:\n * - Subscribers are checked for both \"due\" (their own `intervalMs` has\n * elapsed) and \"forced\" (a `wake()` call), so a fast-changing subscriber and\n * a rarely-changing one can share one clock without either starving\n * - A subscriber's key is internal and never the caller-supplied `id`, so two\n * subscribers that reuse the same `id` string (a plausible mistake with\n * per-run subscriber names) cannot make one's `unregister()` delete the\n * other's registration\n * - `run()` is synchronous. Every current subscriber is a file check, and a\n * synchronous contract is what keeps the scheduler's behaviour deterministic\n * under fake timers; an async contract would need microtask flushing on top\n * of timer advancement in every test\n *\n * AVOID:\n * - Reading `this.floorMs` (or the other seam fields) from the constructor:\n * field initializer order runs the base class's defaults before a test\n * subclass's overrides are applied, so a value read in the constructor would\n * see the base default even when a subclass overrides it. Everything that\n * needs these values reads them lazily, from `start()` or `tick()`, which run\n * after construction is complete\n * - Letting a subscriber's thrown error escape `tick()`; it must not stop the\n * tick or affect its peers. It is recorded rather than discarded, since a\n * subscriber that throws every tick otherwise looks identical to an idle one\n */\n\nimport { injectable } from 'inversify';\n\n/** Minimum spacing between ticks once nothing has reported work in a while. */\nconst DEFAULT_FLOOR_MS = 200;\n/** Ceiling the backoff grows toward while idle. */\nconst DEFAULT_CEILING_MS = 5_000;\n/** Growth factor applied to the tick interval on every idle tick. */\nconst DEFAULT_BACKOFF_MULTIPLIER = 2;\n\nexport interface PollSubscription {\n /** For readability in logs and error messages only; not used as a map key. */\n id: string;\n /** How often this subscriber is checked, in milliseconds. Must be positive. */\n intervalMs: number;\n /**\n * Called when due. Return `true` when it did real work, which resets the\n * scheduler's backoff to the floor. Return `false` or nothing when there was\n * nothing to do, which lets idle backoff continue growing.\n */\n run: () => boolean | void;\n}\n\nexport interface IPollScheduler {\n /** Register a subscriber. Call the returned function to unregister it. */\n register(subscription: PollSubscription): () => void;\n /** Run every subscriber immediately, bypassing backoff and due-checks. */\n wake(): void;\n /** Begin ticking. Idempotent: a second call while running does nothing. */\n start(): void;\n /** Stop ticking and clear the pending timer. Idempotent. */\n stop(): void;\n}\n\ninterface InternalSubscriber extends PollSubscription {\n /** `undefined` until the first tick runs it; never `0`, which a test clock may legitimately start at. */\n lastRunAt: number | undefined;\n}\n\n@injectable()\nexport class PollScheduler implements IPollScheduler {\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 backoff curve, so a test subclass overrides these instead.\n */\n protected readonly floorMs: number = DEFAULT_FLOOR_MS;\n protected readonly ceilingMs: number = DEFAULT_CEILING_MS;\n protected readonly backoffMultiplier: number = DEFAULT_BACKOFF_MULTIPLIER;\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * Records a subscriber that threw. Overridable, and deliberately not a\n * stdio write: this runs inside a detached runner whose stdout is the child's\n * captured transcript. The composition root routes these into that process's\n * own diagnostics; the default keeps the most recent one queryable so a\n * failing subscriber can still be found after the fact.\n */\n protected onSubscriberError(id: string, error: unknown): void {\n this.lastSubscriberError = { id, error, at: this.now() };\n this.subscriberErrorCount += 1;\n }\n\n /** The most recent subscriber failure, for diagnostics. */\n lastSubscriberError: { id: string; error: unknown; at: number } | undefined;\n /** How many subscriber failures this scheduler has absorbed. */\n subscriberErrorCount = 0;\n\n private readonly subscribers = new Map<number, InternalSubscriber>();\n private nextSubscriberKey = 0;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private currentIntervalMs: number | undefined;\n private running = false;\n\n register(subscription: PollSubscription): () => void {\n if (!(subscription.intervalMs > 0)) {\n throw new Error(`Poll subscriber \"${subscription.id}\" must have a positive intervalMs.`);\n }\n const key = this.nextSubscriberKey++;\n this.subscribers.set(key, { ...subscription, lastRunAt: undefined });\n return () => {\n this.subscribers.delete(key);\n };\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.currentIntervalMs = this.floorMs;\n this.tick(false);\n }\n\n stop(): void {\n this.running = false;\n if (this.timer) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n wake(): void {\n if (!this.running) return;\n if (this.timer) clearTimeout(this.timer);\n this.timer = undefined;\n this.tick(true);\n }\n\n /**\n * Run every due subscriber (or, when `forced`, every subscriber), fold the\n * outcome into the backoff curve, and schedule the next tick.\n */\n private tick(forced: boolean): void {\n const timestamp = this.now();\n let workHappened = false;\n let anySubscriberRan = false;\n\n for (const subscriber of this.subscribers.values()) {\n const due =\n forced || subscriber.lastRunAt === undefined || timestamp - subscriber.lastRunAt >= subscriber.intervalMs;\n if (!due) continue;\n subscriber.lastRunAt = timestamp;\n anySubscriberRan = true;\n try {\n if (subscriber.run()) workHappened = true;\n } catch (error) {\n // A subscriber's failure is its own: the tick, and every other\n // subscriber's turn within it, continues regardless. But it is not\n // discarded, because a subscriber that throws on every tick is\n // invisible otherwise and looks exactly like one with nothing to do.\n this.onSubscriberError(subscriber.id, error);\n }\n }\n\n // Backoff only moves on evidence. A tick where nothing was even due says\n // nothing about whether the system is idle, so it must not be mistaken for\n // one where subscribers looked and found nothing: growing the interval\n // there would penalise a scheduler whose subscribers simply poll slower\n // than it ticks, and would let a stale interval survive a stop/start.\n if (anySubscriberRan) {\n const previousIntervalMs = this.currentIntervalMs ?? this.floorMs;\n this.currentIntervalMs = workHappened\n ? this.floorMs\n : Math.min(previousIntervalMs * this.backoffMultiplier, this.ceilingMs);\n }\n\n this.scheduleNext();\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n const delayMs = this.currentIntervalMs ?? this.floorMs;\n this.timer = setTimeout(() => this.tick(false), delayMs);\n // A pending background poll must never be the reason the process stays\n // alive; `unref` is optional only because some test timer shims omit it.\n this.timer.unref?.();\n }\n}\n"],"mappings":"0JA6FO,EAAA,KAA8C,CAMnD,QAAqC,IACrC,UAAuC,IACvC,kBAA+C,EAE/C,KAAwB,CACtB,OAAO,KAAK,KAAK,CAUnB,kBAA4B,EAAY,EAAsB,CAC5D,KAAK,oBAAsB,CAAE,KAAI,QAAO,GAAI,KAAK,KAAK,CAAE,CACxD,KAAK,sBAAwB,EAI/B,oBAEA,qBAAuB,EAEvB,YAA+B,IAAI,IACnC,kBAA4B,EAC5B,MACA,kBACA,QAAkB,GAElB,SAAS,EAA4C,CACnD,GAAI,EAAE,EAAa,WAAa,GAC9B,MAAU,MAAM,oBAAoB,EAAa,GAAG,oCAAoC,CAE1F,IAAM,EAAM,KAAK,oBAEjB,OADA,KAAK,YAAY,IAAI,EAAK,CAAE,GAAG,EAAc,UAAW,IAAA,GAAW,CAAC,KACvD,CACX,KAAK,YAAY,OAAO,EAAI,EAIhC,OAAc,CACR,KAAK,UACT,KAAK,QAAU,GACf,KAAK,kBAAoB,KAAK,QAC9B,KAAK,KAAK,GAAM,EAGlB,MAAa,CACX,KAAK,QAAU,GACX,KAAK,OAAO,aAAa,KAAK,MAAM,CACxC,KAAK,MAAQ,IAAA,GAGf,MAAa,CACN,KAAK,UACN,KAAK,OAAO,aAAa,KAAK,MAAM,CACxC,KAAK,MAAQ,IAAA,GACb,KAAK,KAAK,GAAK,EAOjB,KAAa,EAAuB,CAClC,IAAM,EAAY,KAAK,KAAK,CACxB,EAAe,GACf,EAAmB,GAEvB,IAAK,IAAM,KAAc,KAAK,YAAY,QAAQ,CAE9C,MAAU,EAAW,YAAc,IAAA,IAAa,EAAY,EAAW,WAAa,EAAW,WAGjG,CADA,EAAW,UAAY,EACvB,EAAmB,GACnB,GAAI,CACE,EAAW,KAAK,GAAE,EAAe,UAC9B,EAAO,CAKd,KAAK,kBAAkB,EAAW,GAAI,EAAM,EAShD,GAAI,EAAkB,CACpB,IAAM,EAAqB,KAAK,mBAAqB,KAAK,QAC1D,KAAK,kBAAoB,EACrB,KAAK,QACL,KAAK,IAAI,EAAqB,KAAK,kBAAmB,KAAK,UAAU,CAG3E,KAAK,cAAc,CAGrB,cAA6B,CAC3B,GAAI,CAAC,KAAK,QAAS,OACnB,IAAM,EAAU,KAAK,mBAAqB,KAAK,QAC/C,KAAK,MAAQ,eAAiB,KAAK,KAAK,GAAM,CAAE,EAAQ,CAGxD,KAAK,MAAM,SAAS,qCApHX,CAAA,CAAA,EAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
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{injectable as r}from"inversify";var i,a,o,s,c=e((()=>{n(),i=200,a=5e3,o=2,s=class{floorMs=i;ceilingMs=a;backoffMultiplier=o;now(){return Date.now()}onSubscriberError(e,t){this.lastSubscriberError={id:e,error:t,at:this.now()},this.subscriberErrorCount+=1}lastSubscriberError;subscriberErrorCount=0;subscribers=new Map;nextSubscriberKey=0;timer;currentIntervalMs;running=!1;register(e){if(!(e.intervalMs>0))throw Error(`Poll subscriber "${e.id}" must have a positive intervalMs.`);let t=this.nextSubscriberKey++;return this.subscribers.set(t,{...e,lastRunAt:void 0}),()=>{this.subscribers.delete(t)}}start(){this.running||(this.running=!0,this.currentIntervalMs=this.floorMs,this.tick(!1))}stop(){this.running=!1,this.timer&&clearTimeout(this.timer),this.timer=void 0}wake(){this.running&&(this.timer&&clearTimeout(this.timer),this.timer=void 0,this.tick(!0))}tick(e){let t=this.now(),n=!1,r=!1;for(let i of this.subscribers.values())if(e||i.lastRunAt===void 0||t-i.lastRunAt>=i.intervalMs){i.lastRunAt=t,r=!0;try{i.run()&&(n=!0)}catch(e){this.onSubscriberError(i.id,e)}}if(r){let e=this.currentIntervalMs??this.floorMs;this.currentIntervalMs=n?this.floorMs:Math.min(e*this.backoffMultiplier,this.ceilingMs)}this.scheduleNext()}scheduleNext(){if(!this.running)return;let e=this.currentIntervalMs??this.floorMs;this.timer=setTimeout(()=>this.tick(!1),e),this.timer.unref?.()}},s=t([r()],s)}));c();export{s as PollScheduler,c as init_pollScheduler};
|
|
2
|
+
//# sourceMappingURL=pollScheduler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pollScheduler.mjs","names":[],"sources":["../../src/shared/pollScheduler.ts"],"sourcesContent":["/**\n * One master tick that drives every background subscriber in the runner\n * process: channels, watchers, inspectors, anything that used to own its own\n * `setInterval`.\n *\n * WHY ONE TICK AND NOT ONE TIMER PER SUBSCRIBER:\n * The predecessor package (`doom-pi-subagents`) gave every channel, watcher\n * and inspector its own independent interval. None of them coordinated, all of\n * them kept firing when nothing was happening, and the parent process burned\n * CPU while completely idle. A single scheduler that every subscriber reports\n * to can decide, once, whether there is any reason to keep ticking quickly.\n *\n * IDLE BACKOFF:\n * The tick interval starts at `floorMs`. A tick where nothing reported work\n * multiplies the interval by `backoffMultiplier`, capped at `ceilingMs`. A tick\n * where anything reported work resets it straight back to `floorMs`. \"Reported\n * work\" means a subscriber's `run()` returned `true`; returning `false` or\n * `void` means it checked and found nothing to do.\n *\n * WATCH-FIRST, POLL AS A SAFETY NET:\n * The intended pattern is: a subscriber's own `intervalMs` is set generously\n * (roughly 4x looser than how quickly you actually want it checked), and a\n * filesystem watch is the fast path. When the watch fires, its handler calls\n * `wake()`, which runs every subscriber immediately regardless of backoff.\n * Polling on `intervalMs` only exists to cover the platforms and mount types\n * where filesystem watches are unreliable or simply do not fire. Neither path\n * is trusted alone: the watch gives latency, the poll interval gives a bound\n * on how late a check can ever be.\n *\n * DESIGN PATTERNS:\n * - Subscribers are checked for both \"due\" (their own `intervalMs` has\n * elapsed) and \"forced\" (a `wake()` call), so a fast-changing subscriber and\n * a rarely-changing one can share one clock without either starving\n * - A subscriber's key is internal and never the caller-supplied `id`, so two\n * subscribers that reuse the same `id` string (a plausible mistake with\n * per-run subscriber names) cannot make one's `unregister()` delete the\n * other's registration\n * - `run()` is synchronous. Every current subscriber is a file check, and a\n * synchronous contract is what keeps the scheduler's behaviour deterministic\n * under fake timers; an async contract would need microtask flushing on top\n * of timer advancement in every test\n *\n * AVOID:\n * - Reading `this.floorMs` (or the other seam fields) from the constructor:\n * field initializer order runs the base class's defaults before a test\n * subclass's overrides are applied, so a value read in the constructor would\n * see the base default even when a subclass overrides it. Everything that\n * needs these values reads them lazily, from `start()` or `tick()`, which run\n * after construction is complete\n * - Letting a subscriber's thrown error escape `tick()`; it must not stop the\n * tick or affect its peers. It is recorded rather than discarded, since a\n * subscriber that throws every tick otherwise looks identical to an idle one\n */\n\nimport { injectable } from 'inversify';\n\n/** Minimum spacing between ticks once nothing has reported work in a while. */\nconst DEFAULT_FLOOR_MS = 200;\n/** Ceiling the backoff grows toward while idle. */\nconst DEFAULT_CEILING_MS = 5_000;\n/** Growth factor applied to the tick interval on every idle tick. */\nconst DEFAULT_BACKOFF_MULTIPLIER = 2;\n\nexport interface PollSubscription {\n /** For readability in logs and error messages only; not used as a map key. */\n id: string;\n /** How often this subscriber is checked, in milliseconds. Must be positive. */\n intervalMs: number;\n /**\n * Called when due. Return `true` when it did real work, which resets the\n * scheduler's backoff to the floor. Return `false` or nothing when there was\n * nothing to do, which lets idle backoff continue growing.\n */\n run: () => boolean | void;\n}\n\nexport interface IPollScheduler {\n /** Register a subscriber. Call the returned function to unregister it. */\n register(subscription: PollSubscription): () => void;\n /** Run every subscriber immediately, bypassing backoff and due-checks. */\n wake(): void;\n /** Begin ticking. Idempotent: a second call while running does nothing. */\n start(): void;\n /** Stop ticking and clear the pending timer. Idempotent. */\n stop(): void;\n}\n\ninterface InternalSubscriber extends PollSubscription {\n /** `undefined` until the first tick runs it; never `0`, which a test clock may legitimately start at. */\n lastRunAt: number | undefined;\n}\n\n@injectable()\nexport class PollScheduler implements IPollScheduler {\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 backoff curve, so a test subclass overrides these instead.\n */\n protected readonly floorMs: number = DEFAULT_FLOOR_MS;\n protected readonly ceilingMs: number = DEFAULT_CEILING_MS;\n protected readonly backoffMultiplier: number = DEFAULT_BACKOFF_MULTIPLIER;\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * Records a subscriber that threw. Overridable, and deliberately not a\n * stdio write: this runs inside a detached runner whose stdout is the child's\n * captured transcript. The composition root routes these into that process's\n * own diagnostics; the default keeps the most recent one queryable so a\n * failing subscriber can still be found after the fact.\n */\n protected onSubscriberError(id: string, error: unknown): void {\n this.lastSubscriberError = { id, error, at: this.now() };\n this.subscriberErrorCount += 1;\n }\n\n /** The most recent subscriber failure, for diagnostics. */\n lastSubscriberError: { id: string; error: unknown; at: number } | undefined;\n /** How many subscriber failures this scheduler has absorbed. */\n subscriberErrorCount = 0;\n\n private readonly subscribers = new Map<number, InternalSubscriber>();\n private nextSubscriberKey = 0;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private currentIntervalMs: number | undefined;\n private running = false;\n\n register(subscription: PollSubscription): () => void {\n if (!(subscription.intervalMs > 0)) {\n throw new Error(`Poll subscriber \"${subscription.id}\" must have a positive intervalMs.`);\n }\n const key = this.nextSubscriberKey++;\n this.subscribers.set(key, { ...subscription, lastRunAt: undefined });\n return () => {\n this.subscribers.delete(key);\n };\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.currentIntervalMs = this.floorMs;\n this.tick(false);\n }\n\n stop(): void {\n this.running = false;\n if (this.timer) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n wake(): void {\n if (!this.running) return;\n if (this.timer) clearTimeout(this.timer);\n this.timer = undefined;\n this.tick(true);\n }\n\n /**\n * Run every due subscriber (or, when `forced`, every subscriber), fold the\n * outcome into the backoff curve, and schedule the next tick.\n */\n private tick(forced: boolean): void {\n const timestamp = this.now();\n let workHappened = false;\n let anySubscriberRan = false;\n\n for (const subscriber of this.subscribers.values()) {\n const due =\n forced || subscriber.lastRunAt === undefined || timestamp - subscriber.lastRunAt >= subscriber.intervalMs;\n if (!due) continue;\n subscriber.lastRunAt = timestamp;\n anySubscriberRan = true;\n try {\n if (subscriber.run()) workHappened = true;\n } catch (error) {\n // A subscriber's failure is its own: the tick, and every other\n // subscriber's turn within it, continues regardless. But it is not\n // discarded, because a subscriber that throws on every tick is\n // invisible otherwise and looks exactly like one with nothing to do.\n this.onSubscriberError(subscriber.id, error);\n }\n }\n\n // Backoff only moves on evidence. A tick where nothing was even due says\n // nothing about whether the system is idle, so it must not be mistaken for\n // one where subscribers looked and found nothing: growing the interval\n // there would penalise a scheduler whose subscribers simply poll slower\n // than it ticks, and would let a stale interval survive a stop/start.\n if (anySubscriberRan) {\n const previousIntervalMs = this.currentIntervalMs ?? this.floorMs;\n this.currentIntervalMs = workHappened\n ? this.floorMs\n : Math.min(previousIntervalMs * this.backoffMultiplier, this.ceilingMs);\n }\n\n this.scheduleNext();\n }\n\n private scheduleNext(): void {\n if (!this.running) return;\n const delayMs = this.currentIntervalMs ?? this.floorMs;\n this.timer = setTimeout(() => this.tick(false), delayMs);\n // A pending background poll must never be the reason the process stays\n // alive; `unref` is optional only because some test timer shims omit it.\n this.timer.unref?.();\n }\n}\n"],"mappings":"6OAyDM,EAAmB,IAEnB,EAAqB,IAErB,EAA6B,IAgC5B,KAA8C,CAMnD,QAAqC,EACrC,UAAuC,EACvC,kBAA+C,EAE/C,KAAwB,CACtB,OAAO,KAAK,KAAK,CAUnB,kBAA4B,EAAY,EAAsB,CAC5D,KAAK,oBAAsB,CAAE,KAAI,QAAO,GAAI,KAAK,KAAK,CAAE,CACxD,KAAK,sBAAwB,EAI/B,oBAEA,qBAAuB,EAEvB,YAA+B,IAAI,IACnC,kBAA4B,EAC5B,MACA,kBACA,QAAkB,GAElB,SAAS,EAA4C,CACnD,GAAI,EAAE,EAAa,WAAa,GAC9B,MAAU,MAAM,oBAAoB,EAAa,GAAG,oCAAoC,CAE1F,IAAM,EAAM,KAAK,oBAEjB,OADA,KAAK,YAAY,IAAI,EAAK,CAAE,GAAG,EAAc,UAAW,IAAA,GAAW,CAAC,KACvD,CACX,KAAK,YAAY,OAAO,EAAI,EAIhC,OAAc,CACR,KAAK,UACT,KAAK,QAAU,GACf,KAAK,kBAAoB,KAAK,QAC9B,KAAK,KAAK,GAAM,EAGlB,MAAa,CACX,KAAK,QAAU,GACX,KAAK,OAAO,aAAa,KAAK,MAAM,CACxC,KAAK,MAAQ,IAAA,GAGf,MAAa,CACN,KAAK,UACN,KAAK,OAAO,aAAa,KAAK,MAAM,CACxC,KAAK,MAAQ,IAAA,GACb,KAAK,KAAK,GAAK,EAOjB,KAAa,EAAuB,CAClC,IAAM,EAAY,KAAK,KAAK,CACxB,EAAe,GACf,EAAmB,GAEvB,IAAK,IAAM,KAAc,KAAK,YAAY,QAAQ,CAE9C,MAAU,EAAW,YAAc,IAAA,IAAa,EAAY,EAAW,WAAa,EAAW,WAGjG,CADA,EAAW,UAAY,EACvB,EAAmB,GACnB,GAAI,CACE,EAAW,KAAK,GAAE,EAAe,UAC9B,EAAO,CAKd,KAAK,kBAAkB,EAAW,GAAI,EAAM,EAShD,GAAI,EAAkB,CACpB,IAAM,EAAqB,KAAK,mBAAqB,KAAK,QAC1D,KAAK,kBAAoB,EACrB,KAAK,QACL,KAAK,IAAI,EAAqB,KAAK,kBAAmB,KAAK,UAAU,CAG3E,KAAK,cAAc,CAGrB,cAA6B,CAC3B,GAAI,CAAC,KAAK,QAAS,OACnB,IAAM,EAAU,KAAK,mBAAqB,KAAK,QAC/C,KAAK,MAAQ,eAAiB,KAAK,KAAK,GAAM,CAAE,EAAQ,CAGxD,KAAK,MAAM,SAAS,QApHvB,GAAY,CAAA,CAAA,EAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`./atomicJson.cjs`),n=require(`./paths.cjs`),r=require(`./versioned.cjs`);let i=require(`node:fs`);i=e.__toESM(i,1);function a(e,r=Date.now()){let i={version:1,rootSessionId:e.rootSessionId,hostPid:process.pid,startedAt:r};return t.writeAtomicJson(n.scopeOwnerPath(e),i),i}function o(e){let t;try{t=i.readFileSync(n.scopeOwnerPath(e),`utf-8`)}catch{return}let a;try{a=JSON.parse(t)}catch{return}let o=r.parseVersioned(a,[1]);if(!o.ok)return;let s=o.value;if(!(typeof s.rootSessionId!=`string`||!s.rootSessionId)&&Number.isInteger(s.hostPid))return s}exports.readScopeOwner=o,exports.writeScopeOwner=a;
|
|
2
|
+
//# sourceMappingURL=scopeOwner.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scopeOwner.cjs","names":["scopeOwnerPath","fs","parseVersioned"],"sources":["../../src/shared/scopeOwner.ts"],"sourcesContent":["/**\n * Who owns a session scope, recorded on disk so a later sweep can tell an\n * abandoned tree from a live one.\n *\n * WHY A FILE AND NOT AN IN-MEMORY REGISTRY:\n * The process that owns a scope is exactly the process that cannot report on\n * it once it is gone. A crashed session leaves its whole tree behind, and the\n * only thing able to reclaim that is a different process reading something the\n * dead one left written down.\n *\n * WHY LIVENESS IS NOT DECIDED HERE:\n * This module records `hostPid` and reads it back; it does not decide whether a\n * scope is reapable. That rule needs run liveness too - after `/new` the\n * abandoned scope's `hostPid` is the still-live current process, so a\n * hostPid-only test would never reap it - and it belongs with the run registry\n * that knows about runs.\n *\n * AVOID:\n * - Treating a missing or unreadable owner file as \"safe to delete\". An\n * unreadable owner is unknown, not dead\n */\n\nimport * as fs from 'node:fs';\n\nimport { writeAtomicJson } from './atomicJson.ts';\nimport { scopeOwnerPath, type SessionScope } from './paths.ts';\nimport { parseVersioned } from './versioned.ts';\n\nconst SCOPE_OWNER_VERSION = 1;\n\nexport interface ScopeOwnerRecord {\n version: typeof SCOPE_OWNER_VERSION;\n rootSessionId: string;\n /** The process that adopted this scope. Dead pid plus no live runs means reapable. */\n hostPid: number;\n startedAt: number;\n}\n\n/**\n * Claim a scope for this process.\n *\n * Rewritten rather than written once: `/resume` reopens the same session id,\n * so the same scope is legitimately re-adopted by a later process, and the\n * record has to name the process that owns it now rather than the one that\n * first created the directory.\n */\nexport function writeScopeOwner(scope: SessionScope, now: number = Date.now()): ScopeOwnerRecord {\n const record: ScopeOwnerRecord = {\n version: SCOPE_OWNER_VERSION,\n rootSessionId: scope.rootSessionId,\n hostPid: process.pid,\n startedAt: now,\n };\n writeAtomicJson(scopeOwnerPath(scope), record);\n return record;\n}\n\n/** `undefined` when the scope has no readable owner: unknown, which is not the same as dead. */\nexport function readScopeOwner(scope: SessionScope): ScopeOwnerRecord | undefined {\n let raw: string;\n try {\n raw = fs.readFileSync(scopeOwnerPath(scope), 'utf-8');\n } catch {\n // Absent or unreadable. Both mean \"cannot say who owns this\", and the\n // caller must treat that as unknown rather than unowned.\n return undefined;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n\n const result = parseVersioned<ScopeOwnerRecord>(parsed, [SCOPE_OWNER_VERSION]);\n if (!result.ok) return undefined;\n const record = result.value;\n if (typeof record.rootSessionId !== 'string' || !record.rootSessionId) return undefined;\n if (!Number.isInteger(record.hostPid)) return undefined;\n return record;\n}\n"],"mappings":"mLA8CA,SAAgB,EAAgB,EAAqB,EAAc,KAAK,KAAK,CAAoB,CAC/F,IAAM,EAA2B,CAC/B,QAAS,EACT,cAAe,EAAM,cACrB,QAAS,QAAQ,IACjB,UAAW,EACZ,CAED,OADA,EAAA,gBAAgBA,EAAAA,eAAe,EAAM,CAAE,EAAO,CACvC,EAIT,SAAgB,EAAe,EAAmD,CAChF,IAAI,EACJ,GAAI,CACF,EAAMC,EAAG,aAAaD,EAAAA,eAAe,EAAM,CAAE,QAAQ,MAC/C,CAGN,OAGF,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,EAAI,MAClB,CACN,OAGF,IAAM,EAASE,EAAAA,eAAiC,EAAQ,CAAC,EAAoB,CAAC,CAC9E,GAAI,CAAC,EAAO,GAAI,OAChB,IAAM,EAAS,EAAO,MAClB,YAAO,EAAO,eAAkB,UAAY,CAAC,EAAO,gBACnD,OAAO,UAAU,EAAO,QAAQ,CACrC,OAAO"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{init_atomicJson as t,writeAtomicJson as n}from"./atomicJson.mjs";import{init_paths as r,scopeOwnerPath as i}from"./paths.mjs";import{init_versioned as a,parseVersioned as o}from"./versioned.mjs";import*as s from"node:fs";function c(e,t=Date.now()){let r={version:u,rootSessionId:e.rootSessionId,hostPid:process.pid,startedAt:t};return n(i(e),r),r}function l(e){let t;try{t=s.readFileSync(i(e),`utf-8`)}catch{return}let n;try{n=JSON.parse(t)}catch{return}let r=o(n,[u]);if(!r.ok)return;let a=r.value;if(!(typeof a.rootSessionId!=`string`||!a.rootSessionId)&&Number.isInteger(a.hostPid))return a}var u,d=e((()=>{t(),r(),a(),u=1}));d();export{d as init_scopeOwner,l as readScopeOwner,c as writeScopeOwner};
|
|
2
|
+
//# sourceMappingURL=scopeOwner.mjs.map
|