@agimon-ai/doompi-team 0.0.1-alpha.2 → 0.0.1-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -17
- package/dist/adapters/asyncJobTracker.cjs +2 -0
- package/dist/adapters/asyncJobTracker.cjs.map +1 -0
- package/dist/adapters/asyncJobTracker.mjs +2 -0
- package/dist/adapters/asyncJobTracker.mjs.map +1 -0
- package/dist/adapters/atomicJson.cjs +2 -0
- package/dist/adapters/atomicJson.cjs.map +1 -0
- package/dist/adapters/atomicJson.mjs +2 -0
- package/dist/adapters/atomicJson.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pollScheduler.cjs +2 -0
- package/dist/adapters/pollScheduler.cjs.map +1 -0
- package/dist/adapters/pollScheduler.mjs +2 -0
- package/dist/adapters/pollScheduler.mjs.map +1 -0
- package/dist/adapters/process/childTranscript.cjs +5 -0
- package/dist/adapters/process/childTranscript.cjs.map +1 -0
- package/dist/adapters/process/childTranscript.mjs +5 -0
- package/dist/adapters/process/childTranscript.mjs.map +1 -0
- package/dist/adapters/process/env.cjs +2 -0
- package/dist/adapters/process/env.cjs.map +1 -0
- package/dist/adapters/process/env.d.cts +69 -0
- package/dist/adapters/process/env.d.cts.map +1 -0
- package/dist/adapters/process/env.d.mts +69 -0
- package/dist/adapters/process/env.d.mts.map +1 -0
- package/dist/adapters/process/env.mjs +2 -0
- package/dist/adapters/process/env.mjs.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.cjs +2 -0
- package/dist/adapters/process/sdkRunnerEntry.cjs.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.d.cts +5 -0
- package/dist/adapters/process/sdkRunnerEntry.d.cts.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.d.mts +5 -0
- package/dist/adapters/process/sdkRunnerEntry.d.mts.map +1 -0
- package/dist/adapters/process/sdkRunnerEntry.mjs +2 -0
- package/dist/adapters/process/sdkRunnerEntry.mjs.map +1 -0
- package/dist/adapters/processTerminal.cjs +2 -0
- package/dist/adapters/processTerminal.cjs.map +1 -0
- package/dist/adapters/processTerminal.mjs +2 -0
- package/dist/adapters/processTerminal.mjs.map +1 -0
- package/dist/adapters/resultWatcher.cjs +2 -0
- package/dist/adapters/resultWatcher.cjs.map +1 -0
- package/dist/adapters/resultWatcher.mjs +2 -0
- package/dist/adapters/resultWatcher.mjs.map +1 -0
- package/dist/adapters/runIdResolver.cjs +2 -0
- package/dist/adapters/runIdResolver.cjs.map +1 -0
- package/dist/adapters/runIdResolver.mjs +2 -0
- package/dist/adapters/runIdResolver.mjs.map +1 -0
- package/dist/adapters/runRegistry.cjs +2 -0
- package/dist/adapters/runRegistry.cjs.map +1 -0
- package/dist/adapters/runRegistry.mjs +2 -0
- package/dist/adapters/runRegistry.mjs.map +1 -0
- package/dist/adapters/scopeOwner.cjs +2 -0
- package/dist/adapters/scopeOwner.cjs.map +1 -0
- package/dist/adapters/scopeOwner.mjs +2 -0
- package/dist/adapters/scopeOwner.mjs.map +1 -0
- package/dist/adapters/staleRunReconciler.cjs +2 -0
- package/dist/adapters/staleRunReconciler.cjs.map +1 -0
- package/dist/adapters/staleRunReconciler.mjs +2 -0
- package/dist/adapters/staleRunReconciler.mjs.map +1 -0
- package/dist/adapters/statusReader.cjs +2 -0
- package/dist/adapters/statusReader.cjs.map +1 -0
- package/dist/adapters/statusReader.mjs +2 -0
- package/dist/adapters/statusReader.mjs.map +1 -0
- package/dist/adapters/suspendedRuns.cjs +6 -0
- package/dist/adapters/suspendedRuns.cjs.map +1 -0
- package/dist/adapters/suspendedRuns.mjs +6 -0
- package/dist/adapters/suspendedRuns.mjs.map +1 -0
- package/dist/api/backgroundWork.cjs +1 -1
- package/dist/api/backgroundWork.d.cts +2 -2
- package/dist/api/backgroundWork.d.mts +2 -2
- package/dist/api/backgroundWork.mjs +1 -1
- package/dist/api/capabilityCeiling.cjs +1 -1
- package/dist/api/capabilityCeiling.d.cts +3 -3
- package/dist/api/capabilityCeiling.d.mts +3 -3
- package/dist/api/capabilityCeiling.mjs +1 -2
- package/dist/api/delegation.cjs +1 -1
- package/dist/api/delegation.d.cts +1 -1
- package/dist/api/delegation.d.mts +1 -1
- package/dist/api/delegation.mjs +1 -1
- package/dist/api/teamSnapshot.cjs +1 -1
- package/dist/api/teamSnapshot.d.cts +2 -2
- package/dist/api/teamSnapshot.d.mts +2 -2
- package/dist/api/teamSnapshot.mjs +1 -2
- package/dist/commands/slash/chainExpression.cjs.map +1 -0
- package/dist/commands/slash/chainExpression.mjs.map +1 -0
- package/dist/commands/slash/doctor.cjs +5 -0
- package/dist/commands/slash/doctor.cjs.map +1 -0
- package/dist/commands/slash/doctor.mjs +5 -0
- package/dist/{slash/doctor.cjs.map → commands/slash/doctor.mjs.map} +1 -1
- package/dist/{slash → commands/slash}/slashCommands.cjs +1 -1
- package/dist/commands/slash/slashCommands.cjs.map +1 -0
- package/dist/{slash → commands/slash}/slashCommands.mjs +2 -2
- package/dist/commands/slash/slashCommands.mjs.map +1 -0
- package/dist/commands/slash/spawnRequestMapping.cjs.map +1 -0
- package/dist/commands/slash/spawnRequestMapping.mjs.map +1 -0
- package/dist/commands/slash/subagentLaunch.cjs +2 -0
- package/dist/commands/slash/subagentLaunch.cjs.map +1 -0
- package/dist/commands/slash/subagentLaunch.mjs +2 -0
- package/dist/commands/slash/subagentLaunch.mjs.map +1 -0
- package/dist/container/globalBridge.cjs.map +1 -1
- package/dist/container/globalBridge.d.cts +1 -1
- package/dist/container/globalBridge.d.mts +1 -1
- package/dist/container/globalBridge.mjs.map +1 -1
- package/dist/container/index.cjs +1 -1
- package/dist/container/index.cjs.map +1 -1
- package/dist/container/index.d.cts +2 -1
- package/dist/container/index.d.cts.map +1 -1
- package/dist/container/index.d.mts +2 -1
- package/dist/container/index.d.mts.map +1 -1
- package/dist/container/index.mjs +1 -1
- package/dist/container/index.mjs.map +1 -1
- package/dist/container/modules/agents.cjs +1 -1
- package/dist/container/modules/agents.cjs.map +1 -1
- package/dist/container/modules/agents.mjs +1 -1
- package/dist/container/modules/agents.mjs.map +1 -1
- package/dist/container/modules/core.cjs +1 -1
- package/dist/container/modules/core.cjs.map +1 -1
- package/dist/container/modules/core.mjs +1 -1
- package/dist/container/modules/core.mjs.map +1 -1
- package/dist/container/modules/extensions.cjs +1 -1
- package/dist/container/modules/extensions.cjs.map +1 -1
- package/dist/container/modules/extensions.mjs +1 -1
- package/dist/container/modules/extensions.mjs.map +1 -1
- package/dist/container/modules/intercom.cjs +1 -1
- package/dist/container/modules/intercom.cjs.map +1 -1
- package/dist/container/modules/intercom.mjs +1 -1
- package/dist/container/modules/intercom.mjs.map +1 -1
- package/dist/container/modules/runs.cjs +1 -1
- package/dist/container/modules/runs.cjs.map +1 -1
- package/dist/container/modules/runs.mjs +1 -1
- package/dist/container/modules/runs.mjs.map +1 -1
- package/dist/container/types.cjs +1 -1
- package/dist/container/types.cjs.map +1 -1
- package/dist/container/types.d.cts +1 -0
- package/dist/container/types.d.cts.map +1 -1
- package/dist/container/types.d.mts +1 -0
- package/dist/container/types.d.mts.map +1 -1
- package/dist/container/types.mjs +1 -1
- package/dist/container/types.mjs.map +1 -1
- package/dist/env.cjs +1 -2
- package/dist/env.d.cts +2 -65
- package/dist/env.d.mts +2 -65
- package/dist/env.mjs +1 -1
- package/dist/env.mjs.map +1 -1
- package/dist/extensions/config.cjs.map +1 -1
- package/dist/extensions/config.mjs.map +1 -1
- package/dist/extensions/delegationBridge.cjs.map +1 -1
- package/dist/extensions/delegationBridge.mjs.map +1 -1
- package/dist/extensions/doom.cjs +1 -1
- package/dist/extensions/doom.cjs.map +1 -1
- package/dist/extensions/doom.mjs +1 -1
- package/dist/extensions/doom.mjs.map +1 -1
- package/dist/extensions/fablePlanBridge.cjs +1 -1
- package/dist/extensions/fablePlanBridge.cjs.map +1 -1
- package/dist/extensions/fablePlanBridge.mjs +1 -1
- package/dist/extensions/fablePlanBridge.mjs.map +1 -1
- package/dist/extensions/managementActions.cjs +1 -1
- package/dist/extensions/managementActions.cjs.map +1 -1
- package/dist/extensions/managementActions.mjs +1 -1
- package/dist/extensions/managementActions.mjs.map +1 -1
- package/dist/extensions/operationJournal.cjs +1 -1
- package/dist/extensions/operationJournal.cjs.map +1 -1
- package/dist/extensions/operationJournal.mjs +1 -1
- package/dist/extensions/operationJournal.mjs.map +1 -1
- package/dist/extensions/orchestratorPrompt.cjs +5 -6
- package/dist/extensions/orchestratorPrompt.cjs.map +1 -1
- package/dist/extensions/orchestratorPrompt.mjs +5 -6
- package/dist/extensions/orchestratorPrompt.mjs.map +1 -1
- package/dist/extensions/schemas.mjs.map +1 -1
- package/dist/extensions/spawnPlan.cjs +3 -2
- package/dist/extensions/spawnPlan.cjs.map +1 -1
- package/dist/extensions/spawnPlan.mjs +3 -2
- package/dist/extensions/spawnPlan.mjs.map +1 -1
- package/dist/extensions/standard.cjs +1 -1
- package/dist/extensions/standard.cjs.map +1 -1
- package/dist/extensions/standard.mjs +1 -1
- package/dist/extensions/standard.mjs.map +1 -1
- package/dist/extensions/statusViews.cjs +1 -1
- package/dist/extensions/statusViews.cjs.map +1 -1
- package/dist/extensions/statusViews.mjs +2 -2
- package/dist/extensions/statusViews.mjs.map +1 -1
- package/dist/extensions/subagentPromptRuntime.cjs +11 -11
- package/dist/extensions/subagentPromptRuntime.cjs.map +1 -1
- package/dist/extensions/subagentPromptRuntime.d.cts.map +1 -1
- package/dist/extensions/subagentPromptRuntime.d.mts.map +1 -1
- package/dist/extensions/subagentPromptRuntime.mjs +5 -5
- package/dist/extensions/subagentPromptRuntime.mjs.map +1 -1
- package/dist/extensions/subagentTool.cjs +5 -6
- package/dist/extensions/subagentTool.cjs.map +1 -1
- package/dist/extensions/subagentTool.mjs +5 -6
- package/dist/extensions/subagentTool.mjs.map +1 -1
- package/dist/extensions/toolDescription.cjs +1 -1
- package/dist/extensions/toolDescription.cjs.map +1 -1
- package/dist/extensions/toolDescription.mjs +1 -1
- package/dist/extensions/toolDescription.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/runs/background/cliRunnerEntry.cjs +1 -2
- package/dist/runs/background/cliRunnerEntry.mjs +1 -2
- package/dist/runs/sdkRunnerEntry.cjs +1 -2
- package/dist/runs/sdkRunnerEntry.d.cts +2 -1
- package/dist/runs/sdkRunnerEntry.d.mts +2 -1
- package/dist/runs/sdkRunnerEntry.mjs +1 -2
- package/dist/schemas/team/backgroundWork.cjs +2 -0
- package/dist/schemas/team/backgroundWork.cjs.map +1 -0
- package/dist/{protocol → schemas/team}/backgroundWork.d.cts +2 -2
- package/dist/schemas/team/backgroundWork.d.cts.map +1 -0
- package/dist/{protocol → schemas/team}/backgroundWork.d.mts +2 -2
- package/dist/schemas/team/backgroundWork.d.mts.map +1 -0
- package/dist/schemas/team/backgroundWork.mjs +2 -0
- package/dist/{protocol/backgroundWork.cjs.map → schemas/team/backgroundWork.mjs.map} +1 -1
- package/dist/schemas/team/capabilityCeiling.cjs +2 -0
- package/dist/schemas/team/capabilityCeiling.cjs.map +1 -0
- package/dist/{protocol → schemas/team}/capabilityCeiling.d.cts +3 -3
- package/dist/schemas/team/capabilityCeiling.d.cts.map +1 -0
- package/dist/{protocol → schemas/team}/capabilityCeiling.d.mts +3 -3
- package/dist/schemas/team/capabilityCeiling.d.mts.map +1 -0
- package/dist/schemas/team/capabilityCeiling.mjs +2 -0
- package/dist/schemas/team/capabilityCeiling.mjs.map +1 -0
- package/dist/schemas/team/delegation.cjs.map +1 -0
- package/dist/{protocol → schemas/team}/delegation.d.cts +1 -1
- package/dist/schemas/team/delegation.d.cts.map +1 -0
- package/dist/{protocol → schemas/team}/delegation.d.mts +1 -1
- package/dist/schemas/team/delegation.d.mts.map +1 -0
- package/dist/schemas/team/delegation.mjs.map +1 -0
- package/dist/schemas/team/packageConfig.cjs +2 -0
- package/dist/schemas/team/packageConfig.cjs.map +1 -0
- package/dist/schemas/team/packageConfig.mjs +2 -0
- package/dist/schemas/team/packageConfig.mjs.map +1 -0
- package/dist/services/agents/discovery.cjs +2 -0
- package/dist/services/agents/discovery.cjs.map +1 -0
- package/dist/services/agents/discovery.mjs +2 -0
- package/dist/services/agents/discovery.mjs.map +1 -0
- package/dist/services/agents/frontmatter.cjs.map +1 -0
- package/dist/{agents → services/agents}/frontmatter.mjs +1 -1
- package/dist/services/agents/frontmatter.mjs.map +1 -0
- package/dist/services/agents/loader.cjs +2 -0
- package/dist/services/agents/loader.cjs.map +1 -0
- package/dist/services/agents/loader.mjs +2 -0
- package/dist/services/agents/loader.mjs.map +1 -0
- package/dist/services/agents/memory.cjs +3 -0
- package/dist/services/agents/memory.cjs.map +1 -0
- package/dist/{agents → services/agents}/memory.mjs +1 -1
- package/dist/services/agents/memory.mjs.map +1 -0
- package/dist/services/agents/projectRoot.cjs +2 -0
- package/dist/services/agents/projectRoot.cjs.map +1 -0
- package/dist/services/agents/projectRoot.mjs +2 -0
- package/dist/services/agents/projectRoot.mjs.map +1 -0
- package/dist/services/agents/settings.cjs +2 -0
- package/dist/services/agents/settings.cjs.map +1 -0
- package/dist/services/agents/settings.mjs +2 -0
- package/dist/services/agents/settings.mjs.map +1 -0
- package/dist/services/agents/skills.cjs +0 -0
- package/dist/services/agents/skills.cjs.map +1 -0
- package/dist/services/agents/skills.mjs +5 -0
- package/dist/services/agents/skills.mjs.map +1 -0
- package/dist/{agents → services/agents}/types.d.cts +4 -4
- package/dist/services/agents/types.d.cts.map +1 -0
- package/dist/{agents → services/agents}/types.d.mts +4 -4
- package/dist/services/agents/types.d.mts.map +1 -0
- package/dist/services/api/backgroundWork.cjs +1 -0
- package/dist/services/api/backgroundWork.d.cts +1 -0
- package/dist/services/api/backgroundWork.d.mts +1 -0
- package/dist/services/api/backgroundWork.mjs +1 -0
- package/dist/services/api/capabilityCeiling.cjs +1 -0
- package/dist/services/api/capabilityCeiling.d.cts +2 -0
- package/dist/services/api/capabilityCeiling.d.mts +2 -0
- package/dist/services/api/capabilityCeiling.mjs +2 -0
- package/dist/services/api/capabilityCeiling.mjs.map +1 -0
- package/dist/services/api/teamSnapshot.cjs +1 -0
- package/dist/services/api/teamSnapshot.d.cts +1 -0
- package/dist/services/api/teamSnapshot.d.mts +1 -0
- package/dist/services/api/teamSnapshot.mjs +2 -0
- package/dist/services/api/teamSnapshot.mjs.map +1 -0
- package/dist/services/intercom/intercomBridge.cjs.map +1 -0
- package/dist/services/intercom/intercomBridge.mjs.map +1 -0
- package/dist/services/intercom/nativeTeamChannel.cjs +5 -0
- package/dist/services/intercom/nativeTeamChannel.cjs.map +1 -0
- package/dist/{intercom → services/intercom}/nativeTeamChannel.d.cts +1 -1
- package/dist/services/intercom/nativeTeamChannel.d.cts.map +1 -0
- package/dist/{intercom → services/intercom}/nativeTeamChannel.d.mts +1 -1
- package/dist/services/intercom/nativeTeamChannel.d.mts.map +1 -0
- package/dist/services/intercom/nativeTeamChannel.mjs +5 -0
- package/dist/services/intercom/nativeTeamChannel.mjs.map +1 -0
- package/dist/services/intercom/supervisorControlChannel.cjs +2 -0
- package/dist/services/intercom/supervisorControlChannel.cjs.map +1 -0
- package/dist/{intercom → services/intercom}/supervisorControlChannel.mjs +1 -1
- package/dist/services/intercom/supervisorControlChannel.mjs.map +1 -0
- package/dist/services/runs/background/asyncExecution.cjs +2 -0
- package/dist/services/runs/background/asyncExecution.cjs.map +1 -0
- package/dist/services/runs/background/asyncExecution.mjs +2 -0
- package/dist/services/runs/background/asyncExecution.mjs.map +1 -0
- package/dist/services/runs/background/asyncJobTracker.cjs +1 -0
- package/dist/services/runs/background/asyncJobTracker.mjs +2 -0
- package/dist/services/runs/background/asyncJobTracker.mjs.map +1 -0
- package/dist/services/runs/background/cliRunnerEntry.cjs +2 -0
- package/dist/services/runs/background/cliRunnerEntry.cjs.map +1 -0
- package/dist/services/runs/background/cliRunnerEntry.mjs +2 -0
- package/dist/services/runs/background/cliRunnerEntry.mjs.map +1 -0
- package/dist/services/runs/background/completionBatcher.cjs.map +1 -0
- package/dist/services/runs/background/completionBatcher.mjs +2 -0
- package/dist/services/runs/background/completionBatcher.mjs.map +1 -0
- package/dist/{runs → services/runs}/background/notify.cjs +1 -1
- package/dist/services/runs/background/notify.cjs.map +1 -0
- package/dist/{runs → services/runs}/background/notify.mjs +1 -1
- package/dist/services/runs/background/notify.mjs.map +1 -0
- package/dist/services/runs/background/parentWatchdog.cjs.map +1 -0
- package/dist/services/runs/background/parentWatchdog.mjs.map +1 -0
- package/dist/services/runs/background/processTerminal.cjs +1 -0
- package/dist/services/runs/background/processTerminal.mjs +2 -0
- package/dist/services/runs/background/processTerminal.mjs.map +1 -0
- package/dist/services/runs/background/resultWatcher.cjs +1 -0
- package/dist/services/runs/background/resultWatcher.mjs +2 -0
- package/dist/services/runs/background/resultWatcher.mjs.map +1 -0
- package/dist/services/runs/background/runIdResolver.cjs +1 -0
- package/dist/services/runs/background/runIdResolver.mjs +2 -0
- package/dist/services/runs/background/runIdResolver.mjs.map +1 -0
- package/dist/services/runs/background/runnerBootstrap.cjs +2 -0
- package/dist/services/runs/background/runnerBootstrap.cjs.map +1 -0
- package/dist/services/runs/background/runnerBootstrap.mjs +2 -0
- package/dist/services/runs/background/runnerBootstrap.mjs.map +1 -0
- package/dist/services/runs/background/runnerExecution.cjs +2 -0
- package/dist/services/runs/background/runnerExecution.cjs.map +1 -0
- package/dist/services/runs/background/runnerExecution.mjs +2 -0
- package/dist/services/runs/background/runnerExecution.mjs.map +1 -0
- package/dist/{runs → services/runs}/background/runnerReporting.cjs +2 -2
- package/dist/services/runs/background/runnerReporting.cjs.map +1 -0
- package/dist/{runs → services/runs}/background/runnerReporting.mjs +2 -2
- package/dist/services/runs/background/runnerReporting.mjs.map +1 -0
- package/dist/services/runs/background/runnerTeamMembership.cjs +2 -0
- package/dist/services/runs/background/runnerTeamMembership.cjs.map +1 -0
- package/dist/services/runs/background/runnerTeamMembership.mjs +2 -0
- package/dist/services/runs/background/runnerTeamMembership.mjs.map +1 -0
- package/dist/services/runs/background/spawnHandshake.cjs +2 -0
- package/dist/services/runs/background/spawnHandshake.cjs.map +1 -0
- package/dist/services/runs/background/spawnHandshake.mjs +2 -0
- package/dist/services/runs/background/spawnHandshake.mjs.map +1 -0
- package/dist/services/runs/background/staleRunReconciler.cjs +1 -0
- package/dist/services/runs/background/staleRunReconciler.mjs +2 -0
- package/dist/services/runs/background/staleRunReconciler.mjs.map +1 -0
- package/dist/services/runs/background/statusReader.cjs +1 -0
- package/dist/services/runs/background/statusReader.mjs +2 -0
- package/dist/services/runs/background/statusReader.mjs.map +1 -0
- package/dist/services/runs/background/statusWriter.cjs +2 -0
- package/dist/services/runs/background/statusWriter.cjs.map +1 -0
- package/dist/services/runs/background/statusWriter.mjs +2 -0
- package/dist/services/runs/background/statusWriter.mjs.map +1 -0
- package/dist/services/runs/background/subagentWait.cjs +2 -0
- package/dist/services/runs/background/subagentWait.cjs.map +1 -0
- package/dist/services/runs/background/subagentWait.mjs +2 -0
- package/dist/services/runs/background/subagentWait.mjs.map +1 -0
- package/dist/{runs/background/terminalPersistence.mjs → services/runs/background/terminalPersistence.cjs} +2 -2
- package/dist/services/runs/background/terminalPersistence.cjs.map +1 -0
- package/dist/services/runs/background/terminalPersistence.mjs +2 -0
- package/dist/services/runs/background/terminalPersistence.mjs.map +1 -0
- package/dist/services/runs/extensionErrorTelemetry.cjs +2 -0
- package/dist/services/runs/extensionErrorTelemetry.cjs.map +1 -0
- package/dist/services/runs/extensionErrorTelemetry.mjs +2 -0
- package/dist/services/runs/extensionErrorTelemetry.mjs.map +1 -0
- package/dist/services/runs/registry/processGroup.cjs.map +1 -0
- package/dist/services/runs/registry/processGroup.mjs.map +1 -0
- package/dist/services/runs/registry/runRegistry.cjs +1 -0
- package/dist/services/runs/registry/runRegistry.mjs +2 -0
- package/dist/services/runs/registry/runRegistry.mjs.map +1 -0
- package/dist/services/runs/registry/sessionLifecycle.cjs +2 -0
- package/dist/services/runs/registry/sessionLifecycle.cjs.map +1 -0
- package/dist/services/runs/registry/sessionLifecycle.mjs +2 -0
- package/dist/services/runs/registry/sessionLifecycle.mjs.map +1 -0
- package/dist/services/runs/registry/suspendedRuns.cjs +1 -0
- package/dist/services/runs/registry/suspendedRuns.mjs +2 -0
- package/dist/services/runs/registry/suspendedRuns.mjs.map +1 -0
- package/dist/services/runs/sdkRunnerEntry.cjs +1 -0
- package/dist/services/runs/sdkRunnerEntry.mjs +1 -0
- package/dist/services/runs/shared/claudeFableProfile.cjs +4 -0
- package/dist/services/runs/shared/claudeFableProfile.cjs.map +1 -0
- package/dist/services/runs/shared/claudeFableProfile.mjs +4 -0
- package/dist/services/runs/shared/claudeFableProfile.mjs.map +1 -0
- package/dist/services/runs/shared/depthGuard.cjs +2 -0
- package/dist/services/runs/shared/depthGuard.cjs.map +1 -0
- package/dist/services/runs/shared/depthGuard.mjs +2 -0
- package/dist/services/runs/shared/depthGuard.mjs.map +1 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.cjs +2 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.cjs.map +1 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.mjs +2 -0
- package/dist/services/runs/shared/mcpDirectToolAllowlist.mjs.map +1 -0
- package/dist/services/runs/shared/modelFallback.cjs +2 -0
- package/dist/services/runs/shared/modelFallback.cjs.map +1 -0
- package/dist/services/runs/shared/modelFallback.mjs +2 -0
- package/dist/services/runs/shared/modelFallback.mjs.map +1 -0
- package/dist/services/runs/shared/modelScope.cjs +2 -0
- package/dist/services/runs/shared/modelScope.cjs.map +1 -0
- package/dist/services/runs/shared/modelScope.mjs +2 -0
- package/dist/services/runs/shared/modelScope.mjs.map +1 -0
- package/dist/services/runs/shared/nestedPath.cjs +2 -0
- package/dist/services/runs/shared/nestedPath.cjs.map +1 -0
- package/dist/services/runs/shared/nestedPath.mjs +2 -0
- package/dist/services/runs/shared/nestedPath.mjs.map +1 -0
- package/dist/services/runs/shared/piArgs.cjs +2 -0
- package/dist/services/runs/shared/piArgs.cjs.map +1 -0
- package/dist/services/runs/shared/piArgs.mjs +2 -0
- package/dist/services/runs/shared/piArgs.mjs.map +1 -0
- package/dist/services/runs/shared/piSpawn.cjs +2 -0
- package/dist/services/runs/shared/piSpawn.cjs.map +1 -0
- package/dist/services/runs/shared/piSpawn.mjs +2 -0
- package/dist/services/runs/shared/piSpawn.mjs.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.cjs +2 -0
- package/dist/services/runs/shared/runWithConcurrency.cjs.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.d.cts +25 -0
- package/dist/services/runs/shared/runWithConcurrency.d.cts.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.d.mts +25 -0
- package/dist/services/runs/shared/runWithConcurrency.d.mts.map +1 -0
- package/dist/services/runs/shared/runWithConcurrency.mjs +2 -0
- package/dist/services/runs/shared/runWithConcurrency.mjs.map +1 -0
- package/dist/services/runs/shared/runtimeRegistry.cjs +2 -0
- package/dist/services/runs/shared/runtimeRegistry.cjs.map +1 -0
- package/dist/services/runs/shared/runtimeRegistry.mjs +2 -0
- package/dist/services/runs/shared/runtimeRegistry.mjs.map +1 -0
- package/dist/services/runs/shared/structuredOutput.cjs +2 -0
- package/dist/{runs → services/runs}/shared/structuredOutput.cjs.map +1 -1
- package/dist/services/runs/shared/structuredOutput.mjs +2 -0
- package/dist/{runs → services/runs}/shared/structuredOutput.mjs.map +1 -1
- package/dist/services/runs/shared/toolAvailability.cjs +2 -0
- package/dist/services/runs/shared/toolAvailability.cjs.map +1 -0
- package/dist/services/runs/shared/toolAvailability.mjs +2 -0
- package/dist/services/runs/shared/toolAvailability.mjs.map +1 -0
- package/dist/services/runs/shared/toolBudget.cjs +2 -0
- package/dist/services/runs/shared/toolBudget.cjs.map +1 -0
- package/dist/services/runs/shared/toolBudget.mjs +2 -0
- package/dist/services/runs/shared/toolBudget.mjs.map +1 -0
- package/dist/shared/artifacts.cjs.map +1 -1
- package/dist/shared/artifacts.mjs.map +1 -1
- package/dist/shared/atomicJson.cjs +1 -2
- package/dist/shared/atomicJson.mjs +1 -1
- package/dist/shared/atomicJson.mjs.map +1 -1
- package/dist/shared/childTranscript.cjs +1 -5
- package/dist/shared/childTranscript.mjs +1 -4
- package/dist/shared/childTranscript.mjs.map +1 -1
- package/dist/shared/fileSystemRetry.cjs +1 -1
- package/dist/shared/fileSystemRetry.cjs.map +1 -1
- package/dist/shared/fileSystemRetry.mjs +1 -1
- package/dist/shared/fileSystemRetry.mjs.map +1 -1
- package/dist/shared/formatters.cjs.map +1 -1
- package/dist/shared/formatters.mjs.map +1 -1
- package/dist/shared/paths.cjs +1 -1
- package/dist/shared/paths.cjs.map +1 -1
- package/dist/shared/paths.mjs +1 -1
- package/dist/shared/paths.mjs.map +1 -1
- package/dist/shared/pollScheduler.cjs +1 -2
- package/dist/shared/pollScheduler.mjs +1 -1
- package/dist/shared/pollScheduler.mjs.map +1 -1
- package/dist/shared/scopeOwner.cjs +1 -2
- package/dist/shared/scopeOwner.mjs +1 -1
- package/dist/shared/scopeOwner.mjs.map +1 -1
- package/dist/tui/agentCatalog.cjs +1 -1
- package/dist/tui/agentCatalog.cjs.map +1 -1
- package/dist/tui/agentCatalog.mjs +1 -1
- package/dist/tui/agentCatalog.mjs.map +1 -1
- package/dist/tui/agentResourceProjection.cjs +1 -1
- package/dist/tui/agentResourceProjection.cjs.map +1 -1
- package/dist/tui/agentResourceProjection.mjs +1 -1
- package/dist/tui/agentResourceProjection.mjs.map +1 -1
- package/dist/tui/completionNotice.cjs +1 -1
- package/dist/tui/completionNotice.cjs.map +1 -1
- package/dist/tui/completionNotice.mjs +1 -1
- package/dist/tui/completionNotice.mjs.map +1 -1
- package/dist/tui/fleet.cjs +2 -2
- package/dist/tui/fleet.cjs.map +1 -1
- package/dist/tui/fleet.mjs +2 -2
- package/dist/tui/fleet.mjs.map +1 -1
- package/dist/tui/fleetAgentView.cjs.map +1 -1
- package/dist/tui/fleetAgentView.mjs.map +1 -1
- package/dist/tui/fleetStatus.cjs +1 -1
- package/dist/tui/fleetStatus.cjs.map +1 -1
- package/dist/tui/fleetStatus.mjs +1 -1
- package/dist/tui/fleetStatus.mjs.map +1 -1
- package/dist/tui/fleetTranscript.cjs +1 -1
- package/dist/tui/fleetTranscript.cjs.map +1 -1
- package/dist/tui/fleetTranscript.mjs +1 -1
- package/dist/tui/fleetTranscript.mjs.map +1 -1
- package/dist/tui/register.cjs +1 -1
- package/dist/tui/register.cjs.map +1 -1
- package/dist/tui/register.mjs +1 -1
- package/dist/tui/register.mjs.map +1 -1
- package/dist/tui/subagentToolRender.cjs +3 -0
- package/dist/tui/subagentToolRender.cjs.map +1 -0
- package/dist/tui/subagentToolRender.mjs +3 -0
- package/dist/tui/subagentToolRender.mjs.map +1 -0
- package/dist/{types.d.cts → types/index.d.cts} +2 -2
- package/dist/types/index.d.cts.map +1 -0
- package/dist/{types.d.mts → types/index.d.mts} +2 -2
- package/dist/types/index.d.mts.map +1 -0
- package/package.json +23 -16
- package/dist/agents/discovery.cjs +0 -2
- package/dist/agents/discovery.cjs.map +0 -1
- package/dist/agents/discovery.mjs +0 -2
- package/dist/agents/discovery.mjs.map +0 -1
- package/dist/agents/frontmatter.cjs.map +0 -1
- package/dist/agents/frontmatter.mjs.map +0 -1
- package/dist/agents/loader.cjs +0 -2
- package/dist/agents/loader.cjs.map +0 -1
- package/dist/agents/loader.mjs +0 -2
- package/dist/agents/loader.mjs.map +0 -1
- package/dist/agents/memory.cjs +0 -3
- package/dist/agents/memory.cjs.map +0 -1
- package/dist/agents/memory.mjs.map +0 -1
- package/dist/agents/projectRoot.cjs +0 -2
- package/dist/agents/projectRoot.cjs.map +0 -1
- package/dist/agents/projectRoot.mjs +0 -2
- package/dist/agents/projectRoot.mjs.map +0 -1
- package/dist/agents/settings.cjs +0 -2
- package/dist/agents/settings.cjs.map +0 -1
- package/dist/agents/settings.mjs +0 -2
- package/dist/agents/settings.mjs.map +0 -1
- package/dist/agents/skills.cjs +0 -0
- package/dist/agents/skills.cjs.map +0 -1
- package/dist/agents/skills.mjs +0 -5
- package/dist/agents/skills.mjs.map +0 -1
- package/dist/agents/types.d.cts.map +0 -1
- package/dist/agents/types.d.mts.map +0 -1
- package/dist/api/capabilityCeiling.mjs.map +0 -1
- package/dist/api/teamSnapshot.mjs.map +0 -1
- package/dist/env.cjs.map +0 -1
- package/dist/env.d.cts.map +0 -1
- package/dist/env.d.mts.map +0 -1
- package/dist/extension.cjs +0 -2
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.mjs +0 -2
- package/dist/extension.mjs.map +0 -1
- package/dist/intercom/intercomBridge.cjs.map +0 -1
- package/dist/intercom/intercomBridge.mjs.map +0 -1
- package/dist/intercom/nativeTeamChannel.cjs +0 -5
- package/dist/intercom/nativeTeamChannel.cjs.map +0 -1
- package/dist/intercom/nativeTeamChannel.d.cts.map +0 -1
- package/dist/intercom/nativeTeamChannel.d.mts.map +0 -1
- package/dist/intercom/nativeTeamChannel.mjs +0 -5
- package/dist/intercom/nativeTeamChannel.mjs.map +0 -1
- package/dist/intercom/supervisorControlChannel.cjs +0 -2
- package/dist/intercom/supervisorControlChannel.cjs.map +0 -1
- package/dist/intercom/supervisorControlChannel.mjs.map +0 -1
- package/dist/protocol/backgroundWork.cjs +0 -2
- package/dist/protocol/backgroundWork.d.cts.map +0 -1
- package/dist/protocol/backgroundWork.d.mts.map +0 -1
- package/dist/protocol/backgroundWork.mjs +0 -2
- package/dist/protocol/backgroundWork.mjs.map +0 -1
- package/dist/protocol/capabilityCeiling.cjs +0 -2
- package/dist/protocol/capabilityCeiling.cjs.map +0 -1
- package/dist/protocol/capabilityCeiling.d.cts.map +0 -1
- package/dist/protocol/capabilityCeiling.d.mts.map +0 -1
- package/dist/protocol/capabilityCeiling.mjs +0 -2
- package/dist/protocol/capabilityCeiling.mjs.map +0 -1
- package/dist/protocol/delegation.cjs.map +0 -1
- package/dist/protocol/delegation.d.cts.map +0 -1
- package/dist/protocol/delegation.d.mts.map +0 -1
- package/dist/protocol/delegation.mjs.map +0 -1
- package/dist/runs/background/asyncExecution.cjs +0 -2
- package/dist/runs/background/asyncExecution.cjs.map +0 -1
- package/dist/runs/background/asyncExecution.mjs +0 -2
- package/dist/runs/background/asyncExecution.mjs.map +0 -1
- package/dist/runs/background/asyncJobTracker.cjs +0 -2
- package/dist/runs/background/asyncJobTracker.cjs.map +0 -1
- package/dist/runs/background/asyncJobTracker.mjs +0 -2
- package/dist/runs/background/asyncJobTracker.mjs.map +0 -1
- package/dist/runs/background/cliRunnerEntry.cjs.map +0 -1
- package/dist/runs/background/cliRunnerEntry.mjs.map +0 -1
- package/dist/runs/background/completionBatcher.cjs.map +0 -1
- package/dist/runs/background/completionBatcher.mjs +0 -2
- package/dist/runs/background/completionBatcher.mjs.map +0 -1
- package/dist/runs/background/notify.cjs.map +0 -1
- package/dist/runs/background/notify.mjs.map +0 -1
- package/dist/runs/background/parentWatchdog.cjs.map +0 -1
- package/dist/runs/background/parentWatchdog.mjs.map +0 -1
- package/dist/runs/background/processTerminal.cjs +0 -2
- package/dist/runs/background/processTerminal.cjs.map +0 -1
- package/dist/runs/background/processTerminal.mjs +0 -2
- package/dist/runs/background/processTerminal.mjs.map +0 -1
- package/dist/runs/background/resultWatcher.cjs +0 -2
- package/dist/runs/background/resultWatcher.cjs.map +0 -1
- package/dist/runs/background/resultWatcher.mjs +0 -2
- package/dist/runs/background/resultWatcher.mjs.map +0 -1
- package/dist/runs/background/runIdResolver.cjs +0 -2
- package/dist/runs/background/runIdResolver.cjs.map +0 -1
- package/dist/runs/background/runIdResolver.mjs +0 -2
- package/dist/runs/background/runIdResolver.mjs.map +0 -1
- package/dist/runs/background/runnerBootstrap.cjs +0 -2
- package/dist/runs/background/runnerBootstrap.cjs.map +0 -1
- package/dist/runs/background/runnerBootstrap.mjs +0 -2
- package/dist/runs/background/runnerBootstrap.mjs.map +0 -1
- package/dist/runs/background/runnerExecution.cjs +0 -2
- package/dist/runs/background/runnerExecution.cjs.map +0 -1
- package/dist/runs/background/runnerExecution.mjs +0 -2
- package/dist/runs/background/runnerExecution.mjs.map +0 -1
- package/dist/runs/background/runnerReporting.cjs.map +0 -1
- package/dist/runs/background/runnerReporting.mjs.map +0 -1
- package/dist/runs/background/runnerTeamMembership.cjs +0 -2
- package/dist/runs/background/runnerTeamMembership.cjs.map +0 -1
- package/dist/runs/background/runnerTeamMembership.mjs +0 -2
- package/dist/runs/background/runnerTeamMembership.mjs.map +0 -1
- package/dist/runs/background/spawnHandshake.cjs +0 -2
- package/dist/runs/background/spawnHandshake.cjs.map +0 -1
- package/dist/runs/background/spawnHandshake.mjs +0 -2
- package/dist/runs/background/spawnHandshake.mjs.map +0 -1
- package/dist/runs/background/staleRunReconciler.cjs +0 -2
- package/dist/runs/background/staleRunReconciler.cjs.map +0 -1
- package/dist/runs/background/staleRunReconciler.mjs +0 -2
- package/dist/runs/background/staleRunReconciler.mjs.map +0 -1
- package/dist/runs/background/statusReader.cjs +0 -2
- package/dist/runs/background/statusReader.cjs.map +0 -1
- package/dist/runs/background/statusReader.mjs +0 -2
- package/dist/runs/background/statusReader.mjs.map +0 -1
- package/dist/runs/background/statusWriter.cjs +0 -2
- package/dist/runs/background/statusWriter.cjs.map +0 -1
- package/dist/runs/background/statusWriter.mjs +0 -2
- package/dist/runs/background/statusWriter.mjs.map +0 -1
- package/dist/runs/background/subagentWait.cjs +0 -2
- package/dist/runs/background/subagentWait.cjs.map +0 -1
- package/dist/runs/background/subagentWait.mjs +0 -2
- package/dist/runs/background/subagentWait.mjs.map +0 -1
- package/dist/runs/background/terminalPersistence.cjs +0 -2
- package/dist/runs/background/terminalPersistence.cjs.map +0 -1
- package/dist/runs/background/terminalPersistence.mjs.map +0 -1
- package/dist/runs/registry/processGroup.cjs.map +0 -1
- package/dist/runs/registry/processGroup.mjs.map +0 -1
- package/dist/runs/registry/runRegistry.cjs +0 -2
- package/dist/runs/registry/runRegistry.cjs.map +0 -1
- package/dist/runs/registry/runRegistry.mjs +0 -2
- package/dist/runs/registry/runRegistry.mjs.map +0 -1
- package/dist/runs/registry/sessionLifecycle.cjs +0 -2
- package/dist/runs/registry/sessionLifecycle.cjs.map +0 -1
- package/dist/runs/registry/sessionLifecycle.mjs +0 -2
- package/dist/runs/registry/sessionLifecycle.mjs.map +0 -1
- package/dist/runs/registry/suspendedRuns.cjs +0 -4
- package/dist/runs/registry/suspendedRuns.cjs.map +0 -1
- package/dist/runs/registry/suspendedRuns.mjs +0 -4
- package/dist/runs/registry/suspendedRuns.mjs.map +0 -1
- package/dist/runs/sdkRunnerEntry.cjs.map +0 -1
- package/dist/runs/sdkRunnerEntry.mjs.map +0 -1
- package/dist/runs/shared/claudeFableProfile.cjs +0 -4
- package/dist/runs/shared/claudeFableProfile.cjs.map +0 -1
- package/dist/runs/shared/claudeFableProfile.mjs +0 -4
- package/dist/runs/shared/claudeFableProfile.mjs.map +0 -1
- package/dist/runs/shared/depthGuard.cjs +0 -2
- package/dist/runs/shared/depthGuard.cjs.map +0 -1
- package/dist/runs/shared/depthGuard.mjs +0 -2
- package/dist/runs/shared/depthGuard.mjs.map +0 -1
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs +0 -2
- package/dist/runs/shared/mcpDirectToolAllowlist.cjs.map +0 -1
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs +0 -2
- package/dist/runs/shared/mcpDirectToolAllowlist.mjs.map +0 -1
- package/dist/runs/shared/modelFallback.cjs +0 -2
- package/dist/runs/shared/modelFallback.cjs.map +0 -1
- package/dist/runs/shared/modelFallback.mjs +0 -2
- package/dist/runs/shared/modelFallback.mjs.map +0 -1
- package/dist/runs/shared/modelScope.cjs +0 -2
- package/dist/runs/shared/modelScope.cjs.map +0 -1
- package/dist/runs/shared/modelScope.mjs +0 -2
- package/dist/runs/shared/modelScope.mjs.map +0 -1
- package/dist/runs/shared/nestedPath.cjs +0 -2
- package/dist/runs/shared/nestedPath.cjs.map +0 -1
- package/dist/runs/shared/nestedPath.mjs +0 -2
- package/dist/runs/shared/nestedPath.mjs.map +0 -1
- package/dist/runs/shared/piArgs.cjs +0 -2
- package/dist/runs/shared/piArgs.cjs.map +0 -1
- package/dist/runs/shared/piArgs.mjs +0 -2
- package/dist/runs/shared/piArgs.mjs.map +0 -1
- package/dist/runs/shared/piSpawn.cjs +0 -2
- package/dist/runs/shared/piSpawn.cjs.map +0 -1
- package/dist/runs/shared/piSpawn.mjs +0 -2
- package/dist/runs/shared/piSpawn.mjs.map +0 -1
- package/dist/runs/shared/runWithConcurrency.cjs +0 -2
- package/dist/runs/shared/runWithConcurrency.cjs.map +0 -1
- package/dist/runs/shared/runWithConcurrency.mjs +0 -2
- package/dist/runs/shared/runWithConcurrency.mjs.map +0 -1
- package/dist/runs/shared/runtimeRegistry.cjs +0 -2
- package/dist/runs/shared/runtimeRegistry.cjs.map +0 -1
- package/dist/runs/shared/runtimeRegistry.mjs +0 -2
- package/dist/runs/shared/runtimeRegistry.mjs.map +0 -1
- package/dist/runs/shared/structuredOutput.cjs +0 -2
- package/dist/runs/shared/structuredOutput.mjs +0 -2
- package/dist/runs/shared/toolAvailability.cjs +0 -2
- package/dist/runs/shared/toolAvailability.cjs.map +0 -1
- package/dist/runs/shared/toolAvailability.mjs +0 -2
- package/dist/runs/shared/toolAvailability.mjs.map +0 -1
- package/dist/runs/shared/toolBudget.cjs +0 -2
- package/dist/runs/shared/toolBudget.cjs.map +0 -1
- package/dist/runs/shared/toolBudget.mjs +0 -2
- package/dist/runs/shared/toolBudget.mjs.map +0 -1
- package/dist/shared/atomicJson.cjs.map +0 -1
- package/dist/shared/childTranscript.cjs.map +0 -1
- package/dist/shared/pollScheduler.cjs.map +0 -1
- package/dist/shared/scopeOwner.cjs.map +0 -1
- package/dist/slash/chainExpression.cjs.map +0 -1
- package/dist/slash/chainExpression.mjs.map +0 -1
- package/dist/slash/doctor.cjs +0 -5
- package/dist/slash/doctor.mjs +0 -5
- package/dist/slash/doctor.mjs.map +0 -1
- package/dist/slash/slashCommands.cjs.map +0 -1
- package/dist/slash/slashCommands.mjs.map +0 -1
- package/dist/slash/spawnRequestMapping.cjs.map +0 -1
- package/dist/slash/spawnRequestMapping.mjs.map +0 -1
- package/dist/slash/subagentLaunch.cjs +0 -2
- package/dist/slash/subagentLaunch.cjs.map +0 -1
- package/dist/slash/subagentLaunch.mjs +0 -2
- package/dist/slash/subagentLaunch.mjs.map +0 -1
- package/dist/types.d.cts.map +0 -1
- package/dist/types.d.mts.map +0 -1
- /package/dist/{slash → commands/slash}/chainExpression.cjs +0 -0
- /package/dist/{slash → commands/slash}/chainExpression.mjs +0 -0
- /package/dist/{slash → commands/slash}/spawnRequestMapping.cjs +0 -0
- /package/dist/{slash → commands/slash}/spawnRequestMapping.mjs +0 -0
- /package/dist/{protocol → schemas/team}/delegation.cjs +0 -0
- /package/dist/{protocol → schemas/team}/delegation.mjs +0 -0
- /package/dist/{agents → services/agents}/frontmatter.cjs +0 -0
- /package/dist/{intercom → services/intercom}/intercomBridge.cjs +0 -0
- /package/dist/{intercom → services/intercom}/intercomBridge.mjs +0 -0
- /package/dist/{runs → services/runs}/background/completionBatcher.cjs +0 -0
- /package/dist/{runs → services/runs}/background/parentWatchdog.cjs +0 -0
- /package/dist/{runs → services/runs}/background/parentWatchdog.mjs +0 -0
- /package/dist/{runs → services/runs}/registry/processGroup.cjs +0 -0
- /package/dist/{runs → services/runs}/registry/processGroup.mjs +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolAvailability.cjs","names":[],"sources":["../../../../src/services/runs/shared/toolAvailability.ts"],"sourcesContent":["/**\n * Diagnostics for child tools an agent asked for that the child never registered.\n *\n * The `tools` field on an agent is a strict allowlist: it selects from what the\n * child process already has, it does not cause extension code to load. When the\n * two disagree the child notices, not the parent, and the child's stderr is not\n * where a user looks. So the child drops a small file the parent reads after the\n * run and turns into an actionable message.\n *\n * DESIGN PATTERNS:\n * - The file is a negative signal only. A run with nothing missing removes it,\n * so a stale file from an earlier run can never be reported against a later one\n * - Written 0600 and atomically, because it is parsed by a different process and\n * a torn read is indistinguishable from a corrupt file\n * - Reads validate every field. The file is JSON on disk that any process could\n * have written, so nothing is trusted on shape alone\n *\n * AVOID:\n * - Treating a malformed diagnostic as \"nothing missing\"; it is reported as a\n * read failure instead, because a silent empty result hides the tool gap\n */\n\nimport * as fs from 'node:fs';\nimport { writePrivateAtomicJson } from '../../../shared/atomicJson.ts';\n\nexport interface ChildToolDiagnostic {\n agent?: string;\n required: string[];\n available: string[];\n missing: string[];\n missingMcpDirectTools?: string[];\n}\n\n/**\n * Tools every child has regardless of what it reported.\n *\n * The child registry snapshot can be taken before the builtins finish\n * registering, so treating these as always available avoids a false alarm.\n */\nconst CORE_CHILD_TOOLS = new Set(['bash', 'edit', 'find', 'grep', 'ls', 'read', 'write']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === 'string' && entry.length > 0);\n}\n\n/**\n * Record the gap between requested and registered tools, or clear a stale record.\n *\n * Returns the diagnostic when there is one so the caller can report it without\n * reading back what it just wrote.\n */\nexport function writeChildToolDiagnostic(\n filePath: string,\n required: string[],\n available: string[],\n agent?: string,\n mcpDirectTools?: string[],\n): ChildToolDiagnostic | undefined {\n const availableNames = new Set([...available, ...CORE_CHILD_TOOLS]);\n const missing = required.filter((name) => !availableNames.has(name));\n if (missing.length === 0) {\n fs.rmSync(filePath, { force: true });\n return undefined;\n }\n\n const missingMcpDirectTools = mcpDirectTools?.length ? missing.filter((name) => mcpDirectTools.includes(name)) : [];\n const diagnostic: ChildToolDiagnostic = {\n ...(agent !== undefined ? { agent } : {}),\n required,\n available,\n missing,\n ...(missingMcpDirectTools.length > 0 ? { missingMcpDirectTools } : {}),\n };\n writePrivateAtomicJson(filePath, diagnostic);\n return diagnostic;\n}\n\n/** Read a diagnostic written by a child. Throws when the file exists but is not one. */\nexport function readChildToolDiagnostic(filePath: string | undefined): ChildToolDiagnostic | undefined {\n if (!filePath || !fs.existsSync(filePath)) return undefined;\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n if (\n !isRecord(parsed) ||\n !isNonEmptyStringArray(parsed.required) ||\n !isNonEmptyStringArray(parsed.available) ||\n !isNonEmptyStringArray(parsed.missing) ||\n (parsed.agent !== undefined && typeof parsed.agent !== 'string') ||\n (parsed.missingMcpDirectTools !== undefined && !isNonEmptyStringArray(parsed.missingMcpDirectTools))\n ) {\n throw new Error(`Malformed child tool diagnostic at '${filePath}'.`);\n }\n return {\n ...(parsed.agent ? { agent: parsed.agent } : {}),\n required: parsed.required,\n available: parsed.available,\n missing: parsed.missing,\n ...(parsed.missingMcpDirectTools ? { missingMcpDirectTools: parsed.missingMcpDirectTools } : {}),\n };\n}\n\nexport function formatChildToolDiagnostic(diagnostic: ChildToolDiagnostic): string {\n const subject = diagnostic.agent ? `Agent '${diagnostic.agent}'` : 'Subagent';\n return [\n `${subject} requested unavailable child tools: ${diagnostic.missing.join(', ')}.`,\n 'The `tools` field is a strict allowlist; it does not load extension code.',\n ...(diagnostic.missingMcpDirectTools?.length\n ? [\n `Resolved MCP direct tools missing from the child registry: ${diagnostic.missingMcpDirectTools.join(', ')}. This indicates a doom-mcp registration problem, not a tool-call failure.`,\n ]\n : []),\n 'For extension tools, add the provider path to `subagentOnlyExtensions` (child-only), `extensions`, or as a path-like entry in `tools`, while keeping each registered tool name in `tools`.',\n 'For MCP tools, check `/mcp` in the parent session for the server state, and verify the selected tool names. For builtin tools, verify the name against the installed Pi version.',\n ].join('\\n');\n}\n\n/**\n * The reportable message for a run, or undefined when nothing was missing.\n *\n * A read failure is surfaced as its own message rather than swallowed: the file\n * existing at all means the child had a tool gap, so returning undefined here\n * would report a broken run as a clean one.\n */\nexport function readChildToolDiagnosticError(filePath: string | undefined): string | undefined {\n try {\n const diagnostic = readChildToolDiagnostic(filePath);\n return diagnostic ? formatChildToolDiagnostic(diagnostic) : undefined;\n } catch (error) {\n return `Failed to read child tool availability diagnostic: ${error instanceof Error ? error.message : String(error)}`;\n }\n}\n"],"mappings":"6LAuCA,MAAM,EAAmB,IAAI,IAAI,CAAC,OAAQ,OAAQ,OAAQ,OAAQ,KAAM,OAAQ,QAAQ,CAAC,CAgBzF,SAAgB,EACd,EACA,EACA,EACA,EACA,EACiC,CACjC,IAAM,EAAiB,IAAI,IAAI,CAAC,GAAG,EAAW,GAAG,EAAiB,CAAC,CAC7D,EAAU,EAAS,OAAQ,GAAS,CAAC,EAAe,IAAI,EAAK,CAAC,CACpE,GAAI,EAAQ,SAAW,EAAG,CACxB,EAAG,OAAO,EAAU,CAAE,MAAO,GAAM,CAAC,CACpC,OAGF,IAAM,EAAwB,GAAgB,OAAS,EAAQ,OAAQ,GAAS,EAAe,SAAS,EAAK,CAAC,CAAG,EAAE,CAC7G,EAAkC,CACtC,GAAI,IAAU,IAAA,GAAwB,EAAE,CAAd,CAAE,QAAO,CACnC,WACA,YACA,UACA,GAAI,EAAsB,OAAS,EAAI,CAAE,wBAAuB,CAAG,EAAE,CACtE,CAED,OADA,EAAA,uBAAuB,EAAU,EAAW,CACrC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{writePrivateAtomicJson as t}from"../../../adapters/atomicJson.mjs";import{init_atomicJson as n}from"../../../shared/atomicJson.mjs";import*as r from"node:fs";function i(e,n,i,o,s){let c=new Set([...i,...a]),l=n.filter(e=>!c.has(e));if(l.length===0){r.rmSync(e,{force:!0});return}let u=s?.length?l.filter(e=>s.includes(e)):[],d={...o===void 0?{}:{agent:o},required:n,available:i,missing:l,...u.length>0?{missingMcpDirectTools:u}:{}};return t(e,d),d}var a,o=e((()=>{n(),a=new Set([`bash`,`edit`,`find`,`grep`,`ls`,`read`,`write`])}));o();export{o as init_toolAvailability,i as writeChildToolDiagnostic};
|
|
2
|
+
//# sourceMappingURL=toolAvailability.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolAvailability.mjs","names":[],"sources":["../../../../src/services/runs/shared/toolAvailability.ts"],"sourcesContent":["/**\n * Diagnostics for child tools an agent asked for that the child never registered.\n *\n * The `tools` field on an agent is a strict allowlist: it selects from what the\n * child process already has, it does not cause extension code to load. When the\n * two disagree the child notices, not the parent, and the child's stderr is not\n * where a user looks. So the child drops a small file the parent reads after the\n * run and turns into an actionable message.\n *\n * DESIGN PATTERNS:\n * - The file is a negative signal only. A run with nothing missing removes it,\n * so a stale file from an earlier run can never be reported against a later one\n * - Written 0600 and atomically, because it is parsed by a different process and\n * a torn read is indistinguishable from a corrupt file\n * - Reads validate every field. The file is JSON on disk that any process could\n * have written, so nothing is trusted on shape alone\n *\n * AVOID:\n * - Treating a malformed diagnostic as \"nothing missing\"; it is reported as a\n * read failure instead, because a silent empty result hides the tool gap\n */\n\nimport * as fs from 'node:fs';\nimport { writePrivateAtomicJson } from '../../../shared/atomicJson.ts';\n\nexport interface ChildToolDiagnostic {\n agent?: string;\n required: string[];\n available: string[];\n missing: string[];\n missingMcpDirectTools?: string[];\n}\n\n/**\n * Tools every child has regardless of what it reported.\n *\n * The child registry snapshot can be taken before the builtins finish\n * registering, so treating these as always available avoids a false alarm.\n */\nconst CORE_CHILD_TOOLS = new Set(['bash', 'edit', 'find', 'grep', 'ls', 'read', 'write']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === 'string' && entry.length > 0);\n}\n\n/**\n * Record the gap between requested and registered tools, or clear a stale record.\n *\n * Returns the diagnostic when there is one so the caller can report it without\n * reading back what it just wrote.\n */\nexport function writeChildToolDiagnostic(\n filePath: string,\n required: string[],\n available: string[],\n agent?: string,\n mcpDirectTools?: string[],\n): ChildToolDiagnostic | undefined {\n const availableNames = new Set([...available, ...CORE_CHILD_TOOLS]);\n const missing = required.filter((name) => !availableNames.has(name));\n if (missing.length === 0) {\n fs.rmSync(filePath, { force: true });\n return undefined;\n }\n\n const missingMcpDirectTools = mcpDirectTools?.length ? missing.filter((name) => mcpDirectTools.includes(name)) : [];\n const diagnostic: ChildToolDiagnostic = {\n ...(agent !== undefined ? { agent } : {}),\n required,\n available,\n missing,\n ...(missingMcpDirectTools.length > 0 ? { missingMcpDirectTools } : {}),\n };\n writePrivateAtomicJson(filePath, diagnostic);\n return diagnostic;\n}\n\n/** Read a diagnostic written by a child. Throws when the file exists but is not one. */\nexport function readChildToolDiagnostic(filePath: string | undefined): ChildToolDiagnostic | undefined {\n if (!filePath || !fs.existsSync(filePath)) return undefined;\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n if (\n !isRecord(parsed) ||\n !isNonEmptyStringArray(parsed.required) ||\n !isNonEmptyStringArray(parsed.available) ||\n !isNonEmptyStringArray(parsed.missing) ||\n (parsed.agent !== undefined && typeof parsed.agent !== 'string') ||\n (parsed.missingMcpDirectTools !== undefined && !isNonEmptyStringArray(parsed.missingMcpDirectTools))\n ) {\n throw new Error(`Malformed child tool diagnostic at '${filePath}'.`);\n }\n return {\n ...(parsed.agent ? { agent: parsed.agent } : {}),\n required: parsed.required,\n available: parsed.available,\n missing: parsed.missing,\n ...(parsed.missingMcpDirectTools ? { missingMcpDirectTools: parsed.missingMcpDirectTools } : {}),\n };\n}\n\nexport function formatChildToolDiagnostic(diagnostic: ChildToolDiagnostic): string {\n const subject = diagnostic.agent ? `Agent '${diagnostic.agent}'` : 'Subagent';\n return [\n `${subject} requested unavailable child tools: ${diagnostic.missing.join(', ')}.`,\n 'The `tools` field is a strict allowlist; it does not load extension code.',\n ...(diagnostic.missingMcpDirectTools?.length\n ? [\n `Resolved MCP direct tools missing from the child registry: ${diagnostic.missingMcpDirectTools.join(', ')}. This indicates a doom-mcp registration problem, not a tool-call failure.`,\n ]\n : []),\n 'For extension tools, add the provider path to `subagentOnlyExtensions` (child-only), `extensions`, or as a path-like entry in `tools`, while keeping each registered tool name in `tools`.',\n 'For MCP tools, check `/mcp` in the parent session for the server state, and verify the selected tool names. For builtin tools, verify the name against the installed Pi version.',\n ].join('\\n');\n}\n\n/**\n * The reportable message for a run, or undefined when nothing was missing.\n *\n * A read failure is surfaced as its own message rather than swallowed: the file\n * existing at all means the child had a tool gap, so returning undefined here\n * would report a broken run as a clean one.\n */\nexport function readChildToolDiagnosticError(filePath: string | undefined): string | undefined {\n try {\n const diagnostic = readChildToolDiagnostic(filePath);\n return diagnostic ? formatChildToolDiagnostic(diagnostic) : undefined;\n } catch (error) {\n return `Failed to read child tool availability diagnostic: ${error instanceof Error ? error.message : String(error)}`;\n }\n}\n"],"mappings":"wOAuDA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACiC,CACjC,IAAM,EAAiB,IAAI,IAAI,CAAC,GAAG,EAAW,GAAG,EAAiB,CAAC,CAC7D,EAAU,EAAS,OAAQ,GAAS,CAAC,EAAe,IAAI,EAAK,CAAC,CACpE,GAAI,EAAQ,SAAW,EAAG,CACxB,EAAG,OAAO,EAAU,CAAE,MAAO,GAAM,CAAC,CACpC,OAGF,IAAM,EAAwB,GAAgB,OAAS,EAAQ,OAAQ,GAAS,EAAe,SAAS,EAAK,CAAC,CAAG,EAAE,CAC7G,EAAkC,CACtC,GAAI,IAAU,IAAA,GAAwB,EAAE,CAAd,CAAE,QAAO,CACnC,WACA,YACA,UACA,GAAI,EAAsB,OAAS,EAAI,CAAE,wBAAuB,CAAG,EAAE,CACtE,CAED,OADA,EAAuB,EAAU,EAAW,CACrC,qBAvD8D,CAgBjE,EAAmB,IAAI,IAAI,CAAC,OAAQ,OAAQ,OAAQ,OAAQ,KAAM,OAAQ,QAAQ,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require(`../../../adapters/process/env.cjs`),t=[`read`,`grep`,`find`,`ls`];function n(e){return e===`*`?`*`:e===void 0?[...t]:[...new Set(e.map(e=>e.trim()).filter(Boolean))]}function r(e,t=`toolBudget`,r={}){if(e===void 0)return{};if(typeof e!=`object`||!e||Array.isArray(e))return{error:`${t} must be an object with hard and optional soft/block.`};let i=e,a=r.minimumHard??1,o=i.hard;if(typeof o!=`number`||!Number.isInteger(o)||o<a)return{error:`${t}.hard must be an integer >= ${a}.`};let s=i.soft;if(s!==void 0&&(typeof s!=`number`||!Number.isInteger(s)||s<1))return{error:`${t}.soft must be an integer >= 1 when provided.`};if(s!==void 0&&s>o)return{error:`${t}.soft must be <= ${t}.hard.`};let c=i.block,l;if(c!==void 0&&c!==`*`){if(!Array.isArray(c))return{error:`${t}.block must be "*" or an array of tool names.`};if(c.length===0)return{error:`${t}.block must contain at least one tool name.`};for(let e of c)if(typeof e!=`string`||!e.trim())return{error:`${t}.block must contain non-empty tool names.`};l=c}else l=c;return{budget:{hard:o,...s===void 0?{}:{soft:s},block:n(l)}}}function i(e,t,n){return n<=e.hard?!1:e.block===`*`||e.block.includes(t)}function a(e,t){return`Tool budget soft limit reached after ${t} tool call${t===1?``:`s`} (soft ${e.soft}, hard ${e.hard}). Stop starting new browsing/search work and finalize from the context you already have.`}function o(e,t,n){return`Tool budget hard limit reached after ${n} tool call${n===1?``:`s`} (hard ${e.hard}). The '${t}' tool is blocked so you can finalize from the context you already have.`}function s(e){return e?JSON.stringify(e):void 0}function c(t,n={}){if(!t?.trim())return;let i=r(JSON.parse(t),e.TOOL_BUDGET_ENV,n.allowZero?{minimumHard:0}:void 0);if(i.error)throw Error(i.error);return i.budget}exports.decodeToolBudgetEnv=c,exports.encodeToolBudgetEnv=s,exports.shouldBlockToolForBudget=i,exports.toolBudgetBlockedMessage=o,exports.toolBudgetSoftNudge=a;
|
|
2
|
+
//# sourceMappingURL=toolBudget.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolBudget.cjs","names":["TOOL_BUDGET_ENV"],"sources":["../../../../src/services/runs/shared/toolBudget.ts"],"sourcesContent":["/**\n * Tool-call budget validation, enforcement predicates and its process handoff.\n *\n * DESIGN PATTERNS:\n * - Pure functions over a resolved budget; the running tool count is the\n * caller's, so this module has nothing to reset between runs\n * - The budget crosses into the child as one JSON environment value, encoded and\n * decoded here, so parent and child can never disagree about its shape\n *\n * WHY BLOCKING IS NARROWED TO A TOOL LIST:\n * The point of the hard limit is to stop open-ended browsing and searching, not\n * to strand a child that still has to write its answer. Blocking only the\n * read-style tools leaves `edit`, `write` and the reporting path available past\n * the limit, so an over-budget child finishes rather than fails.\n *\n * AVOID:\n * - Blocking on the call that reaches the hard limit; the count must exceed it\n * first, so the budgeted number of calls actually happens\n * - Reading the environment variable name from anywhere but `src/env.ts`\n */\n\nimport { TOOL_BUDGET_ENV } from '../../../adapters/process/env.ts';\nimport type { ToolBudgetConfig } from '../../../types/index.ts';\n\n/** A tool budget with its block list settled. */\nexport interface ResolvedToolBudget {\n soft?: number;\n hard: number;\n block: string[] | '*';\n}\n\n/** How a run stands against its tool budget. */\nexport type ToolBudgetOutcome = 'within-budget' | 'soft-reached' | 'hard-blocked';\n\n/** The budget plus everything observed about it during one run. */\nexport interface ToolBudgetState extends ResolvedToolBudget {\n outcome: ToolBudgetOutcome;\n toolCount: number;\n softReachedAt?: number;\n hardReachedAt?: number;\n blockedTool?: string;\n}\n\n/**\n * Blocked by default: the read-style tools a child loops on when it is stuck.\n *\n * Deliberately excludes `edit` and `write` so an over-budget child can still\n * land the work it has already reasoned about.\n */\nexport const DEFAULT_TOOL_BUDGET_BLOCK = ['read', 'grep', 'find', 'ls'] as const;\n\nconst BLOCK_ALL_TOOLS = '*';\nconst MIN_SOFT_LIMIT = 1;\nconst DEFAULT_MINIMUM_HARD_LIMIT = 1;\n\n/** Deduplicate and trim an authored block list, or pass through the block-all marker. */\nexport function normalizeToolBudgetBlock(block: ToolBudgetConfig['block'] | undefined): '*' | string[] {\n if (block === BLOCK_ALL_TOOLS) return BLOCK_ALL_TOOLS;\n if (block === undefined) return [...DEFAULT_TOOL_BUDGET_BLOCK];\n return [...new Set(block.map((tool) => tool.trim()).filter(Boolean))];\n}\n\n/**\n * Validate an authored tool budget and settle its block list.\n *\n * `minimumHard` is 0 only for the zero-authorised path, where a caller has\n * explicitly asked for a budget that blocks every tool from the first call.\n */\nexport function validateToolBudgetConfig(\n raw: unknown,\n label = 'toolBudget',\n options: { minimumHard?: 0 | 1 } = {},\n): { budget?: ResolvedToolBudget; error?: string } {\n if (raw === undefined) return {};\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { error: `${label} must be an object with hard and optional soft/block.` };\n }\n const value: Record<string, unknown> = raw as Record<string, unknown>;\n const minimumHard = options.minimumHard ?? DEFAULT_MINIMUM_HARD_LIMIT;\n\n const hard = value.hard;\n if (typeof hard !== 'number' || !Number.isInteger(hard) || hard < minimumHard) {\n return { error: `${label}.hard must be an integer >= ${minimumHard}.` };\n }\n const soft = value.soft;\n if (soft !== undefined && (typeof soft !== 'number' || !Number.isInteger(soft) || soft < MIN_SOFT_LIMIT)) {\n return { error: `${label}.soft must be an integer >= ${MIN_SOFT_LIMIT} when provided.` };\n }\n if (soft !== undefined && soft > hard) {\n return { error: `${label}.soft must be <= ${label}.hard.` };\n }\n\n const rawBlock = value.block;\n let block: ToolBudgetConfig['block'] | undefined;\n if (rawBlock !== undefined && rawBlock !== BLOCK_ALL_TOOLS) {\n if (!Array.isArray(rawBlock)) return { error: `${label}.block must be \"*\" or an array of tool names.` };\n if (rawBlock.length === 0) return { error: `${label}.block must contain at least one tool name.` };\n for (const item of rawBlock) {\n if (typeof item !== 'string' || !item.trim()) {\n return { error: `${label}.block must contain non-empty tool names.` };\n }\n }\n block = rawBlock as string[];\n } else {\n block = rawBlock;\n }\n\n return {\n budget: {\n hard,\n ...(soft !== undefined ? { soft } : {}),\n block: normalizeToolBudgetBlock(block),\n },\n };\n}\n\nexport function initialToolBudgetState(budget: ResolvedToolBudget): ToolBudgetState {\n return { ...budget, toolCount: 0, outcome: 'within-budget' };\n}\n\nexport function toolBudgetState(budget: ResolvedToolBudget, toolCount: number, blockedTool?: string): ToolBudgetState {\n const overHard = toolCount > budget.hard;\n const overSoft = budget.soft !== undefined && toolCount >= budget.soft;\n return {\n ...budget,\n toolCount,\n outcome: overHard ? 'hard-blocked' : overSoft ? 'soft-reached' : 'within-budget',\n ...(overSoft ? { softReachedAt: budget.soft } : {}),\n ...(overHard ? { hardReachedAt: budget.hard, blockedTool } : {}),\n };\n}\n\n/**\n * Whether this call must be refused.\n *\n * `nextToolCount` is the count including the call being decided, so the budgeted\n * number of calls runs and only the one after it is refused.\n */\nexport function shouldBlockToolForBudget(budget: ResolvedToolBudget, toolName: string, nextToolCount: number): boolean {\n if (nextToolCount <= budget.hard) return false;\n return budget.block === BLOCK_ALL_TOOLS || budget.block.includes(toolName);\n}\n\nexport function toolBudgetSoftNudge(budget: ResolvedToolBudget, toolCount: number): string {\n return `Tool budget soft limit reached after ${toolCount} tool call${toolCount === 1 ? '' : 's'} (soft ${budget.soft}, hard ${budget.hard}). Stop starting new browsing/search work and finalize from the context you already have.`;\n}\n\nexport function toolBudgetBlockedMessage(budget: ResolvedToolBudget, toolName: string, toolCount: number): string {\n return `Tool budget hard limit reached after ${toolCount} tool call${toolCount === 1 ? '' : 's'} (hard ${budget.hard}). The '${toolName}' tool is blocked so you can finalize from the context you already have.`;\n}\n\nexport function encodeToolBudgetEnv(budget: ResolvedToolBudget | undefined): string | undefined {\n return budget ? JSON.stringify(budget) : undefined;\n}\n\n/**\n * Read the budget a parent handed to this child.\n *\n * Throws rather than returning an error string: an unreadable budget means the\n * child cannot know what it is allowed to do, and running unbudgeted would\n * silently defeat the cap the parent set.\n */\nexport function decodeToolBudgetEnv(\n value: string | undefined,\n options: { allowZero?: boolean } = {},\n): ResolvedToolBudget | undefined {\n if (!value?.trim()) return undefined;\n const parsed: unknown = JSON.parse(value);\n const normalized = validateToolBudgetConfig(\n parsed,\n TOOL_BUDGET_ENV,\n options.allowZero ? { minimumHard: 0 } : undefined,\n );\n if (normalized.error) throw new Error(normalized.error);\n return normalized.budget;\n}\n"],"mappings":"qDAiDa,EAA4B,CAAC,OAAQ,OAAQ,OAAQ,KAAK,CAOvE,SAAgB,EAAyB,EAA8D,CAGrG,OAFI,IAAU,IAAwB,IAClC,IAAU,IAAA,GAAkB,CAAC,GAAG,EAA0B,CACvD,CAAC,GAAG,IAAI,IAAI,EAAM,IAAK,GAAS,EAAK,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,CASvE,SAAgB,EACd,EACA,EAAQ,aACR,EAAmC,EAAE,CACY,CACjD,GAAI,IAAQ,IAAA,GAAW,MAAO,EAAE,CAChC,GAAI,OAAO,GAAQ,WAAY,GAAgB,MAAM,QAAQ,EAAI,CAC/D,MAAO,CAAE,MAAO,GAAG,EAAM,uDAAwD,CAEnF,IAAM,EAAiC,EACjC,EAAc,EAAQ,aAAe,EAErC,EAAO,EAAM,KACnB,GAAI,OAAO,GAAS,UAAY,CAAC,OAAO,UAAU,EAAK,EAAI,EAAO,EAChE,MAAO,CAAE,MAAO,GAAG,EAAM,8BAA8B,EAAY,GAAI,CAEzE,IAAM,EAAO,EAAM,KACnB,GAAI,IAAS,IAAA,KAAc,OAAO,GAAS,UAAY,CAAC,OAAO,UAAU,EAAK,EAAI,EAAO,GACvF,MAAO,CAAE,MAAO,GAAG,EAAM,8CAA+D,CAE1F,GAAI,IAAS,IAAA,IAAa,EAAO,EAC/B,MAAO,CAAE,MAAO,GAAG,EAAM,mBAAmB,EAAM,QAAS,CAG7D,IAAM,EAAW,EAAM,MACnB,EACJ,GAAI,IAAa,IAAA,IAAa,IAAa,IAAiB,CAC1D,GAAI,CAAC,MAAM,QAAQ,EAAS,CAAE,MAAO,CAAE,MAAO,GAAG,EAAM,+CAAgD,CACvG,GAAI,EAAS,SAAW,EAAG,MAAO,CAAE,MAAO,GAAG,EAAM,6CAA8C,CAClG,IAAK,IAAM,KAAQ,EACjB,GAAI,OAAO,GAAS,UAAY,CAAC,EAAK,MAAM,CAC1C,MAAO,CAAE,MAAO,GAAG,EAAM,2CAA4C,CAGzE,EAAQ,OAER,EAAQ,EAGV,MAAO,CACL,OAAQ,CACN,OACA,GAAI,IAAS,IAAA,GAAuB,EAAE,CAAb,CAAE,OAAM,CACjC,MAAO,EAAyB,EAAM,CACvC,CACF,CAyBH,SAAgB,EAAyB,EAA4B,EAAkB,EAAgC,CAErH,OADI,GAAiB,EAAO,KAAa,GAClC,EAAO,QAAU,KAAmB,EAAO,MAAM,SAAS,EAAS,CAG5E,SAAgB,EAAoB,EAA4B,EAA2B,CACzF,MAAO,wCAAwC,EAAU,YAAY,IAAc,EAAI,GAAK,IAAI,SAAS,EAAO,KAAK,SAAS,EAAO,KAAK,2FAG5I,SAAgB,EAAyB,EAA4B,EAAkB,EAA2B,CAChH,MAAO,wCAAwC,EAAU,YAAY,IAAc,EAAI,GAAK,IAAI,SAAS,EAAO,KAAK,UAAU,EAAS,0EAG1I,SAAgB,EAAoB,EAA4D,CAC9F,OAAO,EAAS,KAAK,UAAU,EAAO,CAAG,IAAA,GAU3C,SAAgB,EACd,EACA,EAAmC,EAAE,CACL,CAChC,GAAI,CAAC,GAAO,MAAM,CAAE,OAEpB,IAAM,EAAa,EADK,KAAK,MAAM,EAE3B,CACNA,EAAAA,gBACA,EAAQ,UAAY,CAAE,YAAa,EAAG,CAAG,IAAA,GAC1C,CACD,GAAI,EAAW,MAAO,MAAU,MAAM,EAAW,MAAM,CACvD,OAAO,EAAW"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{TOOL_BUDGET_ENV as t,init_env as n}from"../../../adapters/process/env.mjs";function r(e){return e===d?d:e===void 0?[...u]:[...new Set(e.map(e=>e.trim()).filter(Boolean))]}function i(e,t=`toolBudget`,n={}){if(e===void 0)return{};if(typeof e!=`object`||!e||Array.isArray(e))return{error:`${t} must be an object with hard and optional soft/block.`};let i=e,a=n.minimumHard??p,o=i.hard;if(typeof o!=`number`||!Number.isInteger(o)||o<a)return{error:`${t}.hard must be an integer >= ${a}.`};let s=i.soft;if(s!==void 0&&(typeof s!=`number`||!Number.isInteger(s)||s<f))return{error:`${t}.soft must be an integer >= ${f} when provided.`};if(s!==void 0&&s>o)return{error:`${t}.soft must be <= ${t}.hard.`};let c=i.block,l;if(c!==void 0&&c!==d){if(!Array.isArray(c))return{error:`${t}.block must be "*" or an array of tool names.`};if(c.length===0)return{error:`${t}.block must contain at least one tool name.`};for(let e of c)if(typeof e!=`string`||!e.trim())return{error:`${t}.block must contain non-empty tool names.`};l=c}else l=c;return{budget:{hard:o,...s===void 0?{}:{soft:s},block:r(l)}}}function a(e,t,n){return n<=e.hard?!1:e.block===d||e.block.includes(t)}function o(e,t){return`Tool budget soft limit reached after ${t} tool call${t===1?``:`s`} (soft ${e.soft}, hard ${e.hard}). Stop starting new browsing/search work and finalize from the context you already have.`}function s(e,t,n){return`Tool budget hard limit reached after ${n} tool call${n===1?``:`s`} (hard ${e.hard}). The '${t}' tool is blocked so you can finalize from the context you already have.`}function c(e){return e?JSON.stringify(e):void 0}function l(e,n={}){if(!e?.trim())return;let r=i(JSON.parse(e),t,n.allowZero?{minimumHard:0}:void 0);if(r.error)throw Error(r.error);return r.budget}var u,d,f,p,m=e((()=>{n(),u=[`read`,`grep`,`find`,`ls`],d=`*`,f=1,p=1}));m();export{l as decodeToolBudgetEnv,c as encodeToolBudgetEnv,m as init_toolBudget,a as shouldBlockToolForBudget,s as toolBudgetBlockedMessage,o as toolBudgetSoftNudge};
|
|
2
|
+
//# sourceMappingURL=toolBudget.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolBudget.mjs","names":[],"sources":["../../../../src/services/runs/shared/toolBudget.ts"],"sourcesContent":["/**\n * Tool-call budget validation, enforcement predicates and its process handoff.\n *\n * DESIGN PATTERNS:\n * - Pure functions over a resolved budget; the running tool count is the\n * caller's, so this module has nothing to reset between runs\n * - The budget crosses into the child as one JSON environment value, encoded and\n * decoded here, so parent and child can never disagree about its shape\n *\n * WHY BLOCKING IS NARROWED TO A TOOL LIST:\n * The point of the hard limit is to stop open-ended browsing and searching, not\n * to strand a child that still has to write its answer. Blocking only the\n * read-style tools leaves `edit`, `write` and the reporting path available past\n * the limit, so an over-budget child finishes rather than fails.\n *\n * AVOID:\n * - Blocking on the call that reaches the hard limit; the count must exceed it\n * first, so the budgeted number of calls actually happens\n * - Reading the environment variable name from anywhere but `src/env.ts`\n */\n\nimport { TOOL_BUDGET_ENV } from '../../../adapters/process/env.ts';\nimport type { ToolBudgetConfig } from '../../../types/index.ts';\n\n/** A tool budget with its block list settled. */\nexport interface ResolvedToolBudget {\n soft?: number;\n hard: number;\n block: string[] | '*';\n}\n\n/** How a run stands against its tool budget. */\nexport type ToolBudgetOutcome = 'within-budget' | 'soft-reached' | 'hard-blocked';\n\n/** The budget plus everything observed about it during one run. */\nexport interface ToolBudgetState extends ResolvedToolBudget {\n outcome: ToolBudgetOutcome;\n toolCount: number;\n softReachedAt?: number;\n hardReachedAt?: number;\n blockedTool?: string;\n}\n\n/**\n * Blocked by default: the read-style tools a child loops on when it is stuck.\n *\n * Deliberately excludes `edit` and `write` so an over-budget child can still\n * land the work it has already reasoned about.\n */\nexport const DEFAULT_TOOL_BUDGET_BLOCK = ['read', 'grep', 'find', 'ls'] as const;\n\nconst BLOCK_ALL_TOOLS = '*';\nconst MIN_SOFT_LIMIT = 1;\nconst DEFAULT_MINIMUM_HARD_LIMIT = 1;\n\n/** Deduplicate and trim an authored block list, or pass through the block-all marker. */\nexport function normalizeToolBudgetBlock(block: ToolBudgetConfig['block'] | undefined): '*' | string[] {\n if (block === BLOCK_ALL_TOOLS) return BLOCK_ALL_TOOLS;\n if (block === undefined) return [...DEFAULT_TOOL_BUDGET_BLOCK];\n return [...new Set(block.map((tool) => tool.trim()).filter(Boolean))];\n}\n\n/**\n * Validate an authored tool budget and settle its block list.\n *\n * `minimumHard` is 0 only for the zero-authorised path, where a caller has\n * explicitly asked for a budget that blocks every tool from the first call.\n */\nexport function validateToolBudgetConfig(\n raw: unknown,\n label = 'toolBudget',\n options: { minimumHard?: 0 | 1 } = {},\n): { budget?: ResolvedToolBudget; error?: string } {\n if (raw === undefined) return {};\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { error: `${label} must be an object with hard and optional soft/block.` };\n }\n const value: Record<string, unknown> = raw as Record<string, unknown>;\n const minimumHard = options.minimumHard ?? DEFAULT_MINIMUM_HARD_LIMIT;\n\n const hard = value.hard;\n if (typeof hard !== 'number' || !Number.isInteger(hard) || hard < minimumHard) {\n return { error: `${label}.hard must be an integer >= ${minimumHard}.` };\n }\n const soft = value.soft;\n if (soft !== undefined && (typeof soft !== 'number' || !Number.isInteger(soft) || soft < MIN_SOFT_LIMIT)) {\n return { error: `${label}.soft must be an integer >= ${MIN_SOFT_LIMIT} when provided.` };\n }\n if (soft !== undefined && soft > hard) {\n return { error: `${label}.soft must be <= ${label}.hard.` };\n }\n\n const rawBlock = value.block;\n let block: ToolBudgetConfig['block'] | undefined;\n if (rawBlock !== undefined && rawBlock !== BLOCK_ALL_TOOLS) {\n if (!Array.isArray(rawBlock)) return { error: `${label}.block must be \"*\" or an array of tool names.` };\n if (rawBlock.length === 0) return { error: `${label}.block must contain at least one tool name.` };\n for (const item of rawBlock) {\n if (typeof item !== 'string' || !item.trim()) {\n return { error: `${label}.block must contain non-empty tool names.` };\n }\n }\n block = rawBlock as string[];\n } else {\n block = rawBlock;\n }\n\n return {\n budget: {\n hard,\n ...(soft !== undefined ? { soft } : {}),\n block: normalizeToolBudgetBlock(block),\n },\n };\n}\n\nexport function initialToolBudgetState(budget: ResolvedToolBudget): ToolBudgetState {\n return { ...budget, toolCount: 0, outcome: 'within-budget' };\n}\n\nexport function toolBudgetState(budget: ResolvedToolBudget, toolCount: number, blockedTool?: string): ToolBudgetState {\n const overHard = toolCount > budget.hard;\n const overSoft = budget.soft !== undefined && toolCount >= budget.soft;\n return {\n ...budget,\n toolCount,\n outcome: overHard ? 'hard-blocked' : overSoft ? 'soft-reached' : 'within-budget',\n ...(overSoft ? { softReachedAt: budget.soft } : {}),\n ...(overHard ? { hardReachedAt: budget.hard, blockedTool } : {}),\n };\n}\n\n/**\n * Whether this call must be refused.\n *\n * `nextToolCount` is the count including the call being decided, so the budgeted\n * number of calls runs and only the one after it is refused.\n */\nexport function shouldBlockToolForBudget(budget: ResolvedToolBudget, toolName: string, nextToolCount: number): boolean {\n if (nextToolCount <= budget.hard) return false;\n return budget.block === BLOCK_ALL_TOOLS || budget.block.includes(toolName);\n}\n\nexport function toolBudgetSoftNudge(budget: ResolvedToolBudget, toolCount: number): string {\n return `Tool budget soft limit reached after ${toolCount} tool call${toolCount === 1 ? '' : 's'} (soft ${budget.soft}, hard ${budget.hard}). Stop starting new browsing/search work and finalize from the context you already have.`;\n}\n\nexport function toolBudgetBlockedMessage(budget: ResolvedToolBudget, toolName: string, toolCount: number): string {\n return `Tool budget hard limit reached after ${toolCount} tool call${toolCount === 1 ? '' : 's'} (hard ${budget.hard}). The '${toolName}' tool is blocked so you can finalize from the context you already have.`;\n}\n\nexport function encodeToolBudgetEnv(budget: ResolvedToolBudget | undefined): string | undefined {\n return budget ? JSON.stringify(budget) : undefined;\n}\n\n/**\n * Read the budget a parent handed to this child.\n *\n * Throws rather than returning an error string: an unreadable budget means the\n * child cannot know what it is allowed to do, and running unbudgeted would\n * silently defeat the cap the parent set.\n */\nexport function decodeToolBudgetEnv(\n value: string | undefined,\n options: { allowZero?: boolean } = {},\n): ResolvedToolBudget | undefined {\n if (!value?.trim()) return undefined;\n const parsed: unknown = JSON.parse(value);\n const normalized = validateToolBudgetConfig(\n parsed,\n TOOL_BUDGET_ENV,\n options.allowZero ? { minimumHard: 0 } : undefined,\n );\n if (normalized.error) throw new Error(normalized.error);\n return normalized.budget;\n}\n"],"mappings":"qJAwDA,SAAgB,EAAyB,EAA8D,CAGrG,OAFI,IAAU,EAAwB,EAClC,IAAU,IAAA,GAAkB,CAAC,GAAG,EAA0B,CACvD,CAAC,GAAG,IAAI,IAAI,EAAM,IAAK,GAAS,EAAK,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,CASvE,SAAgB,EACd,EACA,EAAQ,aACR,EAAmC,EAAE,CACY,CACjD,GAAI,IAAQ,IAAA,GAAW,MAAO,EAAE,CAChC,GAAI,OAAO,GAAQ,WAAY,GAAgB,MAAM,QAAQ,EAAI,CAC/D,MAAO,CAAE,MAAO,GAAG,EAAM,uDAAwD,CAEnF,IAAM,EAAiC,EACjC,EAAc,EAAQ,aAAe,EAErC,EAAO,EAAM,KACnB,GAAI,OAAO,GAAS,UAAY,CAAC,OAAO,UAAU,EAAK,EAAI,EAAO,EAChE,MAAO,CAAE,MAAO,GAAG,EAAM,8BAA8B,EAAY,GAAI,CAEzE,IAAM,EAAO,EAAM,KACnB,GAAI,IAAS,IAAA,KAAc,OAAO,GAAS,UAAY,CAAC,OAAO,UAAU,EAAK,EAAI,EAAO,GACvF,MAAO,CAAE,MAAO,GAAG,EAAM,8BAA8B,EAAe,iBAAkB,CAE1F,GAAI,IAAS,IAAA,IAAa,EAAO,EAC/B,MAAO,CAAE,MAAO,GAAG,EAAM,mBAAmB,EAAM,QAAS,CAG7D,IAAM,EAAW,EAAM,MACnB,EACJ,GAAI,IAAa,IAAA,IAAa,IAAa,EAAiB,CAC1D,GAAI,CAAC,MAAM,QAAQ,EAAS,CAAE,MAAO,CAAE,MAAO,GAAG,EAAM,+CAAgD,CACvG,GAAI,EAAS,SAAW,EAAG,MAAO,CAAE,MAAO,GAAG,EAAM,6CAA8C,CAClG,IAAK,IAAM,KAAQ,EACjB,GAAI,OAAO,GAAS,UAAY,CAAC,EAAK,MAAM,CAC1C,MAAO,CAAE,MAAO,GAAG,EAAM,2CAA4C,CAGzE,EAAQ,OAER,EAAQ,EAGV,MAAO,CACL,OAAQ,CACN,OACA,GAAI,IAAS,IAAA,GAAuB,EAAE,CAAb,CAAE,OAAM,CACjC,MAAO,EAAyB,EAAM,CACvC,CACF,CAyBH,SAAgB,EAAyB,EAA4B,EAAkB,EAAgC,CAErH,OADI,GAAiB,EAAO,KAAa,GAClC,EAAO,QAAU,GAAmB,EAAO,MAAM,SAAS,EAAS,CAG5E,SAAgB,EAAoB,EAA4B,EAA2B,CACzF,MAAO,wCAAwC,EAAU,YAAY,IAAc,EAAI,GAAK,IAAI,SAAS,EAAO,KAAK,SAAS,EAAO,KAAK,2FAG5I,SAAgB,EAAyB,EAA4B,EAAkB,EAA2B,CAChH,MAAO,wCAAwC,EAAU,YAAY,IAAc,EAAI,GAAK,IAAI,SAAS,EAAO,KAAK,UAAU,EAAS,0EAG1I,SAAgB,EAAoB,EAA4D,CAC9F,OAAO,EAAS,KAAK,UAAU,EAAO,CAAG,IAAA,GAU3C,SAAgB,EACd,EACA,EAAmC,EAAE,CACL,CAChC,GAAI,CAAC,GAAO,MAAM,CAAE,OAEpB,IAAM,EAAa,EADK,KAAK,MAAM,EAE3B,CACN,EACA,EAAQ,UAAY,CAAE,YAAa,EAAG,CAAG,IAAA,GAC1C,CACD,GAAI,EAAW,MAAO,MAAU,MAAM,EAAW,MAAM,CACvD,OAAO,EAAW,gCAzJ+C,CA4BtD,EAA4B,CAAC,OAAQ,OAAQ,OAAQ,KAAK,CAEjE,EAAkB,IAClB,EAAiB,EACjB,EAA6B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.cjs","names":["path","TEMP_ARTIFACTS_DIR"],"sources":["../../src/shared/artifacts.ts"],"sourcesContent":["/**\n * Artifact file layout and retention for subagent runs.\n *\n * DESIGN PATTERNS:\n * - Path shape is derived, never stored: `getArtifactPaths` is pure so a reader\n * can reconstruct every file for a run from the run id and agent name alone\n * - The agent name is sanitised into the filename because it comes from\n * user-authored frontmatter and would otherwise let a run write outside the\n * artifacts directory\n * - Cleanup is rate-limited by a marker file rather than by an in-process timer,\n * so several concurrent extension hosts do not each sweep on every startup\n *\n * AVOID:\n * - Failing a run because housekeeping failed; every cleanup error is swallowed\n * deliberately and the sweep continues with the next entry\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { ArtifactDirPreference, ArtifactPaths } from '../types.ts';\nimport { writeAtomicJson } from './atomicJson.ts';\nimport { getAgentDir } from './configDir.ts';\nimport { TEMP_ARTIFACTS_DIR } from './paths.ts';\n\nconst CLEANUP_MARKER_FILE = '.last-cleanup';\n/**\n * In-project artifact root, named for this package rather than its predecessor.\n *\n * Both packages can be installed at once during cutover, and both sweep their\n * root on a retention timer. A shared name would let one package's cleanup delete\n * the other's live artifacts, so the roots are kept disjoint.\n */\nconst PROJECT_ARTIFACT_ROOT = '.doom-team';\nconst PROJECT_ARTIFACTS_DIR_NAME = 'artifacts';\nconst PROJECT_CHAIN_RUNS_DIR_NAME = 'chain-runs';\nconst SESSION_ARTIFACTS_DIR_NAME = 'subagent-artifacts';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst UNSAFE_AGENT_NAME_CHARS = /[^\\w.-]/g;\nconst AGENT_NAME_REPLACEMENT = '_';\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = MS_PER_DAY;\n\nexport function getProjectSubagentsDir(cwd: string): string {\n return path.join(cwd, PROJECT_ARTIFACT_ROOT);\n}\n\nexport function getProjectArtifactsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_ARTIFACTS_DIR_NAME);\n}\n\nexport function getProjectChainRunsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_CHAIN_RUNS_DIR_NAME);\n}\n\nexport function getArtifactsDir(\n sessionFile: string | null,\n projectCwd?: string,\n dirPreference: ArtifactDirPreference = 'session',\n): string {\n switch (dirPreference) {\n case 'session':\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n case 'temp':\n return TEMP_ARTIFACTS_DIR;\n case 'project':\n if (projectCwd) return getProjectArtifactsDir(projectCwd);\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n default:\n throw new Error(\n `Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected \"project\", \"session\", or \"temp\".`,\n );\n }\n}\n\nexport function getArtifactPaths(artifactsDir: string, runId: string, agent: string, index?: number): ArtifactPaths {\n const suffix = index !== undefined ? `_${index}` : '';\n const safeAgent = agent.replace(UNSAFE_AGENT_NAME_CHARS, AGENT_NAME_REPLACEMENT);\n const base = `${runId}_${safeAgent}${suffix}`;\n return {\n inputPath: path.join(artifactsDir, `${base}_input.md`),\n outputPath: path.join(artifactsDir, `${base}_output.md`),\n jsonlPath: path.join(artifactsDir, `${base}.jsonl`),\n transcriptPath: path.join(artifactsDir, `${base}_transcript.jsonl`),\n metadataPath: path.join(artifactsDir, `${base}_meta.json`),\n };\n}\n\nexport function ensureArtifactsDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nexport function writeArtifact(filePath: string, content: string): void {\n fs.writeFileSync(filePath, content, 'utf-8');\n}\n\nexport function formatOutputArtifactContent(input: {\n output: string;\n error?: string;\n transcriptPath?: string;\n metadataPath?: string;\n}): string {\n // An empty output artifact tells the reader nothing about why a run produced\n // nothing, so a failure with no output is replaced by the error and its evidence.\n if (input.output.trim() || !input.error) return input.output;\n const lines = ['Subagent run failed before producing output.', '', 'Error:', input.error];\n if (input.transcriptPath) lines.push('', `Transcript: ${input.transcriptPath}`);\n if (input.metadataPath) lines.push(`Metadata: ${input.metadataPath}`);\n return lines.join('\\n');\n}\n\n/**\n * Publish a run's metadata sidecar.\n *\n * Written atomically because the parent polls this file while the run is still\n * producing it, and a direct write lets that poll observe a half-serialised object.\n */\nexport function writeMetadata(filePath: string, metadata: object): void {\n writeAtomicJson(filePath, metadata);\n}\n\nexport function appendJsonl(filePath: string, line: string): void {\n fs.appendFileSync(filePath, `${line}\\n`);\n}\n\nexport function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {\n if (!fs.existsSync(dir)) return;\n\n const markerPath = path.join(dir, CLEANUP_MARKER_FILE);\n const now = Date.now();\n\n if (fs.existsSync(markerPath)) {\n if (now - fs.statSync(markerPath).mtimeMs < CLEANUP_INTERVAL_MS) return;\n }\n\n const maxAgeMs = maxAgeDays * MS_PER_DAY;\n const cutoff = now - maxAgeMs;\n\n for (const file of fs.readdirSync(dir)) {\n if (file === CLEANUP_MARKER_FILE) continue;\n const filePath = path.join(dir, file);\n try {\n const stat = fs.statSync(filePath);\n if (stat.mtimeMs < cutoff) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // Artifact cleanup is best-effort housekeeping. Skip files that disappear\n // or become unreadable while scanning so one bad entry does not block the rest.\n }\n }\n\n fs.writeFileSync(markerPath, String(now));\n}\n\nexport function cleanupAllArtifactDirs(maxAgeDays: number): void {\n cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);\n\n const sessionsBase = path.join(getAgentDir(), SESSIONS_DIR_NAME);\n if (!fs.existsSync(sessionsBase)) return;\n\n let dirs: string[];\n try {\n dirs = fs.readdirSync(sessionsBase);\n } catch {\n // Session artifact cleanup is best-effort. If the sessions root cannot be read,\n // skip cleanup instead of failing extension startup.\n return;\n }\n\n for (const dir of dirs) {\n const artifactsDir = path.join(sessionsBase, dir, SESSION_ARTIFACTS_DIR_NAME);\n try {\n cleanupOldArtifacts(artifactsDir, maxAgeDays);\n } catch {\n // Session cleanup is best-effort. Keep going so one unreadable session dir\n // does not block cleanup for the rest.\n }\n }\n}\n"],"mappings":"sMAgCA,MAGM,EAA6B,qBAE7B,EAA0B,WAKhC,SAAgB,EAAuB,EAAqB,CAC1D,OAAOA,EAAK,KAAK,EAAK,aAAsB,CAG9C,SAAgB,EAAuB,EAAqB,CAC1D,OAAOA,EAAK,KAAK,EAAuB,EAAI,CAAE,YAA2B,CAO3E,SAAgB,EACd,EACA,EACA,EAAuC,UAC/B,CACR,OAAQ,EAAR,CACE,IAAK,UACH,GAAI,EAAa,CACf,IAAM,EAAaA,EAAK,QAAQ,EAAY,CAC5C,OAAOA,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAOC,EAAAA,mBACT,IAAK,OACH,OAAOA,EAAAA,mBACT,IAAK,UACH,GAAI,EAAY,OAAO,EAAuB,EAAW,CACzD,GAAI,EAAa,CACf,IAAM,EAAaD,EAAK,QAAQ,EAAY,CAC5C,OAAOA,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAOC,EAAAA,mBACT,QACE,MAAU,MACR,2BAA2B,KAAK,UAAU,EAAc,CAAC,6CAC1D,EAIP,SAAgB,EAAiB,EAAsB,EAAe,EAAe,EAA+B,CAClH,IAAM,EAAS,IAAU,IAAA,GAA0B,GAAd,IAAI,IAEnC,EAAO,GAAG,EAAM,GADJ,EAAM,QAAQ,EAAyB,IACvB,GAAG,IACrC,MAAO,CACL,UAAWD,EAAK,KAAK,EAAc,GAAG,EAAK,WAAW,CACtD,WAAYA,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CACxD,UAAWA,EAAK,KAAK,EAAc,GAAG,EAAK,QAAQ,CACnD,eAAgBA,EAAK,KAAK,EAAc,GAAG,EAAK,mBAAmB,CACnE,aAAcA,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CAC3D,CAoCH,SAAgB,EAAY,EAAkB,EAAoB,CAChE,EAAG,eAAe,EAAU,GAAG,EAAK,IAAI"}
|
|
1
|
+
{"version":3,"file":"artifacts.cjs","names":["path","TEMP_ARTIFACTS_DIR"],"sources":["../../src/shared/artifacts.ts"],"sourcesContent":["/**\n * Artifact file layout and retention for subagent runs.\n *\n * DESIGN PATTERNS:\n * - Path shape is derived, never stored: `getArtifactPaths` is pure so a reader\n * can reconstruct every file for a run from the run id and agent name alone\n * - The agent name is sanitised into the filename because it comes from\n * user-authored frontmatter and would otherwise let a run write outside the\n * artifacts directory\n * - Cleanup is rate-limited by a marker file rather than by an in-process timer,\n * so several concurrent extension hosts do not each sweep on every startup\n *\n * AVOID:\n * - Failing a run because housekeeping failed; every cleanup error is swallowed\n * deliberately and the sweep continues with the next entry\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { ArtifactDirPreference, ArtifactPaths } from '../types/index.ts';\nimport { writeAtomicJson } from './atomicJson.ts';\nimport { getAgentDir } from './configDir.ts';\nimport { TEMP_ARTIFACTS_DIR } from './paths.ts';\n\nconst CLEANUP_MARKER_FILE = '.last-cleanup';\n/**\n * In-project artifact root, named for this package rather than its predecessor.\n *\n * Both packages can be installed at once during cutover, and both sweep their\n * root on a retention timer. A shared name would let one package's cleanup delete\n * the other's live artifacts, so the roots are kept disjoint.\n */\nconst PROJECT_ARTIFACT_ROOT = '.doom-team';\nconst PROJECT_ARTIFACTS_DIR_NAME = 'artifacts';\nconst PROJECT_CHAIN_RUNS_DIR_NAME = 'chain-runs';\nconst SESSION_ARTIFACTS_DIR_NAME = 'subagent-artifacts';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst UNSAFE_AGENT_NAME_CHARS = /[^\\w.-]/g;\nconst AGENT_NAME_REPLACEMENT = '_';\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = MS_PER_DAY;\n\nexport function getProjectSubagentsDir(cwd: string): string {\n return path.join(cwd, PROJECT_ARTIFACT_ROOT);\n}\n\nexport function getProjectArtifactsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_ARTIFACTS_DIR_NAME);\n}\n\nexport function getProjectChainRunsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_CHAIN_RUNS_DIR_NAME);\n}\n\nexport function getArtifactsDir(\n sessionFile: string | null,\n projectCwd?: string,\n dirPreference: ArtifactDirPreference = 'session',\n): string {\n switch (dirPreference) {\n case 'session':\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n case 'temp':\n return TEMP_ARTIFACTS_DIR;\n case 'project':\n if (projectCwd) return getProjectArtifactsDir(projectCwd);\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n default:\n throw new Error(\n `Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected \"project\", \"session\", or \"temp\".`,\n );\n }\n}\n\nexport function getArtifactPaths(artifactsDir: string, runId: string, agent: string, index?: number): ArtifactPaths {\n const suffix = index !== undefined ? `_${index}` : '';\n const safeAgent = agent.replace(UNSAFE_AGENT_NAME_CHARS, AGENT_NAME_REPLACEMENT);\n const base = `${runId}_${safeAgent}${suffix}`;\n return {\n inputPath: path.join(artifactsDir, `${base}_input.md`),\n outputPath: path.join(artifactsDir, `${base}_output.md`),\n jsonlPath: path.join(artifactsDir, `${base}.jsonl`),\n transcriptPath: path.join(artifactsDir, `${base}_transcript.jsonl`),\n metadataPath: path.join(artifactsDir, `${base}_meta.json`),\n };\n}\n\nexport function ensureArtifactsDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nexport function writeArtifact(filePath: string, content: string): void {\n fs.writeFileSync(filePath, content, 'utf-8');\n}\n\nexport function formatOutputArtifactContent(input: {\n output: string;\n error?: string;\n transcriptPath?: string;\n metadataPath?: string;\n}): string {\n // An empty output artifact tells the reader nothing about why a run produced\n // nothing, so a failure with no output is replaced by the error and its evidence.\n if (input.output.trim() || !input.error) return input.output;\n const lines = ['Subagent run failed before producing output.', '', 'Error:', input.error];\n if (input.transcriptPath) lines.push('', `Transcript: ${input.transcriptPath}`);\n if (input.metadataPath) lines.push(`Metadata: ${input.metadataPath}`);\n return lines.join('\\n');\n}\n\n/**\n * Publish a run's metadata sidecar.\n *\n * Written atomically because the parent polls this file while the run is still\n * producing it, and a direct write lets that poll observe a half-serialised object.\n */\nexport function writeMetadata(filePath: string, metadata: object): void {\n writeAtomicJson(filePath, metadata);\n}\n\nexport function appendJsonl(filePath: string, line: string): void {\n fs.appendFileSync(filePath, `${line}\\n`);\n}\n\nexport function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {\n if (!fs.existsSync(dir)) return;\n\n const markerPath = path.join(dir, CLEANUP_MARKER_FILE);\n const now = Date.now();\n\n if (fs.existsSync(markerPath)) {\n if (now - fs.statSync(markerPath).mtimeMs < CLEANUP_INTERVAL_MS) return;\n }\n\n const maxAgeMs = maxAgeDays * MS_PER_DAY;\n const cutoff = now - maxAgeMs;\n\n for (const file of fs.readdirSync(dir)) {\n if (file === CLEANUP_MARKER_FILE) continue;\n const filePath = path.join(dir, file);\n try {\n const stat = fs.statSync(filePath);\n if (stat.mtimeMs < cutoff) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // Artifact cleanup is best-effort housekeeping. Skip files that disappear\n // or become unreadable while scanning so one bad entry does not block the rest.\n }\n }\n\n fs.writeFileSync(markerPath, String(now));\n}\n\nexport function cleanupAllArtifactDirs(maxAgeDays: number): void {\n cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);\n\n const sessionsBase = path.join(getAgentDir(), SESSIONS_DIR_NAME);\n if (!fs.existsSync(sessionsBase)) return;\n\n let dirs: string[];\n try {\n dirs = fs.readdirSync(sessionsBase);\n } catch {\n // Session artifact cleanup is best-effort. If the sessions root cannot be read,\n // skip cleanup instead of failing extension startup.\n return;\n }\n\n for (const dir of dirs) {\n const artifactsDir = path.join(sessionsBase, dir, SESSION_ARTIFACTS_DIR_NAME);\n try {\n cleanupOldArtifacts(artifactsDir, maxAgeDays);\n } catch {\n // Session cleanup is best-effort. Keep going so one unreadable session dir\n // does not block cleanup for the rest.\n }\n }\n}\n"],"mappings":"sMAgCA,MAGM,EAA6B,qBAE7B,EAA0B,WAKhC,SAAgB,EAAuB,EAAqB,CAC1D,OAAOA,EAAK,KAAK,EAAK,aAAsB,CAG9C,SAAgB,EAAuB,EAAqB,CAC1D,OAAOA,EAAK,KAAK,EAAuB,EAAI,CAAE,YAA2B,CAO3E,SAAgB,EACd,EACA,EACA,EAAuC,UAC/B,CACR,OAAQ,EAAR,CACE,IAAK,UACH,GAAI,EAAa,CACf,IAAM,EAAaA,EAAK,QAAQ,EAAY,CAC5C,OAAOA,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAOC,EAAAA,mBACT,IAAK,OACH,OAAOA,EAAAA,mBACT,IAAK,UACH,GAAI,EAAY,OAAO,EAAuB,EAAW,CACzD,GAAI,EAAa,CACf,IAAM,EAAaD,EAAK,QAAQ,EAAY,CAC5C,OAAOA,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAOC,EAAAA,mBACT,QACE,MAAU,MACR,2BAA2B,KAAK,UAAU,EAAc,CAAC,6CAC1D,EAIP,SAAgB,EAAiB,EAAsB,EAAe,EAAe,EAA+B,CAClH,IAAM,EAAS,IAAU,IAAA,GAA0B,GAAd,IAAI,IAEnC,EAAO,GAAG,EAAM,GADJ,EAAM,QAAQ,EAAyB,IACvB,GAAG,IACrC,MAAO,CACL,UAAWD,EAAK,KAAK,EAAc,GAAG,EAAK,WAAW,CACtD,WAAYA,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CACxD,UAAWA,EAAK,KAAK,EAAc,GAAG,EAAK,QAAQ,CACnD,eAAgBA,EAAK,KAAK,EAAc,GAAG,EAAK,mBAAmB,CACnE,aAAcA,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CAC3D,CAoCH,SAAgB,EAAY,EAAkB,EAAoB,CAChE,EAAG,eAAe,EAAU,GAAG,EAAK,IAAI"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"artifacts.mjs","names":[],"sources":["../../src/shared/artifacts.ts"],"sourcesContent":["/**\n * Artifact file layout and retention for subagent runs.\n *\n * DESIGN PATTERNS:\n * - Path shape is derived, never stored: `getArtifactPaths` is pure so a reader\n * can reconstruct every file for a run from the run id and agent name alone\n * - The agent name is sanitised into the filename because it comes from\n * user-authored frontmatter and would otherwise let a run write outside the\n * artifacts directory\n * - Cleanup is rate-limited by a marker file rather than by an in-process timer,\n * so several concurrent extension hosts do not each sweep on every startup\n *\n * AVOID:\n * - Failing a run because housekeeping failed; every cleanup error is swallowed\n * deliberately and the sweep continues with the next entry\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { ArtifactDirPreference, ArtifactPaths } from '../types.ts';\nimport { writeAtomicJson } from './atomicJson.ts';\nimport { getAgentDir } from './configDir.ts';\nimport { TEMP_ARTIFACTS_DIR } from './paths.ts';\n\nconst CLEANUP_MARKER_FILE = '.last-cleanup';\n/**\n * In-project artifact root, named for this package rather than its predecessor.\n *\n * Both packages can be installed at once during cutover, and both sweep their\n * root on a retention timer. A shared name would let one package's cleanup delete\n * the other's live artifacts, so the roots are kept disjoint.\n */\nconst PROJECT_ARTIFACT_ROOT = '.doom-team';\nconst PROJECT_ARTIFACTS_DIR_NAME = 'artifacts';\nconst PROJECT_CHAIN_RUNS_DIR_NAME = 'chain-runs';\nconst SESSION_ARTIFACTS_DIR_NAME = 'subagent-artifacts';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst UNSAFE_AGENT_NAME_CHARS = /[^\\w.-]/g;\nconst AGENT_NAME_REPLACEMENT = '_';\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = MS_PER_DAY;\n\nexport function getProjectSubagentsDir(cwd: string): string {\n return path.join(cwd, PROJECT_ARTIFACT_ROOT);\n}\n\nexport function getProjectArtifactsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_ARTIFACTS_DIR_NAME);\n}\n\nexport function getProjectChainRunsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_CHAIN_RUNS_DIR_NAME);\n}\n\nexport function getArtifactsDir(\n sessionFile: string | null,\n projectCwd?: string,\n dirPreference: ArtifactDirPreference = 'session',\n): string {\n switch (dirPreference) {\n case 'session':\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n case 'temp':\n return TEMP_ARTIFACTS_DIR;\n case 'project':\n if (projectCwd) return getProjectArtifactsDir(projectCwd);\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n default:\n throw new Error(\n `Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected \"project\", \"session\", or \"temp\".`,\n );\n }\n}\n\nexport function getArtifactPaths(artifactsDir: string, runId: string, agent: string, index?: number): ArtifactPaths {\n const suffix = index !== undefined ? `_${index}` : '';\n const safeAgent = agent.replace(UNSAFE_AGENT_NAME_CHARS, AGENT_NAME_REPLACEMENT);\n const base = `${runId}_${safeAgent}${suffix}`;\n return {\n inputPath: path.join(artifactsDir, `${base}_input.md`),\n outputPath: path.join(artifactsDir, `${base}_output.md`),\n jsonlPath: path.join(artifactsDir, `${base}.jsonl`),\n transcriptPath: path.join(artifactsDir, `${base}_transcript.jsonl`),\n metadataPath: path.join(artifactsDir, `${base}_meta.json`),\n };\n}\n\nexport function ensureArtifactsDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nexport function writeArtifact(filePath: string, content: string): void {\n fs.writeFileSync(filePath, content, 'utf-8');\n}\n\nexport function formatOutputArtifactContent(input: {\n output: string;\n error?: string;\n transcriptPath?: string;\n metadataPath?: string;\n}): string {\n // An empty output artifact tells the reader nothing about why a run produced\n // nothing, so a failure with no output is replaced by the error and its evidence.\n if (input.output.trim() || !input.error) return input.output;\n const lines = ['Subagent run failed before producing output.', '', 'Error:', input.error];\n if (input.transcriptPath) lines.push('', `Transcript: ${input.transcriptPath}`);\n if (input.metadataPath) lines.push(`Metadata: ${input.metadataPath}`);\n return lines.join('\\n');\n}\n\n/**\n * Publish a run's metadata sidecar.\n *\n * Written atomically because the parent polls this file while the run is still\n * producing it, and a direct write lets that poll observe a half-serialised object.\n */\nexport function writeMetadata(filePath: string, metadata: object): void {\n writeAtomicJson(filePath, metadata);\n}\n\nexport function appendJsonl(filePath: string, line: string): void {\n fs.appendFileSync(filePath, `${line}\\n`);\n}\n\nexport function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {\n if (!fs.existsSync(dir)) return;\n\n const markerPath = path.join(dir, CLEANUP_MARKER_FILE);\n const now = Date.now();\n\n if (fs.existsSync(markerPath)) {\n if (now - fs.statSync(markerPath).mtimeMs < CLEANUP_INTERVAL_MS) return;\n }\n\n const maxAgeMs = maxAgeDays * MS_PER_DAY;\n const cutoff = now - maxAgeMs;\n\n for (const file of fs.readdirSync(dir)) {\n if (file === CLEANUP_MARKER_FILE) continue;\n const filePath = path.join(dir, file);\n try {\n const stat = fs.statSync(filePath);\n if (stat.mtimeMs < cutoff) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // Artifact cleanup is best-effort housekeeping. Skip files that disappear\n // or become unreadable while scanning so one bad entry does not block the rest.\n }\n }\n\n fs.writeFileSync(markerPath, String(now));\n}\n\nexport function cleanupAllArtifactDirs(maxAgeDays: number): void {\n cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);\n\n const sessionsBase = path.join(getAgentDir(), SESSIONS_DIR_NAME);\n if (!fs.existsSync(sessionsBase)) return;\n\n let dirs: string[];\n try {\n dirs = fs.readdirSync(sessionsBase);\n } catch {\n // Session artifact cleanup is best-effort. If the sessions root cannot be read,\n // skip cleanup instead of failing extension startup.\n return;\n }\n\n for (const dir of dirs) {\n const artifactsDir = path.join(sessionsBase, dir, SESSION_ARTIFACTS_DIR_NAME);\n try {\n cleanupOldArtifacts(artifactsDir, maxAgeDays);\n } catch {\n // Session cleanup is best-effort. Keep going so one unreadable session dir\n // does not block cleanup for the rest.\n }\n }\n}\n"],"mappings":"uOA0CA,SAAgB,EAAuB,EAAqB,CAC1D,OAAO,EAAK,KAAK,EAAK,EAAsB,CAG9C,SAAgB,EAAuB,EAAqB,CAC1D,OAAO,EAAK,KAAK,EAAuB,EAAI,CAAE,EAA2B,CAO3E,SAAgB,EACd,EACA,EACA,EAAuC,UAC/B,CACR,OAAQ,EAAR,CACE,IAAK,UACH,GAAI,EAAa,CACf,IAAM,EAAa,EAAK,QAAQ,EAAY,CAC5C,OAAO,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAO,EACT,IAAK,OACH,OAAO,EACT,IAAK,UACH,GAAI,EAAY,OAAO,EAAuB,EAAW,CACzD,GAAI,EAAa,CACf,IAAM,EAAa,EAAK,QAAQ,EAAY,CAC5C,OAAO,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAO,EACT,QACE,MAAU,MACR,2BAA2B,KAAK,UAAU,EAAc,CAAC,6CAC1D,EAIP,SAAgB,EAAiB,EAAsB,EAAe,EAAe,EAA+B,CAClH,IAAM,EAAS,IAAU,IAAA,GAA0B,GAAd,IAAI,IAEnC,EAAO,GAAG,EAAM,GADJ,EAAM,QAAQ,EAAyB,EACvB,GAAG,IACrC,MAAO,CACL,UAAW,EAAK,KAAK,EAAc,GAAG,EAAK,WAAW,CACtD,WAAY,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CACxD,UAAW,EAAK,KAAK,EAAc,GAAG,EAAK,QAAQ,CACnD,eAAgB,EAAK,KAAK,EAAc,GAAG,EAAK,mBAAmB,CACnE,aAAc,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CAC3D,CAoCH,SAAgB,EAAY,EAAkB,EAAoB,CAChE,EAAG,eAAe,EAAU,GAAG,EAAK,IAAI,4BA7GQ,IAEF,CAU1C,EAAwB,aACxB,EAA6B,YAE7B,EAA6B,qBAE7B,EAA0B,WAC1B,EAAyB"}
|
|
1
|
+
{"version":3,"file":"artifacts.mjs","names":[],"sources":["../../src/shared/artifacts.ts"],"sourcesContent":["/**\n * Artifact file layout and retention for subagent runs.\n *\n * DESIGN PATTERNS:\n * - Path shape is derived, never stored: `getArtifactPaths` is pure so a reader\n * can reconstruct every file for a run from the run id and agent name alone\n * - The agent name is sanitised into the filename because it comes from\n * user-authored frontmatter and would otherwise let a run write outside the\n * artifacts directory\n * - Cleanup is rate-limited by a marker file rather than by an in-process timer,\n * so several concurrent extension hosts do not each sweep on every startup\n *\n * AVOID:\n * - Failing a run because housekeeping failed; every cleanup error is swallowed\n * deliberately and the sweep continues with the next entry\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { ArtifactDirPreference, ArtifactPaths } from '../types/index.ts';\nimport { writeAtomicJson } from './atomicJson.ts';\nimport { getAgentDir } from './configDir.ts';\nimport { TEMP_ARTIFACTS_DIR } from './paths.ts';\n\nconst CLEANUP_MARKER_FILE = '.last-cleanup';\n/**\n * In-project artifact root, named for this package rather than its predecessor.\n *\n * Both packages can be installed at once during cutover, and both sweep their\n * root on a retention timer. A shared name would let one package's cleanup delete\n * the other's live artifacts, so the roots are kept disjoint.\n */\nconst PROJECT_ARTIFACT_ROOT = '.doom-team';\nconst PROJECT_ARTIFACTS_DIR_NAME = 'artifacts';\nconst PROJECT_CHAIN_RUNS_DIR_NAME = 'chain-runs';\nconst SESSION_ARTIFACTS_DIR_NAME = 'subagent-artifacts';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst UNSAFE_AGENT_NAME_CHARS = /[^\\w.-]/g;\nconst AGENT_NAME_REPLACEMENT = '_';\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst CLEANUP_INTERVAL_MS = MS_PER_DAY;\n\nexport function getProjectSubagentsDir(cwd: string): string {\n return path.join(cwd, PROJECT_ARTIFACT_ROOT);\n}\n\nexport function getProjectArtifactsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_ARTIFACTS_DIR_NAME);\n}\n\nexport function getProjectChainRunsDir(cwd: string): string {\n return path.join(getProjectSubagentsDir(cwd), PROJECT_CHAIN_RUNS_DIR_NAME);\n}\n\nexport function getArtifactsDir(\n sessionFile: string | null,\n projectCwd?: string,\n dirPreference: ArtifactDirPreference = 'session',\n): string {\n switch (dirPreference) {\n case 'session':\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n case 'temp':\n return TEMP_ARTIFACTS_DIR;\n case 'project':\n if (projectCwd) return getProjectArtifactsDir(projectCwd);\n if (sessionFile) {\n const sessionDir = path.dirname(sessionFile);\n return path.join(sessionDir, SESSION_ARTIFACTS_DIR_NAME);\n }\n return TEMP_ARTIFACTS_DIR;\n default:\n throw new Error(\n `Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected \"project\", \"session\", or \"temp\".`,\n );\n }\n}\n\nexport function getArtifactPaths(artifactsDir: string, runId: string, agent: string, index?: number): ArtifactPaths {\n const suffix = index !== undefined ? `_${index}` : '';\n const safeAgent = agent.replace(UNSAFE_AGENT_NAME_CHARS, AGENT_NAME_REPLACEMENT);\n const base = `${runId}_${safeAgent}${suffix}`;\n return {\n inputPath: path.join(artifactsDir, `${base}_input.md`),\n outputPath: path.join(artifactsDir, `${base}_output.md`),\n jsonlPath: path.join(artifactsDir, `${base}.jsonl`),\n transcriptPath: path.join(artifactsDir, `${base}_transcript.jsonl`),\n metadataPath: path.join(artifactsDir, `${base}_meta.json`),\n };\n}\n\nexport function ensureArtifactsDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nexport function writeArtifact(filePath: string, content: string): void {\n fs.writeFileSync(filePath, content, 'utf-8');\n}\n\nexport function formatOutputArtifactContent(input: {\n output: string;\n error?: string;\n transcriptPath?: string;\n metadataPath?: string;\n}): string {\n // An empty output artifact tells the reader nothing about why a run produced\n // nothing, so a failure with no output is replaced by the error and its evidence.\n if (input.output.trim() || !input.error) return input.output;\n const lines = ['Subagent run failed before producing output.', '', 'Error:', input.error];\n if (input.transcriptPath) lines.push('', `Transcript: ${input.transcriptPath}`);\n if (input.metadataPath) lines.push(`Metadata: ${input.metadataPath}`);\n return lines.join('\\n');\n}\n\n/**\n * Publish a run's metadata sidecar.\n *\n * Written atomically because the parent polls this file while the run is still\n * producing it, and a direct write lets that poll observe a half-serialised object.\n */\nexport function writeMetadata(filePath: string, metadata: object): void {\n writeAtomicJson(filePath, metadata);\n}\n\nexport function appendJsonl(filePath: string, line: string): void {\n fs.appendFileSync(filePath, `${line}\\n`);\n}\n\nexport function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {\n if (!fs.existsSync(dir)) return;\n\n const markerPath = path.join(dir, CLEANUP_MARKER_FILE);\n const now = Date.now();\n\n if (fs.existsSync(markerPath)) {\n if (now - fs.statSync(markerPath).mtimeMs < CLEANUP_INTERVAL_MS) return;\n }\n\n const maxAgeMs = maxAgeDays * MS_PER_DAY;\n const cutoff = now - maxAgeMs;\n\n for (const file of fs.readdirSync(dir)) {\n if (file === CLEANUP_MARKER_FILE) continue;\n const filePath = path.join(dir, file);\n try {\n const stat = fs.statSync(filePath);\n if (stat.mtimeMs < cutoff) {\n fs.unlinkSync(filePath);\n }\n } catch {\n // Artifact cleanup is best-effort housekeeping. Skip files that disappear\n // or become unreadable while scanning so one bad entry does not block the rest.\n }\n }\n\n fs.writeFileSync(markerPath, String(now));\n}\n\nexport function cleanupAllArtifactDirs(maxAgeDays: number): void {\n cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);\n\n const sessionsBase = path.join(getAgentDir(), SESSIONS_DIR_NAME);\n if (!fs.existsSync(sessionsBase)) return;\n\n let dirs: string[];\n try {\n dirs = fs.readdirSync(sessionsBase);\n } catch {\n // Session artifact cleanup is best-effort. If the sessions root cannot be read,\n // skip cleanup instead of failing extension startup.\n return;\n }\n\n for (const dir of dirs) {\n const artifactsDir = path.join(sessionsBase, dir, SESSION_ARTIFACTS_DIR_NAME);\n try {\n cleanupOldArtifacts(artifactsDir, maxAgeDays);\n } catch {\n // Session cleanup is best-effort. Keep going so one unreadable session dir\n // does not block cleanup for the rest.\n }\n }\n}\n"],"mappings":"uOA0CA,SAAgB,EAAuB,EAAqB,CAC1D,OAAO,EAAK,KAAK,EAAK,EAAsB,CAG9C,SAAgB,EAAuB,EAAqB,CAC1D,OAAO,EAAK,KAAK,EAAuB,EAAI,CAAE,EAA2B,CAO3E,SAAgB,EACd,EACA,EACA,EAAuC,UAC/B,CACR,OAAQ,EAAR,CACE,IAAK,UACH,GAAI,EAAa,CACf,IAAM,EAAa,EAAK,QAAQ,EAAY,CAC5C,OAAO,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAO,EACT,IAAK,OACH,OAAO,EACT,IAAK,UACH,GAAI,EAAY,OAAO,EAAuB,EAAW,CACzD,GAAI,EAAa,CACf,IAAM,EAAa,EAAK,QAAQ,EAAY,CAC5C,OAAO,EAAK,KAAK,EAAY,EAA2B,CAE1D,OAAO,EACT,QACE,MAAU,MACR,2BAA2B,KAAK,UAAU,EAAc,CAAC,6CAC1D,EAIP,SAAgB,EAAiB,EAAsB,EAAe,EAAe,EAA+B,CAClH,IAAM,EAAS,IAAU,IAAA,GAA0B,GAAd,IAAI,IAEnC,EAAO,GAAG,EAAM,GADJ,EAAM,QAAQ,EAAyB,EACvB,GAAG,IACrC,MAAO,CACL,UAAW,EAAK,KAAK,EAAc,GAAG,EAAK,WAAW,CACtD,WAAY,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CACxD,UAAW,EAAK,KAAK,EAAc,GAAG,EAAK,QAAQ,CACnD,eAAgB,EAAK,KAAK,EAAc,GAAG,EAAK,mBAAmB,CACnE,aAAc,EAAK,KAAK,EAAc,GAAG,EAAK,YAAY,CAC3D,CAoCH,SAAgB,EAAY,EAAkB,EAAoB,CAChE,EAAG,eAAe,EAAU,GAAG,EAAK,IAAI,4BA7GQ,IAEF,CAU1C,EAAwB,aACxB,EAA6B,YAE7B,EAA6B,qBAE7B,EAA0B,WAC1B,EAAyB"}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//# sourceMappingURL=atomicJson.cjs.map
|
|
1
|
+
require(`../adapters/atomicJson.cjs`);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{init_atomicJson as t}from"../adapters/atomicJson.mjs";var n=e((()=>{t()}));n();export{n as init_atomicJson};
|
|
2
2
|
//# sourceMappingURL=atomicJson.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomicJson.mjs","names":[],"sources":["../../src/shared/atomicJson.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"atomicJson.mjs","names":[],"sources":["../../src/shared/atomicJson.ts"],"sourcesContent":["export {\n createAtomicJsonWriter,\n writeAtomicJson,\n writeAtomicJsonAsync,\n writePrivateAtomicJson,\n} from '../adapters/atomicJson.ts';\n"],"mappings":"2IAKmC"}
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
… payload truncated`,a=[`command`,`path`,`file_path`,`pattern`,`query`,`url`,`task`,`describe`,`search`];function o(e){return e instanceof Error?e.message:String(e)}function s(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function c(e,t=32768){let n;if(typeof e==`string`)n=e;else try{let t=JSON.stringify(e,null,2);if(t===void 0)return;n=t}catch{return}if(!n.trim())return;let r=Buffer.from(n,`utf-8`);if(r.length<=t)return{text:n,truncated:!1};let a=Buffer.byteLength(i,`utf-8`),o=Math.max(0,t-a);for(;o>0&&((r[o]??0)&192)==128;)o--;return{text:`${r.subarray(0,o).toString(`utf-8`)}${i}`,truncated:!0}}function l(e){if(!e||typeof e!=`object`)return;let t=e,n=t.cost,r=n&&typeof n==`object`?s(n.total)??0:s(n)??0;return{input:s(t.input)??s(t.inputTokens)??0,output:s(t.output)??s(t.outputTokens)??0,cacheRead:s(t.cacheRead)??0,cacheWrite:s(t.cacheWrite)??0,cost:r}}function u(e){let t=l(e.usage);if(t)return t.input+t.output+t.cacheRead+t.cacheWrite}function d(e,t){return e.length>t?`${e.slice(0,t-3)}...`:e}function f(e){if(typeof e==`string`&&e.trim().length>0)return e;if(typeof e==`number`||typeof e==`boolean`)return String(e)}function p(e){if(!Array.isArray(e)||e.length===0)return;let t=f(e[0]);if(t)return`${t}${e.length>1?` (+${e.length-1} more)`:``}`}function m(e){if(typeof e.tool==`string`&&e.tool){let t=typeof e.server==`string`&&e.server?`${e.server}/`:``,n=typeof e.args==`string`&&e.args?` ${e.args.slice(0,40)}`:``;return`${t}${e.tool}${n}`}let t=p(e.queries);if(t)return d(t,60);if(typeof e.query==`string`&&e.query.trim().length>0)return d(e.query,60);if(typeof e.workflow==`string`&&e.workflow.trim().length>0)return`workflow=${d(e.workflow,48)}`;if(typeof e.url==`string`&&e.url.trim().length>0)return d(e.url,60);let n=p(e.urls);if(n)return d(n,60);if(typeof e.prompt==`string`&&e.prompt.trim().length>0)return d(e.prompt,60);for(let t of a){let n=e[t];if(typeof n==`string`&&n.length>0)return d(n,60)}for(let[t,n]of Object.entries(e)){let e=p(n);if(e)return`${t}=${d(e,50)}`;if(typeof n==`string`&&n.length>0)return`${t}=${d(n,50)}`}return``}function h(e){if(!e)return``;if(typeof e==`string`)return e;if(!Array.isArray(e))return``;let t=[];for(let n of e){if(!n||typeof n!=`object`)continue;let e=n;if(e.type===`text`&&`text`in e)t.push(String(e.text));else if(e.type===`tool_result`&&`content`in e){let n=h(e.content);n&&t.push(n)}else `text`in e&&t.push(String(e.text))}return t.join(`
|
|
4
|
-
`)}function g(e){return e.args&&typeof e.args==`object`&&!Array.isArray(e.args)?e.args:{}}function _(e){let r=0,i,a=!1,s=e.maxBytes??52428800,u=t=>{let n=Date.now();return{version:1,recordType:t,source:e.source,runId:e.runId,agent:e.agent,...e.childIndex===void 0?{}:{childIndex:e.childIndex},cwd:e.cwd,ts:n,timestamp:new Date(n).toISOString()}},d=()=>`${JSON.stringify({...u(`truncated`),maxBytes:s,message:`Child transcript exceeded ${s} bytes; further records were omitted.`})}\n`,f=Buffer.byteLength(d(),`utf-8`),p=()=>{a=!0;let n=d(),c=Buffer.byteLength(n,`utf-8`);if(!(r+c>s))try{t.appendFileSync(e.transcriptPath,n,`utf-8`),r+=c}catch(t){i=`Failed to write child transcript '${e.transcriptPath}': ${o(t)}`}},_=n=>{if(i||a)return;let c=`${JSON.stringify(n)}\n`,l=Buffer.byteLength(c,`utf-8`);if(r+l+f>s){p();return}try{t.appendFileSync(e.transcriptPath,c,`utf-8`),r+=l}catch(t){i=`Failed to write child transcript '${e.transcriptPath}': ${o(t)}`}};try{t.mkdirSync(n.dirname(e.transcriptPath),{recursive:!0}),t.writeFileSync(e.transcriptPath,``,`utf-8`)}catch(t){i=`Failed to initialize child transcript '${e.transcriptPath}': ${o(t)}`}let v=(e,t)=>{let n=h(t.content),r=l(t.usage);if(t.role===`toolResult`){let r=c(n);_({...u(`message`),sourceEventType:e,role:t.role,toolCallId:t.toolCallId,toolName:t.toolName,isError:t.isError,...r?{text:r.text,outputTruncated:r.truncated}:{},message:{role:t.role,toolCallId:t.toolCallId,toolName:t.toolName,isError:t.isError,content:r?[{type:`text`,text:r.text}]:[],...t.timestamp===void 0?{}:{timestamp:t.timestamp}}});return}_({...u(`message`),sourceEventType:e,role:t.role,...n?{text:n}:{},...t.model?{model:t.model}:{},...t.stopReason?{stopReason:t.stopReason}:{},...t.errorMessage?{errorMessage:t.errorMessage}:{},...r?{usage:r}:{},message:t})},y=e=>{e.trim()&&_({...u(`stderr`),text:e})};return{path:e.transcriptPath,writeInitialUserMessage(e){_({...u(`message`),sourceEventType:`initial_prompt`,role:`user`,text:e,message:{role:`user`,content:[{type:`text`,text:e}]}})},writeChildEvent(e){if((e.type===`message_end`||e.type===`tool_result_end`)&&e.message){v(e.type,e.message);return}if(e.type===`tool_execution_start`&&e.toolName){let t=g(e),n=c(t);_({...u(`tool_start`),sourceEventType:e.type,...e.toolCallId?{toolCallId:e.toolCallId}:{},toolName:e.toolName,...Object.keys(t).length>0?{argsPreview:m(t)}:{},...n?{argsPayload:n.text}:{}});return}e.type===`tool_execution_end`&&_({...u(`tool_end`),sourceEventType:e.type,...e.toolCallId?{toolCallId:e.toolCallId}:{},...e.toolName?{toolName:e.toolName}:{},...typeof e.isError==`boolean`?{isError:e.isError}:{}})},writeStdoutLine(e){e.trim()&&_({...u(`stdout`),text:e})},writeStderrLine:y,writeStderrText(e){for(let t of e.split(/\r?\n/))y(t)},getError(){return i}}}exports.createChildTranscriptWriter=_,exports.getMessageUsageTokens=u;
|
|
5
|
-
//# sourceMappingURL=childTranscript.cjs.map
|
|
1
|
+
require(`../adapters/process/childTranscript.cjs`);
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import
|
|
2
|
-
`)}function p(e){return e.args&&typeof e.args==`object`&&!Array.isArray(e.args)?e.args:{}}function m(e){let i=0,s,c=!1,l=e.maxBytes??v,u=t=>{let n=Date.now();return{version:1,recordType:t,source:e.source,runId:e.runId,agent:e.agent,...e.childIndex===void 0?{}:{childIndex:e.childIndex},cwd:e.cwd,ts:n,timestamp:new Date(n).toISOString()}},m=()=>`${JSON.stringify({...u(`truncated`),maxBytes:l,message:`Child transcript exceeded ${l} bytes; further records were omitted.`})}\n`,h=Buffer.byteLength(m(),`utf-8`),g=()=>{c=!0;let n=m(),a=Buffer.byteLength(n,`utf-8`);if(!(i+a>l))try{t.appendFileSync(e.transcriptPath,n,`utf-8`),i+=a}catch(t){s=`Failed to write child transcript '${e.transcriptPath}': ${r(t)}`}},_=n=>{if(s||c)return;let a=`${JSON.stringify(n)}\n`,o=Buffer.byteLength(a,`utf-8`);if(i+o+h>l){g();return}try{t.appendFileSync(e.transcriptPath,a,`utf-8`),i+=o}catch(t){s=`Failed to write child transcript '${e.transcriptPath}': ${r(t)}`}};try{t.mkdirSync(n.dirname(e.transcriptPath),{recursive:!0}),t.writeFileSync(e.transcriptPath,``,`utf-8`)}catch(t){s=`Failed to initialize child transcript '${e.transcriptPath}': ${r(t)}`}let y=(e,t)=>{let n=f(t.content),r=o(t.usage);if(t.role===`toolResult`){let r=a(n);_({...u(`message`),sourceEventType:e,role:t.role,toolCallId:t.toolCallId,toolName:t.toolName,isError:t.isError,...r?{text:r.text,outputTruncated:r.truncated}:{},message:{role:t.role,toolCallId:t.toolCallId,toolName:t.toolName,isError:t.isError,content:r?[{type:`text`,text:r.text}]:[],...t.timestamp===void 0?{}:{timestamp:t.timestamp}}});return}_({...u(`message`),sourceEventType:e,role:t.role,...n?{text:n}:{},...t.model?{model:t.model}:{},...t.stopReason?{stopReason:t.stopReason}:{},...t.errorMessage?{errorMessage:t.errorMessage}:{},...r?{usage:r}:{},message:t})},b=e=>{e.trim()&&_({...u(`stderr`),text:e})};return{path:e.transcriptPath,writeInitialUserMessage(e){_({...u(`message`),sourceEventType:`initial_prompt`,role:`user`,text:e,message:{role:`user`,content:[{type:`text`,text:e}]}})},writeChildEvent(e){if((e.type===`message_end`||e.type===`tool_result_end`)&&e.message){y(e.type,e.message);return}if(e.type===`tool_execution_start`&&e.toolName){let t=p(e),n=a(t);_({...u(`tool_start`),sourceEventType:e.type,...e.toolCallId?{toolCallId:e.toolCallId}:{},toolName:e.toolName,...Object.keys(t).length>0?{argsPreview:d(t)}:{},...n?{argsPayload:n.text}:{}});return}e.type===`tool_execution_end`&&_({...u(`tool_end`),sourceEventType:e.type,...e.toolCallId?{toolCallId:e.toolCallId}:{},...e.toolName?{toolName:e.toolName}:{},...typeof e.isError==`boolean`?{isError:e.isError}:{}})},writeStdoutLine(e){e.trim()&&_({...u(`stdout`),text:e})},writeStderrLine:b,writeStderrText(e){for(let t of e.split(/\r?\n/))b(t)},getError(){return s}}}var h,g,_,v,y,b,x,S,C,w,T,E,D,O,k=e((()=>{h=1024,g=1024*h,_=32*h,v=50*g,y=`
|
|
3
|
-
|
|
4
|
-
… payload truncated`,b=2,x=192,S=128,C=`...`,w=60,T=50,E=48,D=40,O=[`command`,`path`,`file_path`,`pattern`,`query`,`url`,`task`,`describe`,`search`]}));k();export{m as createChildTranscriptWriter,s as getMessageUsageTokens,k as init_childTranscript};
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{init_childTranscript as t}from"../adapters/process/childTranscript.mjs";var n=e((()=>{t()}));n();export{n as init_childTranscript};
|
|
5
2
|
//# sourceMappingURL=childTranscript.mjs.map
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"childTranscript.mjs","names":[],"sources":["../../src/shared/childTranscript.ts"],"sourcesContent":["export * from '../adapters/process/childTranscript.ts';\n"],"mappings":""}
|
|
@@ -1,2 +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
|
|
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){return e<=0?Promise.resolve():new Promise(t=>{setTimeout(t,e)})}function o(e){let t=e?.code;return typeof t==`string`&&n.has(t)}function s(e,t={}){let n=t.retryDelaysMs??r,a=t.wait??i;for(let t=0;;t++)try{return e()}catch(e){let r=n[t];if(r===void 0||!o(e))throw e;a(r)}}async function c(e,t={}){let n=t.retryDelaysMs??r,i=t.wait??a;for(let t=0;;t++)try{return await e()}catch(e){let r=n[t];if(r===void 0||!o(e))throw e;await i(r)}}exports.DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS=r,exports.runFileSystemOperationWithRetry=s,exports.runFileSystemOperationWithRetryAsync=c,exports.waitForFileSystemRetry=i;
|
|
2
2
|
//# sourceMappingURL=fileSystemRetry.cjs.map
|
|
@@ -1 +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,
|
|
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,KAMtB,SAAgB,EAAwB,EAAgC,CAEtE,OADI,GAAW,EAAU,QAAQ,SAAS,CACnC,IAAI,QAAS,GAAY,CAC9B,WAAW,EAAS,EAAQ,EAC5B,CAGJ,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,EAKnB,eAAsB,EACpB,EACA,EAAuC,EAAE,CAC7B,CACZ,IAAM,EAAgB,EAAQ,eAAiB,EACzC,EAAO,EAAQ,MAAQ,EAC7B,IAAK,IAAI,EAAU,GAAK,IACtB,GAAI,CACF,OAAO,MAAM,GAAW,OACjB,EAAO,CACd,IAAM,EAAU,EAAc,GAC9B,GAAI,IAAY,IAAA,IAAa,CAAC,EAA2B,EAAM,CAAE,MAAM,EACvE,MAAM,EAAK,EAAQ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";function t(e){if(e<=0)return;if(
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";function t(e){if(e<=0)return;if(s)try{Atomics.wait(s,0,0,e);return}catch{}let t=Date.now()+e;for(;Date.now()<t;);}function n(e){return e<=0?Promise.resolve():new Promise(t=>{setTimeout(t,e)})}function r(e){let t=e?.code;return typeof t==`string`&&c.has(t)}function i(e,n={}){let i=n.retryDelaysMs??l,a=n.wait??t;for(let t=0;;t++)try{return e()}catch(e){let n=i[t];if(n===void 0||!r(e))throw e;a(n)}}async function a(e,t={}){let i=t.retryDelaysMs??l,a=t.wait??n;for(let t=0;;t++)try{return await e()}catch(e){let n=i[t];if(n===void 0||!r(e))throw e;await a(n)}}var o,s,c,l,u=e((()=>{o=typeof SharedArrayBuffer<`u`?new SharedArrayBuffer(4):void 0,s=o?new Int32Array(o):void 0,c=new Set([`EACCES`,`EBUSY`,`EPERM`]),l=[10,25,50,100,200,500,1e3,2e3,4e3]}));u();export{l as DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS,u as init_fileSystemRetry,i as runFileSystemOperationWithRetry,a as runFileSystemOperationWithRetryAsync,t as waitForFileSystemRetry};
|
|
2
2
|
//# sourceMappingURL=fileSystemRetry.mjs.map
|
|
@@ -1 +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,
|
|
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,KAMtB,SAAgB,EAAwB,EAAgC,CAEtE,OADI,GAAW,EAAU,QAAQ,SAAS,CACnC,IAAI,QAAS,GAAY,CAC9B,WAAW,EAAS,EAAQ,EAC5B,CAGJ,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,EAKnB,eAAsB,EACpB,EACA,EAAuC,EAAE,CAC7B,CACZ,IAAM,EAAgB,EAAQ,eAAiB,EACzC,EAAO,EAAQ,MAAQ,EAC7B,IAAK,IAAI,EAAU,GAAK,IACtB,GAAI,CACF,OAAO,MAAM,GAAW,OACjB,EAAO,CACd,IAAM,EAAU,EAAc,GAC9B,GAAI,IAAY,IAAA,IAAa,CAAC,EAA2B,EAAM,CAAE,MAAM,EACvE,MAAM,EAAK,EAAQ,wBA/EnB,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"}
|
|
@@ -1 +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"}
|
|
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/index.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"}
|
|
@@ -1 +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"}
|
|
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/index.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"}
|
package/dist/shared/paths.cjs
CHANGED
|
@@ -1,2 +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;
|
|
1
|
+
const e=require(`../_virtual/_rolldown/runtime.cjs`);require(`../adapters/process/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
2
|
//# sourceMappingURL=paths.cjs.map
|
|
@@ -1 +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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"}
|
|
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 '../adapters/process/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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"}
|
package/dist/shared/paths.mjs
CHANGED
|
@@ -1,2 +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};
|
|
1
|
+
import{__esmMin as e}from"../_virtual/_rolldown/runtime.mjs";import{SUBAGENT_ROOT_SESSION_ENV as t,init_env as n}from"../adapters/process/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
2
|
//# sourceMappingURL=paths.mjs.map
|