@cat-factory/worker 0.128.0 → 0.128.2
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/dist/infrastructure/container-assembly.d.ts +5 -0
- package/dist/infrastructure/container-assembly.d.ts.map +1 -1
- package/dist/infrastructure/container-assembly.js +3 -1
- package/dist/infrastructure/container-assembly.js.map +1 -1
- package/dist/infrastructure/container-executor-deps.d.ts +81 -0
- package/dist/infrastructure/container-executor-deps.d.ts.map +1 -0
- package/dist/infrastructure/container-executor-deps.js +346 -0
- package/dist/infrastructure/container-executor-deps.js.map +1 -0
- package/dist/infrastructure/container-model-resolver.d.ts +14 -0
- package/dist/infrastructure/container-model-resolver.d.ts.map +1 -0
- package/dist/infrastructure/container-model-resolver.js +110 -0
- package/dist/infrastructure/container-model-resolver.js.map +1 -0
- package/dist/infrastructure/container-vcs-identity.d.ts +39 -0
- package/dist/infrastructure/container-vcs-identity.d.ts.map +1 -0
- package/dist/infrastructure/container-vcs-identity.js +88 -0
- package/dist/infrastructure/container-vcs-identity.js.map +1 -0
- package/dist/infrastructure/container.d.ts +8 -74
- package/dist/infrastructure/container.d.ts.map +1 -1
- package/dist/infrastructure/container.js +22 -481
- package/dist/infrastructure/container.js.map +1 -1
- package/package.json +16 -16
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { CompositeNotificationChannel,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ConfluenceProvider, FigmaProvider, ZeplinProvider, GitHubDocsProvider, GitHubIssuesProvider, JiraProvider, LinearDocumentProvider, LinearTaskProvider, NotionProvider, EMAIL_CIPHER_INFO, PersonalSubscriptionService, ProviderSubscriptionService, RunnerPoolConnectionService, ProvisioningLogRecorder, LoggingRunnerTransport, SLACK_CIPHER_INFO, NOTIFICATION_WEBHOOK_CIPHER_INFO, SlackNotificationChannel, buildNotificationWebhookSupport, OBSERVABILITY_CIPHER_INFO, RegistryReleaseHealthProvider, defaultObservabilityRegistry, RegistrySubscriptionQuotaProvider, defaultSubscriptionQuotaRegistry, WorkspaceIncidentEnrichmentProvider, INCIDENT_ENRICHMENT_CIPHER_INFO, AccountSettingsService, ACCOUNT_SETTINGS_CIPHER_INFO, createEmailSender, } from '@cat-factory/integrations';
|
|
1
|
+
import { CompositeNotificationChannel, NoopWorkRunner, } from '@cat-factory/kernel';
|
|
2
|
+
import { createTierInstallationResolvers, resolveAgentConfig, isProxyableProvider, } from '@cat-factory/agents';
|
|
3
|
+
import { ConfluenceProvider, FigmaProvider, ZeplinProvider, GitHubDocsProvider, GitHubIssuesProvider, JiraProvider, LinearDocumentProvider, LinearTaskProvider, NotionProvider, EMAIL_CIPHER_INFO, ProvisioningLogRecorder, SLACK_CIPHER_INFO, NOTIFICATION_WEBHOOK_CIPHER_INFO, SlackNotificationChannel, buildNotificationWebhookSupport, OBSERVABILITY_CIPHER_INFO, RegistryReleaseHealthProvider, defaultObservabilityRegistry, WorkspaceIncidentEnrichmentProvider, INCIDENT_ENRICHMENT_CIPHER_INFO, AccountSettingsService, ACCOUNT_SETTINGS_CIPHER_INFO, createEmailSender, } from '@cat-factory/integrations';
|
|
5
4
|
// Opt-in AWS EKS backends (runner + environment), registered by reference on BOTH facades so
|
|
6
5
|
// the runtimes stay symmetric with the native `kubernetes` backend they extend (which likewise
|
|
7
6
|
// rides both). They are pass-throughs until a workspace connects an `eks` backend, and carry NO
|
|
@@ -10,35 +9,34 @@ import { ConfluenceProvider, FigmaProvider, ZeplinProvider, GitHubDocsProvider,
|
|
|
10
9
|
// custom CA (Node/local) — exactly like a private-CA `kubernetes` connection — so on the Worker
|
|
11
10
|
// the kind is offered but a connection to such a cluster fails TLS at run time, not silently.
|
|
12
11
|
import { resolveWorkerRegistries } from './container-registries.js';
|
|
13
|
-
import {
|
|
12
|
+
import { selectTraceSink } from './container-trace-sinks.js';
|
|
14
13
|
export { selectTraceSink };
|
|
15
14
|
import { assembleWorkerContainer } from './container-assembly.js';
|
|
16
|
-
import {
|
|
15
|
+
import { buildAppRegistry, buildResolveRepoTarget } from './container-vcs-identity.js';
|
|
16
|
+
// The App registry + repo-target resolvers moved to `container-vcs-identity.ts`; re-exported so
|
|
17
|
+
// the sibling modules that already read them off the composition root keep one import surface.
|
|
18
|
+
export { buildAppRegistry, buildResolveRepoTarget };
|
|
19
|
+
import { buildModelProviderResolver } from './container-model-resolver.js';
|
|
20
|
+
import { buildDefaultWebSearchUpstream, buildResolveTransport, maybeWrapConsensus, selectAgentExecutor, } from './container-executor-deps.js';
|
|
21
|
+
// The executor wiring moved to `container-executor-deps.ts`; re-exported so
|
|
22
|
+
// `container-assembly.ts` keeps importing the composition root's public surface from one place.
|
|
23
|
+
export { maybeWrapConsensus, selectAgentExecutor };
|
|
24
|
+
import { AgentContextObservabilityService, SearchQueryObservabilityService, PACKAGE_REGISTRY_CIPHER_INFO, resolvePackageRegistriesForDispatch, } from '@cat-factory/orchestration';
|
|
17
25
|
import { ISOLATE_SAFE_APP_CACHES_PROFILE, createAppCaches } from '@cat-factory/caching';
|
|
18
|
-
import {
|
|
26
|
+
import { ContainerEnvConfigRepairer, makeResolveDeployCloneTarget, RunnerJobClient, makeResolveBinaryArtifactStore, FanOutEventPublisher, InAppNotificationChannel, PatPreferringAppRegistry, logger, buildInfrastructureCapabilities, GitHubIdentityResolver, resolveUrlSafetyPolicy, } from '@cat-factory/server';
|
|
19
27
|
import { loadConfig } from './config';
|
|
20
|
-
import { loadLangfuseConfig } from './config/langfuse';
|
|
21
|
-
import { loadObservabilityConfig } from './config/observability';
|
|
22
|
-
import { loadOtelConfig } from './config/otel';
|
|
23
28
|
import { requireDb, requireTelemetryDb } from './env';
|
|
24
|
-
import { baseUrlFor } from './ai/providerEndpoints';
|
|
25
|
-
import { resolveExtraRegistries } from './ai/registries';
|
|
26
29
|
import { CfGitHubWebhookIngest } from './gateways/GitHubGateways';
|
|
27
|
-
import {
|
|
30
|
+
import {} from './ai/ContainerAgentExecutor';
|
|
28
31
|
import { CloudflareContainerTransport } from './containers/CloudflareContainerTransport';
|
|
29
|
-
import { ContainerInstanceRegistry } from './containers/ContainerInstanceRegistry';
|
|
30
|
-
import { D1LiveContainerRepository } from './repositories/D1LiveContainerRepository';
|
|
31
32
|
import { HttpRunnerPoolProvider } from './runners/HttpRunnerPoolProvider';
|
|
32
33
|
import { D1RunnerPoolConnectionRepository } from './repositories/D1RunnerPoolConnectionRepository';
|
|
33
34
|
import { ContainerRepoBootstrapper } from './ai/ContainerRepoBootstrapper';
|
|
34
|
-
import { CompositeAgentExecutor } from './ai/CompositeAgentExecutor';
|
|
35
35
|
import { ContainerSessionService } from './containers/ContainerSessionService';
|
|
36
36
|
import { DurableObjectEventPublisher } from './events/DurableObjectEventPublisher';
|
|
37
37
|
import { WorkflowsWorkRunner } from './workflows/WorkflowsWorkRunner';
|
|
38
38
|
import { D1BlockRepository } from './repositories/D1BlockRepository';
|
|
39
|
-
import { D1ServiceRepository } from './repositories/D1ServiceRepository';
|
|
40
39
|
import { D1WorkspaceMountRepository } from './repositories/D1WorkspaceMountRepository';
|
|
41
|
-
import { D1LlmCallMetricRepository } from './repositories/D1LlmCallMetricRepository';
|
|
42
40
|
import { D1AgentContextSnapshotRepository } from './repositories/D1AgentContextSnapshotRepository';
|
|
43
41
|
import { D1AgentSearchQueryRepository } from './repositories/D1AgentSearchQueryRepository';
|
|
44
42
|
import { D1ProvisioningLogRepository } from './repositories/D1ProvisioningLogRepository';
|
|
@@ -62,8 +60,6 @@ import { D1RequirementReviewRepository } from './repositories/D1RequirementRevie
|
|
|
62
60
|
import { D1DocInterviewRepository } from './repositories/D1DocInterviewRepository';
|
|
63
61
|
import { D1KaizenGradingRepository } from './repositories/D1KaizenGradingRepository';
|
|
64
62
|
import { D1KaizenVerifiedComboRepository } from './repositories/D1KaizenVerifiedComboRepository';
|
|
65
|
-
import { D1ConsensusSessionRepository } from './repositories/D1ConsensusSessionRepository';
|
|
66
|
-
import { ConsensusAgentExecutor, registerConsensusTraits } from '@cat-factory/consensus';
|
|
67
63
|
import { D1ConsensusGroupRepository } from './repositories/D1ConsensusGroupRepository';
|
|
68
64
|
import { D1ClarityReviewRepository } from './repositories/D1ClarityReviewRepository';
|
|
69
65
|
import { D1BrainstormSessionRepository } from './repositories/D1BrainstormSessionRepository';
|
|
@@ -76,7 +72,6 @@ import { D1SandboxPromptVersionRepository, D1SandboxFixtureRepository, D1Sandbox
|
|
|
76
72
|
import { D1WorkspaceSettingsRepository } from './repositories/D1WorkspaceSettingsRepository';
|
|
77
73
|
import { D1UserSettingsRepository } from './repositories/D1UserSettingsRepository';
|
|
78
74
|
import { D1ObservabilityConnectionRepository } from './repositories/D1ObservabilityConnectionRepository';
|
|
79
|
-
import { D1SubscriptionQuotaCycleRepository } from './repositories/D1SubscriptionQuotaCycleRepository';
|
|
80
75
|
import { D1PackageRegistryConnectionRepository } from './repositories/D1PackageRegistryConnectionRepository';
|
|
81
76
|
import { D1IncidentEnrichmentConnectionRepository } from './repositories/D1IncidentEnrichmentConnectionRepository';
|
|
82
77
|
import { D1AccountSettingsRepository } from './repositories/D1AccountSettingsRepository';
|
|
@@ -90,15 +85,13 @@ import { D1ServiceFragmentDefaultsRepository } from './repositories/D1ServiceFra
|
|
|
90
85
|
// then wires each gate's provider below.
|
|
91
86
|
import { wireCiStatusProvider, wireMergeabilityProvider, wireReleaseHealthProvider, wireIncidentEnrichment, wirePullRequestReviewProvider, wireDocQualityProvider, } from '@cat-factory/gates';
|
|
92
87
|
import { buildGitLabEngineClient, GitLabIdentityResolver, registerGitLab, StaticGitLabTokenSource, } from '@cat-factory/gitlab';
|
|
93
|
-
import { GitHubDocQualityProvider, GitHubPrReportPublisher, GitHubPullRequestReviewProvider,
|
|
88
|
+
import { GitHubDocQualityProvider, GitHubPrReportPublisher, GitHubPullRequestReviewProvider, } from '@cat-factory/server';
|
|
94
89
|
import { GitHubCiStatusProvider } from './github/GitHubCiStatusProvider';
|
|
95
90
|
import { GitHubMergeabilityProvider } from './github/GitHubMergeabilityProvider';
|
|
96
91
|
import { GitHubBranchUpdater } from './github/GitHubBranchUpdater';
|
|
97
92
|
import { GitHubPullRequestMerger } from './github/GitHubPullRequestMerger';
|
|
98
93
|
import { WebCryptoSecretCipher } from './environments/WebCryptoSecretCipher';
|
|
99
94
|
import { D1NotificationWebhookRepository } from './repositories/D1NotificationWebhookRepository';
|
|
100
|
-
import { GitHubAppAuth } from './github/GitHubAppAuth';
|
|
101
|
-
import { GitHubAppRegistry } from './github/GitHubAppRegistry';
|
|
102
95
|
import { FetchGitHubClient } from './github/FetchGitHubClient';
|
|
103
96
|
import { D1TaskConnectionRepository } from './repositories/D1TaskConnectionRepository';
|
|
104
97
|
import { D1TaskSourceSettingsRepository } from './repositories/D1TaskSourceSettingsRepository';
|
|
@@ -112,288 +105,6 @@ import { D1SkillSourceRepository } from './repositories/D1SkillSourceRepository'
|
|
|
112
105
|
import { LlmFragmentSelector } from './ai/LlmFragmentSelector';
|
|
113
106
|
import { buildApiKeyService, buildLocalModelEndpointService, buildOpenRouterCatalogService, buildPersonalSubscriptionService, buildPublicApiKeyService, buildResolveUserGitHubToken, buildSubscriptionService, buildTestSecretsService, buildUserSecretService, buildValidationConfigService, } from './wireCredentialServices';
|
|
114
107
|
import { CryptoIdGenerator, SystemClock } from './runtime';
|
|
115
|
-
/**
|
|
116
|
-
* The Worker's {@link ModelProvider}: the base registry plus any extra provider
|
|
117
|
-
* registries an installation registered (see ./ai/registries). Used everywhere a
|
|
118
|
-
* model provider is needed so every path — agent executor, requirements reviewer,
|
|
119
|
-
* doc planner, fragment selector — sees the same provider set. When Langfuse is
|
|
120
|
-
* configured the provider is wrapped so those INLINE (non-proxied) calls surface on
|
|
121
|
-
* the same trace sink the LLM proxy fans container calls out to.
|
|
122
|
-
*/
|
|
123
|
-
// Memoised per `(Env, db)`: every inline consumer (agent executor, requirements
|
|
124
|
-
// reviewer, doc planner, fragment selector) shares ONE resolver — and so ONE Langfuse
|
|
125
|
-
// sink — for a container build. The resolver builds a per-scope provider from the
|
|
126
|
-
// DB-backed API-key pool plus the opt-in Cloudflare binding + Bedrock registries.
|
|
127
|
-
const modelResolverCache = new WeakMap();
|
|
128
|
-
function buildModelProviderResolver(env, db) {
|
|
129
|
-
const cached = modelResolverCache.get(env);
|
|
130
|
-
if (cached)
|
|
131
|
-
return cached;
|
|
132
|
-
// Opt-in provider registries that need no per-scope DB key: the Cloudflare Workers
|
|
133
|
-
// AI binding (when bound) and any extra registries (e.g. Bedrock). NOT assumed —
|
|
134
|
-
// `workers-ai` resolves only when the `AI` binding is present.
|
|
135
|
-
const extraRegistries = [
|
|
136
|
-
...(env.AI ? [cloudflareBindingRegistry({ binding: env.AI })] : []),
|
|
137
|
-
...resolveExtraRegistries(env),
|
|
138
|
-
];
|
|
139
|
-
// Instrument inline (non-proxied) calls with the SAME composed trace sink the proxied
|
|
140
|
-
// path uses — Langfuse and/or the OTLP exporter, whichever are enabled.
|
|
141
|
-
const traceSink = composeTraceSinks([
|
|
142
|
-
buildLangfuseSink(loadLangfuseConfig(env)),
|
|
143
|
-
buildOtelSink(loadOtelConfig(env)),
|
|
144
|
-
]);
|
|
145
|
-
// Persist inline calls to the SAME `llm_call_metrics` store the proxy writes for Pi and
|
|
146
|
-
// the executor writes for a subscription harness, so an inline agent kind (`doc-researcher`,
|
|
147
|
-
// the judges, consensus, the requirements writer) is visible to `ObservabilityPanel`, the
|
|
148
|
-
// per-step rollups and `/api/v1/debug/*` rather than only to an external trace backend.
|
|
149
|
-
// Composed through the shared factory so the recorder's service and the provider's fallback
|
|
150
|
-
// sink cannot be handed two DIFFERENT instances — the service owns the fan-out for a call it
|
|
151
|
-
// records, so a mismatch would split the trace (and wiring both to the provider would double
|
|
152
|
-
// every inline generation on Langfuse/OTel). No cache handle is passed because
|
|
153
|
-
// `workspaceSettings` is a pass-through in the isolate-safe profile.
|
|
154
|
-
const instrument = createInlineInstrumentation({
|
|
155
|
-
llmCallMetricRepository: new D1LlmCallMetricRepository({ db: requireTelemetryDb(env) }),
|
|
156
|
-
...(traceSink ? { traceSink } : {}),
|
|
157
|
-
recordPrompts: loadObservabilityConfig(env).recordPrompts,
|
|
158
|
-
workspaceSettingsRepository: new D1WorkspaceSettingsRepository({ db }),
|
|
159
|
-
idGenerator: new CryptoIdGenerator(),
|
|
160
|
-
clock: new SystemClock(),
|
|
161
|
-
logger,
|
|
162
|
-
});
|
|
163
|
-
const localModelEndpoints = buildLocalModelEndpointService(env, db, { now: () => Date.now() });
|
|
164
|
-
const scoped = createScopedModelProviderResolver({
|
|
165
|
-
apiKeys: buildApiKeyService(env, db, { now: () => Date.now() }),
|
|
166
|
-
baseUrlFor: (provider) => baseUrlFor(provider, env) ?? undefined,
|
|
167
|
-
extraRegistries,
|
|
168
|
-
localEndpointsFor: localModelEndpoints
|
|
169
|
-
? (userId) => localModelEndpoints.listResolved(userId)
|
|
170
|
-
: undefined,
|
|
171
|
-
});
|
|
172
|
-
// Observe inline calls, then cap concurrency, through the ONE composer that owns their order
|
|
173
|
-
// (instrumentation inside, limiter outermost). The Worker has no facade wrap that substitutes a
|
|
174
|
-
// resolved model today, so it could not hit the local-mode blind spot this order exists for —
|
|
175
|
-
// but going through the shared composer is what keeps that true for a Worker-side wrap added
|
|
176
|
-
// later, and it keeps the two facades textually symmetric. The limiter bounds concurrency within
|
|
177
|
-
// one isolate only (no cross-isolate/global limiting — see
|
|
178
|
-
// backend/docs/concurrency-and-redis.md), and since the Worker's inline path degrades
|
|
179
|
-
// subscription refs before resolve, it is a wired pass-through here in practice.
|
|
180
|
-
const resolver = wrapResolverWithTelemetry(scoped, {
|
|
181
|
-
...(instrument ? { instrument } : {}),
|
|
182
|
-
limiter: vendorConcurrencyLimiterFromEnv((key) => env[key]),
|
|
183
|
-
});
|
|
184
|
-
modelResolverCache.set(env, resolver);
|
|
185
|
-
return resolver;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* The resolver every executor consults for a step's default model (block-pinned >
|
|
189
|
-
* the task's selected/default model preset > env routing). Backed by the D1
|
|
190
|
-
* model-preset repo; shared by the inline LLM executor and the container executor so
|
|
191
|
-
* both honour the workspace presets identically. The built-in default preset points
|
|
192
|
-
* every agent kind at Kimi K2.7, so an unpinned step resolves to it even before the
|
|
193
|
-
* preset library is materialised.
|
|
194
|
-
*/
|
|
195
|
-
function buildResolveWorkspaceModelDefault(db) {
|
|
196
|
-
const repo = new D1ModelPresetRepository({ db });
|
|
197
|
-
return (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(repo, workspaceId, agentKind, modelPresetId);
|
|
198
|
-
}
|
|
199
|
-
export function selectAgentExecutor(deps) {
|
|
200
|
-
const { env, config, db, agentKindRegistry } = deps;
|
|
201
|
-
const inline = new AiAgentExecutor({
|
|
202
|
-
modelProviderResolver: buildModelProviderResolver(env, db),
|
|
203
|
-
agentRouting: config.agents.routing,
|
|
204
|
-
resolveBlockModel: config.agents.resolveBlockModel,
|
|
205
|
-
// Inline (non-sandbox) kinds honour the workspace's per-kind defaults too, so
|
|
206
|
-
// the resolution precedence is uniform across every agent kind, not just the
|
|
207
|
-
// container kinds.
|
|
208
|
-
resolveWorkspaceModelDefault: buildResolveWorkspaceModelDefault(db),
|
|
209
|
-
// Opt-in provider web search for the inline design/research kinds (no-op unless
|
|
210
|
-
// INLINE_WEB_SEARCH_ENABLED and an Anthropic/OpenAI model).
|
|
211
|
-
webSearch: inlineWebSearchOptionsFromEnv(env),
|
|
212
|
-
agentKindRegistry,
|
|
213
|
-
});
|
|
214
|
-
// The sandbox MUST build — a null here means a prerequisite (GitHub App private
|
|
215
|
-
// key, WORKER_PUBLIC_URL, AUTH_SESSION_SECRET, or a runner backend: the
|
|
216
|
-
// EXEC_CONTAINER binding or a registered runner pool) is missing. We refuse to
|
|
217
|
-
// start with a half-configured implementer rather than quietly running the
|
|
218
|
-
// repo-operating steps as useless one-shot LLM calls.
|
|
219
|
-
const container = buildContainerExecutor(deps);
|
|
220
|
-
if (!container) {
|
|
221
|
-
throw configProblem({ key: 'CONTAINER_EXECUTOR', ...ENV_HELP.CONTAINER_EXECUTOR });
|
|
222
|
-
}
|
|
223
|
-
// Always the composite: non-sandbox kinds run inline; sandbox kinds run in the
|
|
224
|
-
// container.
|
|
225
|
-
return new CompositeAgentExecutor(inline, container, agentKindRegistry);
|
|
226
|
-
}
|
|
227
|
-
/** Truthy env flag (`true`/`1`/`yes`). */
|
|
228
|
-
function isTruthy(value) {
|
|
229
|
-
return value === 'true' || value === '1' || value === 'yes';
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Wrap the standard executor with the optional consensus mechanism when
|
|
233
|
-
* `CONSENSUS_ENABLED` is set: register the consensus capability traits (so the builder
|
|
234
|
-
* offers "Enable Consensus" on eligible steps) and route consensus-enabled steps through
|
|
235
|
-
* a multi-model process, persisting + pushing the transcript. Off ⇒ returns `standard`
|
|
236
|
-
* unchanged (no traits, no wrapping), so behaviour is identical to before.
|
|
237
|
-
*/
|
|
238
|
-
export function maybeWrapConsensus(standard, env, config, db, eventPublisher, agentKindRegistry) {
|
|
239
|
-
if (!isTruthy(env.CONSENSUS_ENABLED))
|
|
240
|
-
return standard;
|
|
241
|
-
registerConsensusTraits(agentKindRegistry);
|
|
242
|
-
return new ConsensusAgentExecutor({
|
|
243
|
-
standard,
|
|
244
|
-
modelProviderResolver: buildModelProviderResolver(env, db),
|
|
245
|
-
agentRouting: config.agents.routing,
|
|
246
|
-
resolveBlockModel: config.agents.resolveBlockModel,
|
|
247
|
-
resolveWorkspaceModelDefault: buildResolveWorkspaceModelDefault(db),
|
|
248
|
-
sessionRepository: new D1ConsensusSessionRepository({ db }),
|
|
249
|
-
...(eventPublisher ? { eventPublisher } : {}),
|
|
250
|
-
agentKindRegistry,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Build the factory that picks a job's runner backend: a workspace's own
|
|
255
|
-
* self-hosted runner pool when one is registered (and runner pools are enabled),
|
|
256
|
-
* otherwise the per-run Cloudflare Container. Returns null when neither backend is
|
|
257
|
-
* available, so {@link buildContainerExecutor} falls back to inline work.
|
|
258
|
-
*/
|
|
259
|
-
function buildResolveTransport(deps) {
|
|
260
|
-
const { env, config, db, clock, provisioningLog, runnerBackendRegistry, injectedPoolProvider } = deps;
|
|
261
|
-
// The Cloudflare backend folds in instance-level reaping: the registry records
|
|
262
|
-
// each dispatched container in the live inventory and clears it on release, so the
|
|
263
|
-
// cron reaper (index.ts) can kill anything that outlived its lifetime — covering
|
|
264
|
-
// run/blueprint/bootstrap through this one transport with no per-flow wiring.
|
|
265
|
-
const cloudflare = env.EXEC_CONTAINER
|
|
266
|
-
? new CloudflareContainerTransport(env.EXEC_CONTAINER, new ContainerInstanceRegistry(env.EXEC_CONTAINER, new D1LiveContainerRepository({ db }), clock), env.HARNESS_SHARED_SECRET?.trim() || undefined)
|
|
267
|
-
: null;
|
|
268
|
-
// The self-hosted backend path: a connection service that resolves each workspace's
|
|
269
|
-
// runner-backend config (manifest pool OR native Kubernetes) to a live transport via
|
|
270
|
-
// the runner-backend provider registry. The shared manifest HTTP provider (its OAuth
|
|
271
|
-
// cache reused) is threaded in for the `manifest` kind.
|
|
272
|
-
let runnerService;
|
|
273
|
-
if (config.runners.enabled) {
|
|
274
|
-
const urlPolicy = resolveUrlSafetyPolicy(config.runners);
|
|
275
|
-
runnerService = new RunnerPoolConnectionService({
|
|
276
|
-
runnerPoolConnectionRepository: new D1RunnerPoolConnectionRepository({ db }),
|
|
277
|
-
workspaceRepository: new D1WorkspaceRepository({ db }),
|
|
278
|
-
secretCipher: new WebCryptoSecretCipher({
|
|
279
|
-
masterKeyBase64: config.runners.encryptionKey,
|
|
280
|
-
info: 'cat-factory:runners',
|
|
281
|
-
}),
|
|
282
|
-
clock,
|
|
283
|
-
logger,
|
|
284
|
-
runnerBackendRegistry,
|
|
285
|
-
...(urlPolicy ? { urlPolicy } : {}),
|
|
286
|
-
runnerPoolProvider: injectedPoolProvider ?? new HttpRunnerPoolProvider(urlPolicy ? { urlPolicy } : {}),
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
if (!cloudflare && !runnerService)
|
|
290
|
-
return null;
|
|
291
|
-
// Wrap a resolved transport so every dispatch/release/poll-failure appends a
|
|
292
|
-
// provisioning-log event tagged with the right subsystem (a self-hosted pool vs a
|
|
293
|
-
// per-run Cloudflare container). No-op when the separate log store isn't wired.
|
|
294
|
-
// The dedup set is closure-owned so it outlives each (per-resolution) wrapper.
|
|
295
|
-
const loggedPollFailures = new Set();
|
|
296
|
-
const log = (inner, subsystem, workspaceId, providerId) => provisioningLog
|
|
297
|
-
? new LoggingRunnerTransport({
|
|
298
|
-
inner,
|
|
299
|
-
recorder: provisioningLog,
|
|
300
|
-
workspaceId: workspaceId ?? '',
|
|
301
|
-
subsystem,
|
|
302
|
-
providerId,
|
|
303
|
-
loggedPollFailures,
|
|
304
|
-
})
|
|
305
|
-
: inner;
|
|
306
|
-
return async (workspaceId) => {
|
|
307
|
-
if (runnerService && workspaceId) {
|
|
308
|
-
const resolved = await runnerService.resolve(workspaceId);
|
|
309
|
-
if (resolved) {
|
|
310
|
-
return log(resolved.transport, 'runner-pool', workspaceId, resolved.providerId);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
if (cloudflare)
|
|
314
|
-
return log(cloudflare, 'container', workspaceId);
|
|
315
|
-
// The shared factory throws a ConflictError carrying the machine reason (see its doc): a clean
|
|
316
|
-
// 409 synchronously, and classifyDispatchFailure lifts the reason onto the run's AgentFailure on
|
|
317
|
-
// the async dispatch path (SPA shows "Agent backend not configured", not "container failed to
|
|
318
|
-
// start"). The Cloudflare facade also offers "enable Cloudflare Containers" in the remedy.
|
|
319
|
-
throw noRunnerBackendAvailableError(workspaceId, { cloudflareContainers: true });
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Build the container-based implementation executor, or return null when its
|
|
324
|
-
* prerequisites are missing (a runner backend — Cloudflare Containers and/or a
|
|
325
|
-
* self-hosted pool — plus a configured GitHub App, the proxy's public URL and the
|
|
326
|
-
* signing secret) — the caller then falls back to inline work.
|
|
327
|
-
*/
|
|
328
|
-
/**
|
|
329
|
-
* Build the multi-App registry (ADR 0005): the default App always, plus the
|
|
330
|
-
* privileged App when configured. It resolves which App's key to use per
|
|
331
|
-
* installation (from the binding's recorded appId), so every token mint / app-JWT
|
|
332
|
-
* call routes correctly. Callers guard on `config.github.enabled`, which requires
|
|
333
|
-
* GITHUB_APP_PRIVATE_KEY, so the default key is present.
|
|
334
|
-
*/
|
|
335
|
-
export function buildAppRegistry(env, config, db, clock) {
|
|
336
|
-
const installationRepository = new D1GitHubInstallationRepository({ db });
|
|
337
|
-
const makeAuth = (appId, privateKeyPem) => new GitHubAppAuth({
|
|
338
|
-
appId,
|
|
339
|
-
privateKeyPem,
|
|
340
|
-
installationRepository,
|
|
341
|
-
clock,
|
|
342
|
-
apiBase: config.github.apiBase,
|
|
343
|
-
});
|
|
344
|
-
const privileged = config.github.privilegedApp && env.GITHUB_PRIVILEGED_APP_PRIVATE_KEY
|
|
345
|
-
? {
|
|
346
|
-
appId: config.github.privilegedApp.appId,
|
|
347
|
-
auth: makeAuth(config.github.privilegedApp.appId, env.GITHUB_PRIVILEGED_APP_PRIVATE_KEY),
|
|
348
|
-
}
|
|
349
|
-
: undefined;
|
|
350
|
-
return new GitHubAppRegistry({
|
|
351
|
-
default: {
|
|
352
|
-
appId: config.github.appId,
|
|
353
|
-
auth: makeAuth(config.github.appId, env.GITHUB_APP_PRIVATE_KEY),
|
|
354
|
-
},
|
|
355
|
-
privileged,
|
|
356
|
-
installationRepository,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Resolve the repo linked to a running block's enclosing service, via the shared
|
|
361
|
-
* runtime-neutral `buildResolveRepoTarget` (the ancestry walk + no-fallback policy
|
|
362
|
-
* live in `@cat-factory/server` so the Worker and Node service can't drift). This
|
|
363
|
-
* wrapper just binds the D1 repositories. Shared by the container executor, the CI
|
|
364
|
-
* status provider and the PR merger.
|
|
365
|
-
*
|
|
366
|
-
* No `repoProjectionCache` is threaded here (unlike the Node facade, which caches the
|
|
367
|
-
* whole-projection re-list per workspace — caching-layer slice 3): the repo projection
|
|
368
|
-
* is our own mutable D1 state, and the Worker's isolate-safe profile makes that cache
|
|
369
|
-
* pass-through (no cross-isolate invalidation bus), so an in-isolate TTL would serve
|
|
370
|
-
* stale repos after a write on another isolate. Reading live IS the isolate-safe
|
|
371
|
-
* behaviour. The shared GitHub sync/webhook services still receive the (pass-through)
|
|
372
|
-
* handle via `createGitHubModule`, so their invalidation code path stays symmetric.
|
|
373
|
-
*/
|
|
374
|
-
export function buildResolveRepoTarget(db) {
|
|
375
|
-
return buildSharedResolveRepoTarget({
|
|
376
|
-
installationRepository: new D1GitHubInstallationRepository({ db }),
|
|
377
|
-
repoProjectionRepository: new D1RepoProjectionRepository({ db }),
|
|
378
|
-
blockRepository: new D1BlockRepository({ db }),
|
|
379
|
-
serviceRepository: new D1ServiceRepository({ db }),
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* The MULTI-REPO resolver (service-connections phase 3): the task's own repo plus each
|
|
384
|
-
* connected involved-service repo, deduped. Wired from the SAME D1 repos as the singular
|
|
385
|
-
* resolver (the D1 service repo's batched `listByFrameBlocks` resolves the involved frames'
|
|
386
|
-
* repos in one query). Fed to the container executor so the implementer can fan a
|
|
387
|
-
* cross-service change out across sibling checkouts.
|
|
388
|
-
*/
|
|
389
|
-
function buildResolveRepoTargets(db) {
|
|
390
|
-
return buildSharedResolveRepoTargets({
|
|
391
|
-
installationRepository: new D1GitHubInstallationRepository({ db }),
|
|
392
|
-
repoProjectionRepository: new D1RepoProjectionRepository({ db }),
|
|
393
|
-
blockRepository: new D1BlockRepository({ db }),
|
|
394
|
-
serviceRepository: new D1ServiceRepository({ db }),
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
108
|
/**
|
|
398
109
|
* Build the merge-lifecycle ports. The notification repository + merge-preset
|
|
399
110
|
* repository are wired unconditionally (the inbox + presets are always available);
|
|
@@ -836,181 +547,6 @@ function buildSystemEmailSender(config) {
|
|
|
836
547
|
return undefined;
|
|
837
548
|
return async () => sender;
|
|
838
549
|
}
|
|
839
|
-
// The deployment-wide trusted web-search upstream for CONTAINER agents, built from this
|
|
840
|
-
// facade's own `WEB_SEARCH_*` env — the fallback the search proxy uses when a run's account
|
|
841
|
-
// configured none of its own (see `createDefaultWebSearchUpstream` in @cat-factory/server).
|
|
842
|
-
// Public endpoints only on workerd (no loopback-SearXNG story); kept symmetric with the Node
|
|
843
|
-
// facade so a stock Cloudflare deployment can also set a deployment-wide default.
|
|
844
|
-
function buildDefaultWebSearchUpstream(env) {
|
|
845
|
-
return createDefaultWebSearchUpstream({
|
|
846
|
-
braveApiKey: env.WEB_SEARCH_BRAVE_API_KEY,
|
|
847
|
-
searxngUrl: env.WEB_SEARCH_SEARXNG_URL,
|
|
848
|
-
searxngApiKey: env.WEB_SEARCH_SEARXNG_API_KEY,
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
function buildContainerExecutor(deps) {
|
|
852
|
-
const { env, config, db, clock, resolveTransport, agentKindRegistry, subscriptions, personalSubscriptions, agentContextObservability, } = deps;
|
|
853
|
-
if (!config.github.enabled ||
|
|
854
|
-
!env.GITHUB_APP_PRIVATE_KEY ||
|
|
855
|
-
!env.WORKER_PUBLIC_URL ||
|
|
856
|
-
!env.AUTH_SESSION_SECRET) {
|
|
857
|
-
return null;
|
|
858
|
-
}
|
|
859
|
-
if (!resolveTransport)
|
|
860
|
-
return null;
|
|
861
|
-
const registry = buildAppRegistry(env, config, db, clock);
|
|
862
|
-
const resolveRepoTarget = buildResolveRepoTarget(db);
|
|
863
|
-
// Record a subscription harness's (Claude Code / Codex) per-call telemetry into the
|
|
864
|
-
// SAME `llm_call_metrics` store the LLM proxy writes for Pi — those harnesses bypass
|
|
865
|
-
// the proxy, so the executor lifts the metrics off the CLI stream and feeds them here.
|
|
866
|
-
// A standalone service over the required telemetry DB (the proxy path builds its own
|
|
867
|
-
// from the same table; both are stateless writers). The settings repository is REQUIRED
|
|
868
|
-
// here, not optional hygiene: a subscription harness's `stream-json` carries the FULL
|
|
869
|
-
// prompt and response, and an absent repository makes `createStoreAgentContextGate` an
|
|
870
|
-
// open gate — so without it an opted-out workspace's bodies are retained anyway, which is
|
|
871
|
-
// exactly the privacy half of C2 (observability-logging-gaps.md) wearing a different hat.
|
|
872
|
-
const recordHarnessCalls = makeHarnessCallRecorder(new LlmObservabilityService({
|
|
873
|
-
llmCallMetricRepository: new D1LlmCallMetricRepository({ db: requireTelemetryDb(env) }),
|
|
874
|
-
idGenerator: new CryptoIdGenerator(),
|
|
875
|
-
clock,
|
|
876
|
-
recordPrompts: config.observability.recordPrompts,
|
|
877
|
-
workspaceSettingsRepository: new D1WorkspaceSettingsRepository({ db }),
|
|
878
|
-
logger,
|
|
879
|
-
}));
|
|
880
|
-
// Modeled subscription quota-cycle provider (usage-and-quota-tracking, Part B): folds a
|
|
881
|
-
// finished subscription run's tokens into rolling windows (real vendor reads land in B2,
|
|
882
|
-
// so its adapter registry is empty today — every vendor reports modeled).
|
|
883
|
-
const subscriptionQuotaProvider = new RegistrySubscriptionQuotaProvider({
|
|
884
|
-
subscriptionQuotaCycleRepository: new D1SubscriptionQuotaCycleRepository({ db }),
|
|
885
|
-
idGenerator: new CryptoIdGenerator(),
|
|
886
|
-
clock,
|
|
887
|
-
registry: defaultSubscriptionQuotaRegistry,
|
|
888
|
-
});
|
|
889
|
-
// Prefer the run initiator's per-user PAT (when stored) over the App token, so the
|
|
890
|
-
// container's clone/push/PR is attributed to them. Falls back to the App token.
|
|
891
|
-
const resolveUserGitHubToken = buildResolveUserGitHubToken(env, db, clock);
|
|
892
|
-
const mintInstallationToken = async (installationId, ctx) => {
|
|
893
|
-
if (resolveUserGitHubToken && ctx?.initiatedBy) {
|
|
894
|
-
const pat = await resolveUserGitHubToken(ctx.initiatedBy);
|
|
895
|
-
if (pat)
|
|
896
|
-
return pat;
|
|
897
|
-
}
|
|
898
|
-
return registry.installationToken(installationId);
|
|
899
|
-
};
|
|
900
|
-
// Advertise Pi's `web_search` tool to a run only when a usable upstream exists — either the
|
|
901
|
-
// deployment-wide default below (⇒ always on) or the run's account has its own keys (else the
|
|
902
|
-
// tool would just fail/return nothing). The per-account check runs off the account-settings
|
|
903
|
-
// store (its own short-TTL cache).
|
|
904
|
-
const resolvePackageRegistries = buildResolvePackageRegistries(env, db);
|
|
905
|
-
// Decrypt the service frame's sensitive test credentials onto the tester job body (out of band).
|
|
906
|
-
const testSecretsForDispatch = buildTestSecretsService(env, db, clock);
|
|
907
|
-
const resolveTestSecrets = testSecretsForDispatch
|
|
908
|
-
? (workspaceId, blockId) => testSecretsForDispatch.resolveValuesForBlock(workspaceId, blockId)
|
|
909
|
-
: undefined;
|
|
910
|
-
const defaultWebSearchUpstream = buildDefaultWebSearchUpstream(env);
|
|
911
|
-
// No `settingsCache` threaded to this dedicated web-search-availability instance: the
|
|
912
|
-
// `accountSettings` slice is pass-through on the Worker's isolate-safe profile, so caching it
|
|
913
|
-
// here is a no-op (the primary `accountSettings` instance in `buildContainer` — the one whose
|
|
914
|
-
// decrypted view drives the runtime resolvers — gets the shared slice). The Node facade, where
|
|
915
|
-
// the slice is enabled, wires its web-search instance from `options.caches` directly.
|
|
916
|
-
const webSearchSettings = buildAccountSettings(env, db, clock);
|
|
917
|
-
const resolveWebSearchAvailability = defaultWebSearchUpstream || webSearchSettings
|
|
918
|
-
? async (workspaceId) => {
|
|
919
|
-
// Mirror the proxy's own resolution (`accountUpstream ?? defaultWebSearchUpstream`):
|
|
920
|
-
// the run's account keys WIN and the deployment default is only the fallback, so the
|
|
921
|
-
// surfaced provider matches the one that will actually serve the run's searches. Build
|
|
922
|
-
// the account upstream the SAME way the proxy does before falling back to the default.
|
|
923
|
-
if (webSearchSettings) {
|
|
924
|
-
const accountId = await new D1WorkspaceRepository({ db }).accountOf(workspaceId);
|
|
925
|
-
if (accountId) {
|
|
926
|
-
const accountUpstream = createWebSearchUpstream((await webSearchSettings.resolve(accountId)).webSearch ?? {});
|
|
927
|
-
if (accountUpstream)
|
|
928
|
-
return { available: true, provider: accountUpstream.provider };
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (defaultWebSearchUpstream)
|
|
932
|
-
return { available: true, provider: defaultWebSearchUpstream.provider };
|
|
933
|
-
return { available: false, provider: null };
|
|
934
|
-
}
|
|
935
|
-
: undefined;
|
|
936
|
-
return new ContainerAgentExecutor({
|
|
937
|
-
resolveTransport,
|
|
938
|
-
agentRouting: config.agents.routing,
|
|
939
|
-
resolveBlockModel: config.agents.resolveBlockModel,
|
|
940
|
-
// The workspace's per-agent-kind default model, consulted when a block pins none
|
|
941
|
-
// (block-pinned > workspace per-kind default > env routing > env default).
|
|
942
|
-
resolveWorkspaceModelDefault: buildResolveWorkspaceModelDefault(db),
|
|
943
|
-
resolveRepoTarget,
|
|
944
|
-
// Multi-repo coding (service-connections phase 3): the implementer fans a cross-service
|
|
945
|
-
// change out across the task's own repo + each connected involved-service repo.
|
|
946
|
-
resolveRepoTargets: buildResolveRepoTargets(db),
|
|
947
|
-
// Resolve the workspace's owning account so the proxy can lease account-scoped keys.
|
|
948
|
-
resolveAccountId: (workspaceId) => new D1WorkspaceRepository({ db }).accountOf(workspaceId),
|
|
949
|
-
mintInstallationToken,
|
|
950
|
-
// Ensure the shared per-task work branch up front so every agent (including the
|
|
951
|
-
// read-only architect) operates on the same branch — idempotent, best-effort. Writers
|
|
952
|
-
// create it from base; read-only agents only probe (`options.create`).
|
|
953
|
-
ensureWorkBranch: async (repo, branch, options) => ensureWorkBranchViaRest({
|
|
954
|
-
...(config.github.apiBase ? { apiBase: config.github.apiBase } : {}),
|
|
955
|
-
token: await registry.installationToken(repo.installationId),
|
|
956
|
-
owner: repo.owner,
|
|
957
|
-
name: repo.name,
|
|
958
|
-
baseBranch: repo.baseBranch,
|
|
959
|
-
branch,
|
|
960
|
-
create: options.create,
|
|
961
|
-
}),
|
|
962
|
-
sessionService: new ContainerSessionService({ secret: env.AUTH_SESSION_SECRET }),
|
|
963
|
-
// The subscription harnesses (Claude Code / Codex) lease a pooled token and
|
|
964
|
-
// attribute usage back for usage-aware rotation; absent ⇒ those harnesses are
|
|
965
|
-
// unavailable and a subscription-only model fails loudly at dispatch.
|
|
966
|
-
...(subscriptions
|
|
967
|
-
? {
|
|
968
|
-
leaseSubscriptionToken: (workspaceId, vendor) => subscriptions.leaseToken(workspaceId, vendor),
|
|
969
|
-
recordSubscriptionUsage: (workspaceId, tokenId, usage) => subscriptions.recordTokenUsage(workspaceId, tokenId, usage),
|
|
970
|
-
hasSubscriptionToken: (workspaceId, vendor) => subscriptions.hasToken(workspaceId, vendor),
|
|
971
|
-
}
|
|
972
|
-
: {}),
|
|
973
|
-
// Per-call telemetry for the subscription harnesses (proxy-bypassing), recorded
|
|
974
|
-
// into `llm_call_metrics` alongside the proxy-metered Pi rows.
|
|
975
|
-
recordHarnessCalls,
|
|
976
|
-
// Modeled subscription quota-cycle tracking (Part B): fold a finished subscription
|
|
977
|
-
// run's tokens into the rolling windows, for BOTH pooled and personal runs.
|
|
978
|
-
recordSubscriptionQuotaUsage: (target, usage) => subscriptionQuotaProvider.recordUsage(target, usage),
|
|
979
|
-
// Individual-usage harnesses (Claude) lease the run-initiator's OWN activated
|
|
980
|
-
// personal credential; absent ⇒ such models fail loudly at dispatch.
|
|
981
|
-
...(personalSubscriptions
|
|
982
|
-
? {
|
|
983
|
-
leasePersonalSubscriptionToken: (executionId, userId, vendor) => personalSubscriptions.leaseForRun(executionId, userId, vendor),
|
|
984
|
-
// Route a dual-mode individual model (GLM) to the initiator's own subscription
|
|
985
|
-
// when they have one; otherwise dispatch keeps it on the Cloudflare base.
|
|
986
|
-
hasPersonalSubscription: (userId, vendor) => personalSubscriptions.has(userId, vendor),
|
|
987
|
-
}
|
|
988
|
-
: {}),
|
|
989
|
-
proxyBaseUrl: `${env.WORKER_PUBLIC_URL.replace(/\/+$/, '')}/v1`,
|
|
990
|
-
// Point container agents' web search at the backend search proxy (no provider key in
|
|
991
|
-
// the sandbox), but only for a run whose account has keys (see resolver above).
|
|
992
|
-
...(resolveWebSearchAvailability ? { resolveWebSearchAvailability } : {}),
|
|
993
|
-
// Decrypt the workspace's private-registry entries onto the job body (rendered by
|
|
994
|
-
// the harness into ~/.npmrc), so private dependencies resolve on install.
|
|
995
|
-
...(resolvePackageRegistries ? { resolvePackageRegistries } : {}),
|
|
996
|
-
// Decrypt the service frame's SENSITIVE test credentials onto the tester job body (out of
|
|
997
|
-
// band — injected as container env vars by the harness, never in the prompt/telemetry).
|
|
998
|
-
...(resolveTestSecrets ? { resolveTestSecrets } : {}),
|
|
999
|
-
// Resolve the credentials a registered kind's TOOL SERVER (MCP) declared, off the Worker's own
|
|
1000
|
-
// configured vars. A deployment needing per-workspace credentials replaces this with its own
|
|
1001
|
-
// `ToolSecretResolver`; the rest of the dispatch path is unchanged either way.
|
|
1002
|
-
resolveToolSecrets: createEnvToolSecretResolver(env),
|
|
1003
|
-
logger,
|
|
1004
|
-
githubApiBase: config.github.apiBase,
|
|
1005
|
-
// Forward container tool spans to the external trace sink(s) (Langfuse and/or OTLP)
|
|
1006
|
-
// grouped under the run trace — the same sink the LLM proxy fans generations to.
|
|
1007
|
-
// (Langfuse nests them as children; the OTLP exporter groups them by shared trace id.)
|
|
1008
|
-
llmTraceSink: buildTraceSink(config),
|
|
1009
|
-
// Record the complete provided context per dispatch (best-effort, gated in the sink).
|
|
1010
|
-
...(agentContextObservability ? { agentContextObservability } : {}),
|
|
1011
|
-
agentKindRegistry,
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
550
|
/**
|
|
1015
551
|
* Pick how runs are driven:
|
|
1016
552
|
* - a Workflows binding present → durable, server-driven execution
|
|
@@ -1690,6 +1226,11 @@ export function buildContainer(env, overrides = {}, opts = {}) {
|
|
|
1690
1226
|
agentContextObservability,
|
|
1691
1227
|
searchQueryObservability,
|
|
1692
1228
|
accountSettings,
|
|
1229
|
+
// The executor's own two handles. `webSearchAccountSettings` is a SECOND, deliberately
|
|
1230
|
+
// uncached account-settings reader (see `WorkerExecutorDeps`); building both here keeps the
|
|
1231
|
+
// module graph one-way — `container-executor-deps.ts` never imports the root.
|
|
1232
|
+
executorPackageRegistries: buildResolvePackageRegistries(env, db),
|
|
1233
|
+
webSearchAccountSettings: buildAccountSettings(env, db, clock),
|
|
1693
1234
|
defaultWebSearchUpstream,
|
|
1694
1235
|
resolveBinaryArtifactStore,
|
|
1695
1236
|
githubWebhookIngest,
|