@cat-factory/server 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/agents/CompositeAgentExecutor.d.ts +39 -0
- package/dist/agents/CompositeAgentExecutor.d.ts.map +1 -0
- package/dist/agents/CompositeAgentExecutor.js +169 -0
- package/dist/agents/CompositeAgentExecutor.js.map +1 -0
- package/dist/agents/ContainerAgentExecutor.d.ts +235 -0
- package/dist/agents/ContainerAgentExecutor.d.ts.map +1 -0
- package/dist/agents/ContainerAgentExecutor.js +825 -0
- package/dist/agents/ContainerAgentExecutor.js.map +1 -0
- package/dist/agents/ContainerRepoBootstrapper.d.ts +78 -0
- package/dist/agents/ContainerRepoBootstrapper.d.ts.map +1 -0
- package/dist/agents/ContainerRepoBootstrapper.js +279 -0
- package/dist/agents/ContainerRepoBootstrapper.js.map +1 -0
- package/dist/agents/ModelRouter.d.ts +69 -0
- package/dist/agents/ModelRouter.d.ts.map +1 -0
- package/dist/agents/ModelRouter.js +84 -0
- package/dist/agents/ModelRouter.js.map +1 -0
- package/dist/agents/RunnerJobClient.d.ts +41 -0
- package/dist/agents/RunnerJobClient.d.ts.map +1 -0
- package/dist/agents/RunnerJobClient.js +43 -0
- package/dist/agents/RunnerJobClient.js.map +1 -0
- package/dist/agents/modelProviderResolver.d.ts +33 -0
- package/dist/agents/modelProviderResolver.d.ts.map +1 -0
- package/dist/agents/modelProviderResolver.js +48 -0
- package/dist/agents/modelProviderResolver.js.map +1 -0
- package/dist/agents/providerCapabilities.d.ts +22 -0
- package/dist/agents/providerCapabilities.d.ts.map +1 -0
- package/dist/agents/providerCapabilities.js +43 -0
- package/dist/agents/providerCapabilities.js.map +1 -0
- package/dist/agents/resolveRepoTarget.d.ts +33 -0
- package/dist/agents/resolveRepoTarget.d.ts.map +1 -0
- package/dist/agents/resolveRepoTarget.js +81 -0
- package/dist/agents/resolveRepoTarget.js.map +1 -0
- package/dist/app.d.ts +12 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +102 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/GitHubOAuth.d.ts +39 -0
- package/dist/auth/GitHubOAuth.d.ts.map +1 -0
- package/dist/auth/GitHubOAuth.js +90 -0
- package/dist/auth/GitHubOAuth.js.map +1 -0
- package/dist/auth/GoogleOAuth.d.ts +35 -0
- package/dist/auth/GoogleOAuth.d.ts.map +1 -0
- package/dist/auth/GoogleOAuth.js +66 -0
- package/dist/auth/GoogleOAuth.js.map +1 -0
- package/dist/auth/middleware.d.ts +15 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +63 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/signing.d.ts +50 -0
- package/dist/auth/signing.d.ts.map +1 -0
- package/dist/auth/signing.js +96 -0
- package/dist/auth/signing.js.map +1 -0
- package/dist/auth/wsTicket.d.ts +34 -0
- package/dist/auth/wsTicket.d.ts.map +1 -0
- package/dist/auth/wsTicket.js +50 -0
- package/dist/auth/wsTicket.js.map +1 -0
- package/dist/config/types.d.ts +294 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/url-safety.d.ts +8 -0
- package/dist/config/url-safety.d.ts.map +1 -0
- package/dist/config/url-safety.js +11 -0
- package/dist/config/url-safety.js.map +1 -0
- package/dist/containers/ContainerSessionService.d.ts +67 -0
- package/dist/containers/ContainerSessionService.d.ts.map +1 -0
- package/dist/containers/ContainerSessionService.js +44 -0
- package/dist/containers/ContainerSessionService.js.map +1 -0
- package/dist/crypto/WebCryptoPasswordHasher.d.ts +9 -0
- package/dist/crypto/WebCryptoPasswordHasher.d.ts.map +1 -0
- package/dist/crypto/WebCryptoPasswordHasher.js +67 -0
- package/dist/crypto/WebCryptoPasswordHasher.js.map +1 -0
- package/dist/crypto/WebCryptoPersonalSecretCipher.d.ts +6 -0
- package/dist/crypto/WebCryptoPersonalSecretCipher.d.ts.map +1 -0
- package/dist/crypto/WebCryptoPersonalSecretCipher.js +57 -0
- package/dist/crypto/WebCryptoPersonalSecretCipher.js.map +1 -0
- package/dist/crypto/WebCryptoSecretCipher.d.ts +23 -0
- package/dist/crypto/WebCryptoSecretCipher.d.ts.map +1 -0
- package/dist/crypto/WebCryptoSecretCipher.js +60 -0
- package/dist/crypto/WebCryptoSecretCipher.js.map +1 -0
- package/dist/crypto/encoding.d.ts +14 -0
- package/dist/crypto/encoding.d.ts.map +1 -0
- package/dist/crypto/encoding.js +58 -0
- package/dist/crypto/encoding.js.map +1 -0
- package/dist/events/FanOutEventPublisher.d.ts +32 -0
- package/dist/events/FanOutEventPublisher.d.ts.map +1 -0
- package/dist/events/FanOutEventPublisher.js +76 -0
- package/dist/events/FanOutEventPublisher.js.map +1 -0
- package/dist/events/InAppNotificationChannel.d.ts +20 -0
- package/dist/events/InAppNotificationChannel.d.ts.map +1 -0
- package/dist/events/InAppNotificationChannel.js +23 -0
- package/dist/events/InAppNotificationChannel.js.map +1 -0
- package/dist/github/FetchGitHubClient.d.ts +72 -0
- package/dist/github/FetchGitHubClient.d.ts.map +1 -0
- package/dist/github/FetchGitHubClient.js +485 -0
- package/dist/github/FetchGitHubClient.js.map +1 -0
- package/dist/github/FetchGitHubProvisioningClient.d.ts +13 -0
- package/dist/github/FetchGitHubProvisioningClient.d.ts.map +1 -0
- package/dist/github/FetchGitHubProvisioningClient.js +59 -0
- package/dist/github/FetchGitHubProvisioningClient.js.map +1 -0
- package/dist/github/GitHubAppAuth.d.ts +30 -0
- package/dist/github/GitHubAppAuth.d.ts.map +1 -0
- package/dist/github/GitHubAppAuth.js +95 -0
- package/dist/github/GitHubAppAuth.js.map +1 -0
- package/dist/github/GitHubAppRegistry.d.ts +57 -0
- package/dist/github/GitHubAppRegistry.d.ts.map +1 -0
- package/dist/github/GitHubAppRegistry.js +51 -0
- package/dist/github/GitHubAppRegistry.js.map +1 -0
- package/dist/github/GitHubCiStatusProvider.d.ts +21 -0
- package/dist/github/GitHubCiStatusProvider.d.ts.map +1 -0
- package/dist/github/GitHubCiStatusProvider.js +39 -0
- package/dist/github/GitHubCiStatusProvider.js.map +1 -0
- package/dist/github/GitHubMergeabilityProvider.d.ts +26 -0
- package/dist/github/GitHubMergeabilityProvider.d.ts.map +1 -0
- package/dist/github/GitHubMergeabilityProvider.js +38 -0
- package/dist/github/GitHubMergeabilityProvider.js.map +1 -0
- package/dist/github/GitHubPullRequestMerger.d.ts +23 -0
- package/dist/github/GitHubPullRequestMerger.d.ts.map +1 -0
- package/dist/github/GitHubPullRequestMerger.js +38 -0
- package/dist/github/GitHubPullRequestMerger.js.map +1 -0
- package/dist/github/WebCryptoWebhookVerifier.d.ts +9 -0
- package/dist/github/WebCryptoWebhookVerifier.d.ts.map +1 -0
- package/dist/github/WebCryptoWebhookVerifier.js +40 -0
- package/dist/github/WebCryptoWebhookVerifier.js.map +1 -0
- package/dist/github/ensureWorkBranch.d.ts +26 -0
- package/dist/github/ensureWorkBranch.d.ts.map +1 -0
- package/dist/github/ensureWorkBranch.js +97 -0
- package/dist/github/ensureWorkBranch.js.map +1 -0
- package/dist/github/state.d.ts +19 -0
- package/dist/github/state.d.ts.map +1 -0
- package/dist/github/state.js +55 -0
- package/dist/github/state.js.map +1 -0
- package/dist/http/authGate.d.ts +21 -0
- package/dist/http/authGate.d.ts.map +1 -0
- package/dist/http/authGate.js +77 -0
- package/dist/http/authGate.js.map +1 -0
- package/dist/http/cors.d.ts +13 -0
- package/dist/http/cors.d.ts.map +1 -0
- package/dist/http/cors.js +30 -0
- package/dist/http/cors.js.map +1 -0
- package/dist/http/env.d.ts +68 -0
- package/dist/http/env.d.ts.map +1 -0
- package/dist/http/env.js +2 -0
- package/dist/http/env.js.map +1 -0
- package/dist/http/errorHandler.d.ts +4 -0
- package/dist/http/errorHandler.d.ts.map +1 -0
- package/dist/http/errorHandler.js +33 -0
- package/dist/http/errorHandler.js.map +1 -0
- package/dist/http/params.d.ts +8 -0
- package/dist/http/params.d.ts.map +1 -0
- package/dist/http/params.js +13 -0
- package/dist/http/params.js.map +1 -0
- package/dist/http/validation.d.ts +12 -0
- package/dist/http/validation.d.ts.map +1 -0
- package/dist/http/validation.js +21 -0
- package/dist/http/validation.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/accounts/AccountController.d.ts +10 -0
- package/dist/modules/accounts/AccountController.d.ts.map +1 -0
- package/dist/modules/accounts/AccountController.js +197 -0
- package/dist/modules/accounts/AccountController.js.map +1 -0
- package/dist/modules/agentRuns/AgentRunController.d.ts +10 -0
- package/dist/modules/agentRuns/AgentRunController.d.ts.map +1 -0
- package/dist/modules/agentRuns/AgentRunController.js +65 -0
- package/dist/modules/agentRuns/AgentRunController.js.map +1 -0
- package/dist/modules/auth/AuthController.d.ts +12 -0
- package/dist/modules/auth/AuthController.d.ts.map +1 -0
- package/dist/modules/auth/AuthController.js +457 -0
- package/dist/modules/auth/AuthController.js.map +1 -0
- package/dist/modules/board/BoardController.d.ts +8 -0
- package/dist/modules/board/BoardController.d.ts.map +1 -0
- package/dist/modules/board/BoardController.js +89 -0
- package/dist/modules/board/BoardController.js.map +1 -0
- package/dist/modules/boardScan/BoardScanController.d.ts +10 -0
- package/dist/modules/boardScan/BoardScanController.d.ts.map +1 -0
- package/dist/modules/boardScan/BoardScanController.js +53 -0
- package/dist/modules/boardScan/BoardScanController.js.map +1 -0
- package/dist/modules/bootstrap/BootstrapController.d.ts +10 -0
- package/dist/modules/bootstrap/BootstrapController.d.ts.map +1 -0
- package/dist/modules/bootstrap/BootstrapController.js +75 -0
- package/dist/modules/bootstrap/BootstrapController.js.map +1 -0
- package/dist/modules/clarity/ClarityReviewController.d.ts +11 -0
- package/dist/modules/clarity/ClarityReviewController.d.ts.map +1 -0
- package/dist/modules/clarity/ClarityReviewController.js +97 -0
- package/dist/modules/clarity/ClarityReviewController.js.map +1 -0
- package/dist/modules/consensus/ConsensusController.d.ts +12 -0
- package/dist/modules/consensus/ConsensusController.d.ts.map +1 -0
- package/dist/modules/consensus/ConsensusController.js +23 -0
- package/dist/modules/consensus/ConsensusController.js.map +1 -0
- package/dist/modules/documents/DocumentSourceController.d.ts +10 -0
- package/dist/modules/documents/DocumentSourceController.d.ts.map +1 -0
- package/dist/modules/documents/DocumentSourceController.js +116 -0
- package/dist/modules/documents/DocumentSourceController.js.map +1 -0
- package/dist/modules/environments/EnvironmentController.d.ts +10 -0
- package/dist/modules/environments/EnvironmentController.d.ts.map +1 -0
- package/dist/modules/environments/EnvironmentController.js +95 -0
- package/dist/modules/environments/EnvironmentController.js.map +1 -0
- package/dist/modules/events/EventsController.d.ts +26 -0
- package/dist/modules/events/EventsController.d.ts.map +1 -0
- package/dist/modules/events/EventsController.js +56 -0
- package/dist/modules/events/EventsController.js.map +1 -0
- package/dist/modules/execution/ExecutionController.d.ts +10 -0
- package/dist/modules/execution/ExecutionController.d.ts.map +1 -0
- package/dist/modules/execution/ExecutionController.js +156 -0
- package/dist/modules/execution/ExecutionController.js.map +1 -0
- package/dist/modules/fragmentLibrary/FragmentLibraryController.d.ts +14 -0
- package/dist/modules/fragmentLibrary/FragmentLibraryController.d.ts.map +1 -0
- package/dist/modules/fragmentLibrary/FragmentLibraryController.js +128 -0
- package/dist/modules/fragmentLibrary/FragmentLibraryController.js.map +1 -0
- package/dist/modules/github/GitHubController.d.ts +12 -0
- package/dist/modules/github/GitHubController.d.ts.map +1 -0
- package/dist/modules/github/GitHubController.js +234 -0
- package/dist/modules/github/GitHubController.js.map +1 -0
- package/dist/modules/github/GitHubWebhookController.d.ts +13 -0
- package/dist/modules/github/GitHubWebhookController.d.ts.map +1 -0
- package/dist/modules/github/GitHubWebhookController.js +74 -0
- package/dist/modules/github/GitHubWebhookController.js.map +1 -0
- package/dist/modules/llmProxy/LlmProxyController.d.ts +18 -0
- package/dist/modules/llmProxy/LlmProxyController.d.ts.map +1 -0
- package/dist/modules/llmProxy/LlmProxyController.js +567 -0
- package/dist/modules/llmProxy/LlmProxyController.js.map +1 -0
- package/dist/modules/localModels/LocalModelEndpointController.d.ts +4 -0
- package/dist/modules/localModels/LocalModelEndpointController.d.ts.map +1 -0
- package/dist/modules/localModels/LocalModelEndpointController.js +58 -0
- package/dist/modules/localModels/LocalModelEndpointController.js.map +1 -0
- package/dist/modules/merge/MergePresetController.d.ts +9 -0
- package/dist/modules/merge/MergePresetController.d.ts.map +1 -0
- package/dist/modules/merge/MergePresetController.js +46 -0
- package/dist/modules/merge/MergePresetController.js.map +1 -0
- package/dist/modules/modelDefaults/ModelDefaultsController.d.ts +9 -0
- package/dist/modules/modelDefaults/ModelDefaultsController.d.ts.map +1 -0
- package/dist/modules/modelDefaults/ModelDefaultsController.js +32 -0
- package/dist/modules/modelDefaults/ModelDefaultsController.js.map +1 -0
- package/dist/modules/models/ModelController.d.ts +11 -0
- package/dist/modules/models/ModelController.d.ts.map +1 -0
- package/dist/modules/models/ModelController.js +38 -0
- package/dist/modules/models/ModelController.js.map +1 -0
- package/dist/modules/notifications/NotificationController.d.ts +13 -0
- package/dist/modules/notifications/NotificationController.d.ts.map +1 -0
- package/dist/modules/notifications/NotificationController.js +67 -0
- package/dist/modules/notifications/NotificationController.js.map +1 -0
- package/dist/modules/pipelines/PipelineController.d.ts +5 -0
- package/dist/modules/pipelines/PipelineController.d.ts.map +1 -0
- package/dist/modules/pipelines/PipelineController.js +46 -0
- package/dist/modules/pipelines/PipelineController.js.map +1 -0
- package/dist/modules/promptFragments/PromptFragmentController.d.ts +11 -0
- package/dist/modules/promptFragments/PromptFragmentController.d.ts.map +1 -0
- package/dist/modules/promptFragments/PromptFragmentController.js +18 -0
- package/dist/modules/promptFragments/PromptFragmentController.js.map +1 -0
- package/dist/modules/providers/ApiKeyController.d.ts +13 -0
- package/dist/modules/providers/ApiKeyController.d.ts.map +1 -0
- package/dist/modules/providers/ApiKeyController.js +98 -0
- package/dist/modules/providers/ApiKeyController.js.map +1 -0
- package/dist/modules/providers/PersonalSubscriptionController.d.ts +4 -0
- package/dist/modules/providers/PersonalSubscriptionController.d.ts.map +1 -0
- package/dist/modules/providers/PersonalSubscriptionController.js +48 -0
- package/dist/modules/providers/PersonalSubscriptionController.js.map +1 -0
- package/dist/modules/providers/VendorCredentialController.d.ts +4 -0
- package/dist/modules/providers/VendorCredentialController.d.ts.map +1 -0
- package/dist/modules/providers/VendorCredentialController.js +55 -0
- package/dist/modules/providers/VendorCredentialController.js.map +1 -0
- package/dist/modules/providers/personalCredentialGate.d.ts +34 -0
- package/dist/modules/providers/personalCredentialGate.d.ts.map +1 -0
- package/dist/modules/providers/personalCredentialGate.js +106 -0
- package/dist/modules/providers/personalCredentialGate.js.map +1 -0
- package/dist/modules/recurring/RecurringPipelineController.d.ts +8 -0
- package/dist/modules/recurring/RecurringPipelineController.d.ts.map +1 -0
- package/dist/modules/recurring/RecurringPipelineController.js +58 -0
- package/dist/modules/recurring/RecurringPipelineController.js.map +1 -0
- package/dist/modules/recurring/TrackerSettingsController.d.ts +8 -0
- package/dist/modules/recurring/TrackerSettingsController.d.ts.map +1 -0
- package/dist/modules/recurring/TrackerSettingsController.js +30 -0
- package/dist/modules/recurring/TrackerSettingsController.js.map +1 -0
- package/dist/modules/releaseHealth/ReleaseHealthController.d.ts +9 -0
- package/dist/modules/releaseHealth/ReleaseHealthController.d.ts.map +1 -0
- package/dist/modules/releaseHealth/ReleaseHealthController.js +58 -0
- package/dist/modules/releaseHealth/ReleaseHealthController.js.map +1 -0
- package/dist/modules/requirements/RequirementReviewController.d.ts +12 -0
- package/dist/modules/requirements/RequirementReviewController.d.ts.map +1 -0
- package/dist/modules/requirements/RequirementReviewController.js +107 -0
- package/dist/modules/requirements/RequirementReviewController.js.map +1 -0
- package/dist/modules/runners/RunnerPoolController.d.ts +10 -0
- package/dist/modules/runners/RunnerPoolController.d.ts.map +1 -0
- package/dist/modules/runners/RunnerPoolController.js +52 -0
- package/dist/modules/runners/RunnerPoolController.js.map +1 -0
- package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsController.d.ts +9 -0
- package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsController.d.ts.map +1 -0
- package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsController.js +32 -0
- package/dist/modules/serviceFragmentDefaults/ServiceFragmentDefaultsController.js.map +1 -0
- package/dist/modules/services/ServiceMountController.d.ts +11 -0
- package/dist/modules/services/ServiceMountController.d.ts.map +1 -0
- package/dist/modules/services/ServiceMountController.js +64 -0
- package/dist/modules/services/ServiceMountController.js.map +1 -0
- package/dist/modules/settings/WorkspaceSettingsController.d.ts +9 -0
- package/dist/modules/settings/WorkspaceSettingsController.d.ts.map +1 -0
- package/dist/modules/settings/WorkspaceSettingsController.js +32 -0
- package/dist/modules/settings/WorkspaceSettingsController.js.map +1 -0
- package/dist/modules/slack/SlackController.d.ts +17 -0
- package/dist/modules/slack/SlackController.d.ts.map +1 -0
- package/dist/modules/slack/SlackController.js +135 -0
- package/dist/modules/slack/SlackController.js.map +1 -0
- package/dist/modules/tasks/TaskSourceController.d.ts +9 -0
- package/dist/modules/tasks/TaskSourceController.d.ts.map +1 -0
- package/dist/modules/tasks/TaskSourceController.js +103 -0
- package/dist/modules/tasks/TaskSourceController.js.map +1 -0
- package/dist/modules/webSearch/WebSearchProxyController.d.ts +4 -0
- package/dist/modules/webSearch/WebSearchProxyController.d.ts.map +1 -0
- package/dist/modules/webSearch/WebSearchProxyController.js +78 -0
- package/dist/modules/webSearch/WebSearchProxyController.js.map +1 -0
- package/dist/modules/webSearch/upstreams.d.ts +50 -0
- package/dist/modules/webSearch/upstreams.d.ts.map +1 -0
- package/dist/modules/webSearch/upstreams.js +107 -0
- package/dist/modules/webSearch/upstreams.js.map +1 -0
- package/dist/modules/workspaces/WorkspaceController.d.ts +5 -0
- package/dist/modules/workspaces/WorkspaceController.d.ts.map +1 -0
- package/dist/modules/workspaces/WorkspaceController.js +167 -0
- package/dist/modules/workspaces/WorkspaceController.js.map +1 -0
- package/dist/observability/logger.d.ts +9 -0
- package/dist/observability/logger.d.ts.map +1 -0
- package/dist/observability/logger.js +39 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/persistence/mappers.d.ts +101 -0
- package/dist/persistence/mappers.d.ts.map +1 -0
- package/dist/persistence/mappers.js +260 -0
- package/dist/persistence/mappers.js.map +1 -0
- package/dist/runtime/escalateNotifications.d.ts +12 -0
- package/dist/runtime/escalateNotifications.d.ts.map +1 -0
- package/dist/runtime/escalateNotifications.js +25 -0
- package/dist/runtime/escalateNotifications.js.map +1 -0
- package/dist/runtime/gateways.d.ts +159 -0
- package/dist/runtime/gateways.d.ts.map +1 -0
- package/dist/runtime/gateways.js +2 -0
- package/dist/runtime/gateways.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
import {} from '@cat-factory/kernel';
|
|
2
|
+
import { CredentialRequiredError, SUBSCRIPTION_VENDORS, isIndividualVendor, } from '@cat-factory/kernel';
|
|
3
|
+
import { isLocalRunner, resolveInstanceTypeId } from '@cat-factory/contracts';
|
|
4
|
+
import { composeBlockSystemPrompt, FINAL_ANSWER_IN_REPLY, isReadOnlyAgentKind, systemPromptFor, userPromptFor, webResearchGuidanceFor, } from '@cat-factory/agents';
|
|
5
|
+
import { ModelRouter } from './ModelRouter.js';
|
|
6
|
+
import { CI_FIXER_AGENT_KIND, CONFLICT_RESOLVER_AGENT_KIND, FIXER_AGENT_KIND, MERGER_AGENT_KIND, ON_CALL_AGENT_KIND, SPEC_WRITER_AGENT_KIND, TESTER_AGENT_KIND, } from '@cat-factory/orchestration';
|
|
7
|
+
import { RunnerJobClient } from './RunnerJobClient.js';
|
|
8
|
+
/**
|
|
9
|
+
* The repo spec every container job body carries: clone coordinates plus, for a
|
|
10
|
+
* monorepo service, the subdirectory the harness should run the agent within. Built
|
|
11
|
+
* here once so the (six) agent-kind job bodies can't drift on which repo fields they
|
|
12
|
+
* forward.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The harness job id for one pipeline step: the run (execution) id plus the agent
|
|
16
|
+
* kind. A run executes a sequence of steps that all share the one per-run container,
|
|
17
|
+
* so each needs an id that is UNIQUE WITHIN THE RUN — the harness keys its per-kind
|
|
18
|
+
* job registries by it, and two steps sharing an id alias there (the bug where an
|
|
19
|
+
* `architect` /explore poll read back the `spec-writer`'s /spec result). The run is
|
|
20
|
+
* addressed separately by the execution id (the {@link RunnerJobRef.runId}).
|
|
21
|
+
*/
|
|
22
|
+
function stepJobId(executionId, agentKind) {
|
|
23
|
+
return `${executionId}-${agentKind}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The {@link RunnerJobRef} a job handle addresses: the run (for the per-run container)
|
|
27
|
+
* plus the per-step job id. Falls back to the job id as the run id for a handle minted
|
|
28
|
+
* before run ids were carried (or a single-job flow where the two coincide).
|
|
29
|
+
*/
|
|
30
|
+
function refForHandle(handle) {
|
|
31
|
+
return { runId: handle.runId ?? handle.jobId, jobId: handle.jobId };
|
|
32
|
+
}
|
|
33
|
+
function buildRepoSpec(repo) {
|
|
34
|
+
return {
|
|
35
|
+
owner: repo.owner,
|
|
36
|
+
name: repo.name,
|
|
37
|
+
baseBranch: repo.baseBranch,
|
|
38
|
+
cloneUrl: `https://github.com/${repo.owner}/${repo.name}.git`,
|
|
39
|
+
...(repo.serviceDirectory ? { serviceDirectory: repo.serviceDirectory } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Poll cadence for the non-durable `run()` fallback (the durable driver sleeps between polls itself). */
|
|
43
|
+
const RUN_POLL_INTERVAL_MS = 5_000;
|
|
44
|
+
/** Role prompt the Blueprinter step's agent runs under (returns the tree as JSON). */
|
|
45
|
+
const BLUEPRINT_SYSTEM_PROMPT = 'You are a Domain-Driven Design architect mapping this repository. Decompose it ' +
|
|
46
|
+
'into ONE top-level service and the modules inside it, where each module is a ' +
|
|
47
|
+
'DOMAIN — a cohesive area of the BUSINESS, in the language of the problem space ' +
|
|
48
|
+
'(a DDD bounded context / aggregate / subdomain). Name modules after business ' +
|
|
49
|
+
'concepts, not technical layers. ' +
|
|
50
|
+
'A module MUST represent a business capability or domain model (e.g. Billing, ' +
|
|
51
|
+
'Catalog, Ordering, Identity), NOT a technical layer or shape: "api", "routes", ' +
|
|
52
|
+
'"controllers", "utils", "helpers", "lib", "common", "config", "types", "models", ' +
|
|
53
|
+
'"db" and the like are NOT domains and MUST NOT be modules. ' +
|
|
54
|
+
'Group the genuinely non-business, technical/cross-cutting plumbing (persistence ' +
|
|
55
|
+
'wiring, HTTP/transport, logging, configuration, auth middleware, build/deploy, ' +
|
|
56
|
+
'shared utilities) into a SINGLE module named "infrastructure" rather than ' +
|
|
57
|
+
'scattering it into many technical modules. ' +
|
|
58
|
+
'Prefer organising code by domain (the ubiquitous language) over organising by ' +
|
|
59
|
+
'file type. Anchor every node to the codebase with explicit repo-relative ' +
|
|
60
|
+
'file/directory references. Keep names short and descriptive. ' +
|
|
61
|
+
'Respond with ONLY a JSON object of shape {"type","name","summary","references":[],' +
|
|
62
|
+
'"modules":[{"name","summary","references":[]}]} — no prose, no code fences. ' +
|
|
63
|
+
FINAL_ANSWER_IN_REPLY;
|
|
64
|
+
/** Role prompt the spec-writer step runs under (returns the spec doc as JSON). */
|
|
65
|
+
const SPEC_WRITER_SYSTEM_PROMPT = 'You maintain the PRESCRIPTIVE specification for a service. You are given the ' +
|
|
66
|
+
'specification already committed to the repository (the baseline) and the ' +
|
|
67
|
+
'requirements of ONE task. Apply that task as an INCREMENT onto the baseline: add ' +
|
|
68
|
+
'requirements for what the task introduces, and adjust existing requirements ONLY ' +
|
|
69
|
+
'where the task changes their expected behaviour. Leave every other part of the ' +
|
|
70
|
+
'baseline spec untouched. Translate ONLY what the task requirements state — do NOT ' +
|
|
71
|
+
'invent requirements, fill gaps, or design beyond them (missing requirements are the ' +
|
|
72
|
+
'requirements step’s job, not yours). Requirements are grouped by capability, each ' +
|
|
73
|
+
'phrased as "The system SHALL …" with a MoSCoW priority (must/should/could) and ' +
|
|
74
|
+
'structured Given/When/Then acceptance criteria, plus cross-cutting domain rules / ' +
|
|
75
|
+
'invariants. Acceptance-scenario coverage is a FIRST-CLASS deliverable: every ' +
|
|
76
|
+
'requirement the task adds or changes MUST carry complete acceptance criteria — the ' +
|
|
77
|
+
'happy path AND the invalid-input / error / edge / boundary cases the requirements ' +
|
|
78
|
+
'imply — since the Gherkin `.feature` files and the runnable tests are derived ' +
|
|
79
|
+
'mechanically from them. Preserve the baseline’s existing `sourceBlockIds`; tag the ' +
|
|
80
|
+
'requirements this task adds or changes with this task’s block id. Return the ' +
|
|
81
|
+
'COMPLETE updated specification (baseline plus this increment), not a diff. You have ' +
|
|
82
|
+
'NO repository write access and MUST NOT write, edit, or commit any file: the platform ' +
|
|
83
|
+
'persists the specification you return, so returning it IS the whole job. Respond ' +
|
|
84
|
+
'with ONLY a JSON object of ' +
|
|
85
|
+
'shape {"service","summary","groups":[{"name","summary","requirements":[{"id",' +
|
|
86
|
+
'"title","statement","kind","priority","sourceBlockIds":[],"acceptance":[{"id",' +
|
|
87
|
+
'"given","when","outcome"}]}]}],"rules":[{"id","rule","rationale","sourceBlockIds":[]}]} ' +
|
|
88
|
+
'(each acceptance criterion is a Given/When/Then, with the Then clause in `outcome`) — ' +
|
|
89
|
+
'no prose, no code fences. ' +
|
|
90
|
+
FINAL_ANSWER_IN_REPLY;
|
|
91
|
+
/** Role prompt the `merger` step runs under (scores the PR; returns JSON only). */
|
|
92
|
+
const MERGER_SYSTEM_PROMPT = 'You are a release manager assessing a pull request before merge. Inspect the ' +
|
|
93
|
+
'diff between the PR head branch and the base branch and judge three axes, each ' +
|
|
94
|
+
'as a number from 0 (trivial/safe) to 1 (severe): complexity (how intricate the ' +
|
|
95
|
+
'change is), risk (how likely it is to break something), and impact (blast radius ' +
|
|
96
|
+
'if it does). Be conservative. Respond with ONLY a JSON object of shape ' +
|
|
97
|
+
'{"complexity":0.0,"risk":0.0,"impact":0.0,"rationale":"…"} — no prose, no code fences. ' +
|
|
98
|
+
FINAL_ANSWER_IN_REPLY;
|
|
99
|
+
const ON_CALL_SYSTEM_PROMPT = 'You are an on-call engineer investigating a possible post-release regression. A ' +
|
|
100
|
+
'recently merged pull request shipped, and the evidence below (alerting Datadog ' +
|
|
101
|
+
'monitors/SLOs and recent error logs) suggests the service regressed afterward. Read ' +
|
|
102
|
+
'the PR diff on the head branch and weigh whether THIS change is the likely cause — ' +
|
|
103
|
+
'beware correlation vs causation; a coincident deploy is not proof. You may read and ' +
|
|
104
|
+
'inspect any file, but you MUST NOT modify, commit or revert anything; a human decides ' +
|
|
105
|
+
'whether to revert. Respond with ONLY a JSON object of shape ' +
|
|
106
|
+
'{"culpritConfidence":0.0,"recommendation":"revert"|"hold"|"monitor","rationale":"…",' +
|
|
107
|
+
'"evidence":["…"]} — no prose, no code fences. ' +
|
|
108
|
+
FINAL_ANSWER_IN_REPLY;
|
|
109
|
+
/**
|
|
110
|
+
* An {@link AgentExecutor} that performs implementation work in a real sandbox:
|
|
111
|
+
* it dispatches a per-run container running the Pi coding agent (a per-run
|
|
112
|
+
* Cloudflare Container, or an org's self-hosted runner pool), feeds it the block's
|
|
113
|
+
* composed prompt fragments as context, and has it clone the repo, implement the
|
|
114
|
+
* block, push a branch and open a PR.
|
|
115
|
+
*
|
|
116
|
+
* Secrets never reach the container image. Provider keys stay in the backend; the
|
|
117
|
+
* container reaches models only through the facade's LLM proxy using a
|
|
118
|
+
* short-lived, model-locked session token, and clones/pushes with a short-lived
|
|
119
|
+
* GitHub installation token — both handed over per job. Token usage is metered
|
|
120
|
+
* by the proxy (the single metering point), so this executor reports no `usage`
|
|
121
|
+
* to avoid double-counting in the execution engine.
|
|
122
|
+
*/
|
|
123
|
+
export class ContainerAgentExecutor {
|
|
124
|
+
deps;
|
|
125
|
+
/** Shared backend-polymorphic dispatch/poll/release plumbing (see RunnerJobClient). */
|
|
126
|
+
jobs;
|
|
127
|
+
/**
|
|
128
|
+
* Job ids whose subscription usage has already been folded into the leased token.
|
|
129
|
+
* `recordSubscriptionUsage` is additive, and the durable driver polls a finished
|
|
130
|
+
* job inside a retriable step — so a poll that records usage and then throws (or
|
|
131
|
+
* whose surrounding upsert/emit throws) would replay and double-count, unfairly
|
|
132
|
+
* penalising the token in the usage-aware rotation. Recording once per job id
|
|
133
|
+
* guards that. Best-effort + bounded: cleared wholesale past a cap, and it cannot
|
|
134
|
+
* survive a cold isolate replay — a re-record there is the documented, benign
|
|
135
|
+
* worst case (one extra job's tokens on one row), never silent over-counting.
|
|
136
|
+
*/
|
|
137
|
+
recordedUsageJobs = new Set();
|
|
138
|
+
/** Resolves which model + subscription path a step runs on (routing policy). */
|
|
139
|
+
modelRouter;
|
|
140
|
+
constructor(deps) {
|
|
141
|
+
this.deps = deps;
|
|
142
|
+
this.jobs = new RunnerJobClient(deps.resolveTransport);
|
|
143
|
+
this.modelRouter = new ModelRouter({
|
|
144
|
+
agentRouting: deps.agentRouting,
|
|
145
|
+
resolveBlockModel: deps.resolveBlockModel,
|
|
146
|
+
resolveWorkspaceModelDefault: deps.resolveWorkspaceModelDefault,
|
|
147
|
+
hasSubscriptionToken: deps.hasSubscriptionToken,
|
|
148
|
+
hasPersonalSubscription: deps.hasPersonalSubscription,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Repo-operating steps always run as polled async jobs (the coding can be long). */
|
|
152
|
+
runsAsync(_context) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Dispatch the implementation job to this run's container and return a handle.
|
|
157
|
+
* Returns as soon as the job is accepted — the work continues in the container,
|
|
158
|
+
* polled via {@link pollJob}. Idempotent: the harness re-attaches to a job
|
|
159
|
+
* already running for `executionId`, so a replayed dispatch never duplicates work.
|
|
160
|
+
*/
|
|
161
|
+
async startJob(context) {
|
|
162
|
+
const { workspaceId, executionId } = this.requireIds(context);
|
|
163
|
+
const { body, model, kind, subscriptionTokenId } = await this.buildJobBody(context);
|
|
164
|
+
// The job's id is per-STEP (run id + agent kind), so sibling steps that share this
|
|
165
|
+
// run's container never collide in the harness's per-kind job registries; the run
|
|
166
|
+
// itself is addressed by the execution id, so its container is reclaimed as a unit.
|
|
167
|
+
const jobId = body.jobId;
|
|
168
|
+
const ref = { runId: executionId, jobId };
|
|
169
|
+
await this.jobs.dispatch(workspaceId, ref, body, kind, this.dispatchOptions(context));
|
|
170
|
+
// Carry the run id + workspace on the handle so the poll/stop site can re-address
|
|
171
|
+
// the same per-run container (Cloudflare vs. self-hosted pool) given only the
|
|
172
|
+
// handle; carry the leased subscription token id so a finished subscription job
|
|
173
|
+
// can attribute its usage back to the right pool row.
|
|
174
|
+
return {
|
|
175
|
+
jobId,
|
|
176
|
+
runId: executionId,
|
|
177
|
+
model,
|
|
178
|
+
workspaceId,
|
|
179
|
+
agentKind: context.agentKind,
|
|
180
|
+
...(subscriptionTokenId ? { subscriptionTokenId } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/** Poll a dispatched job for its state, mapping the runner view into an update. */
|
|
184
|
+
async pollJob(handle) {
|
|
185
|
+
const view = await this.jobs.poll(handle.workspaceId, refForHandle(handle));
|
|
186
|
+
// Forward any tool spans the harness drained on this poll to the trace sink, as
|
|
187
|
+
// child spans under the RUN's trace (the run id is the trace id the LLM proxy's
|
|
188
|
+
// generations also use, so per-step jobs share one trace). Isolated + best-effort:
|
|
189
|
+
// never affects the lifecycle.
|
|
190
|
+
if (this.deps.llmTraceSink?.recordToolSpans && view.spans && view.spans.length > 0) {
|
|
191
|
+
try {
|
|
192
|
+
await this.deps.llmTraceSink.recordToolSpans({
|
|
193
|
+
workspaceId: handle.workspaceId ?? null,
|
|
194
|
+
executionId: handle.runId ?? handle.jobId,
|
|
195
|
+
agentKind: handle.agentKind ?? 'agent',
|
|
196
|
+
}, view.spans);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Swallowed: the sink logs its own errors; observability never breaks a run.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (view.state === 'running') {
|
|
203
|
+
// Forward the latest subtask counts (if any) so the engine can surface
|
|
204
|
+
// live "N/M done" progress on the step; the shapes match field-for-field.
|
|
205
|
+
return view.progress ? { state: 'running', subtasks: view.progress } : { state: 'running' };
|
|
206
|
+
}
|
|
207
|
+
if (view.state === 'failed') {
|
|
208
|
+
return { state: 'failed', error: view.error ?? 'Implementation job failed' };
|
|
209
|
+
}
|
|
210
|
+
// Completed: a structured `error` (e.g. "no file changes") is still a failure.
|
|
211
|
+
const result = view.result ?? {};
|
|
212
|
+
if (result.error)
|
|
213
|
+
return { state: 'failed', error: `Implementation failed: ${result.error}` };
|
|
214
|
+
// Attribute a subscription harness's reported usage to its leased pool token
|
|
215
|
+
// (usage-aware rotation) and the telemetry sink. Best-effort: a missing usage
|
|
216
|
+
// signal or unconfigured recorder is a no-op; recorded at most once per job id
|
|
217
|
+
// so a retried/replayed poll can't double-count (see `recordedUsageJobs`).
|
|
218
|
+
if (handle.subscriptionTokenId &&
|
|
219
|
+
handle.workspaceId &&
|
|
220
|
+
result.usage &&
|
|
221
|
+
this.deps.recordSubscriptionUsage &&
|
|
222
|
+
!this.recordedUsageJobs.has(handle.jobId)) {
|
|
223
|
+
await this.deps.recordSubscriptionUsage(handle.workspaceId, handle.subscriptionTokenId, result.usage);
|
|
224
|
+
// Mark only AFTER a successful write: a failed record is left to retry rather
|
|
225
|
+
// than silently dropped. Bound the set so a long-lived process can't grow it
|
|
226
|
+
// unboundedly (clearing only risks a benign re-record on a later retry).
|
|
227
|
+
if (this.recordedUsageJobs.size >= 10_000)
|
|
228
|
+
this.recordedUsageJobs.clear();
|
|
229
|
+
this.recordedUsageJobs.add(handle.jobId);
|
|
230
|
+
}
|
|
231
|
+
return { state: 'done', result: toRunResult(result) };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Stop a running job and reclaim its backing runner: resolve the same transport
|
|
235
|
+
* the job dispatched to (by workspace) and `release` it — for the Cloudflare
|
|
236
|
+
* backend this SIGKILLs the per-run container instead of letting it idle out.
|
|
237
|
+
* Best-effort/idempotent: a transport without `release`, or an already-gone job,
|
|
238
|
+
* is a no-op.
|
|
239
|
+
*/
|
|
240
|
+
async stopJob(handle) {
|
|
241
|
+
await this.jobs.release(handle.workspaceId, refForHandle(handle));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Synchronous convenience for non-durable callers (and tests): dispatch then
|
|
245
|
+
* poll inline until the job finishes. The durable driver does not use this — it
|
|
246
|
+
* calls {@link startJob}/{@link pollJob} so it can sleep durably between polls.
|
|
247
|
+
*/
|
|
248
|
+
async run(context) {
|
|
249
|
+
const handle = await this.startJob(context);
|
|
250
|
+
for (;;) {
|
|
251
|
+
const update = await this.pollJob(handle);
|
|
252
|
+
if (update.state === 'done') {
|
|
253
|
+
// The poll site can't resolve the model ref, so fold in the label the
|
|
254
|
+
// dispatch captured (matches what the durable path records on the step).
|
|
255
|
+
return { ...update.result, ...(handle.model ? { model: handle.model } : {}) };
|
|
256
|
+
}
|
|
257
|
+
if (update.state === 'failed')
|
|
258
|
+
throw new Error(update.error);
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, RUN_POLL_INTERVAL_MS));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Preview the model this job will run, without dispatching the container. The
|
|
264
|
+
* proxyable-provider guard is deliberately left to `buildJobBody` (the dispatch
|
|
265
|
+
* path) so an unservable model still fails loudly there; this only names it.
|
|
266
|
+
*/
|
|
267
|
+
async resolveModel(context) {
|
|
268
|
+
const ref = await this.modelRouter.resolveRef(context);
|
|
269
|
+
return `${ref.provider}:${ref.model}`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Whether this step will run on a flat-rate subscription (quota) model — it
|
|
273
|
+
* resolves to a Claude Code / Codex harness (a subscription-only model, or a
|
|
274
|
+
* dual-mode model auto-routed to its subscription flavour because the workspace has
|
|
275
|
+
* a token). The engine's spend gate consults this so a quota run is not paused by
|
|
276
|
+
* an exhausted monetary budget it never contributes to. Best-effort: without a
|
|
277
|
+
* workspace id it reports false.
|
|
278
|
+
*/
|
|
279
|
+
async isQuotaBased(context) {
|
|
280
|
+
if (!context.workspaceId)
|
|
281
|
+
return false;
|
|
282
|
+
const { ref } = await this.modelRouter.resolveEffectiveRef(context, context.workspaceId);
|
|
283
|
+
return ref.harness === 'claude-code' || ref.harness === 'codex';
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Per-service provisioning hints for the dispatch: the cloud provider the service
|
|
287
|
+
* runs on and the abstract instance size resolved to the target's concrete
|
|
288
|
+
* instance-type id. Cloudflare maps the id to a Container instance type; a
|
|
289
|
+
* self-hosted pool forwards it (with the provider) and provisions itself. Undefined
|
|
290
|
+
* when the service pins no provider/size (the transport keeps its default).
|
|
291
|
+
*/
|
|
292
|
+
dispatchOptions(context) {
|
|
293
|
+
const provider = context.service?.cloudProvider;
|
|
294
|
+
const size = context.service?.instanceSize;
|
|
295
|
+
if (!provider && !size)
|
|
296
|
+
return undefined;
|
|
297
|
+
return {
|
|
298
|
+
instanceTypeId: resolveInstanceTypeId(provider, size),
|
|
299
|
+
...(provider ? { provider } : {}),
|
|
300
|
+
// Forward the abstract size too, so the local Docker/Podman backend can size
|
|
301
|
+
// the per-job container (`--memory`/`--cpus`) without decoding the cloud id.
|
|
302
|
+
...(size ? { instanceSize: size } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** Validate the ids every container job needs, narrowing them to non-empty strings. */
|
|
306
|
+
requireIds(context) {
|
|
307
|
+
const { workspaceId, executionId } = context;
|
|
308
|
+
const blockId = context.block.id;
|
|
309
|
+
if (!workspaceId || !executionId || !blockId) {
|
|
310
|
+
throw new Error('ContainerAgentExecutor requires workspaceId, executionId and block.id');
|
|
311
|
+
}
|
|
312
|
+
return { workspaceId, executionId, blockId };
|
|
313
|
+
}
|
|
314
|
+
/** Resolve tokens/prompts/target and assemble the harness job body for `context`. */
|
|
315
|
+
async buildJobBody(context) {
|
|
316
|
+
const { workspaceId, executionId, blockId } = this.requireIds(context);
|
|
317
|
+
// Per-STEP harness job id: unique within the run so this step's job never aliases
|
|
318
|
+
// a sibling step's in the (shared) per-run container's job registries.
|
|
319
|
+
const jobId = stepJobId(executionId, context.agentKind);
|
|
320
|
+
// "Subscriptions always win": a subscription-only model carries its harness; a
|
|
321
|
+
// dual-mode GLM/Kimi step pinned to its Cloudflare base is auto-routed to Claude
|
|
322
|
+
// Code when the workspace has a pooled token for the vendor. Shared with
|
|
323
|
+
// isQuotaBased so the dispatch and the spend gate agree on what the step runs.
|
|
324
|
+
const { ref, subscriptionVendor } = await this.modelRouter.resolveEffectiveRef(context, workspaceId);
|
|
325
|
+
const harness = ref.harness ?? 'pi';
|
|
326
|
+
// The Pi harness reaches models through the LLM proxy, so its model must be a
|
|
327
|
+
// provider the proxy can serve; locking it here stops the container choosing
|
|
328
|
+
// another. The subscription harnesses (Claude Code / Codex) talk direct to the
|
|
329
|
+
// vendor with a pooled token, so the proxyable guard does not apply to them.
|
|
330
|
+
if (harness === 'pi' && !isProxyableProvider(ref.provider)) {
|
|
331
|
+
throw new Error(`Container implementation needs a model the LLM proxy can serve ` +
|
|
332
|
+
`(Workers AI, a direct OpenAI-compatible provider, or a local runner); ` +
|
|
333
|
+
`'${ref.provider}' is not supported. Pick a Workers AI model, configure a ` +
|
|
334
|
+
`provider key (QWEN_API_KEY / DEEPSEEK_API_KEY / MOONSHOT_API_KEY), or add a local ` +
|
|
335
|
+
`runner (Ollama / LM Studio / …) and pick that model.`);
|
|
336
|
+
}
|
|
337
|
+
const repo = await this.deps.resolveRepoTarget(workspaceId, blockId);
|
|
338
|
+
if (!repo) {
|
|
339
|
+
throw new Error(`No connected GitHub repository found for workspace '${workspaceId}'`);
|
|
340
|
+
}
|
|
341
|
+
const ghToken = await this.deps.mintInstallationToken(repo.installationId);
|
|
342
|
+
// The shared per-task work branch every agent in this pipeline operates on. Its name
|
|
343
|
+
// is deterministic from the block id (so a retry/replay/sweeper re-drive always targets
|
|
344
|
+
// the SAME branch with no extra persistence), and once a PR is open it IS this branch.
|
|
345
|
+
// Ensure it up front (mechanical, idempotent) so even the read-only design agents clone
|
|
346
|
+
// the branch the earlier writers committed to — e.g. the spec-writer's in-repo `spec/`.
|
|
347
|
+
// Writers create it from base when absent; read-only agents only probe (a missing
|
|
348
|
+
// branch ⇒ nothing to read yet ⇒ fall back to base), so a code-less pipeline never
|
|
349
|
+
// orphans an empty ref. Once this block already has a PR, the branch IS that PR's
|
|
350
|
+
// branch, so we skip the round-trip entirely.
|
|
351
|
+
const workBranch = `cat-factory/${blockId}`;
|
|
352
|
+
const workBranchReady = context.block.pullRequest?.branch === workBranch
|
|
353
|
+
? true
|
|
354
|
+
: this.deps.ensureWorkBranch
|
|
355
|
+
? await this.deps.ensureWorkBranch(repo, workBranch, {
|
|
356
|
+
create: !isReadOnlyAgentKind(context.agentKind),
|
|
357
|
+
})
|
|
358
|
+
: false;
|
|
359
|
+
// Resolve the per-job auth the harness carries: the proxy session token for Pi,
|
|
360
|
+
// or a leased subscription token for Claude Code / Codex. `auth` is spread into
|
|
361
|
+
// every job body so the per-kind bodies can't drift on which auth they forward.
|
|
362
|
+
const { auth, subscriptionTokenId } = await this.resolveAuth(context, {
|
|
363
|
+
harness,
|
|
364
|
+
ref,
|
|
365
|
+
subscriptionVendor,
|
|
366
|
+
workspaceId,
|
|
367
|
+
executionId,
|
|
368
|
+
});
|
|
369
|
+
// The fields EVERY harness job body carries, built once so the per-kind bodies
|
|
370
|
+
// can't drift on which jobId/model/auth/repo/proxy fields they forward.
|
|
371
|
+
const common = {
|
|
372
|
+
jobId,
|
|
373
|
+
model: ref.model,
|
|
374
|
+
...auth,
|
|
375
|
+
ghToken,
|
|
376
|
+
repo: buildRepoSpec(repo),
|
|
377
|
+
...(this.deps.githubApiBase ? { githubApiBase: this.deps.githubApiBase } : {}),
|
|
378
|
+
};
|
|
379
|
+
// The proxy-backed web-tools nudge + switch, shared by the kinds that allow web
|
|
380
|
+
// access (coder/mocker/ci-fixer/fixer/tester/read-only). The harness surfaces the
|
|
381
|
+
// tools only when web search is configured in the container env. Per-kind hint
|
|
382
|
+
// (coder/mocker/analysis/… and any custom container kind resolves its own).
|
|
383
|
+
const webTools = {
|
|
384
|
+
webToolsGuidance: webResearchGuidanceFor(context.agentKind, { fetch: true }),
|
|
385
|
+
...(this.deps.webSearchProxyEnabled ? { webSearch: true } : {}),
|
|
386
|
+
};
|
|
387
|
+
const { body, kind } = this.buildKindBody(context, {
|
|
388
|
+
common,
|
|
389
|
+
webTools,
|
|
390
|
+
repo,
|
|
391
|
+
workBranch,
|
|
392
|
+
workBranchReady,
|
|
393
|
+
});
|
|
394
|
+
return { subscriptionTokenId, body, model: `${ref.provider}:${ref.model}`, kind };
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Resolve the per-job auth the harness carries: the proxy session token for Pi, or a
|
|
398
|
+
* leased subscription token for Claude Code / Codex. Spread into every job body
|
|
399
|
+
* (`common`) so the per-kind bodies can't drift on which auth they forward.
|
|
400
|
+
*/
|
|
401
|
+
async resolveAuth(context, args) {
|
|
402
|
+
const { harness, ref, subscriptionVendor, workspaceId, executionId } = args;
|
|
403
|
+
if (harness === 'pi') {
|
|
404
|
+
const accountId = this.deps.resolveAccountId
|
|
405
|
+
? await this.deps.resolveAccountId(workspaceId)
|
|
406
|
+
: undefined;
|
|
407
|
+
const sessionToken = await this.deps.sessionService.mint({
|
|
408
|
+
workspaceId,
|
|
409
|
+
accountId: accountId ?? undefined,
|
|
410
|
+
userId: context.initiatedByUserId,
|
|
411
|
+
executionId,
|
|
412
|
+
agentKind: context.agentKind,
|
|
413
|
+
provider: ref.provider,
|
|
414
|
+
model: ref.model,
|
|
415
|
+
});
|
|
416
|
+
return { auth: { harness, proxyBaseUrl: this.deps.proxyBaseUrl, sessionToken } };
|
|
417
|
+
}
|
|
418
|
+
if (!subscriptionVendor) {
|
|
419
|
+
throw new Error(`The ${harness} harness is not configured on this deployment; connect a ` +
|
|
420
|
+
`subscription token or pick a different model.`);
|
|
421
|
+
}
|
|
422
|
+
// Individual-usage vendors (Claude) are NOT pooled: lease the run-initiator's OWN
|
|
423
|
+
// activated personal credential. Pooled vendors (GLM/Kimi/DeepSeek/Codex) lease
|
|
424
|
+
// from the workspace pool. Either path hands the RAW credential to the resolved
|
|
425
|
+
// runner transport (see the trust note below).
|
|
426
|
+
let secret;
|
|
427
|
+
let subscriptionTokenId;
|
|
428
|
+
if (isIndividualVendor(subscriptionVendor)) {
|
|
429
|
+
if (!this.deps.leasePersonalSubscriptionToken) {
|
|
430
|
+
throw new Error(`Personal ${subscriptionVendor} subscriptions are not configured on this ` +
|
|
431
|
+
`deployment (no ENCRYPTION_KEY); pick a different model.`);
|
|
432
|
+
}
|
|
433
|
+
if (!context.initiatedByUserId) {
|
|
434
|
+
// No identified initiator (auth-disabled/local dev): an individual-usage
|
|
435
|
+
// credential is owned by a specific user and can't be resolved without one.
|
|
436
|
+
throw new CredentialRequiredError(`Running a ${subscriptionVendor} model requires a signed-in user with a personal subscription.`, { vendor: subscriptionVendor, reason: 'no_subscription' });
|
|
437
|
+
}
|
|
438
|
+
// Throws CredentialRequiredError(password_required) when the run has no live
|
|
439
|
+
// activation — the dispatch path surfaces it as a clear, retriable failure.
|
|
440
|
+
const leased = await this.deps.leasePersonalSubscriptionToken(executionId, context.initiatedByUserId, subscriptionVendor);
|
|
441
|
+
secret = leased.secret;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
if (!this.deps.leaseSubscriptionToken) {
|
|
445
|
+
throw new Error(`The ${harness} harness is not configured on this deployment; connect a ` +
|
|
446
|
+
`subscription token or pick a different model.`);
|
|
447
|
+
}
|
|
448
|
+
const leased = await this.deps.leaseSubscriptionToken(workspaceId, subscriptionVendor);
|
|
449
|
+
subscriptionTokenId = leased.tokenId;
|
|
450
|
+
secret = leased.secret;
|
|
451
|
+
}
|
|
452
|
+
// SECURITY/TRUST: unlike the Pi harness (short-lived, model-locked proxy session
|
|
453
|
+
// token) this hands the RAW, long-lived subscription credential — a Claude OAuth
|
|
454
|
+
// token or a full ChatGPT auth.json — to the resolved runner transport. For the
|
|
455
|
+
// Cloudflare backend that is an ephemeral, managed per-run container. For a
|
|
456
|
+
// self-hosted runner pool it is the WORKSPACE'S OWN BYO infra (it connected the
|
|
457
|
+
// pool), so the credential stays within the workspace's trust domain — but a
|
|
458
|
+
// workspace should only point its subscription-harness steps at a runner pool it
|
|
459
|
+
// operates, since the credential leaves the backend to reach it.
|
|
460
|
+
// Non-Anthropic Claude-Code vendors (GLM/Kimi/DeepSeek) need their Anthropic-
|
|
461
|
+
// compatible base URL; Anthropic itself uses the OAuth token against api.anthropic.com.
|
|
462
|
+
const baseUrl = SUBSCRIPTION_VENDORS[subscriptionVendor].baseUrl;
|
|
463
|
+
return {
|
|
464
|
+
auth: {
|
|
465
|
+
harness,
|
|
466
|
+
subscriptionToken: secret,
|
|
467
|
+
...(baseUrl ? { subscriptionBaseUrl: baseUrl } : {}),
|
|
468
|
+
},
|
|
469
|
+
...(subscriptionTokenId ? { subscriptionTokenId } : {}),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Build the per-kind harness job body: the shared `common` fields plus ONLY the delta
|
|
474
|
+
* specific to this kind's harness endpoint (its prompts, the branch it runs on, and
|
|
475
|
+
* any per-kind extras), and the matching dispatch `kind`. The web-search fields live
|
|
476
|
+
* in `webTools` (shared by the kinds that allow web access). The dispatch precedence
|
|
477
|
+
* matches the original if-ladder exactly: the specific kinds first, then any read-only
|
|
478
|
+
* kind, then the default coder body.
|
|
479
|
+
*/
|
|
480
|
+
buildKindBody(context, parts) {
|
|
481
|
+
const { common, webTools, repo, workBranch, workBranchReady } = parts;
|
|
482
|
+
const prBranch = context.block.pullRequest?.branch;
|
|
483
|
+
const roleSystemPrompt = composeBlockSystemPrompt(systemPromptFor(context.agentKind), context.block);
|
|
484
|
+
switch (context.agentKind) {
|
|
485
|
+
// The Blueprinter step commits the regenerated `blueprints/` folder onto an
|
|
486
|
+
// existing branch (the earlier `coder` step's PR branch when present, else the
|
|
487
|
+
// repo's default branch) — never a fresh branch / new PR. Its body targets the
|
|
488
|
+
// harness `/blueprint` endpoint and returns the decomposition tree.
|
|
489
|
+
case 'blueprints':
|
|
490
|
+
return {
|
|
491
|
+
kind: 'blueprint',
|
|
492
|
+
body: {
|
|
493
|
+
...common,
|
|
494
|
+
systemPrompt: BLUEPRINT_SYSTEM_PROMPT,
|
|
495
|
+
instructions: 'Map (or update) this repository into the canonical service → modules ' +
|
|
496
|
+
'blueprint, anchored to real file/directory references.',
|
|
497
|
+
branch: prBranch ?? repo.baseBranch,
|
|
498
|
+
mode: prBranch ? 'update' : 'create',
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
// The spec-writer commits the regenerated `spec/` folder onto the implementation
|
|
502
|
+
// branch — the earlier `coder` step's PR branch when present, else the
|
|
503
|
+
// deterministic `cat-factory/<blockId>` the coder WILL resume (created from base if
|
|
504
|
+
// absent). It NEVER targets the base branch: the spec is a prescriptive document
|
|
505
|
+
// for not-yet-landed work, so — like the feature-time blueprint — it must merge
|
|
506
|
+
// together WITH the feature, never reach `main` ahead of it. Its body carries ONLY
|
|
507
|
+
// this task's requirements (the block description already IS the task's reworked /
|
|
508
|
+
// incorporated requirements): the writer reads the baseline spec committed on the
|
|
509
|
+
// branch and applies this task as an increment, so an unmerged sibling task's work
|
|
510
|
+
// never bleeds in. Targets the harness `/spec` endpoint.
|
|
511
|
+
case SPEC_WRITER_AGENT_KIND:
|
|
512
|
+
return {
|
|
513
|
+
kind: 'spec',
|
|
514
|
+
body: {
|
|
515
|
+
...common,
|
|
516
|
+
systemPrompt: SPEC_WRITER_SYSTEM_PROMPT,
|
|
517
|
+
instructions: 'Apply this task as an increment onto the specification already committed to ' +
|
|
518
|
+
'the repository: add requirements for what it introduces, adjust existing ' +
|
|
519
|
+
'ones only where it changes their behaviour, and leave the rest untouched. ' +
|
|
520
|
+
'Every requirement you add or change must carry COMPLETE acceptance-scenario ' +
|
|
521
|
+
'coverage. Return the complete updated specification.',
|
|
522
|
+
branch: workBranch,
|
|
523
|
+
task: {
|
|
524
|
+
id: context.block.id,
|
|
525
|
+
title: context.block.title,
|
|
526
|
+
description: context.block.description,
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
// The CI-fixer clones the PR head branch, runs the failing build/tests, fixes
|
|
531
|
+
// them and pushes back to the SAME branch (no new branch / PR) so CI re-runs.
|
|
532
|
+
case CI_FIXER_AGENT_KIND:
|
|
533
|
+
if (!prBranch) {
|
|
534
|
+
throw new Error('CI-fixer needs the implementation PR branch to push fixes to');
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
kind: 'ci-fix',
|
|
538
|
+
body: {
|
|
539
|
+
...common,
|
|
540
|
+
systemPrompt: roleSystemPrompt,
|
|
541
|
+
userPrompt: userPromptFor(context),
|
|
542
|
+
branch: prBranch,
|
|
543
|
+
...webTools,
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
// The conflict-resolver clones the PR head branch, merges the base in, resolves
|
|
547
|
+
// the conflicts and pushes back to the SAME branch (no new branch / PR) so the
|
|
548
|
+
// PR becomes mergeable and CI re-runs.
|
|
549
|
+
//
|
|
550
|
+
// Unlike the CI-fixer it is deliberately NOT given `userPromptFor(context)`: that
|
|
551
|
+
// renders the full task brief + every prior agent's output (the spec-writer's whole
|
|
552
|
+
// spec, etc.), which buries the one-line "resolve a conflict" role and drifts the
|
|
553
|
+
// model onto re-implementing the feature (observed in prod: a resolver that returned
|
|
554
|
+
// a "test report is ready" answer and never touched the markers). The harness
|
|
555
|
+
// discovers the conflicted files in the container and leads the prompt with the
|
|
556
|
+
// actual hunks; the backend supplies only a compact task reference for intent.
|
|
557
|
+
case CONFLICT_RESOLVER_AGENT_KIND: {
|
|
558
|
+
if (!prBranch) {
|
|
559
|
+
throw new Error('Conflict-resolver needs the implementation PR branch to resolve conflicts on');
|
|
560
|
+
}
|
|
561
|
+
const description = context.block.description?.trim();
|
|
562
|
+
return {
|
|
563
|
+
kind: 'resolve-conflicts',
|
|
564
|
+
body: {
|
|
565
|
+
...common,
|
|
566
|
+
systemPrompt: roleSystemPrompt,
|
|
567
|
+
userPrompt: `Task: ${context.block.title}${description ? `\n\n${description}` : ''}`,
|
|
568
|
+
branch: prBranch,
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
// The merger clones the PR head branch to assess the diff vs base; it makes no
|
|
573
|
+
// commits (the engine performs the real merge through the GitHub API on its
|
|
574
|
+
// verdict). Returns ONLY a JSON assessment, mapped to `mergeAssessment`.
|
|
575
|
+
case MERGER_AGENT_KIND:
|
|
576
|
+
return {
|
|
577
|
+
kind: 'merge',
|
|
578
|
+
body: {
|
|
579
|
+
...common,
|
|
580
|
+
systemPrompt: MERGER_SYSTEM_PROMPT,
|
|
581
|
+
instructions: 'Assess the pull request on the head branch against the base branch and ' +
|
|
582
|
+
'return the complexity / risk / impact scores + rationale as JSON.',
|
|
583
|
+
branch: prBranch ?? repo.baseBranch,
|
|
584
|
+
...(context.block.pullRequest?.number !== undefined
|
|
585
|
+
? { prNumber: context.block.pullRequest.number }
|
|
586
|
+
: {}),
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
// The on-call agent investigates a post-release regression: it correlates the
|
|
590
|
+
// RELEASED change with the Datadog regression evidence (handed in via priorOutputs)
|
|
591
|
+
// and returns ONLY a JSON assessment — it makes NO commits and reverts nothing (the
|
|
592
|
+
// engine raises a notification for a human). The gate only escalates AFTER the merger
|
|
593
|
+
// step, which merges the PR and DELETES the work branch, so the head branch is gone by
|
|
594
|
+
// now — clone the BASE branch (which always exists and contains the merged change) and
|
|
595
|
+
// hand the agent the PR number + the now-historical head branch name so it can locate
|
|
596
|
+
// the merged commit in history. Targets the harness `/on-call` endpoint.
|
|
597
|
+
case ON_CALL_AGENT_KIND:
|
|
598
|
+
return {
|
|
599
|
+
kind: 'on-call',
|
|
600
|
+
body: {
|
|
601
|
+
...common,
|
|
602
|
+
systemPrompt: ON_CALL_SYSTEM_PROMPT,
|
|
603
|
+
userPrompt: userPromptFor(context),
|
|
604
|
+
branch: repo.baseBranch,
|
|
605
|
+
...(context.block.pullRequest?.branch
|
|
606
|
+
? { headBranch: context.block.pullRequest.branch }
|
|
607
|
+
: {}),
|
|
608
|
+
...(context.block.pullRequest?.number !== undefined
|
|
609
|
+
? { prNumber: context.block.pullRequest.number }
|
|
610
|
+
: {}),
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
// The tester clones the PR head branch, stands up its dependencies (locally via
|
|
614
|
+
// the service's docker-compose, or against the provisioned ephemeral env — the
|
|
615
|
+
// task's `tester.environment` config picks which), runs the suite and returns a
|
|
616
|
+
// structured report. It makes NO commits (the engine loops the `fixer` on a
|
|
617
|
+
// withheld greenlight). Targets the harness `/test` endpoint; mapped to `testReport`.
|
|
618
|
+
case TESTER_AGENT_KIND: {
|
|
619
|
+
const env = context.block.agentConfig?.['tester.environment'] === 'local' ? 'local' : 'ephemeral';
|
|
620
|
+
const service = context.service;
|
|
621
|
+
return {
|
|
622
|
+
kind: 'test',
|
|
623
|
+
body: {
|
|
624
|
+
...common,
|
|
625
|
+
systemPrompt: roleSystemPrompt,
|
|
626
|
+
userPrompt: userPromptFor(context),
|
|
627
|
+
branch: prBranch ?? repo.baseBranch,
|
|
628
|
+
// How the Tester stands up its dependencies for this run.
|
|
629
|
+
test: {
|
|
630
|
+
environment: env,
|
|
631
|
+
...(env === 'local'
|
|
632
|
+
? {
|
|
633
|
+
noInfraDependencies: service?.noInfraDependencies === true,
|
|
634
|
+
...(service?.testComposePath ? { composePath: service.testComposePath } : {}),
|
|
635
|
+
}
|
|
636
|
+
: {}),
|
|
637
|
+
...(env === 'ephemeral' && context.environment?.url
|
|
638
|
+
? { environmentUrl: context.environment.url }
|
|
639
|
+
: {}),
|
|
640
|
+
},
|
|
641
|
+
...webTools,
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
// The fixer clones the PR head branch, applies fixes for the concerns in the
|
|
646
|
+
// Tester's report (folded into the user prompt via the prior `tester` output) and
|
|
647
|
+
// pushes back to the SAME branch (no new branch / PR) so the Tester can re-run.
|
|
648
|
+
// Mirrors the CI-fixer's body; targets the harness `/fix-tests` endpoint.
|
|
649
|
+
case FIXER_AGENT_KIND:
|
|
650
|
+
if (!prBranch) {
|
|
651
|
+
throw new Error('Fixer needs the implementation PR branch to push fixes to');
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
kind: 'fix-tests',
|
|
655
|
+
body: {
|
|
656
|
+
...common,
|
|
657
|
+
systemPrompt: roleSystemPrompt,
|
|
658
|
+
userPrompt: userPromptFor(context),
|
|
659
|
+
branch: prBranch,
|
|
660
|
+
...webTools,
|
|
661
|
+
},
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
// Read-only agents (architect, analysis) explore a real checkout but never edit
|
|
665
|
+
// it: they clone a branch, produce a prose report/proposal and return it as
|
|
666
|
+
// `output`. They target the harness `/explore` endpoint — which opens no branch,
|
|
667
|
+
// makes no commit, opens no PR, and (unlike `/run`) does NOT treat an edit-free
|
|
668
|
+
// run as a failure. One shared body for every read-only kind. They explore the
|
|
669
|
+
// shared work branch when it exists (so e.g. the architect reads the spec-writer's
|
|
670
|
+
// committed `spec/` and any in-progress implementation), falling back to base when
|
|
671
|
+
// it could not be ensured (no GitHub wired) and no PR branch exists yet.
|
|
672
|
+
if (isReadOnlyAgentKind(context.agentKind)) {
|
|
673
|
+
return {
|
|
674
|
+
kind: 'explore',
|
|
675
|
+
body: {
|
|
676
|
+
...common,
|
|
677
|
+
// The harness explore job's temp-dir/log label. Named `label`, not `kind`:
|
|
678
|
+
// `kind` is the dispatch discriminator the transport stamps onto the body.
|
|
679
|
+
label: context.agentKind,
|
|
680
|
+
systemPrompt: roleSystemPrompt,
|
|
681
|
+
userPrompt: userPromptFor(context),
|
|
682
|
+
branch: workBranchReady ? workBranch : (prBranch ?? repo.baseBranch),
|
|
683
|
+
...webTools,
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
// The default coder (and any other write-and-PR kind): the build-phase role plus
|
|
688
|
+
// the block's selected best-practice fragments, exactly as the inline executor
|
|
689
|
+
// composes (engine-resolved tenant catalog when present, else the manual ids).
|
|
690
|
+
// `headBranch` is deterministic per task (block), NOT per dispatch: a retry mints a
|
|
691
|
+
// fresh executionId but keeps the blockId, and a sweeper re-drive keeps both — so a
|
|
692
|
+
// stable name means every re-dispatch of this task targets the SAME branch. The
|
|
693
|
+
// harness checkpoints commits to it during the run and RESUMES on it if it already
|
|
694
|
+
// exists, so an evicted/failed run's work survives and a retry continues on top of
|
|
695
|
+
// it rather than starting over.
|
|
696
|
+
return {
|
|
697
|
+
kind: 'run',
|
|
698
|
+
body: {
|
|
699
|
+
...common,
|
|
700
|
+
systemPrompt: roleSystemPrompt,
|
|
701
|
+
userPrompt: userPromptFor(context),
|
|
702
|
+
headBranch: workBranch,
|
|
703
|
+
pr: {
|
|
704
|
+
title: `${context.block.title} (${context.pipelineName})`,
|
|
705
|
+
body: prBody(context),
|
|
706
|
+
},
|
|
707
|
+
...webTools,
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/** Map a finished runner {@link RunnerJobResult} into the engine's {@link AgentRunResult}. */
|
|
713
|
+
function toRunResult(result) {
|
|
714
|
+
// A Blueprinter job carries a decomposition tree instead of a PR; surface it so
|
|
715
|
+
// the engine can strictly validate + reconcile it onto the board.
|
|
716
|
+
if (result.service !== undefined) {
|
|
717
|
+
return {
|
|
718
|
+
output: result.summary?.trim() || 'Service blueprint updated.',
|
|
719
|
+
blueprintService: result.service,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
// A spec-writer job carries a prescriptive specification doc instead of a PR;
|
|
723
|
+
// surface it so the engine can strictly validate + persist/surface it.
|
|
724
|
+
if (result.spec !== undefined) {
|
|
725
|
+
return {
|
|
726
|
+
output: result.summary?.trim() || 'Service specification updated.',
|
|
727
|
+
spec: result.spec,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
// A `merger` job carries a PR assessment instead of a PR; surface it so the
|
|
731
|
+
// engine can compare it to the task's thresholds and merge-or-notify.
|
|
732
|
+
if (result.assessment !== undefined) {
|
|
733
|
+
return {
|
|
734
|
+
output: result.summary?.trim() || 'Pull request assessed.',
|
|
735
|
+
mergeAssessment: result.assessment,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
// An `on-call` job carries a release-regression assessment; surface it so the engine
|
|
739
|
+
// can raise the `release_regression` notification + enrich any open incident.
|
|
740
|
+
if (result.onCallAssessment !== undefined) {
|
|
741
|
+
return {
|
|
742
|
+
output: result.summary?.trim() || 'Release regression investigated.',
|
|
743
|
+
onCallAssessment: result.onCallAssessment,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
// A `tester` job carries a structured test report instead of a PR; surface it so
|
|
747
|
+
// the engine can greenlight-or-loop the fixer.
|
|
748
|
+
if (result.report !== undefined) {
|
|
749
|
+
return {
|
|
750
|
+
output: result.summary?.trim() || 'Testing complete.',
|
|
751
|
+
testReport: result.report,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
// A `ci-fixer` job reports whether it pushed a fix. The engine's CI gate ignores
|
|
755
|
+
// this result (it just re-polls CI), but map it to a sensible output regardless.
|
|
756
|
+
if (result.pushed !== undefined) {
|
|
757
|
+
return {
|
|
758
|
+
output: result.summary?.trim() ||
|
|
759
|
+
(result.pushed ? 'Pushed a CI fix to the PR branch.' : 'No CI fix was produced.'),
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
// A `conflict-resolver` job reports whether the branch is now mergeable. The
|
|
763
|
+
// engine's conflicts gate re-checks mergeability regardless; map to an output.
|
|
764
|
+
if (result.resolved !== undefined) {
|
|
765
|
+
return {
|
|
766
|
+
output: result.summary?.trim() ||
|
|
767
|
+
(result.resolved
|
|
768
|
+
? 'Resolved merge conflicts and pushed to the PR branch.'
|
|
769
|
+
: 'Could not fully resolve the merge conflicts.'),
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const summary = result.summary?.trim() || 'Implementation complete.';
|
|
773
|
+
const output = result.prUrl ? `${summary}\n\nPR: ${result.prUrl}` : summary;
|
|
774
|
+
// Surface the opened PR structurally (not just in the output text) so the
|
|
775
|
+
// engine can record it on the block and the board can link straight to it.
|
|
776
|
+
const pullRequest = result.prUrl
|
|
777
|
+
? {
|
|
778
|
+
url: result.prUrl,
|
|
779
|
+
...(prNumberFromUrl(result.prUrl) !== undefined
|
|
780
|
+
? { number: prNumberFromUrl(result.prUrl) }
|
|
781
|
+
: {}),
|
|
782
|
+
...(result.branch ? { branch: result.branch } : {}),
|
|
783
|
+
}
|
|
784
|
+
: undefined;
|
|
785
|
+
// No `model` here: the proxy meters tokens and the async path doesn't carry the
|
|
786
|
+
// provider ref to the poll site. `usage` is likewise omitted (metered by the proxy).
|
|
787
|
+
return {
|
|
788
|
+
output,
|
|
789
|
+
...(pullRequest ? { pullRequest } : {}),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/** Extract the PR number from a GitHub pull-request URL (`.../pull/42`). */
|
|
793
|
+
function prNumberFromUrl(url) {
|
|
794
|
+
const match = /\/pull\/(\d+)/.exec(url);
|
|
795
|
+
if (!match)
|
|
796
|
+
return undefined;
|
|
797
|
+
const n = Number(match[1]);
|
|
798
|
+
return Number.isFinite(n) ? n : undefined;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Providers the LLM proxy can serve: the direct OpenAI Chat Completions-compatible
|
|
802
|
+
* upstreams it forwards to, plus `workers-ai`, which it runs in-Worker through the
|
|
803
|
+
* AI binding (no provider key required), plus the local runners (Ollama / LM Studio /
|
|
804
|
+
* llama.cpp / vLLM / custom), which the proxy forwards to the run initiator's own
|
|
805
|
+
* OpenAI-compatible endpoint (no key lease).
|
|
806
|
+
*/
|
|
807
|
+
function isProxyableProvider(provider) {
|
|
808
|
+
return (provider === 'workers-ai' ||
|
|
809
|
+
provider === 'qwen' ||
|
|
810
|
+
provider === 'deepseek' ||
|
|
811
|
+
provider === 'moonshot' ||
|
|
812
|
+
provider === 'openai' ||
|
|
813
|
+
isLocalRunner(provider));
|
|
814
|
+
}
|
|
815
|
+
function prBody(context) {
|
|
816
|
+
const lines = [
|
|
817
|
+
`Automated implementation for block **${context.block.title}** (${context.block.type}).`,
|
|
818
|
+
'',
|
|
819
|
+
context.block.description || '(no description)',
|
|
820
|
+
'',
|
|
821
|
+
`Pipeline: ${context.pipelineName}`,
|
|
822
|
+
];
|
|
823
|
+
return lines.join('\n');
|
|
824
|
+
}
|
|
825
|
+
//# sourceMappingURL=ContainerAgentExecutor.js.map
|