@codemation/host 0.6.0 → 0.8.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/CHANGELOG.md +483 -0
- package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
- package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
- package/dist/{AppConfigFactory-YnveXE9k.d.ts → AppConfigFactory-BT0y0LVC.d.ts} +8490 -5548
- package/dist/{AppConfigFactory-C6q-CSKb.js → AppConfigFactory-Cx4qQvRk.js} +112 -52
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
- package/dist/{AppContainerFactory-qaqc-R1D.js → AppContainerFactory-DRTjG7nG.js} +7298 -1732
- package/dist/AppContainerFactory-DRTjG7nG.js.map +1 -0
- package/dist/{CodemationAppContext-DRu1Dpri.d.ts → CodemationAppContext-CGFYVcSb.d.ts} +14 -4
- package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-BteaR3Dc.js} +19 -6
- package/dist/CodemationAuthoring.types-BteaR3Dc.js.map +1 -0
- package/dist/{CodemationAuthoring.types-fBRppnmi.d.ts → CodemationAuthoring.types-DiKKogum.d.ts} +30 -5
- package/dist/{CodemationConfigNormalizer-DVko3cVN.d.ts → CodemationConfigNormalizer-48f-T66P.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-By-6tuGc.js} +81 -10
- package/dist/CodemationConsumerConfigLoader-By-6tuGc.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-DJWr86f-.d.ts → CodemationConsumerConfigLoader-_PIYqwVx.d.ts} +18 -2
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
- package/dist/{CodemationPluginListMerger-DGc-jfa2.d.ts → CodemationPluginListMerger-DP7djJ9S.d.ts} +151 -19
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
- package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
- package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
- package/dist/{CredentialContractsRegistry-DrMIDSw8.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
- package/dist/{CredentialServices-UfvHB-rN.d.ts → CredentialServices-BLloBztI.d.ts} +65 -20
- package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
- package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
- package/dist/InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts +17 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
- package/dist/{ItemsInputNormalizer-C-KHg9Mo.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +89 -25
- package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
- package/dist/PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts +82 -0
- package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
- package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
- package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
- package/dist/{TelemetryContracts-DbaNomrH.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
- package/dist/{WorkflowPolicyUiPresentationFactory-DQEY-h_S.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
- package/dist/{WorkflowViewContracts-CzK2KFuz.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +10 -1
- package/dist/authoring.d.ts +5 -5
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +6 -6
- package/dist/consumer.js +2 -2
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/devServerSidecar.js +1 -94
- package/dist/devServerSidecar.js.map +1 -1
- package/dist/dto.d.ts +6 -6
- package/dist/{index-BbBk26m0.d.ts → index-DilAYwnH.d.ts} +49 -3
- package/dist/index.d.ts +141 -21
- package/dist/index.js +109 -14
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +42 -113
- package/dist/nextServer.js +9 -7
- package/dist/pairing.d.ts +93 -0
- package/dist/pairing.js +5 -0
- package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
- package/dist/persistenceServer-B71RGvSj.d.ts +30 -0
- package/dist/{persistenceServer-CmsIKnO9.js → persistenceServer-C-hH4z6l.js} +2 -2
- package/dist/{persistenceServer-CmsIKnO9.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
- package/dist/persistenceServer.d.ts +8 -8
- package/dist/persistenceServer.js +3 -3
- package/dist/{server-MUNGsBYK.d.ts → server-09PKasWR.d.ts} +21 -6
- package/dist/{server-CJFfY67o.js → server-vtRCPgRJ.js} +7 -6
- package/dist/{server-CJFfY67o.js.map → server-vtRCPgRJ.js.map} +1 -1
- package/dist/server.d.ts +14 -14
- package/dist/server.js +13 -11
- package/package.json +47 -58
- package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
- package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
- package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
- package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
- package/prisma/schema.postgresql.prisma +55 -17
- package/prisma/schema.sqlite.prisma +55 -17
- package/prisma-generated/prisma-postgresql-client/edge.js +33 -5
- package/prisma-generated/prisma-postgresql-client/index-browser.js +29 -1
- package/prisma-generated/prisma-postgresql-client/index.d.ts +8933 -5716
- package/prisma-generated/prisma-postgresql-client/index.js +33 -5
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +38 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +33 -5
- package/prisma-generated/prisma-sqlite-client/index-browser.js +29 -1
- package/prisma-generated/prisma-sqlite-client/index.d.ts +8925 -5713
- package/prisma-generated/prisma-sqlite-client/index.js +33 -5
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +38 -0
- package/scripts/check-collections.mjs +18 -0
- package/scripts/generate-prisma-clients.mjs +20 -11
- package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
- package/src/application/contracts/WorkflowViewContracts.ts +6 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
- package/src/application/mapping/WorkflowDefinitionMapper.ts +40 -1
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
- package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
- package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
- package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
- package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
- package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
- package/src/applicationTokens.ts +20 -1
- package/src/audit/IAuditEmitter.ts +32 -0
- package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
- package/src/audit/WorkflowAuditLogWriter.ts +125 -0
- package/src/auth/managed/ManagedAuthConfig.ts +29 -0
- package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
- package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
- package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
- package/src/auth/managed/index.ts +5 -0
- package/src/bootstrap/AppContainerFactory.ts +295 -29
- package/src/bootstrap/AppContainerLifecycle.ts +31 -0
- package/src/bootstrap/perf/BootTimer.ts +168 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
- package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
- package/src/bootstrap/runtime/HeadlessApiRuntime.ts +47 -0
- package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
- package/src/credentials/BrokerClient.ts +49 -0
- package/src/credentials/BrokerRefreshError.ts +12 -0
- package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
- package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
- package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
- package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
- package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
- package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/catalogTypes.ts +4 -0
- package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
- package/src/domain/credentials/CredentialBindingService.ts +54 -2
- package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
- package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
- package/src/domain/telemetry/TelemetryContracts.ts +7 -1
- package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
- package/src/index.ts +9 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
- package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
- package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
- package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
- package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
- package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +26 -3
- package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
- package/src/mcp/McpClientFactory.ts +29 -0
- package/src/mcp/McpConnectionPool.ts +184 -0
- package/src/mcp/McpConnectionPool.types.ts +12 -0
- package/src/mcp/McpServerCatalog.ts +104 -0
- package/src/mcp/index.ts +5 -0
- package/src/pairing/HmacRequestSigner.ts +32 -0
- package/src/pairing/IncomingHmacVerifier.ts +82 -0
- package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
- package/src/pairing/InternalPingRegistrar.ts +25 -0
- package/src/pairing/PairedFetch.ts +33 -0
- package/src/pairing/PairingConfigFactory.ts +35 -0
- package/src/pairing/PairingConfigToken.ts +6 -0
- package/src/pairing/index.ts +14 -0
- package/src/pairing/pairing.types.ts +18 -0
- package/src/pairing.ts +17 -0
- package/src/persistenceServer.ts +1 -0
- package/src/presentation/config/AppConfig.ts +7 -1
- package/src/presentation/config/CodemationAuthConfig.ts +1 -1
- package/src/presentation/config/CodemationAuthoring.types.ts +60 -5
- package/src/presentation/config/CodemationConfig.ts +9 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
- package/src/presentation/config/CodemationPlugin.ts +2 -1
- package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
- package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
- package/src/presentation/http/ApiPaths.ts +4 -4
- package/src/presentation/http/HeadlessHttpServerFactory.ts +56 -0
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
- package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
- package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +59 -7
- package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
- package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
- package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
- package/src/presentation/websocket/WorkflowWebsocketServerFactory.ts +16 -0
- package/src/process/ExecaProcessRunner.ts +41 -0
- package/src/process/ProcessRunner.types.ts +39 -0
- package/src/server.ts +2 -0
- package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
- package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
- package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
- package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
- package/tsconfig.json +2 -0
- package/vitest.shared.ts +5 -0
- package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
- package/dist/AppConfigFactory-C6q-CSKb.js.map +0 -1
- package/dist/AppContainerFactory-qaqc-R1D.js.map +0 -1
- package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
- package/dist/CredentialServices-CgxwguAv.js.map +0 -1
- package/dist/PublicFrontendBootstrapFactory-Cb2pLmDd.d.ts +0 -45
- package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
- package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
- package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
- package/dist/persistenceServer-vtJAGDat.d.ts +0 -9
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
import type { WorkflowDefinition } from "@codemation/core";
|
|
2
|
+
import { WorkflowEdgePortValidator } from "@codemation/core";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Collects exported values that match the {@link WorkflowDefinition} shape.
|
|
5
6
|
* Other exports (helpers, constants, type-only re-exports) are ignored.
|
|
7
|
+
*
|
|
8
|
+
* Throws if any workflow's edges reference output ports not declared by the
|
|
9
|
+
* source node config. All violations are reported at once so an agent can
|
|
10
|
+
* self-correct in a single pass.
|
|
6
11
|
*/
|
|
7
12
|
export class WorkflowDefinitionExportsResolver {
|
|
13
|
+
private readonly portValidator = new WorkflowEdgePortValidator();
|
|
14
|
+
|
|
8
15
|
resolve(moduleExports: Readonly<Record<string, unknown>>): ReadonlyArray<WorkflowDefinition> {
|
|
9
16
|
const workflows: WorkflowDefinition[] = [];
|
|
10
17
|
for (const exportedValue of Object.values(moduleExports)) {
|
|
11
18
|
if (this.isWorkflowDefinition(exportedValue)) {
|
|
19
|
+
this.validatePorts(exportedValue);
|
|
12
20
|
workflows.push(exportedValue);
|
|
13
21
|
}
|
|
14
22
|
}
|
|
15
23
|
return workflows;
|
|
16
24
|
}
|
|
17
25
|
|
|
26
|
+
private validatePorts(workflow: WorkflowDefinition): void {
|
|
27
|
+
const result = this.portValidator.validate(workflow);
|
|
28
|
+
if (!result.valid) {
|
|
29
|
+
const lines = result.errors.map((e) => ` - ${e.message}`).join("\n");
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Workflow "${workflow.id}" ("${workflow.name}") has ${result.errors.length} invalid edge port(s):\n${lines}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
private isWorkflowDefinition(value: unknown): value is WorkflowDefinition {
|
|
19
37
|
if (!value || typeof value !== "object") {
|
|
20
38
|
return false;
|
|
@@ -42,6 +42,17 @@ export class WorkflowModulePathFinder {
|
|
|
42
42
|
|
|
43
43
|
private isWorkflowModulePath(modulePath: string): boolean {
|
|
44
44
|
const extension = path.extname(modulePath);
|
|
45
|
-
|
|
45
|
+
if (!this.workflowExtensions.has(extension)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const basename = path.basename(modulePath);
|
|
49
|
+
if (basename.endsWith(".d.ts") || basename.endsWith(".d.mts")) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const withoutExt = basename.slice(0, -extension.length);
|
|
53
|
+
if (withoutExt.endsWith(".test") || withoutExt.endsWith(".spec")) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
46
57
|
}
|
|
47
58
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { injectable } from "@codemation/core";
|
|
2
|
+
import type { ManagedJwtVerifier, VerifiedManagedPrincipal } from "@codemation/managed-auth";
|
|
3
|
+
import type { WebsocketAuthenticator } from "./WebsocketAuthenticator.types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WebSocket authenticator for `auth.kind: "managed"`.
|
|
7
|
+
*
|
|
8
|
+
* Parses `?token=<jwt>` from the WS upgrade URL and delegates to
|
|
9
|
+
* `ManagedJwtVerifier`. Returns the verified principal on success or `null`
|
|
10
|
+
* when the token is missing, expired, has the wrong audience, or is otherwise
|
|
11
|
+
* invalid.
|
|
12
|
+
*
|
|
13
|
+
* Note: browsers cannot set `Authorization` headers on `new WebSocket(url)`,
|
|
14
|
+
* so the bearer is carried as a query-string parameter.
|
|
15
|
+
*/
|
|
16
|
+
@injectable()
|
|
17
|
+
export class ManagedWebsocketAuthenticator implements WebsocketAuthenticator {
|
|
18
|
+
constructor(private readonly verifier: ManagedJwtVerifier) {}
|
|
19
|
+
|
|
20
|
+
async authenticate(requestUrl: string | undefined): Promise<VerifiedManagedPrincipal | null> {
|
|
21
|
+
if (!requestUrl) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const token = this.extractToken(requestUrl);
|
|
26
|
+
if (!token) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const result = await this.verifier.verify(token);
|
|
31
|
+
if ("failure" in result) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private extractToken(requestUrl: string): string | null {
|
|
39
|
+
// requestUrl is a relative path like "/__codemation/internal/ws?token=..."
|
|
40
|
+
// Use a dummy base so URL can parse relative URLs.
|
|
41
|
+
let url: URL;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(requestUrl, "http://placeholder");
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const token = url.searchParams.get("token");
|
|
48
|
+
return token && token.length > 0 ? token : null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VerifiedManagedPrincipal } from "@codemation/managed-auth";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Authenticates an incoming WebSocket upgrade request.
|
|
5
|
+
*
|
|
6
|
+
* Implementations parse the upgrade URL (e.g. `?token=<jwt>`) and verify the
|
|
7
|
+
* credential. Returns the verified principal on success, or `null` when the
|
|
8
|
+
* request must be rejected with close-code 4401.
|
|
9
|
+
*/
|
|
10
|
+
export interface WebsocketAuthenticator {
|
|
11
|
+
authenticate(requestUrl: string | undefined): Promise<VerifiedManagedPrincipal | null>;
|
|
12
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { WebSocket, WebSocketServer } from "ws";
|
|
2
|
+
import type { IncomingMessage } from "node:http";
|
|
2
3
|
import type { WorkflowWebsocketMessage } from "../../application/contracts/WorkflowWebsocketMessage";
|
|
3
4
|
import type { Logger } from "../../application/logging/Logger";
|
|
4
5
|
import type { WorkflowWebsocketPublisher } from "../../application/websocket/WorkflowWebsocketPublisher";
|
|
5
6
|
import { ApiPaths } from "../http/ApiPaths";
|
|
7
|
+
import type { WebsocketAuthenticator } from "./WebsocketAuthenticator.types";
|
|
6
8
|
|
|
7
9
|
type WorkflowWebsocketClientMessage =
|
|
8
10
|
| Readonly<{ kind: "subscribe"; roomId: string }>
|
|
@@ -26,8 +28,18 @@ export class WorkflowWebsocketServer implements WorkflowWebsocketPublisher {
|
|
|
26
28
|
private readonly port: number,
|
|
27
29
|
private readonly bindHost: string,
|
|
28
30
|
private readonly logger: Logger,
|
|
31
|
+
private readonly authenticator: WebsocketAuthenticator | null = null,
|
|
29
32
|
) {}
|
|
30
33
|
|
|
34
|
+
/** Returns the actual port the server is listening on (useful when constructed with port 0). */
|
|
35
|
+
get listeningPort(): number {
|
|
36
|
+
const addr = this.websocketServer?.address();
|
|
37
|
+
if (!addr || typeof addr === "string") {
|
|
38
|
+
return this.port;
|
|
39
|
+
}
|
|
40
|
+
return addr.port;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
async start(): Promise<void> {
|
|
32
44
|
if (this.started) {
|
|
33
45
|
return;
|
|
@@ -38,8 +50,8 @@ export class WorkflowWebsocketServer implements WorkflowWebsocketPublisher {
|
|
|
38
50
|
path: ApiPaths.workflowWebsocket(),
|
|
39
51
|
});
|
|
40
52
|
this.websocketServer = websocketServer;
|
|
41
|
-
websocketServer.on("connection", (socket) => {
|
|
42
|
-
void this.connect(socket);
|
|
53
|
+
websocketServer.on("connection", (socket, request) => {
|
|
54
|
+
void this.connect(socket, request);
|
|
43
55
|
});
|
|
44
56
|
try {
|
|
45
57
|
await this.awaitListening(websocketServer);
|
|
@@ -98,7 +110,16 @@ export class WorkflowWebsocketServer implements WorkflowWebsocketPublisher {
|
|
|
98
110
|
this.logger.debug(`published room=${roomId} sockets=${deliveredSocketCount} kind=${message.kind}`);
|
|
99
111
|
}
|
|
100
112
|
|
|
101
|
-
private async connect(socket: WebSocket): Promise<void> {
|
|
113
|
+
private async connect(socket: WebSocket, request: IncomingMessage): Promise<void> {
|
|
114
|
+
if (this.authenticator) {
|
|
115
|
+
const principal = await this.authenticator.authenticate(request.url);
|
|
116
|
+
if (!principal) {
|
|
117
|
+
this.logger.warn("websocket auth failed: closing with 4401");
|
|
118
|
+
socket.close(4401, "unauthorized");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
this.sockets.add(socket);
|
|
103
124
|
this.roomIdsBySocket.set(socket, new Set());
|
|
104
125
|
this.logger.debug(`client connected activeSockets=${this.sockets.size}`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AppConfig } from "../config/AppConfig";
|
|
2
|
+
import { WorkflowWebsocketServer } from "./WorkflowWebsocketServer";
|
|
3
|
+
import { logLevelPolicyFactory } from "../../infrastructure/logging/LogLevelPolicyFactory";
|
|
4
|
+
import { ServerLoggerFactory } from "../../infrastructure/logging/ServerLoggerFactory";
|
|
5
|
+
|
|
6
|
+
const loggerFactory = new ServerLoggerFactory(logLevelPolicyFactory);
|
|
7
|
+
|
|
8
|
+
export class WorkflowWebsocketServerFactory {
|
|
9
|
+
create(appConfig: AppConfig): WorkflowWebsocketServer {
|
|
10
|
+
return new WorkflowWebsocketServer(
|
|
11
|
+
appConfig.webSocketPort,
|
|
12
|
+
appConfig.webSocketBindHost,
|
|
13
|
+
loggerFactory.create("codemation-websocket.server"),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
import { execa, execaSync, type Options as ExecaOptions, type SyncOptions as ExecaSyncOptions } from "execa";
|
|
3
|
+
|
|
4
|
+
import type { ProcessRunner, ProcessRunOptions, ProcessRunResult } from "./ProcessRunner.types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Production {@link ProcessRunner}. Defers cross-platform executable resolution (`pnpm` ↔ `pnpm.cmd`,
|
|
8
|
+
* `.cmd` / `.bat` / `.ps1` shims on Windows) and argument quoting to execa so call sites stop having
|
|
9
|
+
* to hand-roll platform conditionals.
|
|
10
|
+
*/
|
|
11
|
+
export class ExecaProcessRunner implements ProcessRunner {
|
|
12
|
+
spawn(command: string, args: ReadonlyArray<string>, options?: ProcessRunOptions): ChildProcess {
|
|
13
|
+
return execa(command, [...args], this.toExecaOptions(options)) as unknown as ChildProcess;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
runSync(command: string, args: ReadonlyArray<string>, options?: ProcessRunOptions): ProcessRunResult {
|
|
17
|
+
const result = execaSync(command, [...args], this.toExecaSyncOptions(options));
|
|
18
|
+
return { exitCode: result.exitCode ?? null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private toExecaOptions(options?: ProcessRunOptions): ExecaOptions {
|
|
22
|
+
return {
|
|
23
|
+
reject: false,
|
|
24
|
+
cwd: options?.cwd,
|
|
25
|
+
env: options?.env,
|
|
26
|
+
stdio: options?.stdio as ExecaOptions["stdio"],
|
|
27
|
+
detached: options?.detached,
|
|
28
|
+
windowsHide: options?.windowsHide,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private toExecaSyncOptions(options?: ProcessRunOptions): ExecaSyncOptions {
|
|
33
|
+
return {
|
|
34
|
+
reject: false,
|
|
35
|
+
cwd: options?.cwd,
|
|
36
|
+
env: options?.env,
|
|
37
|
+
stdio: options?.stdio as ExecaSyncOptions["stdio"],
|
|
38
|
+
windowsHide: options?.windowsHide,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ChildProcess } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export type ProcessRunOptions = Readonly<{
|
|
4
|
+
cwd?: string;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
/**
|
|
7
|
+
* Mirrors `child_process.SpawnOptions["stdio"]`. The runner forwards the value verbatim to the
|
|
8
|
+
* underlying subprocess, so callers that need fine-grained per-fd control (e.g.
|
|
9
|
+
* `["ignore", "pipe", "pipe"]`) can pass a tuple.
|
|
10
|
+
*/
|
|
11
|
+
stdio?: "inherit" | "pipe" | "ignore" | ReadonlyArray<"inherit" | "pipe" | "ignore">;
|
|
12
|
+
/**
|
|
13
|
+
* On Unix this detaches the child from the parent's process group so it becomes the group
|
|
14
|
+
* leader (used by {@link DevTrackedProcessTreeKiller} to broadcast SIGTERM to descendants).
|
|
15
|
+
* On Windows it is ignored — `windowsHide` should be used to suppress console windows instead.
|
|
16
|
+
*/
|
|
17
|
+
detached?: boolean;
|
|
18
|
+
windowsHide?: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
export type ProcessRunResult = Readonly<{
|
|
22
|
+
exitCode: number | null;
|
|
23
|
+
}>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Cross-platform process spawning seam. Implementations resolve bare CLI names (`pnpm`, `prisma`,
|
|
27
|
+
* `next`, …) against the OS PATH using OS-appropriate executable lookup, so call sites stop having
|
|
28
|
+
* to remember `pnpm.cmd` or `shell: true` on Windows.
|
|
29
|
+
*
|
|
30
|
+
* `spawn` returns a Node `ChildProcess` so existing helpers like `DevNextChildProcessOutputFilter`
|
|
31
|
+
* and `DevTrackedProcessTreeKiller` keep working unchanged.
|
|
32
|
+
*/
|
|
33
|
+
export interface ProcessRunner {
|
|
34
|
+
/** Long-lived child (dev watcher, Next dev server). Returns a `ChildProcess`. */
|
|
35
|
+
spawn(command: string, args: ReadonlyArray<string>, options?: ProcessRunOptions): ChildProcess;
|
|
36
|
+
|
|
37
|
+
/** Synchronous one-shot (used by Prisma migrate deploy). */
|
|
38
|
+
runSync(command: string, args: ReadonlyArray<string>, options?: ProcessRunOptions): ProcessRunResult;
|
|
39
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { CodemationPostgresPrismaClientFactory } from "./persistenceServer";
|
|
2
2
|
export type { PrismaClient } from "./persistenceServer";
|
|
3
|
+
export { ExecaProcessRunner } from "./process/ExecaProcessRunner";
|
|
4
|
+
export type { ProcessRunner, ProcessRunOptions, ProcessRunResult } from "./process/ProcessRunner.types";
|
|
3
5
|
export { ApiPaths } from "./presentation/http/ApiPaths";
|
|
4
6
|
export { CodemationServerGateway } from "./presentation/http/CodemationServerGatewayFactory";
|
|
5
7
|
export { CodemationConsumerAppResolver } from "./presentation/server/CodemationConsumerAppResolver";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
4
|
+
import type { CommandBus } from "../application/bus/CommandBus";
|
|
5
|
+
import { SetWorkflowActivationCommand } from "../application/commands/SetWorkflowActivationCommand";
|
|
6
|
+
import { InternalHmacAuthMiddleware } from "../pairing/InternalHmacAuthMiddleware";
|
|
7
|
+
import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Registers POST /internal/workflows/:workflowId/activation — HMAC-verified endpoint
|
|
11
|
+
* that activates or deactivates a workflow. Used by the coding agent to toggle workflow
|
|
12
|
+
* triggers without requiring a user session.
|
|
13
|
+
*/
|
|
14
|
+
@injectable()
|
|
15
|
+
export class InternalWorkflowActivationRegistrar implements InternalHonoApiRouteRegistrar {
|
|
16
|
+
constructor(
|
|
17
|
+
@inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
|
|
18
|
+
@inject(ApplicationTokens.CommandBus) private readonly commandBus: CommandBus,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
register(app: Hono): void {
|
|
22
|
+
app.post("/internal/workflows/:workflowId/activation", this.hmacMiddleware.handle(), async (c) => {
|
|
23
|
+
const workflowId = c.req.param("workflowId");
|
|
24
|
+
let body: { active?: unknown };
|
|
25
|
+
try {
|
|
26
|
+
body = await c.req.json<{ active?: unknown }>();
|
|
27
|
+
} catch {
|
|
28
|
+
return c.json({ error: "Request body must be JSON with boolean active" }, 400);
|
|
29
|
+
}
|
|
30
|
+
if (typeof body.active !== "boolean") {
|
|
31
|
+
return c.json({ error: "Request body must include boolean active" }, 400);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const result = await this.commandBus.execute(new SetWorkflowActivationCommand(workflowId, body.active));
|
|
35
|
+
return c.json(result);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
return c.json({ error: message }, 500);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
4
|
+
import type { QueryBus } from "../application/bus/QueryBus";
|
|
5
|
+
import { GetWorkflowDetailQuery } from "../application/queries/GetWorkflowDetailQuery";
|
|
6
|
+
import { WorkflowDefinitionMapper } from "../application/mapping/WorkflowDefinitionMapper";
|
|
7
|
+
import { InternalHmacAuthMiddleware } from "../pairing/InternalHmacAuthMiddleware";
|
|
8
|
+
import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registers GET /internal/workflows/:workflowId — HMAC-verified endpoint that returns a
|
|
12
|
+
* single workflow's full DAG (nodes + edges). Used by the concierge agent to inspect a
|
|
13
|
+
* specific workflow. Returns 404 (empty body) when the workflow id doesn't exist.
|
|
14
|
+
*/
|
|
15
|
+
@injectable()
|
|
16
|
+
export class InternalWorkflowDetailRegistrar implements InternalHonoApiRouteRegistrar {
|
|
17
|
+
constructor(
|
|
18
|
+
@inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
|
|
19
|
+
@inject(ApplicationTokens.QueryBus) private readonly queryBus: QueryBus,
|
|
20
|
+
@inject(WorkflowDefinitionMapper) private readonly mapper: WorkflowDefinitionMapper,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
register(app: Hono): void {
|
|
24
|
+
app.get("/internal/workflows/:workflowId", this.hmacMiddleware.handle(), async (c) => {
|
|
25
|
+
const workflowId = c.req.param("workflowId");
|
|
26
|
+
const workflow = await this.queryBus.execute(new GetWorkflowDetailQuery(workflowId));
|
|
27
|
+
if (!workflow) {
|
|
28
|
+
return c.body(null, 404);
|
|
29
|
+
}
|
|
30
|
+
return c.json(await this.mapper.map(workflow));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import { Engine } from "@codemation/core/bootstrap";
|
|
3
|
+
import { RunIntentService } from "@codemation/core/bootstrap";
|
|
4
|
+
import type { Hono } from "hono";
|
|
5
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
6
|
+
import type { WorkflowDefinitionRepository } from "../domain/workflows/WorkflowDefinitionRepository";
|
|
7
|
+
import { InternalHmacAuthMiddleware } from "../pairing/InternalHmacAuthMiddleware";
|
|
8
|
+
import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
|
|
9
|
+
|
|
10
|
+
const TEST_RUN_TIMEOUT_MS = 30_000;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Registers POST /internal/workflows/:workflowId/test-run — HMAC-verified endpoint
|
|
14
|
+
* that runs a workflow once synchronously without requiring it to be active.
|
|
15
|
+
* Used by the coding agent to verify a workflow before activating it.
|
|
16
|
+
*/
|
|
17
|
+
@injectable()
|
|
18
|
+
export class InternalWorkflowTestRunRegistrar implements InternalHonoApiRouteRegistrar {
|
|
19
|
+
constructor(
|
|
20
|
+
@inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
|
|
21
|
+
@inject(RunIntentService) private readonly runIntentService: RunIntentService,
|
|
22
|
+
@inject(Engine) private readonly engine: Engine,
|
|
23
|
+
@inject(ApplicationTokens.WorkflowDefinitionRepository)
|
|
24
|
+
private readonly workflowDefinitionRepository: WorkflowDefinitionRepository,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
register(app: Hono): void {
|
|
28
|
+
app.post("/internal/workflows/:workflowId/test-run", this.hmacMiddleware.handle(), async (c) => {
|
|
29
|
+
const workflowId = c.req.param("workflowId");
|
|
30
|
+
const startMs = Date.now();
|
|
31
|
+
|
|
32
|
+
let input: unknown;
|
|
33
|
+
try {
|
|
34
|
+
const body = await c.req.json<{ input?: unknown }>();
|
|
35
|
+
input = body.input;
|
|
36
|
+
} catch {
|
|
37
|
+
input = undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const workflow = await this.workflowDefinitionRepository.getDefinition(workflowId);
|
|
41
|
+
if (!workflow) {
|
|
42
|
+
return c.json({ ok: false, error: `Unknown workflowId: ${workflowId}`, durationMs: Date.now() - startMs }, 404);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const items = input !== undefined ? [{ json: input as Record<string, unknown> }] : [{ json: {} }];
|
|
46
|
+
|
|
47
|
+
let runResult;
|
|
48
|
+
try {
|
|
49
|
+
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
50
|
+
setTimeout(() => reject(new Error("test run timed out after 30s")), TEST_RUN_TIMEOUT_MS),
|
|
51
|
+
);
|
|
52
|
+
const runPromise = (async () => {
|
|
53
|
+
const result = await this.runIntentService.startWorkflow({
|
|
54
|
+
workflow,
|
|
55
|
+
items,
|
|
56
|
+
executionOptions: { localOnly: true },
|
|
57
|
+
});
|
|
58
|
+
if (result.status === "completed") {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
if (result.status === "failed") {
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
// pending — wait for completion
|
|
65
|
+
return await this.engine.waitForCompletion(result.runId);
|
|
66
|
+
})();
|
|
67
|
+
runResult = await Promise.race([runPromise, timeoutPromise]);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
return c.json({ ok: false, error: message, durationMs: Date.now() - startMs });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (runResult.status === "failed") {
|
|
74
|
+
return c.json({
|
|
75
|
+
ok: false,
|
|
76
|
+
runId: runResult.runId,
|
|
77
|
+
error: runResult.error.message,
|
|
78
|
+
durationMs: Date.now() - startMs,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// completed
|
|
83
|
+
return c.json({
|
|
84
|
+
ok: true,
|
|
85
|
+
runId: runResult.runId,
|
|
86
|
+
output: runResult.outputs,
|
|
87
|
+
durationMs: Date.now() - startMs,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import { ApplicationTokens } from "../applicationTokens";
|
|
4
|
+
import type { QueryBus } from "../application/bus/QueryBus";
|
|
5
|
+
import { GetWorkflowSummariesQuery } from "../application/queries/GetWorkflowSummariesQuery";
|
|
6
|
+
import { WorkflowDefinitionMapper } from "../application/mapping/WorkflowDefinitionMapper";
|
|
7
|
+
import { InternalHmacAuthMiddleware } from "../pairing/InternalHmacAuthMiddleware";
|
|
8
|
+
import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registers GET /internal/workflows — HMAC-verified endpoint that returns the list of
|
|
12
|
+
* workflow summaries. Used by the concierge agent to enumerate available workflows.
|
|
13
|
+
*/
|
|
14
|
+
@injectable()
|
|
15
|
+
export class InternalWorkflowsListRegistrar implements InternalHonoApiRouteRegistrar {
|
|
16
|
+
constructor(
|
|
17
|
+
@inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
|
|
18
|
+
@inject(ApplicationTokens.QueryBus) private readonly queryBus: QueryBus,
|
|
19
|
+
@inject(WorkflowDefinitionMapper) private readonly mapper: WorkflowDefinitionMapper,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
register(app: Hono): void {
|
|
23
|
+
app.get("/internal/workflows", this.hmacMiddleware.handle(), async (c) => {
|
|
24
|
+
const workflows = await this.queryBus.execute(new GetWorkflowSummariesQuery());
|
|
25
|
+
return c.json(workflows.map((w) => this.mapper.toSummary(w)));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from "@codemation/core";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Callable that imports a module from an absolute path and returns its exports.
|
|
7
|
+
* Injectable for testability — production callers use a dynamic `import()`.
|
|
8
|
+
*/
|
|
9
|
+
export type WorkflowModuleImporter = (absolutePath: string) => Promise<Readonly<Record<string, unknown>>>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Walks a directory recursively, imports every `*.ts` / `*.tsx` file
|
|
13
|
+
* (excluding `*.test.*` and `*.d.ts`), and returns all exported {@link WorkflowDefinition}
|
|
14
|
+
* values, deduplicated by id.
|
|
15
|
+
*/
|
|
16
|
+
export class WorkflowDirectoryDiscoverer {
|
|
17
|
+
constructor(private readonly importer: WorkflowModuleImporter) {}
|
|
18
|
+
|
|
19
|
+
async discover(
|
|
20
|
+
args: Readonly<{ consumerRoot: string; workflowsDir: string }>,
|
|
21
|
+
): Promise<ReadonlyArray<WorkflowDefinition>> {
|
|
22
|
+
const absoluteDir = path.resolve(args.consumerRoot, args.workflowsDir);
|
|
23
|
+
const filePaths = await this.collectFilePaths(absoluteDir);
|
|
24
|
+
const workflowsById = new Map<string, WorkflowDefinition>();
|
|
25
|
+
for (const filePath of filePaths) {
|
|
26
|
+
const moduleExports = await this.importer(filePath);
|
|
27
|
+
for (const exported of Object.values(moduleExports)) {
|
|
28
|
+
if (this.isWorkflowDefinition(exported)) {
|
|
29
|
+
workflowsById.set(exported.id, exported);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [...workflowsById.values()];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async collectFilePaths(absoluteDir: string): Promise<ReadonlyArray<string>> {
|
|
37
|
+
let entries;
|
|
38
|
+
try {
|
|
39
|
+
entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const filePaths: string[] = [];
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const entryPath = path.join(absoluteDir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
filePaths.push(...(await this.collectFilePaths(entryPath)));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (this.isDiscoverableFile(entry.name)) {
|
|
51
|
+
filePaths.push(entryPath);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return filePaths;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private isDiscoverableFile(filename: string): boolean {
|
|
58
|
+
const ext = path.extname(filename);
|
|
59
|
+
if (ext !== ".ts" && ext !== ".tsx") {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (filename.endsWith(".d.ts")) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// Exclude test files: *.test.ts, *.test.tsx, *.spec.ts, *.spec.tsx, etc.
|
|
66
|
+
const withoutExt = filename.slice(0, -ext.length);
|
|
67
|
+
if (withoutExt.endsWith(".test") || withoutExt.endsWith(".spec")) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private isWorkflowDefinition(value: unknown): value is WorkflowDefinition {
|
|
74
|
+
if (!value || typeof value !== "object") {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return "id" in value && "name" in value && "nodes" in value && "edges" in value;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"@codemation/core-nodes": ["../core-nodes/src/index.ts"],
|
|
16
16
|
"@codemation/core-nodes-gmail": ["../core-nodes-gmail/src/index.ts"],
|
|
17
17
|
"@codemation/eventbus-redis": ["../eventbus-redis/src/index.ts"],
|
|
18
|
+
"@codemation/managed-auth": ["../managed-auth/src/index.ts"],
|
|
18
19
|
"@codemation/host": ["./src/index.ts"],
|
|
19
20
|
"@codemation/host/authoring": ["./src/authoring.ts"],
|
|
20
21
|
"@codemation/host/client": ["./src/client.ts"],
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"@codemation/host/server": ["./src/server.ts"],
|
|
27
28
|
"@codemation/host/dev-server-sidecar": ["./src/devServerSidecar.ts"],
|
|
28
29
|
"@codemation/host/persistence": ["./src/persistenceServer.ts"],
|
|
30
|
+
"@codemation/canvas": ["../canvas/src/index.ts"],
|
|
29
31
|
"@codemation/next-host/src/*": ["../next-host/src/*"],
|
|
30
32
|
"@/*": ["../next-host/src/*"]
|
|
31
33
|
}
|
package/vitest.shared.ts
CHANGED
|
@@ -9,6 +9,11 @@ export const hostVitestSharedConfig = {
|
|
|
9
9
|
},
|
|
10
10
|
resolve: {
|
|
11
11
|
alias: [
|
|
12
|
+
{ find: "@codemation/canvas", replacement: path.resolve(import.meta.dirname, "../canvas/src/index.ts") },
|
|
13
|
+
{
|
|
14
|
+
find: "@codemation/core/contracts",
|
|
15
|
+
replacement: path.resolve(import.meta.dirname, "../core/src/contracts.ts"),
|
|
16
|
+
},
|
|
12
17
|
{ find: "@codemation/core/browser", replacement: path.resolve(import.meta.dirname, "../core/src/browser.ts") },
|
|
13
18
|
{ find: /^@codemation\/next-host\/src\/(.+)$/, replacement: `${nextHostSrc}/$1` },
|
|
14
19
|
{ find: "@codemation/host/client", replacement: path.resolve(import.meta.dirname, "./src/client.ts") },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ApiPaths-CLTHphYZ.js","names":[],"sources":["../src/infrastructure/auth/InAppCallbackUrlPolicy.ts","../src/presentation/http/ApiPaths.ts"],"sourcesContent":["/**\n * Restricts post-login navigation targets to same-origin relative paths (no open redirects).\n */\nexport class InAppCallbackUrlPolicy {\n private static readonly fallbackPath = \"/\";\n\n resolveSafeRelativeCallbackUrl(raw: string | undefined | null): string {\n if (raw === undefined || raw === null) {\n return InAppCallbackUrlPolicy.fallbackPath;\n }\n if (this.containsAsciiControl(raw)) {\n return InAppCallbackUrlPolicy.fallbackPath;\n }\n const trimmed = raw.trim();\n if (trimmed.length === 0) {\n return InAppCallbackUrlPolicy.fallbackPath;\n }\n if (!trimmed.startsWith(\"/\")) {\n return InAppCallbackUrlPolicy.fallbackPath;\n }\n if (trimmed.startsWith(\"//\")) {\n return InAppCallbackUrlPolicy.fallbackPath;\n }\n if (trimmed.includes(\"\\\\\")) {\n return InAppCallbackUrlPolicy.fallbackPath;\n }\n return trimmed;\n }\n\n private containsAsciiControl(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code <= 0x1f || code === 0x7f) {\n return true;\n }\n }\n return false;\n }\n}\n","export class ApiPaths {\n private static readonly apiBasePath = \"/api\";\n\n private static readonly workflowsBasePath = `${this.apiBasePath}/workflows`;\n\n private static readonly runsBasePath = `${this.apiBasePath}/runs`;\n\n private static readonly credentialsBasePath = `${this.apiBasePath}/credentials`;\n\n private static readonly oauth2BasePath = `${this.apiBasePath}/oauth2`;\n\n private static readonly webhooksBasePath = `${this.apiBasePath}/webhooks`;\n\n private static readonly usersBasePath = `${this.apiBasePath}/users`;\n\n private static readonly telemetryBasePath = `${this.apiBasePath}/telemetry`;\n\n private static readonly whitelabelBasePath = `${this.apiBasePath}/whitelabel`;\n\n private static readonly bootstrapBasePath = `${this.apiBasePath}/bootstrap`;\n\n private static readonly authBasePath = `${this.apiBasePath}/auth`;\n\n private static readonly collectionsBasePath = `${this.apiBasePath}/collections`;\n\n static collections(): string {\n return this.collectionsBasePath;\n }\n\n static collection(name: string): string {\n return `${this.collectionsBasePath}/${encodeURIComponent(name)}`;\n }\n\n static collectionRows(name: string): string {\n return `${this.collection(name)}/rows`;\n }\n\n static collectionRow(name: string, id: string): string {\n return `${this.collectionRows(name)}/${encodeURIComponent(id)}`;\n }\n\n static syncCollections(): string {\n return `${this.collectionsBasePath}/sync`;\n }\n\n static workflows(): string {\n return this.workflowsBasePath;\n }\n\n static workflow(workflowId: string): string {\n return `${this.workflowsBasePath}/${encodeURIComponent(workflowId)}`;\n }\n\n static workflowActivation(workflowId: string): string {\n return `${this.workflow(workflowId)}/activation`;\n }\n\n static workflowRuns(workflowId: string): string {\n return `${this.workflow(workflowId)}/runs`;\n }\n\n static workflowTestSuiteRuns(workflowId: string): string {\n return `${this.workflow(workflowId)}/test-suite-runs`;\n }\n\n /**\n * `GET` returns per-assertion-metric trends across the workflow's recent suite runs. With\n * no `names` arg, every distinct assertion name is returned (so the multi-select can populate);\n * with `names`, only the requested subset is returned (order preserved).\n */\n static workflowAssertionMetricTrends(workflowId: string, names?: ReadonlyArray<string>): string {\n const base = `${this.workflow(workflowId)}/assertion-metric-trends`;\n if (!names || names.length === 0) {\n return base;\n }\n return `${base}?names=${names.map((n) => encodeURIComponent(n)).join(\",\")}`;\n }\n\n static testSuiteRun(testSuiteRunId: string): string {\n return `${this.apiBasePath}/test-suite-runs/${encodeURIComponent(testSuiteRunId)}`;\n }\n\n static testSuiteRunAssertions(testSuiteRunId: string): string {\n return `${this.testSuiteRun(testSuiteRunId)}/assertions`;\n }\n\n static testSuiteRunChildRuns(testSuiteRunId: string): string {\n return `${this.testSuiteRun(testSuiteRunId)}/runs`;\n }\n\n static runAssertions(runId: string): string {\n return `${this.runState(runId)}/assertions`;\n }\n\n static workflowCredentialHealth(workflowId: string): string {\n return `${this.workflow(workflowId)}/credential-health`;\n }\n\n static workflowDebuggerOverlay(workflowId: string): string {\n return `${this.workflow(workflowId)}/debugger-overlay`;\n }\n\n static workflowDebuggerOverlayCopyRun(workflowId: string): string {\n return `${this.workflowDebuggerOverlay(workflowId)}/copy-run`;\n }\n\n static workflowDebuggerOverlayBinaryUpload(workflowId: string): string {\n return `${this.workflowDebuggerOverlay(workflowId)}/binary/upload`;\n }\n\n static workflowOverlayBinaryContent(workflowId: string, binaryId: string): string {\n return `${this.workflow(workflowId)}/debugger-overlay/binary/${encodeURIComponent(binaryId)}/content`;\n }\n\n static runs(): string {\n return this.runsBasePath;\n }\n\n static run(): string {\n return this.runs();\n }\n\n static credentialTypes(): string {\n return `${this.credentialsBasePath}/types`;\n }\n\n static credentialsEnvStatus(): string {\n return `${this.credentialsBasePath}/env-status`;\n }\n\n static credentialInstances(): string {\n return `${this.credentialsBasePath}/instances`;\n }\n\n static credentialInstance(instanceId: string, withSecrets?: boolean): string {\n const base = `${this.credentialInstances()}/${encodeURIComponent(instanceId)}`;\n return withSecrets ? `${base}?withSecrets=1` : base;\n }\n\n static credentialInstanceTest(instanceId: string): string {\n return `${this.credentialInstance(instanceId)}/test`;\n }\n\n static credentialBindings(): string {\n return `${this.apiBasePath}/credential-bindings`;\n }\n\n static oauth2Auth(instanceId: string): string {\n return `${this.oauth2BasePath}/auth?instanceId=${encodeURIComponent(instanceId)}`;\n }\n\n static oauth2RedirectUri(): string {\n return `${this.oauth2BasePath}/redirect-uri`;\n }\n\n static oauth2Disconnect(instanceId: string): string {\n return `${this.oauth2BasePath}/disconnect?instanceId=${encodeURIComponent(instanceId)}`;\n }\n\n static workflowWebsocket(): string {\n return `${this.workflowsBasePath}/ws`;\n }\n\n /** Dev gateway: stable browser WebSocket for build lifecycle (CLI → gateway → browser). */\n static devGatewaySocket(): string {\n return `${this.apiBasePath}/dev/socket`;\n }\n\n /** Dev gateway: HTTP notify endpoint used by the Codemation CLI during consumer rebuilds. */\n static devGatewayNotify(): string {\n return `${this.apiBasePath}/dev/notify`;\n }\n\n static webhooks(): string {\n return this.webhooksBasePath;\n }\n\n static users(): string {\n return this.usersBasePath;\n }\n\n static telemetryDashboardSummary(): string {\n return `${this.telemetryBasePath}/dashboard/summary`;\n }\n\n static telemetryDashboardTimeseries(): string {\n return `${this.telemetryBasePath}/dashboard/timeseries`;\n }\n\n static telemetryDashboardDimensions(): string {\n return `${this.telemetryBasePath}/dashboard/dimensions`;\n }\n\n static telemetryDashboardRuns(): string {\n return `${this.telemetryBasePath}/dashboard/runs`;\n }\n\n static telemetryRunTrace(runId: string): string {\n return `${this.telemetryBasePath}/runs/${runId}/trace`;\n }\n\n static authSession(): string {\n return `${this.authBasePath}/session`;\n }\n\n static authLogin(): string {\n return `${this.authBasePath}/login`;\n }\n\n static authLogout(): string {\n return `${this.authBasePath}/logout`;\n }\n\n static authOAuthStart(providerId: string, callbackUrl?: string): string {\n const base = `${this.authBasePath}/oauth/${encodeURIComponent(providerId)}/start`;\n if (!callbackUrl) {\n return base;\n }\n return `${base}?callbackUrl=${encodeURIComponent(callbackUrl)}`;\n }\n\n static authOAuthCallback(providerId: string): string {\n return `${this.authBasePath}/oauth/${encodeURIComponent(providerId)}/callback`;\n }\n\n static userInviteVerify(): string {\n return `${this.usersBasePath}/invites/verify`;\n }\n\n static userInviteAccept(): string {\n return `${this.usersBasePath}/invites/accept`;\n }\n\n static userInvites(): string {\n return `${this.usersBasePath}/invites`;\n }\n\n static userInviteRegenerate(userId: string): string {\n return `${this.usersBasePath}/${encodeURIComponent(userId)}/invites/regenerate`;\n }\n\n static userStatus(userId: string): string {\n return `${this.usersBasePath}/${encodeURIComponent(userId)}/status`;\n }\n\n static runState(runId: string): string {\n return `${this.runsBasePath}/${encodeURIComponent(runId)}`;\n }\n\n static runDetail(runId: string): string {\n return `${this.runState(runId)}/detail`;\n }\n\n static runWorkflowSnapshot(runId: string): string {\n return `${this.runState(runId)}/workflow-snapshot`;\n }\n\n static runNodePin(runId: string, nodeId: string): string {\n return `${this.runState(runId)}/nodes/${encodeURIComponent(nodeId)}/pin`;\n }\n\n static runNode(runId: string, nodeId: string): string {\n return `${this.runState(runId)}/nodes/${encodeURIComponent(nodeId)}/run`;\n }\n\n static runBinaryContent(runId: string, binaryId: string): string {\n return `${this.runState(runId)}/binary/${encodeURIComponent(binaryId)}/content`;\n }\n\n /** Anonymous: consumer logo from `codemation.config.ts` whitelabel.logoPath. */\n static whitelabelLogo(): string {\n return `${this.whitelabelBasePath}/logo`;\n }\n\n static frontendBootstrap(): string {\n return `${this.bootstrapBasePath}/frontend`;\n }\n\n static internalAuthBootstrap(): string {\n return `${this.bootstrapBasePath}/auth/internal`;\n }\n}\n"],"mappings":"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"}
|