@cat-factory/node-server 0.112.0 → 0.113.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/dist/container-core-deps.d.ts +82 -0
- package/dist/container-core-deps.d.ts.map +1 -0
- package/dist/container-core-deps.js +470 -0
- package/dist/container-core-deps.js.map +1 -0
- package/dist/container-foundation.d.ts +81 -0
- package/dist/container-foundation.d.ts.map +1 -0
- package/dist/container-foundation.js +193 -0
- package/dist/container-foundation.js.map +1 -0
- package/dist/container-github-deps.d.ts.map +1 -1
- package/dist/container-github-deps.js +127 -95
- package/dist/container-github-deps.js.map +1 -1
- package/dist/container-realtime-deps.d.ts +8 -0
- package/dist/container-realtime-deps.d.ts.map +1 -1
- package/dist/container-realtime-deps.js +23 -9
- package/dist/container-realtime-deps.js.map +1 -1
- package/dist/container.d.ts +30 -14
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +37 -616
- package/dist/container.js.map +1 -1
- package/package.json +4 -4
package/dist/container.js
CHANGED
|
@@ -1,123 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {} from '@cat-factory/agents';
|
|
2
2
|
// Opt-in AWS EKS backends (runner + environment), registered by reference below (the Worker
|
|
3
3
|
// facade registers the same pair, keeping the runtimes symmetric with the native `kubernetes`
|
|
4
4
|
// backend these extend). They are pass-throughs until a workspace actually connects an `eks`
|
|
5
5
|
// backend, and carry NO runtime AWS SDK dependency (the token is minted with WebCrypto), so this
|
|
6
6
|
// adds no cost to a deployment that never uses EKS.
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { CompositeAgentExecutor, ContainerSessionService, GitHubAppAuth, GitHubAppRegistry, GitHubIdentityResolver, runWithInitiator, WebCryptoPasswordHasher, WebCryptoSecretCipher, buildInfrastructureCapabilities, testEnvHasZeroConfigDefault, buildResolveRepoTarget, buildResolveRepoTargets, makePreviewJobBuilder, logger, resolveUrlSafetyPolicy, resolveWorkspaceCapabilities, } from '@cat-factory/server';
|
|
7
|
+
import {} from '@cat-factory/integrations';
|
|
8
|
+
import {} from '@cat-factory/kernel';
|
|
9
|
+
import { createCore, } from '@cat-factory/orchestration';
|
|
10
|
+
import { CompositeAgentExecutor, ContainerSessionService, GitHubAppAuth, GitHubAppRegistry, GitHubIdentityResolver, testEnvHasZeroConfigDefault, buildResolveRepoTarget, buildResolveRepoTargets, makePreviewJobBuilder, logger, resolveUrlSafetyPolicy, } from '@cat-factory/server';
|
|
12
11
|
// The built-in polling-gate suite (ci / conflicts / post-release-health + on-call). The facade
|
|
13
12
|
// builds an app-owned `GateRegistry` pre-loaded with the suite via `gateRegistryWithBuiltins()`
|
|
14
13
|
// below, then wires each gate's provider.
|
|
15
|
-
import { applyGateProviders,
|
|
16
|
-
import {
|
|
17
|
-
import { loadNodeConfig } from './config.js';
|
|
14
|
+
import { applyGateProviders, warnUnwiredGates, } from '@cat-factory/gates';
|
|
15
|
+
import { GitLabIdentityResolver } from '@cat-factory/gitlab';
|
|
18
16
|
import { selectNodeGitHubDeps } from './container-github-deps.js';
|
|
19
17
|
import { buildNodeModelDeps } from './container-model-deps.js';
|
|
20
18
|
import { buildNodeRunServices } from './container-run-services-deps.js';
|
|
21
19
|
import { buildNodeBootstrapper, buildNodeTransportDeploy } from './container-transport-deps.js';
|
|
22
20
|
import { buildNodeAccountDeps } from './container-account-deps.js';
|
|
23
21
|
import { buildNodeRealtimeDeps } from './container-realtime-deps.js';
|
|
24
|
-
import { executionRuntime } from './execution/config.js';
|
|
25
|
-
import { PgBossBootstrapRunner } from './execution/bootstrapRunner.js';
|
|
26
|
-
import { PgBossEnvConfigRepairRunner } from './execution/envConfigRepairRunner.js';
|
|
27
|
-
import { PgBossEnvironmentTestRunner } from './execution/envTestRunner.js';
|
|
28
|
-
import { PgBossWorkRunner } from './execution/pgBossRunner.js';
|
|
29
22
|
import { createNodeGateways } from './gateways.js';
|
|
30
23
|
import { baseUrlForNode } from './modelProvider.js';
|
|
31
24
|
import { LocalMachineEventRelay } from './machineEventRelay.js';
|
|
32
25
|
import { DrizzleGitHubInstallationRepository, DrizzleRunnerPoolConnectionRepository, } from './repositories/containerExecution.js';
|
|
33
26
|
import { DrizzleRepoProjectionRepository } from './repositories/github.js';
|
|
34
|
-
import { DrizzleSubscriptionActivationRepository } from './repositories/personalSubscription.js';
|
|
35
27
|
import { DrizzleUserRepoAccessRepository } from './repositories/userRepoAccess.js';
|
|
36
28
|
import { DrizzleSealedSecretInventory } from './repositories/drizzle/sealedSecretInventory.js';
|
|
37
|
-
import { createDrizzleRepositories
|
|
38
|
-
import { DrizzleReferenceArchitectureRepository } from './repositories/bootstrap.js';
|
|
39
|
-
import { DrizzleEnvConfigRepairJobRepository } from './repositories/envConfigRepair.js';
|
|
40
|
-
import { DrizzleEnvironmentTestRunRepository } from './repositories/environmentTest.js';
|
|
41
|
-
import { DrizzleDocumentConnectionRepository, DrizzleDocumentRepository, } from './repositories/documents.js';
|
|
42
|
-
import { DrizzleEnvironmentConnectionRepository, DrizzleEnvironmentRegistryRepository, } from './repositories/environments.js';
|
|
43
|
-
import { DrizzleCustomManifestTypeRepository } from './repositories/customManifestType.js';
|
|
44
|
-
import { DrizzleNotificationRepository } from './repositories/notifications.js';
|
|
45
|
-
import { selectNodeFragmentLibraryDeps, selectNodeSkillLibraryDeps, } from './container-content-library-deps.js';
|
|
46
|
-
import {} from './repositories/slack.js';
|
|
47
|
-
import {} from './repositories/tasks.js';
|
|
48
|
-
import { CryptoIdGenerator, SystemClock } from './runtime.js';
|
|
49
|
-
import {} from './wireCredentialServices.js';
|
|
29
|
+
import { createDrizzleRepositories } from './repositories/drizzle.js';
|
|
50
30
|
// The container-agent-executor wiring (transport resolver, provisioning-log wrapper, container
|
|
51
31
|
// executor + bootstrapper + env-config repairer, GitHub-issue filer, trace-sink builder), lifted
|
|
52
32
|
// into a sibling module so this composition root stays within the file-size budget.
|
|
53
|
-
import {
|
|
33
|
+
import { buildNodeContainerExecutor, selectNodeEnvConfigRepairer, } from './container-executor-deps.js';
|
|
34
|
+
import { assembleNodeCoreDependencies } from './container-core-deps.js';
|
|
35
|
+
import { resolveNodeContainerFoundation, } from './container-foundation.js';
|
|
36
|
+
// Re-exported for the mothership routing-seam test + any facade that sources its own repos.
|
|
37
|
+
export { pickRepoSource } from './container-foundation.js';
|
|
54
38
|
// Re-export the public seams the local facade + tests still import from `./container.js`.
|
|
55
39
|
export { buildNodeResolveTransport, missingContainerExecutorPrereqs, withProvisioningLog, } from './container-executor-deps.js';
|
|
56
|
-
/**
|
|
57
|
-
* Source one org/durable repository that a standard build constructs directly from the Drizzle
|
|
58
|
-
* `db`. In mothership mode (no Postgres) `remote` is the full-surface remote registry — a
|
|
59
|
-
* `Proxy` (`createRemoteRepositoryRegistry`) that forwards any repo name to the hosted
|
|
60
|
-
* mothership over the `/internal/persistence` RPC — so the repo comes from THERE instead of the
|
|
61
|
-
* absent db; otherwise `build()` constructs the Drizzle repo over `db` as before. This is the
|
|
62
|
-
* Phase-3 `db: undefined` audit seam: every direct-db store on the board-load + run path routes
|
|
63
|
-
* through it. Routing is orthogonal to the server-side allow-list — an un-allow-listed remote
|
|
64
|
-
* method still returns a clean `unknown_method`, never a `db`-undefined `TypeError`. Mirrors the
|
|
65
|
-
* credential-repo override seam (`providerApiKeyRepository`), which keeps credentials local while
|
|
66
|
-
* org state goes remote. See docs/initiatives/mothership-mode.md (Phase 3, part 1).
|
|
67
|
-
*/
|
|
68
|
-
export function pickRepoSource(remote, name, build) {
|
|
69
|
-
return remote ? remote[name] : build();
|
|
70
|
-
}
|
|
71
40
|
// Memoised per object so a container build shares ONE model provider (hence one inline
|
|
72
41
|
// trace sink) across the agent executor, requirements reviewer, doc planner and
|
|
73
42
|
// fragment selector, and ONE core trace sink — instead of each call constructing its
|
|
74
43
|
// own. Mirrors the Worker's `buildModelProvider` memoisation. Memoisation matters more for
|
|
75
44
|
// OTel than Langfuse: the SDK sink owns batch processors/exporters, so it must be built
|
|
76
45
|
// once per config, not per wiring site.
|
|
77
|
-
/**
|
|
78
|
-
* Wire account invitations + per-account email senders for the Node facade (parity
|
|
79
|
-
* with the Worker's `selectEmailInvitationDeps`). Invitations are always available (an
|
|
80
|
-
* invite link works without email); the email-connection store + cipher are wired only
|
|
81
|
-
* when EMAIL is enabled, so an account can onboard a SendGrid/Resend key in the UI and
|
|
82
|
-
* have invites emailed. The provider key is sealed with the shared ENCRYPTION_KEY.
|
|
83
|
-
*/
|
|
84
|
-
function selectNodeEmailInvitationDeps(config, repos) {
|
|
85
|
-
const deps = {
|
|
86
|
-
invitationRepository: repos.invitationRepository,
|
|
87
|
-
// Password reset works without email (the link is logged in dev); the system sender
|
|
88
|
-
// below upgrades it to real delivery when configured.
|
|
89
|
-
passwordResetTokenRepository: repos.passwordResetTokenRepository,
|
|
90
|
-
resolveSystemEmailSender: buildSystemEmailSender(config),
|
|
91
|
-
appBaseUrl: config.email.appBaseUrl || undefined,
|
|
92
|
-
logger,
|
|
93
|
-
};
|
|
94
|
-
if (config.email.enabled && config.email.encryptionKey) {
|
|
95
|
-
deps.emailConnectionRepository = repos.emailConnectionRepository;
|
|
96
|
-
deps.emailSecretCipher = new WebCryptoSecretCipher({
|
|
97
|
-
masterKeyBase64: config.email.encryptionKey,
|
|
98
|
-
info: EMAIL_CIPHER_INFO,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
return deps;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Build the deployment-level system email sender (auth emails like password reset) from
|
|
105
|
-
* the env-driven `email.system` config, or undefined when not configured.
|
|
106
|
-
*/
|
|
107
|
-
function buildSystemEmailSender(config) {
|
|
108
|
-
const system = config.email.system;
|
|
109
|
-
if (!system)
|
|
110
|
-
return undefined;
|
|
111
|
-
const sender = createEmailSender({
|
|
112
|
-
provider: system.provider,
|
|
113
|
-
from: system.from,
|
|
114
|
-
sendgrid: system.provider === 'sendgrid' ? { apiKey: system.apiKey } : undefined,
|
|
115
|
-
resend: system.provider === 'resend' ? { apiKey: system.apiKey } : undefined,
|
|
116
|
-
});
|
|
117
|
-
if (!sender)
|
|
118
|
-
return undefined;
|
|
119
|
-
return async () => sender;
|
|
120
|
-
}
|
|
121
46
|
/**
|
|
122
47
|
* Rate-limit accounting is best-effort telemetry the Worker persists to D1; the Node
|
|
123
48
|
* facade has no such table, so it drops the snapshots (exactly like the local facade).
|
|
@@ -178,70 +103,6 @@ function buildNodeVcsIdentityRegistry(config) {
|
|
|
178
103
|
}
|
|
179
104
|
return registry;
|
|
180
105
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Resolve which runner backend a workspace's container jobs dispatch to. The Node
|
|
183
|
-
* facade has no built-in per-run container runtime (unlike the Worker's Cloudflare
|
|
184
|
-
* Containers), so it serves a workspace's self-hosted runner pool when one is
|
|
185
|
-
* registered and throws a clear error otherwise. Returns null (no transport at all)
|
|
186
|
-
* when runner pools are not enabled. Mirrors the Worker's `buildResolveTransport`,
|
|
187
|
-
* minus the Cloudflare-container path.
|
|
188
|
-
*/
|
|
189
|
-
/**
|
|
190
|
-
* The Node composition root: assemble the framework-agnostic domain `Core` with
|
|
191
|
-
* Drizzle/Postgres repositories + Node implementations of the runtime ports, then
|
|
192
|
-
* attach the shared-controller extras (`config`, the kind-spanning agent-run repo,
|
|
193
|
-
* the runtime gateways). The same persistence is used in dev, test and prod — tests
|
|
194
|
-
* run against a real Postgres, exactly as the Worker runs against a real D1.
|
|
195
|
-
*
|
|
196
|
-
* Repo-operating agent steps (coder, blueprints, merger, …) run in a container
|
|
197
|
-
* dispatched to a workspace's self-hosted runner pool — the shared
|
|
198
|
-
* `ContainerAgentExecutor`, exactly as on the Worker. When the prerequisites (GitHub
|
|
199
|
-
* App, `PUBLIC_URL`, `AUTH_SESSION_SECRET`, `ENCRYPTION_KEY`) are absent the
|
|
200
|
-
* composite still serves inline kinds but fails container kinds loudly.
|
|
201
|
-
*/
|
|
202
|
-
/**
|
|
203
|
-
* Resolve every app-owned registry the container wires: the backend-kind registries (env + runner
|
|
204
|
-
* + custom-manifest + user-secret), the agent-kind / gate / step-resolver / initiative-preset /
|
|
205
|
-
* VCS-provider / gate-provider registries. Each is the injected instance when `options` supplies it
|
|
206
|
-
* (a deployment's custom entries, or the conformance harness's pre-loaded instance) else the
|
|
207
|
-
* built-ins-only default, and the opt-in AWS EKS backends are registered by reference. Extracted
|
|
208
|
-
* from {@link buildNodeContainer} to keep it under the complexity ceiling.
|
|
209
|
-
*/
|
|
210
|
-
function resolveNodeAppRegistries(options) {
|
|
211
|
-
const { environmentBackendRegistry, runnerBackendRegistry, customManifestTypeRegistry, userSecretKindRegistry, } = options.backendRegistries ?? createBackendRegistries();
|
|
212
|
-
// Register the opt-in AWS EKS backends by reference (the default registries stay AWS-free).
|
|
213
|
-
// Reuses the native Kubernetes transport/provider behind a minted IAM apiserver token; a
|
|
214
|
-
// pass-through until a workspace connects an `eks` backend. Registered on BOTH facades (the
|
|
215
|
-
// Worker registers the same pair in its container build) so the runtimes stay symmetric with
|
|
216
|
-
// the native `kubernetes` backend these extend — a real EKS cluster's private-CA apiserver is
|
|
217
|
-
// only reachable from a runtime that can pin a custom CA (Node/local), the same constraint a
|
|
218
|
-
// private-CA `kubernetes` connection already carries.
|
|
219
|
-
runnerBackendRegistry.register(eksRunnerBackend);
|
|
220
|
-
environmentBackendRegistry.register(eksEnvironmentBackend);
|
|
221
|
-
return {
|
|
222
|
-
environmentBackendRegistry,
|
|
223
|
-
runnerBackendRegistry,
|
|
224
|
-
customManifestTypeRegistry,
|
|
225
|
-
userSecretKindRegistry,
|
|
226
|
-
// The app-owned agent-kind registry: the injected instance (so a deployment's custom kinds
|
|
227
|
-
// are visible) else the built-ins-only default.
|
|
228
|
-
agentKindRegistry: options.agentKindRegistry ?? defaultAgentKindRegistry(),
|
|
229
|
-
// The app-owned gate registry: the injected instance (conformance / a deployment pre-loads it),
|
|
230
|
-
// else a fresh one with the built-in `@cat-factory/gates` suite installed.
|
|
231
|
-
gateRegistry: options.gateRegistry ?? gateRegistryWithBuiltins(),
|
|
232
|
-
// The app-owned step-resolver registry: the injected instance else an empty default (the
|
|
233
|
-
// built-in `merger` resolver is a privileged engine built-in, not a registry entry).
|
|
234
|
-
stepResolverRegistry: options.stepResolverRegistry ?? defaultStepResolverRegistry(),
|
|
235
|
-
// The app-owned initiative-preset registry: the injected instance else the built-ins-only
|
|
236
|
-
// default (generic / docs-refresh / tech-migration).
|
|
237
|
-
initiativePresetRegistry: options.initiativePresetRegistry ?? defaultInitiativePresetRegistry(),
|
|
238
|
-
// Opt-in GitLab VCS provider registry (a no-op unless GITLAB_TOKEN is set; the wiring lives in
|
|
239
|
-
// the caller, symmetric with the Worker facade per "keep the runtimes symmetric").
|
|
240
|
-
vcsRegistry: options.vcsRegistry ?? defaultVcsRegistry(),
|
|
241
|
-
// The app-owned provider registry the built-in gates probe through (fresh empty unless injected).
|
|
242
|
-
providerRegistry: options.providerRegistry ?? defaultProviderRegistry(),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
106
|
function wirePreviewModule(dependencies, options, ctx) {
|
|
246
107
|
const { env, config, repos, resolveRepoTarget, baseDeployMint } = ctx;
|
|
247
108
|
if (options.previewTransport && !dependencies.previewTransport) {
|
|
@@ -340,310 +201,6 @@ function applyMothershipRemoteRepos(dependencies, remoteRepos) {
|
|
|
340
201
|
remoteRepos.skillSourceRepository;
|
|
341
202
|
}
|
|
342
203
|
}
|
|
343
|
-
/**
|
|
344
|
-
* Assemble the engine {@link CoreDependencies} from the {@link NodeCoreDepsBundle} the composition
|
|
345
|
-
* root built. Extracted verbatim from {@link buildNodeContainer} so the runtime object is identical
|
|
346
|
-
* (later spreads still override earlier ones in the same order) — purely the function-size ratchet
|
|
347
|
-
* split, not a behaviour change.
|
|
348
|
-
*/
|
|
349
|
-
function assembleNodeCoreDependencies(bundle) {
|
|
350
|
-
const { config, options, env, db, repos, sourced, idGenerator, clock, gateways, runnerUrlPolicy, githubInstallationRepository, environmentBackendRegistry, runnerBackendRegistry, customManifestTypeRegistry, agentKindRegistry, gateRegistry, stepResolverRegistry, initiativePresetRegistry, providerRegistry, apiKeys, subscriptions, personalSubscriptions, localModelEndpoints, openRouterCatalog, modelProviderResolver, cloudflareModelsEnabled, deployDeps, runnerPoolConnectionRepository, agentContextObservability, searchQueryObservability, resolveTestSecretRefs, githubClient, tasks, fileGitHubIssue, issueWritebackProvider, githubGateDeps, githubModuleDeps, bootstrapJobRepository, repoBootstrapper, slackDeps, executionEventPublisher, agentExecutor, notificationChannel, releaseHealthDeps, packageRegistryDeps, incidentEnrichmentDeps, accountSettings, resolveBinaryArtifactStore, } = bundle;
|
|
351
|
-
return {
|
|
352
|
-
...releaseHealthDeps,
|
|
353
|
-
...incidentEnrichmentDeps,
|
|
354
|
-
...packageRegistryDeps,
|
|
355
|
-
// Fold the service frame's SENSITIVE test-credential refs (key + description, never values)
|
|
356
|
-
// into the tester prompt. Present when ENCRYPTION_KEY is set; absent ⇒ no advertised secrets.
|
|
357
|
-
...(resolveTestSecretRefs ? { resolveTestSecretRefs } : {}),
|
|
358
|
-
// App-owned backend registries (kind → provider) the connection services resolve through.
|
|
359
|
-
environmentBackendRegistry,
|
|
360
|
-
runnerBackendRegistry,
|
|
361
|
-
// The app-owned agent-kind registry (built-ins + any deployment-registered kinds); the
|
|
362
|
-
// engine reads it (traits / inline-surface / pre-post-op hooks) and re-exposes it on Core.
|
|
363
|
-
agentKindRegistry,
|
|
364
|
-
// The app-owned gate + step-resolver registries; the engine's gate machine + completion hub
|
|
365
|
-
// read them, and the gate registry is re-exposed on Core for the boot-time validation.
|
|
366
|
-
gateRegistry,
|
|
367
|
-
stepResolverRegistry,
|
|
368
|
-
// The app-owned provider registry the gate providers were wired onto above; the engine's gate
|
|
369
|
-
// machine reads the SAME instance through its GateContext.
|
|
370
|
-
providerRegistry,
|
|
371
|
-
// The app-owned pipeline registry (deployment-registered extra pipelines); createCore threads
|
|
372
|
-
// it into the workspace + pipeline services and re-exposes it on Core for boot-time validation.
|
|
373
|
-
pipelineRegistry: options.pipelineRegistry,
|
|
374
|
-
// The app-owned custom task-type registry (deployment-registered namespaced task types);
|
|
375
|
-
// createCore threads it into the board service (default-pipeline resolution) and re-exposes it
|
|
376
|
-
// on Core for the snapshot projection (`customTaskTypes`) + boot-time validation.
|
|
377
|
-
taskTypeRegistry: options.taskTypeRegistry,
|
|
378
|
-
// The app-owned initiative-preset registry; the initiative services read it and it is
|
|
379
|
-
// re-exposed on Core for the snapshot descriptors + preset probe.
|
|
380
|
-
initiativePresetRegistry,
|
|
381
|
-
// The code-defined custom provision-type catalog, merged with the workspace rows by
|
|
382
|
-
// `listCustomTypes` so a programmatically-registered type surfaces in the infra editor + the
|
|
383
|
-
// per-service provisioning picker.
|
|
384
|
-
customManifestTypeRegistry,
|
|
385
|
-
...(accountSettings ? { accountSettings } : {}),
|
|
386
|
-
// Resolves the per-account binary-artifact store (screenshots) for the visual-confirmation
|
|
387
|
-
// gate; resolving to null (no storage configured) ⇒ the gate passes through.
|
|
388
|
-
resolveBinaryArtifactStore,
|
|
389
|
-
workspaceRepository: repos.workspaceRepository,
|
|
390
|
-
workspaceMemberRepository: repos.workspaceMemberRepository,
|
|
391
|
-
accountRepository: repos.accountRepository,
|
|
392
|
-
membershipRepository: repos.membershipRepository,
|
|
393
|
-
userRepository: repos.userRepository,
|
|
394
|
-
passwordHasher: new WebCryptoPasswordHasher(),
|
|
395
|
-
blockRepository: repos.blockRepository,
|
|
396
|
-
pipelineRepository: repos.pipelineRepository,
|
|
397
|
-
executionRepository: repos.executionRepository,
|
|
398
|
-
// Clear a finished run's personal-credential activation promptly (TTL sweep is the backstop).
|
|
399
|
-
// In mothership mode its home is the LOCAL `node:sqlite` credential bucket (the activation
|
|
400
|
-
// re-seals the token for the run, and the LOCAL container executor decrypts it), injected via
|
|
401
|
-
// `options.subscriptionActivationRepository` — the SAME instance the personal-subscription
|
|
402
|
-
// service above mints into, so mint + clear agree. Absent (plain Node / siloed-Postgres local)
|
|
403
|
-
// → the Drizzle repo over `db`. This is NEVER routed through `sourced` (the remote registry):
|
|
404
|
-
// every no-db (mothership) caller injects the override — `buildLocalContainer` in production
|
|
405
|
-
// and `makeMothershipConformanceApp` in tests — so `db` here is always a real Postgres handle,
|
|
406
|
-
// and routing an activation clear to the mothership (where `deleteByExecution` isn't
|
|
407
|
-
// allow-listed) is a path no caller takes.
|
|
408
|
-
subscriptionActivationRepository: options.subscriptionActivationRepository ?? new DrizzleSubscriptionActivationRepository(db),
|
|
409
|
-
// In-org shared services. When a realtime hub is wired (start()), the engine's
|
|
410
|
-
// event publisher (composed above) is a `FanOutEventPublisher` over these two repos,
|
|
411
|
-
// so a shared service's live events reach every board that mounts it — parity with
|
|
412
|
-
// the Cloudflare facade. Without a hub (createServer/tests) the engine uses its
|
|
413
|
-
// NoopEventPublisher and nothing is pushed.
|
|
414
|
-
serviceRepository: repos.serviceRepository,
|
|
415
|
-
workspaceMountRepository: repos.workspaceMountRepository,
|
|
416
|
-
tokenUsageRepository: repos.tokenUsageRepository,
|
|
417
|
-
llmCallMetricRepository: repos.llmCallMetricRepository,
|
|
418
|
-
// Deployment-level rollups over `agent_runs` for the operator dashboard.
|
|
419
|
-
platformMetricsRepository: repos.platformMetricsRepository,
|
|
420
|
-
// Unified provisioning event log (its own Postgres schema). Threads the recorder
|
|
421
|
-
// into the env services and exposes the read service for the logs controller.
|
|
422
|
-
provisioningLogRepository: repos.provisioningLogRepository,
|
|
423
|
-
recordLlmPrompts: config.observability.recordPrompts,
|
|
424
|
-
// Re-exposed on the core for the agent-context read endpoint; the same instance
|
|
425
|
-
// is injected into the container executor above for the write path.
|
|
426
|
-
agentContextObservability,
|
|
427
|
-
// Re-exposed on the core for the search-query read endpoint AND the search proxy's
|
|
428
|
-
// write path (it reads it off the request container).
|
|
429
|
-
searchQueryObservability,
|
|
430
|
-
// Opt-in external trace sink(s) — Langfuse and/or OpenTelemetry — fanning every
|
|
431
|
-
// recorded LLM call out as a generation. Built only when configured; otherwise
|
|
432
|
-
// undefined and there is no external emission.
|
|
433
|
-
llmTraceSink: buildTraceSink(config),
|
|
434
|
-
modelPresetRepository: repos.modelPresetRepository,
|
|
435
|
-
// A fresh workspace's model-preset library is seeded with this built-in as the default
|
|
436
|
-
// (Node deploy → Kimi K2.7, the Cloudflare-runnable baseline; the local facade injects
|
|
437
|
-
// Claude). Applied only at first seed, so a user's later manual default choice wins.
|
|
438
|
-
defaultModelPresetId: options.defaultModelPresetId ?? DEFAULT_MODEL_PRESET_ID,
|
|
439
|
-
serviceFragmentDefaultsRepository: repos.serviceFragmentDefaultsRepository,
|
|
440
|
-
// Requirements-review feature (stateless reviewer + the requirements-rework
|
|
441
|
-
// step). Wired identically to the Cloudflare facade's `selectRequirementsDeps`
|
|
442
|
-
// so both runtimes serve the review/rework API AND substitute a block's reworked
|
|
443
|
-
// requirements into the agent context (the cross-runtime conformance suite asserts
|
|
444
|
-
// the substitution against both stores). The reviewer's model resolves exactly
|
|
445
|
-
// like a pipeline step: block-pin > workspace per-kind default > routing default
|
|
446
|
-
// (which falls back to Cloudflare Workers AI unless a direct key is set).
|
|
447
|
-
requirementReviewRepository: repos.requirementReviewRepository,
|
|
448
|
-
// Interactive document-interview sessions (WS5). Wired unconditionally; the interviewer
|
|
449
|
-
// reuses the requirements reviewer's model config resolved just below.
|
|
450
|
-
docInterviewRepository: repos.docInterviewRepository,
|
|
451
|
-
// Kaizen agent (post-run grading). Wired unconditionally, mirroring the Cloudflare
|
|
452
|
-
// facade, so the engine schedules gradings at run completion and the background sweep
|
|
453
|
-
// runs them. The grader resolves its model for the `kaizen` kind exactly like a step.
|
|
454
|
-
kaizenGradingRepository: repos.kaizenGradingRepository,
|
|
455
|
-
kaizenVerifiedComboRepository: repos.kaizenVerifiedComboRepository,
|
|
456
|
-
clarityReviewRepository: repos.clarityReviewRepository,
|
|
457
|
-
brainstormSessionRepository: repos.brainstormSessionRepository,
|
|
458
|
-
// Initiatives (the long-running multi-task work container). Wired unconditionally,
|
|
459
|
-
// mirroring the Worker's `selectMergeLifecycleDeps`, so the create/read API + the
|
|
460
|
-
// planning pipeline's ingest/committer steps work identically on both runtimes.
|
|
461
|
-
initiativeRepository: repos.initiativeRepository,
|
|
462
|
-
// Merge threshold presets: the per-workspace auto-merge ceiling library a task's
|
|
463
|
-
// merge gate resolves (block-pinned preset > workspace default). Wired
|
|
464
|
-
// unconditionally, exactly like the Worker's `selectMergeLifecycleDeps`, so the
|
|
465
|
-
// preset CRUD API + the merger step's threshold resolution work identically.
|
|
466
|
-
riskPolicyRepository: repos.riskPolicyRepository,
|
|
467
|
-
// Shared stacks (long-lived compose infra a consumer environment attaches to). Wired
|
|
468
|
-
// unconditionally like the merge presets so the CRUD API works identically on both
|
|
469
|
-
// runtimes; the bring-up (`ensureUp`) needs a host daemon, so plain Node has no
|
|
470
|
-
// `composeRuntime` — the local facade injects one via `overrides.composeRuntime`.
|
|
471
|
-
sharedStackRepository: repos.sharedStackRepository,
|
|
472
|
-
// Sandbox (parallel prompt/model testing) — contributed as one sandbox-owned mixin,
|
|
473
|
-
// symmetric with the Worker's `...selectSandboxDeps(db)`; the run-driver reuses the
|
|
474
|
-
// reviewer model config below. The container body never enumerates the five repos.
|
|
475
|
-
...createDrizzleSandboxDeps(db),
|
|
476
|
-
// Per-workspace runtime settings (human-wait escalation threshold + per-service task
|
|
477
|
-
// limit). Wired unconditionally so the settings API + the limit enforcement + the
|
|
478
|
-
// escalation sweep work identically to the Worker.
|
|
479
|
-
workspaceSettingsRepository: repos.workspaceSettingsRepository,
|
|
480
|
-
userSettingsRepository: repos.userSettingsRepository,
|
|
481
|
-
modelProviderResolver,
|
|
482
|
-
requirementReviewModel: config.agents.routing.default.ref,
|
|
483
|
-
requirementReviewResolveModel: config.agents.resolveBlockModel,
|
|
484
|
-
// Local mode runs the inline reviewers/brainstorm/estimator on the ambient Claude Code /
|
|
485
|
-
// Codex CLI when the pinned model is a subscription harness (undefined on stock Node, so
|
|
486
|
-
// such refs degrade to the routing default). Also drives the preset satisfiability guard.
|
|
487
|
-
...(config.agents.inlineHarnessRef ? { inlineHarnessRef: config.agents.inlineHarnessRef } : {}),
|
|
488
|
-
// Notifications subsystem (parity with the Worker, which wires it unconditionally):
|
|
489
|
-
// the inbox + the human-action surfaces. Node has no real-time push, so the rows
|
|
490
|
-
// persist (inbox + snapshot) and any channel composed below — e.g. Slack — delivers.
|
|
491
|
-
notificationRepository: sourced('notificationRepository', (d) => new DrizzleNotificationRepository(d)),
|
|
492
|
-
...tasks.deps,
|
|
493
|
-
// Recurring pipelines + the workspace tracker selection. The tracker provider
|
|
494
|
-
// files the tech-debt pipeline's issue by resolving the *workspace's* connected
|
|
495
|
-
// integration: GitHub issues through the workspace's GitHub App installation,
|
|
496
|
-
// Jira tickets from the per-workspace encrypted connection store — both per-tenant.
|
|
497
|
-
pipelineScheduleRepository: repos.pipelineScheduleRepository,
|
|
498
|
-
trackerSettingsRepository: repos.trackerSettingsRepository,
|
|
499
|
-
ticketTrackerProvider: new TicketTrackerService({
|
|
500
|
-
trackerSettingsRepository: repos.trackerSettingsRepository,
|
|
501
|
-
fetchImpl: fetch,
|
|
502
|
-
...(fileGitHubIssue ? { fileGitHubIssue } : {}),
|
|
503
|
-
...(tasks.taskConnectionRepository
|
|
504
|
-
? {
|
|
505
|
-
resolveJiraConnection: async (workspaceId) => {
|
|
506
|
-
const connection = await tasks.taskConnectionRepository.getByWorkspace(workspaceId, 'jira');
|
|
507
|
-
const { baseUrl, accountEmail, apiToken } = connection?.credentials ?? {};
|
|
508
|
-
if (!baseUrl || !accountEmail || !apiToken)
|
|
509
|
-
return null;
|
|
510
|
-
return { baseUrl, accountEmail, apiToken };
|
|
511
|
-
},
|
|
512
|
-
resolveLinearConnection: async (workspaceId) => {
|
|
513
|
-
const connection = await tasks.taskConnectionRepository.getByWorkspace(workspaceId, 'linear');
|
|
514
|
-
const { apiKey, token } = connection?.credentials ?? {};
|
|
515
|
-
return apiKey || token ? { apiKey, token } : null;
|
|
516
|
-
},
|
|
517
|
-
}
|
|
518
|
-
: {}),
|
|
519
|
-
}),
|
|
520
|
-
issueWritebackProvider,
|
|
521
|
-
idGenerator,
|
|
522
|
-
clock,
|
|
523
|
-
agentExecutor,
|
|
524
|
-
spendPricing: config.spend,
|
|
525
|
-
// Price metered dynamic OpenRouter models at their real per-model rate (not the
|
|
526
|
-
// bare-`openrouter` fallback) using this workspace's enabled catalog.
|
|
527
|
-
dynamicModelPricesFor: openRouterCatalog
|
|
528
|
-
? (ws) => openRouterCatalog.capabilitiesFor(ws)
|
|
529
|
-
: undefined,
|
|
530
|
-
// The runner-pool integration assembles when enabled, so a workspace can
|
|
531
|
-
// register the self-hosted pool its container agents dispatch to.
|
|
532
|
-
...(config.runners.enabled && config.runners.encryptionKey
|
|
533
|
-
? {
|
|
534
|
-
runnerPoolConnectionRepository,
|
|
535
|
-
runnerSecretCipher: new WebCryptoSecretCipher({
|
|
536
|
-
masterKeyBase64: config.runners.encryptionKey,
|
|
537
|
-
info: RUNNERS_CIPHER_INFO,
|
|
538
|
-
}),
|
|
539
|
-
// The pool provider instance backs the connection service's describeProvider +
|
|
540
|
-
// testConnection (the manifest editor's secret-key form + a pre-save probe). An
|
|
541
|
-
// injected native adapter wins here too (same instance that drives dispatch), so
|
|
542
|
-
// its describeConfig/testConnection render — else the generic manifest provider
|
|
543
|
-
// (same SSRF policy as the dispatch transport).
|
|
544
|
-
runnerPoolProvider: options.runnerPoolProvider ??
|
|
545
|
-
new HttpRunnerPoolProvider(runnerUrlPolicy ? { urlPolicy: runnerUrlPolicy } : {}),
|
|
546
|
-
// Node (and local) has undici, so it can verify a private CA / skip TLS for a
|
|
547
|
-
// Kubernetes apiserver — accept such a config at registration.
|
|
548
|
-
runnerCustomTlsSupported: true,
|
|
549
|
-
...(runnerUrlPolicy ? { runnerUrlSafetyPolicy: runnerUrlPolicy } : {}),
|
|
550
|
-
}
|
|
551
|
-
: {}),
|
|
552
|
-
...(options.boss
|
|
553
|
-
? {
|
|
554
|
-
workRunner: new PgBossWorkRunner(options.boss, executionRuntime(config, env).queue),
|
|
555
|
-
// The durable bootstrap driver (analogue of the Worker's BootstrapWorkflow):
|
|
556
|
-
// BootstrapService.startRun enqueues a drive job that polls the run to terminal.
|
|
557
|
-
bootstrapRunner: new PgBossBootstrapRunner(options.boss, executionRuntime(config, env).queue),
|
|
558
|
-
// The durable env-config-repair driver (analogue of the Worker's
|
|
559
|
-
// EnvConfigRepairWorkflow): start enqueues a drive job that polls the run to terminal.
|
|
560
|
-
envConfigRepairRunner: new PgBossEnvConfigRepairRunner(options.boss, executionRuntime(config, env).queue),
|
|
561
|
-
// The durable ephemeral-environment self-test driver (analogue of the Worker's
|
|
562
|
-
// EnvironmentTestWorkflow): startRun enqueues a drive job that advances the run.
|
|
563
|
-
environmentTestRunner: new PgBossEnvironmentTestRunner(options.boss, executionRuntime(config, env).queue),
|
|
564
|
-
}
|
|
565
|
-
: {}),
|
|
566
|
-
...githubGateDeps,
|
|
567
|
-
// GitHub installation + repo/branch/PR/issue/commit/check-run projections + the
|
|
568
|
-
// sync/webhook module (inline ingest persists to these repos on Node).
|
|
569
|
-
...githubModuleDeps,
|
|
570
|
-
// Repo-bootstrap: the reference-architecture library + bootstrap-run store make the
|
|
571
|
-
// module + API available; `repoBootstrapper` (when wired) dispatches the bootstrap
|
|
572
|
-
// container through the shared runner seam, and `bootstrapRunner` (pg-boss, below)
|
|
573
|
-
// durably drives its poll loop — parity with the Worker's BootstrapWorkflow.
|
|
574
|
-
referenceArchitectureRepository: sourced('referenceArchitectureRepository', (d) => new DrizzleReferenceArchitectureRepository(d)),
|
|
575
|
-
bootstrapJobRepository,
|
|
576
|
-
...(repoBootstrapper ? { repoBootstrapper } : {}),
|
|
577
|
-
// Env-config-repair runs share the unified agent_runs table (kind-scoped). The job
|
|
578
|
-
// repository is wired unconditionally; the repairer (agent fallback) is wired
|
|
579
|
-
// post-overrides below over the FINAL provider, and the durable runner in the
|
|
580
|
-
// `options.boss` block above — parity with the Worker's EnvConfigRepairWorkflow.
|
|
581
|
-
envConfigRepairJobRepository: sourced('envConfigRepairJobRepository', (d) => new DrizzleEnvConfigRepairJobRepository(d)),
|
|
582
|
-
// Ephemeral-environment self-test runs (their own table). The store is wired
|
|
583
|
-
// unconditionally; the environments module builds the service when it + a git provider
|
|
584
|
-
// are present, and the durable runner is wired in the `options.boss` block above.
|
|
585
|
-
environmentTestRunRepository: sourced('environmentTestRunRepository', (d) => new DrizzleEnvironmentTestRunRepository(d)),
|
|
586
|
-
// Document sources (Confluence / Notion / GitHub docs): wired from the shared
|
|
587
|
-
// integration providers exactly like the Worker, so a workspace can connect a
|
|
588
|
-
// source and import requirement/PRD/RFC pages as agent context.
|
|
589
|
-
...selectNodeDocumentsDeps(config, db, githubClient, githubInstallationRepository),
|
|
590
|
-
// Ephemeral environments (opt-in): a workspace registers its own environment
|
|
591
|
-
// management API; the tester provisions/destroys per-run environments from it. A
|
|
592
|
-
// trusted in-house adapter can replace the default HTTP provider via the seam.
|
|
593
|
-
// The environment integration scopes its own URL/host policy from
|
|
594
|
-
// `config.environments` inside this selector (separate from the runner pool's).
|
|
595
|
-
...selectNodeEnvironmentsDeps(config, db),
|
|
596
|
-
// The async container-backed Kubernetes deploy lifecycle (deployJobClient +
|
|
597
|
-
// resolveDeployCloneTarget) — pool-backed by default, overridable by the local facade.
|
|
598
|
-
...deployDeps,
|
|
599
|
-
// Prompt-fragment library (ADR 0006; opt-in): the managed tenant-scoped catalog
|
|
600
|
-
// of best-practice fragments feeding every agent run, wired exactly like the
|
|
601
|
-
// Worker's selectFragmentLibraryDeps (repos + installation resolver + selector).
|
|
602
|
-
...selectNodeFragmentLibraryDeps(config, env, db, githubClient, githubInstallationRepository, modelProviderResolver),
|
|
603
|
-
// Repo-sourced Claude Skills library (docs/initiatives/repo-skills.md; opt-in): the
|
|
604
|
-
// account's catalog of repo-authored skills, wired exactly like the Worker's
|
|
605
|
-
// selectSkillLibraryDeps (account repos + installation resolver).
|
|
606
|
-
...selectNodeSkillLibraryDeps(config, db, githubClient, githubInstallationRepository),
|
|
607
|
-
// Push-webhook skill-source freshness fan-out (slice 4): resync affected sources via the
|
|
608
|
-
// pg-boss GitHub-sync queue. No boss (pure-logic test) ⇒ no proactive resync; the
|
|
609
|
-
// dispatch-time probe is the freshness backstop.
|
|
610
|
-
enqueueSkillResync: async ({ accountId, sourceId }) => {
|
|
611
|
-
await gateways.githubWebhook.queueSkillResync(accountId, sourceId);
|
|
612
|
-
},
|
|
613
|
-
// Slack: an extra notification transport (the channel) + its management module.
|
|
614
|
-
// Default-off; when enabled its channel is composed into `notificationChannel` below
|
|
615
|
-
// alongside the in-app push, identically to the Worker.
|
|
616
|
-
...slackDeps,
|
|
617
|
-
// Account invitations + per-account email senders (UI-onboarded, DB-stored).
|
|
618
|
-
...selectNodeEmailInvitationDeps(config, repos),
|
|
619
|
-
// The pipeline-start guard resolves what's configured for a workspace + initiator.
|
|
620
|
-
resolveProviderCapabilities: (workspaceId, initiatedBy) => resolveWorkspaceCapabilities({
|
|
621
|
-
apiKeys,
|
|
622
|
-
subscriptions,
|
|
623
|
-
personalSubscriptions,
|
|
624
|
-
cloudflareModelsEnabled,
|
|
625
|
-
baseUrlFor: (provider) => baseUrlForNode(provider, env),
|
|
626
|
-
localModelEndpoints,
|
|
627
|
-
openRouterCatalog,
|
|
628
|
-
accountSettings,
|
|
629
|
-
workspaceAccountOf: (workspaceId) => repos.workspaceRepository.accountOf(workspaceId),
|
|
630
|
-
modelPolicySupported: config.infrastructure?.modelPolicy?.supported ?? false,
|
|
631
|
-
...(options.caches ? { caches: options.caches } : {}),
|
|
632
|
-
}, workspaceId, initiatedBy),
|
|
633
|
-
// Real-time push (when a hub is wired) + the composed notification channel (in-app
|
|
634
|
-
// push + Slack). These come AFTER the spreads so the composite replaces the bare
|
|
635
|
-
// Slack channel `slackDeps` set; both are absent (no override) when nothing is wired.
|
|
636
|
-
...(executionEventPublisher ? { executionEventPublisher } : {}),
|
|
637
|
-
...(notificationChannel ? { notificationChannel } : {}),
|
|
638
|
-
// Run the engine's gate-probe / merge GitHub reads under the run initiator's ambient
|
|
639
|
-
// context, so a per-user PAT (when set) is preferred over the App/env token.
|
|
640
|
-
runInitiatorScope: runWithInitiator,
|
|
641
|
-
// The process-wide cache bag from start() (Redis-notified invalidation when REDIS_URL
|
|
642
|
-
// is set). Absent ⇒ createCore builds bare in-memory defaults.
|
|
643
|
-
...(options.caches ? { caches: options.caches } : {}),
|
|
644
|
-
...options.overrides,
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
204
|
/**
|
|
648
205
|
* Project the assembled engine core + the Node-facade extras onto the {@link ServerContainer}
|
|
649
206
|
* the HTTP layer resolves. Extracted verbatim from {@link buildNodeContainer} (a function-size
|
|
@@ -651,7 +208,7 @@ function assembleNodeCoreDependencies(bundle) {
|
|
|
651
208
|
* per-user credential stores, and the VCS/gateway wiring are surfaced exactly as before.
|
|
652
209
|
*/
|
|
653
210
|
function projectNodeServerContainer(bundle) {
|
|
654
|
-
const { dependencies, config, defaultWebSearchUpstream, resolveRepoTarget, repos, appRegistry, options, repoProjectionRepository, githubInstallationRepository, environmentBackendRegistry, runnerBackendRegistry, resolveBinaryArtifactStore, gateways, vcsRegistry, testSecretsService, subscriptions, personalSubscriptions, apiKeys, publicApiKeys, cloudflareModelsEnabled, env, localModelEndpoints, userSecrets, db, openRouterCatalog, traceSink, } = bundle;
|
|
211
|
+
const { dependencies, config, externalNotificationChannel, defaultWebSearchUpstream, resolveRepoTarget, repos, appRegistry, options, repoProjectionRepository, githubInstallationRepository, environmentBackendRegistry, runnerBackendRegistry, resolveBinaryArtifactStore, gateways, vcsRegistry, testSecretsService, subscriptions, personalSubscriptions, apiKeys, publicApiKeys, cloudflareModelsEnabled, env, localModelEndpoints, userSecrets, db, openRouterCatalog, traceSink, } = bundle;
|
|
655
212
|
return {
|
|
656
213
|
...createCore(dependencies),
|
|
657
214
|
config,
|
|
@@ -690,6 +247,16 @@ function projectNodeServerContainer(bundle) {
|
|
|
690
247
|
...(options.realtimeSink
|
|
691
248
|
? { machineEventRelay: new LocalMachineEventRelay(options.realtimeSink) }
|
|
692
249
|
: {}),
|
|
250
|
+
// Mothership-side notification DELIVERY (`POST /internal/notifications/deliver`): a
|
|
251
|
+
// mothership-mode node persists its notification rows here but holds none of the org's
|
|
252
|
+
// external delivery credentials (the Slack bot token is sealed with THIS deployment's key),
|
|
253
|
+
// so it asks the mothership to deliver a row by id. Wired with the EXTERNAL channels only —
|
|
254
|
+
// the in-app frame for a laptop-raised notification already arrives over the real-time
|
|
255
|
+
// upstream relay, so delivering it here too would double-push it. Wired symmetrically on the
|
|
256
|
+
// Cloudflare facade. No external channel (no Slack) ⇒ the endpoint 503s.
|
|
257
|
+
...(externalNotificationChannel
|
|
258
|
+
? { machineNotificationDelivery: externalNotificationChannel }
|
|
259
|
+
: {}),
|
|
693
260
|
repositories: {
|
|
694
261
|
...dependencies,
|
|
695
262
|
agentRunRepository: repos.agentRunRepository,
|
|
@@ -796,7 +363,7 @@ function finalizeNodeContainer(bundle) {
|
|
|
796
363
|
const { config, options, env, db, repos, sourced, idGenerator, clock, standardAgentExecutor, modelProviderResolver, resolveWorkspaceModelDefault, agentKindRegistry, providerRegistry, packageRegistrySecretCipher, githubInstallationRepository, environmentBackendRegistry, runnerBackendRegistry, customManifestTypeRegistry, gateRegistry, stepResolverRegistry, initiativePresetRegistry, apiKeys, subscriptions, personalSubscriptions, localModelEndpoints, openRouterCatalog, cloudflareModelsEnabled, deployDeps, runnerPoolConnectionRepository, agentContextObservability, searchQueryObservability, resolveTestSecretRefs, githubClient, tasks, fileGitHubIssue, issueWritebackProvider, githubGateDeps, githubModuleDeps, bootstrapJobRepository, repoBootstrapper, resolveRepoTarget, baseDeployMint, resolveTransport, bootstrapMintInstallationToken, remoteRepos, defaultWebSearchUpstream, appRegistry, repoProjectionRepository, vcsRegistry, testSecretsService, publicApiKeys, userSecrets, traceSink, } = bundle;
|
|
797
364
|
// Real-time event publisher + notification channel + optional consensus wrap, lifted into
|
|
798
365
|
// `container-realtime-deps.ts` to keep this root within the file-size budget.
|
|
799
|
-
const { slackDeps, executionEventPublisher, agentExecutor, notificationChannel } = buildNodeRealtimeDeps({
|
|
366
|
+
const { slackDeps, executionEventPublisher, agentExecutor, notificationChannel, externalNotificationChannel, } = buildNodeRealtimeDeps({
|
|
800
367
|
env,
|
|
801
368
|
config,
|
|
802
369
|
repos,
|
|
@@ -806,6 +373,9 @@ function finalizeNodeContainer(bundle) {
|
|
|
806
373
|
modelProviderResolver,
|
|
807
374
|
resolveWorkspaceModelDefault,
|
|
808
375
|
agentKindRegistry,
|
|
376
|
+
...(options.notificationChannels
|
|
377
|
+
? { extraNotificationChannels: options.notificationChannels }
|
|
378
|
+
: {}),
|
|
809
379
|
});
|
|
810
380
|
// Per-account settings + binary-artifact storage + the observability/incident gate-provider
|
|
811
381
|
// wiring (onto `providerRegistry`, before `applyGateProviders` below), plus the package-registry
|
|
@@ -921,6 +491,7 @@ function finalizeNodeContainer(bundle) {
|
|
|
921
491
|
return projectNodeServerContainer({
|
|
922
492
|
dependencies,
|
|
923
493
|
config,
|
|
494
|
+
externalNotificationChannel,
|
|
924
495
|
defaultWebSearchUpstream,
|
|
925
496
|
resolveRepoTarget,
|
|
926
497
|
repos,
|
|
@@ -948,91 +519,14 @@ function finalizeNodeContainer(bundle) {
|
|
|
948
519
|
});
|
|
949
520
|
}
|
|
950
521
|
export function buildNodeContainer(options) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
//
|
|
954
|
-
//
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
const previewTransportWired = Boolean(options.previewTransport ?? options.overrides?.previewTransport);
|
|
960
|
-
// The Node service has no built-in per-run container runtime: repo-operating agents run on
|
|
961
|
-
// a self-hosted runner pool, and Tester environments via the environment provider. Surface
|
|
962
|
-
// that so the SPA's infrastructure selector reads accurately. Local mode pre-sets its own
|
|
963
|
-
// descriptor (host Docker + pool) before calling in, so only fill it when absent.
|
|
964
|
-
config.infrastructure ??= buildInfrastructureCapabilities({
|
|
965
|
-
execution: { available: ['runner-pool'], active: 'runner-pool' },
|
|
966
|
-
testEnv: { available: ['environment-provider'], active: 'environment-provider' },
|
|
967
|
-
frontendPreview: { supported: previewTransportWired },
|
|
968
|
-
// A remote Node deployment has account admins to govern the account-wide model policy.
|
|
969
|
-
// (Local mode sets `config.infrastructure` itself before delegating here, so its
|
|
970
|
-
// mothership-gated value wins over this `??=`.)
|
|
971
|
-
modelPolicy: { supported: true },
|
|
972
|
-
});
|
|
973
|
-
const clock = new SystemClock();
|
|
974
|
-
const idGenerator = new CryptoIdGenerator();
|
|
975
|
-
// Mothership mode runs with NO Postgres (`options.db` undefined): org/durable state is served
|
|
976
|
-
// remotely via `options.repos`, so that set is REQUIRED there. (A standard Node/local build
|
|
977
|
-
// passes `db` and we build the Drizzle set from it.)
|
|
978
|
-
if (!options.repos && !options.db) {
|
|
979
|
-
throw new Error('buildNodeContainer requires `repos` when `db` is undefined (mothership mode supplies the ' +
|
|
980
|
-
'composite remote + local-credential repositories).');
|
|
981
|
-
}
|
|
982
|
-
const repos = options.repos ?? createDrizzleRepositories(options.db, clock);
|
|
983
|
-
// The Drizzle constructors only stash the handle — no build-time work (audited) — so BUILDING
|
|
984
|
-
// the stores below over an `undefined` db is safe; `db` carries the non-null type for those
|
|
985
|
-
// constructions, and the per-user credential services take the OPTIONAL `options.db` and turn
|
|
986
|
-
// themselves off when it is absent.
|
|
987
|
-
const db = options.db;
|
|
988
|
-
// Mothership mode (`options.db` undefined): the org/durable stores a standard build constructs
|
|
989
|
-
// directly from the db — the GitHub installation + projections, runner-pool connection,
|
|
990
|
-
// bootstrap + env-config-repair job stores, notifications, reference-architecture library,
|
|
991
|
-
// task + subscription-activation stores — are sourced from the REMOTE registry instead (here
|
|
992
|
-
// `options.repos` is the full-surface remote `Proxy` from `composeMothership`, which forwards
|
|
993
|
-
// any repo name to the mothership over RPC). `pickRepoSource(remoteRepos, name, build)` picks
|
|
994
|
-
// the remote entry when there is no db, else builds the Drizzle repo — see the Phase-3 audit in
|
|
995
|
-
// docs/initiatives/mothership-mode.md. The feature-flagged integration repos owned by the
|
|
996
|
-
// sub-helpers (tasks/documents/environments/fragments/slack) are opt-in and off by default, so
|
|
997
|
-
// they are NOT on the default board-load + run path and remain a follow-up sub-slice.
|
|
998
|
-
const remoteRepos = options.db ? undefined : repos;
|
|
999
|
-
// `remoteRepos` + `db` are fixed for this build, so bind them once: `sourced('name', (d) => …)`
|
|
1000
|
-
// picks the remote registry entry in mothership mode, else builds the Drizzle repo over `db`.
|
|
1001
|
-
const sourced = (name, build) => pickRepoSource(remoteRepos, name, () => build(db));
|
|
1002
|
-
// The app-owned registries (backend kinds, agent kinds, gates, step resolvers, initiative
|
|
1003
|
-
// presets, VCS providers, gate-provider registry) — injected instances when supplied (a
|
|
1004
|
-
// deployment's custom entries / the conformance harness) else the built-ins-only defaults.
|
|
1005
|
-
// The SAME instances flow to the executors, createCore, and the ServerContainer projection.
|
|
1006
|
-
const { environmentBackendRegistry, runnerBackendRegistry, customManifestTypeRegistry, userSecretKindRegistry, agentKindRegistry, gateRegistry, stepResolverRegistry, initiativePresetRegistry, vcsRegistry, providerRegistry, } = resolveNodeAppRegistries(options);
|
|
1007
|
-
// The built-in gates' providers are wired onto the app-owned `providerRegistry` (fresh unless
|
|
1008
|
-
// injected via `options`). The GitHub + release-health wiring runs only inside its
|
|
1009
|
-
// `enabled`/`githubClient` branches; a fresh registry starts empty, so an unconfigured gate just
|
|
1010
|
-
// stays unwired (pass-through) — no reset needed (the former `clearGateProviders()` guarded a
|
|
1011
|
-
// module-global that no longer exists). Mirrors the Worker facade (keep the runtimes symmetric).
|
|
1012
|
-
// Any test-injected gate providers (`options.gateProviders`) are applied at the END of this build
|
|
1013
|
-
// so they OVERRIDE the config wiring (local mode wires a PAT-backed CI provider here that would
|
|
1014
|
-
// otherwise clobber a faked one) — gates read their provider lazily at probe time, last write wins.
|
|
1015
|
-
let gitlabEngineClient;
|
|
1016
|
-
if (config.gitlab?.enabled && env.GITLAB_TOKEN) {
|
|
1017
|
-
registerGitLab(vcsRegistry, {
|
|
1018
|
-
tokenSource: new StaticGitLabTokenSource(env.GITLAB_TOKEN, config.gitlab.apiBase),
|
|
1019
|
-
clock,
|
|
1020
|
-
webhookSecret: config.gitlab.webhookSecret || undefined,
|
|
1021
|
-
});
|
|
1022
|
-
// Bridge the GitLab VcsClient onto the legacy GitHubClient port the engine's gate / merge /
|
|
1023
|
-
// RepoFiles paths consume, so a GitLab-only deployment (no GitHub App) gates on real CI and
|
|
1024
|
-
// merges the MR for real — the SAME wiring local mode already does, now on the Node facade
|
|
1025
|
-
// too (keep the runtimes symmetric). The GitHub App client wins when both are configured.
|
|
1026
|
-
gitlabEngineClient = buildGitLabEngineClient({
|
|
1027
|
-
token: env.GITLAB_TOKEN,
|
|
1028
|
-
apiBase: config.gitlab.apiBase,
|
|
1029
|
-
clock,
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
// Honour the workspace's model presets at run time (block-pinned > the task's
|
|
1033
|
-
// selected/default model preset > env routing), uniformly for inline and container
|
|
1034
|
-
// kinds. The built-in default preset points every agent kind at Kimi K2.7.
|
|
1035
|
-
const resolveWorkspaceModelDefault = (workspaceId, agentKind, modelPresetId) => resolvePresetModelForKind(repos.modelPresetRepository, workspaceId, agentKind, modelPresetId);
|
|
522
|
+
// The composition-root foundation: the resolved env/config (+ the Node infrastructure
|
|
523
|
+
// descriptor), the clock/id generator, the repository set with its mothership-aware `sourced`
|
|
524
|
+
// picker, the app-owned registries, the opt-in GitLab engine client, and the workspace
|
|
525
|
+
// model-preset resolver. Lifted into `container-foundation.ts` so this root stays within the
|
|
526
|
+
// per-function line budget; every side effect (the `config.infrastructure ??=` fill, the
|
|
527
|
+
// `registerGitLab` registration) still happens here, first, exactly as before.
|
|
528
|
+
const { env, config, clock, idGenerator, repos, db, remoteRepos, sourced, registries, gitlabEngineClient, resolveWorkspaceModelDefault, } = resolveNodeContainerFoundation(options);
|
|
529
|
+
const { environmentBackendRegistry, runnerBackendRegistry, customManifestTypeRegistry, userSecretKindRegistry, agentKindRegistry, gateRegistry, stepResolverRegistry, initiativePresetRegistry, vcsRegistry, providerRegistry, } = registries;
|
|
1036
530
|
// The credential/token stores + the model-provisioning stack (API-key pool, public-API +
|
|
1037
531
|
// local-model-endpoint + user-secret + OpenRouter + subscription + personal-subscription
|
|
1038
532
|
// stores, the trace sink, the model-provider resolver, and the inline executor), lifted into
|
|
@@ -1254,77 +748,4 @@ export function buildNodeContainer(options) {
|
|
|
1254
748
|
* No registered providers → `{ deps: {} }` and both the tasks module and the Jira
|
|
1255
749
|
* tracker stay off (the encryption key is guaranteed present by `loadTasksConfig`).
|
|
1256
750
|
*/
|
|
1257
|
-
/**
|
|
1258
|
-
* Wire the document-source integration for the Node facade, mirroring the Worker's
|
|
1259
|
-
* `selectDocumentsDeps`: the shared `@cat-factory/integrations` provider shells
|
|
1260
|
-
* (Confluence/Notion always; GitHub-docs only when a GitHub client is available, since
|
|
1261
|
-
* it reuses the workspace's App installation), the Drizzle connection/document repos,
|
|
1262
|
-
* and — in `llm` planner mode — the default model ref the doc→board planner runs with
|
|
1263
|
-
* (the container's `modelProvider` is shared). Source credentials are encrypted at rest
|
|
1264
|
-
* under a documents-scoped HKDF info, keyed by the shared ENCRYPTION_KEY.
|
|
1265
|
-
*/
|
|
1266
|
-
function selectNodeDocumentsDeps(config, db, githubClient, installations) {
|
|
1267
|
-
if (!config.documents.enabled || !config.documents.encryptionKey)
|
|
1268
|
-
return {};
|
|
1269
|
-
const providers = [];
|
|
1270
|
-
if (config.documents.sources.includes('confluence'))
|
|
1271
|
-
providers.push(new ConfluenceProvider());
|
|
1272
|
-
if (config.documents.sources.includes('notion'))
|
|
1273
|
-
providers.push(new NotionProvider());
|
|
1274
|
-
// Figma + Zeplin authenticate with a per-workspace PAT (no GitHub client needed), like
|
|
1275
|
-
// Notion/Confluence.
|
|
1276
|
-
if (config.documents.sources.includes('figma'))
|
|
1277
|
-
providers.push(new FigmaProvider());
|
|
1278
|
-
if (config.documents.sources.includes('zeplin'))
|
|
1279
|
-
providers.push(new ZeplinProvider());
|
|
1280
|
-
if (config.documents.sources.includes('linear'))
|
|
1281
|
-
providers.push(new LinearDocumentProvider());
|
|
1282
|
-
if (config.documents.sources.includes('github') && githubClient) {
|
|
1283
|
-
providers.push(new GitHubDocsProvider({ githubClient, installations, logger }));
|
|
1284
|
-
}
|
|
1285
|
-
if (providers.length === 0)
|
|
1286
|
-
return {};
|
|
1287
|
-
return {
|
|
1288
|
-
documentSourceProviders: providers,
|
|
1289
|
-
documentConnectionRepository: new DrizzleDocumentConnectionRepository(db, new WebCryptoSecretCipher({
|
|
1290
|
-
masterKeyBase64: config.documents.encryptionKey,
|
|
1291
|
-
info: 'cat-factory:documents',
|
|
1292
|
-
})),
|
|
1293
|
-
documentRepository: new DrizzleDocumentRepository(db),
|
|
1294
|
-
...(config.documents.planner === 'llm'
|
|
1295
|
-
? { documentPlannerModel: config.agents.routing.default.ref }
|
|
1296
|
-
: {}),
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Wire the ephemeral-environment integration for the Node facade when enabled,
|
|
1301
|
-
* mirroring the Worker's `selectEnvironmentsDeps`: the Drizzle connection + registry repos
|
|
1302
|
-
* and the environment-scoped `SecretCipher`. The provider itself is resolved per-workspace
|
|
1303
|
-
* from the env-backend registry by the stored `kind` (built-in `manifest`/`kubernetes`, or a
|
|
1304
|
-
* deployment's programmatically-registered custom kind), so nothing is injected here.
|
|
1305
|
-
* Per-tenant management-API secrets are encrypted at rest with the shared ENCRYPTION_KEY.
|
|
1306
|
-
* No key configured → `{}` and the module stays off (there is no separate enable flag).
|
|
1307
|
-
*/
|
|
1308
|
-
function selectNodeEnvironmentsDeps(config, db) {
|
|
1309
|
-
if (!config.environments.encryptionKey)
|
|
1310
|
-
return {};
|
|
1311
|
-
// The provider is resolved per-workspace from the env-backend registry by the stored
|
|
1312
|
-
// `kind`. Node honors custom-CA / insecure-skip TLS (undici), so a Kubernetes env config
|
|
1313
|
-
// with a CA is allowed (environmentCustomTlsSupported defaults to supported).
|
|
1314
|
-
const urlPolicy = resolveUrlSafetyPolicy(config.environments);
|
|
1315
|
-
return {
|
|
1316
|
-
environmentConnectionRepository: new DrizzleEnvironmentConnectionRepository(db),
|
|
1317
|
-
environmentRegistryRepository: new DrizzleEnvironmentRegistryRepository(db),
|
|
1318
|
-
// The workspace-defined custom-manifest-type catalog is a workspace feature on every facade.
|
|
1319
|
-
customManifestTypeRepository: new DrizzleCustomManifestTypeRepository(db),
|
|
1320
|
-
secretCipher: new WebCryptoSecretCipher({
|
|
1321
|
-
masterKeyBase64: config.environments.encryptionKey,
|
|
1322
|
-
}),
|
|
1323
|
-
...(urlPolicy ? { environmentUrlSafetyPolicy: urlPolicy } : {}),
|
|
1324
|
-
// Deployment-level, additive extensions to the built-in provisioning-detection conventions.
|
|
1325
|
-
...(config.environments.detectionConventions
|
|
1326
|
-
? { detectionConventions: config.environments.detectionConventions }
|
|
1327
|
-
: {}),
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
751
|
//# sourceMappingURL=container.js.map
|