harper 5.1.20 → 5.2.0-alpha.5
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/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +13 -8
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
package/agent/operations.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Operations API surface for the built-in agent (#626).
|
|
3
3
|
*
|
|
4
|
-
* Six handlers,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Six handlers, super_user by default. Authorization is declared via
|
|
5
|
+
* `requiresSuperUser: true` on each OperationDefinition, so `registerOperation`
|
|
6
|
+
* registers them with the central `verifyPerms` system and they participate in
|
|
7
|
+
* the role `operations` allowlist — an operator can grant a scoped, non-super_user
|
|
8
|
+
* "delegation" role access to these ops (e.g. `operations: ['agent']`) without
|
|
9
|
+
* granting full super_user. verifyPerms runs before the handler for every
|
|
10
|
+
* ops-API request, so no inline gate is needed here.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import type { OperationDefinition } from '../server/serverHelpers/serverUtilities.ts';
|
|
@@ -25,38 +28,37 @@ export function buildOperations(deps: OperationDeps): OperationDefinition[] {
|
|
|
25
28
|
{
|
|
26
29
|
name: OPERATIONS_ENUM.AGENT_PROMPT,
|
|
27
30
|
execute: async (op) => agentPrompt(op, deps),
|
|
31
|
+
requiresSuperUser: true,
|
|
28
32
|
},
|
|
29
33
|
{
|
|
30
34
|
name: OPERATIONS_ENUM.GET_AGENT_SESSION,
|
|
31
35
|
execute: async (op) => getAgentSession(op),
|
|
36
|
+
requiresSuperUser: true,
|
|
32
37
|
},
|
|
33
38
|
{
|
|
34
39
|
name: OPERATIONS_ENUM.LIST_AGENT_SESSIONS,
|
|
35
40
|
execute: async (op) => listAgentSessions(op),
|
|
41
|
+
requiresSuperUser: true,
|
|
36
42
|
},
|
|
37
43
|
{
|
|
38
44
|
name: OPERATIONS_ENUM.CANCEL_AGENT_RUN,
|
|
39
45
|
execute: async (op) => cancelAgentRun(op, deps),
|
|
46
|
+
requiresSuperUser: true,
|
|
40
47
|
},
|
|
41
48
|
{
|
|
42
49
|
name: OPERATIONS_ENUM.APPROVE_AGENT_ACTION,
|
|
43
50
|
execute: async (op) => approveAgentAction(op, deps),
|
|
51
|
+
requiresSuperUser: true,
|
|
44
52
|
},
|
|
45
53
|
{
|
|
46
54
|
name: OPERATIONS_ENUM.SET_AGENT_CONFIG,
|
|
47
55
|
execute: async (op) => setAgentConfig(op, deps),
|
|
56
|
+
requiresSuperUser: true,
|
|
48
57
|
},
|
|
49
58
|
];
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
function requireSuperUser(op: any): void {
|
|
53
|
-
if (!op?.hdb_user?.role?.permission?.super_user) {
|
|
54
|
-
throw new ClientError('Agent operations require super_user', 403);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
61
|
async function agentPrompt(op: any, deps: OperationDeps) {
|
|
59
|
-
requireSuperUser(op);
|
|
60
62
|
const config = deps.getConfig();
|
|
61
63
|
if (!config.enabled) {
|
|
62
64
|
throw new ClientError('Agent component is disabled (agent.enabled=false)', 409);
|
|
@@ -90,7 +92,6 @@ async function agentPrompt(op: any, deps: OperationDeps) {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
async function getAgentSession(op: any) {
|
|
93
|
-
requireSuperUser(op);
|
|
94
95
|
const sessionId = String(op?.session_id ?? '');
|
|
95
96
|
if (!sessionId) throw new ClientError('session_id is required', 400);
|
|
96
97
|
const session = await getSession(sessionId);
|
|
@@ -99,13 +100,11 @@ async function getAgentSession(op: any) {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
async function listAgentSessions(op: any) {
|
|
102
|
-
requireSuperUser(op);
|
|
103
103
|
const limit = Number.isFinite(op?.limit) ? Number(op.limit) : undefined;
|
|
104
104
|
return { sessions: await listSessions({ limit }) };
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
async function cancelAgentRun(op: any, deps: OperationDeps) {
|
|
108
|
-
requireSuperUser(op);
|
|
109
108
|
const sessionId = String(op?.session_id ?? '');
|
|
110
109
|
if (!sessionId) throw new ClientError('session_id is required', 400);
|
|
111
110
|
const session = await getSession(sessionId);
|
|
@@ -120,7 +119,6 @@ async function cancelAgentRun(op: any, deps: OperationDeps) {
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
async function approveAgentAction(op: any, deps: OperationDeps) {
|
|
123
|
-
requireSuperUser(op);
|
|
124
122
|
const sessionId = String(op?.session_id ?? '');
|
|
125
123
|
const approvalId = String(op?.approval_id ?? '');
|
|
126
124
|
if (!sessionId || !approvalId) {
|
|
@@ -135,7 +133,6 @@ async function approveAgentAction(op: any, deps: OperationDeps) {
|
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
async function setAgentConfig(op: any, deps: OperationDeps) {
|
|
138
|
-
requireSuperUser(op);
|
|
139
136
|
const patch: Partial<AgentConfig> = {};
|
|
140
137
|
for (const key of ['enabled', 'provider', 'model', 'maxTurns', 'maxCostUsd', 'autoApprove', 'allowDestructive']) {
|
|
141
138
|
if (op?.[key] !== undefined) (patch as any)[key] = op[key];
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
writeFile,
|
|
21
21
|
} from 'node:fs/promises';
|
|
22
22
|
import { spawn } from 'node:child_process';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
23
24
|
import { randomUUID } from 'node:crypto';
|
|
24
25
|
import { createReadStream, existsSync, readdirSync } from 'node:fs';
|
|
25
26
|
import { Readable } from 'node:stream';
|
|
@@ -37,6 +38,10 @@ interface ApplicationConfig {
|
|
|
37
38
|
timeout?: number;
|
|
38
39
|
allowInstallScripts?: boolean;
|
|
39
40
|
};
|
|
41
|
+
// Private-registry auth in reference form only — each entry names an hdb_secret row, never a
|
|
42
|
+
// token. Recorded by deploy_component so every (cold) install — reboot, new peer, rollback —
|
|
43
|
+
// re-resolves the credential from the store rather than needing it re-supplied.
|
|
44
|
+
registryAuth?: { registry: string; secret: string; scope?: string }[];
|
|
40
45
|
// an application config can have other arbitrary properties
|
|
41
46
|
[key: string]: unknown;
|
|
42
47
|
}
|
|
@@ -73,6 +78,22 @@ export class InvalidInstallTimeoutError extends TypeError {
|
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
export class InvalidRegistryAuthPropertyError extends TypeError {
|
|
82
|
+
constructor(applicationName: string, registryAuth: unknown) {
|
|
83
|
+
super(
|
|
84
|
+
`Invalid 'registryAuth' property for application ${applicationName}: expected array, got ${typeof registryAuth}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class InvalidRegistryAuthEntryError extends TypeError {
|
|
90
|
+
constructor(applicationName: string) {
|
|
91
|
+
super(
|
|
92
|
+
`Invalid 'registryAuth' entry for application ${applicationName}: expected { registry, secret, scope? } reference`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
export function assertApplicationConfig(
|
|
77
98
|
applicationName: string,
|
|
78
99
|
applicationConfig: Record<'package', unknown> & Record<string, unknown>
|
|
@@ -110,6 +131,24 @@ export function assertApplicationConfig(
|
|
|
110
131
|
);
|
|
111
132
|
}
|
|
112
133
|
}
|
|
134
|
+
if ('registryAuth' in applicationConfig && applicationConfig.registryAuth !== undefined) {
|
|
135
|
+
const entries = applicationConfig.registryAuth;
|
|
136
|
+
if (!Array.isArray(entries)) {
|
|
137
|
+
throw new InvalidRegistryAuthPropertyError(applicationName, entries);
|
|
138
|
+
}
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
// Config carries references only — a literal `token` here would mean a plaintext credential
|
|
141
|
+
// was persisted to disk, which the deploy path is designed to prevent.
|
|
142
|
+
if (
|
|
143
|
+
typeof entry !== 'object' ||
|
|
144
|
+
entry === null ||
|
|
145
|
+
typeof (entry as any).registry !== 'string' ||
|
|
146
|
+
typeof (entry as any).secret !== 'string'
|
|
147
|
+
) {
|
|
148
|
+
throw new InvalidRegistryAuthEntryError(applicationName);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
113
152
|
}
|
|
114
153
|
|
|
115
154
|
/**
|
|
@@ -210,7 +249,10 @@ export async function extractApplication(application: Application) {
|
|
|
210
249
|
application.name,
|
|
211
250
|
'npm',
|
|
212
251
|
['pack', '--json', application.packageIdentifier],
|
|
213
|
-
parentDirPath
|
|
252
|
+
parentDirPath,
|
|
253
|
+
undefined,
|
|
254
|
+
undefined,
|
|
255
|
+
application.npmUserconfigPath
|
|
214
256
|
);
|
|
215
257
|
if (code !== 0) {
|
|
216
258
|
if (isSSHAuthFailure(stderr)) {
|
|
@@ -354,7 +396,8 @@ export async function installApplication(application: Application) {
|
|
|
354
396
|
args,
|
|
355
397
|
application.dirPath,
|
|
356
398
|
application.install?.timeout,
|
|
357
|
-
customOnLine
|
|
399
|
+
customOnLine,
|
|
400
|
+
application.npmUserconfigPath
|
|
358
401
|
);
|
|
359
402
|
// if it succeeds, return
|
|
360
403
|
if (code === 0) {
|
|
@@ -414,7 +457,8 @@ export async function installApplication(application: Application) {
|
|
|
414
457
|
application.install?.allowInstallScripts ? ['install'] : ['install', '--ignore-scripts'], // All of `npm`, `yarn`, and `pnpm` support the `install` command. If we need to configure options here we may have to use some other defaults though
|
|
415
458
|
application.dirPath,
|
|
416
459
|
application.install?.timeout,
|
|
417
|
-
pmOnLine
|
|
460
|
+
pmOnLine,
|
|
461
|
+
application.npmUserconfigPath
|
|
418
462
|
);
|
|
419
463
|
|
|
420
464
|
// if it succeeds, return
|
|
@@ -470,7 +514,8 @@ export async function installApplication(application: Application) {
|
|
|
470
514
|
npmInstallArgs,
|
|
471
515
|
application.dirPath,
|
|
472
516
|
application.install?.timeout,
|
|
473
|
-
npmOnLine
|
|
517
|
+
npmOnLine,
|
|
518
|
+
application.npmUserconfigPath
|
|
474
519
|
);
|
|
475
520
|
|
|
476
521
|
// if it succeeds, return
|
|
@@ -506,6 +551,7 @@ interface ApplicationOptions {
|
|
|
506
551
|
packageIdentifier?: string;
|
|
507
552
|
install?: { command?: string; timeout?: number; allowInstallScripts?: boolean };
|
|
508
553
|
onInstallLine?: OnInstallLine;
|
|
554
|
+
registryAuth?: RegistryAuthEntry[];
|
|
509
555
|
}
|
|
510
556
|
|
|
511
557
|
export class Application {
|
|
@@ -517,19 +563,75 @@ export class Application {
|
|
|
517
563
|
dirPath: string;
|
|
518
564
|
logger: Logger;
|
|
519
565
|
packageManagerPrefix: string; // can be used to configure a package manager prefix, specifically "sfw".
|
|
520
|
-
|
|
521
|
-
|
|
566
|
+
// Transient registry auth provided by a deploy. The token is held only in memory and a
|
|
567
|
+
// per-deploy `.npmrc`; it is never persisted to config, hdb_deployment, or replicated.
|
|
568
|
+
registryAuth?: RegistryAuthEntry[];
|
|
569
|
+
// Path to the per-deploy `.npmrc`, set by writeTransientNpmrc() during prepareApplication and
|
|
570
|
+
// passed to the spawn calls; undefined when no registry auth was provided.
|
|
571
|
+
npmUserconfigPath?: string;
|
|
572
|
+
#npmrcTempDir?: string;
|
|
573
|
+
|
|
574
|
+
constructor({ name, payload, packageIdentifier, install, onInstallLine, registryAuth }: ApplicationOptions) {
|
|
522
575
|
this.name = name;
|
|
523
576
|
this.payload = payload;
|
|
524
577
|
this.packageIdentifier = packageIdentifier && derivePackageIdentifier(packageIdentifier);
|
|
525
578
|
this.install = install;
|
|
526
579
|
this.onInstallLine = onInstallLine;
|
|
580
|
+
this.registryAuth = registryAuth;
|
|
527
581
|
const componentsRoot = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT);
|
|
528
582
|
if (!componentsRoot) throw new Error('componentsRoot is not configured');
|
|
529
583
|
this.dirPath = join(componentsRoot, name);
|
|
530
584
|
this.logger = logger.loggerWithTag(name);
|
|
531
585
|
this.packageManagerPrefix = getConfigValue(CONFIG_PARAMS.APPLICATIONS_PACKAGEMANAGERPREFIX);
|
|
532
586
|
}
|
|
587
|
+
|
|
588
|
+
// Write the transient `.npmrc` into a fresh 0700 temp dir (file mode 0600) and record its path
|
|
589
|
+
// so the deploy's npm spawns authenticate against the private registry. No-op without registry auth.
|
|
590
|
+
//
|
|
591
|
+
// Because `nonInteractiveSpawn` points npm at this single file (replacing any inherited
|
|
592
|
+
// npm_config_userconfig), prepend the contents of an already-configured userconfig — e.g. a
|
|
593
|
+
// fabric-injected file carrying cluster registries, a proxy, or a cafile — so those settings
|
|
594
|
+
// survive. The transient auth is appended last so it wins on conflict (npm honors the last
|
|
595
|
+
// value for a given key).
|
|
596
|
+
async writeTransientNpmrc(): Promise<void> {
|
|
597
|
+
if (!this.registryAuth?.length) return;
|
|
598
|
+
// Defensive: if called more than once, remove the prior temp dir first so it isn't leaked.
|
|
599
|
+
if (this.#npmrcTempDir) await this.cleanupTransientNpmrc();
|
|
600
|
+
this.#npmrcTempDir = await mkdtemp(join(tmpdir(), 'harper-npmrc-'));
|
|
601
|
+
const npmrcPath = join(this.#npmrcTempDir, '.npmrc');
|
|
602
|
+
let content = '';
|
|
603
|
+
const inheritedUserconfig = process.env.npm_config_userconfig ?? process.env.NPM_CONFIG_USERCONFIG;
|
|
604
|
+
if (inheritedUserconfig) {
|
|
605
|
+
try {
|
|
606
|
+
const inherited = await readFile(inheritedUserconfig, 'utf8');
|
|
607
|
+
content = inherited.endsWith('\n') ? inherited : inherited + '\n';
|
|
608
|
+
} catch (error: any) {
|
|
609
|
+
// Missing inherited file is fine (npm would have created/ignored it); surface anything else.
|
|
610
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
content += buildNpmrcContent(this.registryAuth);
|
|
614
|
+
await writeFile(npmrcPath, content, { mode: 0o600 });
|
|
615
|
+
this.npmUserconfigPath = npmrcPath;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Remove the transient `.npmrc` (and its temp dir) once the deploy's npm work is done.
|
|
619
|
+
async cleanupTransientNpmrc(): Promise<void> {
|
|
620
|
+
if (!this.#npmrcTempDir) return;
|
|
621
|
+
try {
|
|
622
|
+
await rm(this.#npmrcTempDir, { recursive: true, force: true });
|
|
623
|
+
} catch (error) {
|
|
624
|
+
// Called from prepareApplication's finally; a throw here (e.g. a Windows file lock) would
|
|
625
|
+
// mask the original deploy error and skip broadcastDeployEnd. Log and always clear state.
|
|
626
|
+
this.logger.warn(`Failed to remove transient .npmrc dir ${this.#npmrcTempDir}:`, error);
|
|
627
|
+
} finally {
|
|
628
|
+
this.#npmrcTempDir = undefined;
|
|
629
|
+
this.npmUserconfigPath = undefined;
|
|
630
|
+
// Drop the in-memory token array too, so it can't surface in a later heap dump or error
|
|
631
|
+
// serialization of this Application instance.
|
|
632
|
+
this.registryAuth = undefined;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
533
635
|
}
|
|
534
636
|
|
|
535
637
|
/**
|
|
@@ -569,9 +671,13 @@ export function derivePackageIdentifier(packageIdentifier: string) {
|
|
|
569
671
|
export async function prepareApplication(application: Application) {
|
|
570
672
|
await broadcastDeployStart(application.name);
|
|
571
673
|
try {
|
|
674
|
+
// Materialize the per-deploy `.npmrc` before extraction so both `npm pack` (extract) and
|
|
675
|
+
// `npm install` authenticate against the private registry; always remove it afterward.
|
|
676
|
+
await application.writeTransientNpmrc();
|
|
572
677
|
await extractApplication(application);
|
|
573
678
|
await installApplication(application);
|
|
574
679
|
} finally {
|
|
680
|
+
await application.cleanupTransientNpmrc();
|
|
575
681
|
broadcastDeployEnd(application.name);
|
|
576
682
|
}
|
|
577
683
|
}
|
|
@@ -631,10 +737,29 @@ export async function installApplications() {
|
|
|
631
737
|
// This will throw if the config is invalid
|
|
632
738
|
assertApplicationConfig(name, applicationConfig);
|
|
633
739
|
|
|
740
|
+
// Resolve any private-registry auth references from the store so a cold install (fresh
|
|
741
|
+
// node, wiped components dir, new peer that never installed) can authenticate without the
|
|
742
|
+
// token being re-supplied. Best-effort: if custody isn't available yet or a referenced
|
|
743
|
+
// secret is missing, log and install without it (a truly private package then fails in
|
|
744
|
+
// npm with its own error) rather than blocking boot.
|
|
745
|
+
let resolvedRegistryAuth: RegistryAuthEntry[] | undefined;
|
|
746
|
+
if (applicationConfig.registryAuth?.length) {
|
|
747
|
+
try {
|
|
748
|
+
const { resolveRegistryAuth } = await import('./secretOperations.ts');
|
|
749
|
+
resolvedRegistryAuth = (await resolveRegistryAuth(applicationConfig.registryAuth, name)) as
|
|
750
|
+
RegistryAuthEntry[] | undefined;
|
|
751
|
+
} catch (error) {
|
|
752
|
+
logger.warn?.(
|
|
753
|
+
`Could not resolve registryAuth for application ${name} at install time: ${(error as Error).message}`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
634
758
|
const application = new Application({
|
|
635
759
|
name,
|
|
636
760
|
packageIdentifier: applicationConfig.package,
|
|
637
761
|
install: applicationConfig.install,
|
|
762
|
+
registryAuth: resolvedRegistryAuth,
|
|
638
763
|
});
|
|
639
764
|
|
|
640
765
|
// Lock check: only install if not already installed with matching configuration
|
|
@@ -675,6 +800,49 @@ function getGitSSHCommand() {
|
|
|
675
800
|
}
|
|
676
801
|
}
|
|
677
802
|
|
|
803
|
+
export interface RegistryAuthEntry {
|
|
804
|
+
registry: string;
|
|
805
|
+
token: string;
|
|
806
|
+
scope?: string;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Normalize a registry to a full URL with a scheme and trailing slash, e.g.
|
|
810
|
+
// `npm.pkg.github.com` or `//npm.pkg.github.com` → `https://npm.pkg.github.com/`.
|
|
811
|
+
function normalizeRegistryUrl(registry: string): string {
|
|
812
|
+
let url = registry.trim();
|
|
813
|
+
if (!/^https?:\/\//i.test(url)) {
|
|
814
|
+
url = url.startsWith('//') ? `https:${url}` : `https://${url}`;
|
|
815
|
+
}
|
|
816
|
+
if (!url.endsWith('/')) url += '/';
|
|
817
|
+
return url;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Build the contents of a transient `.npmrc` from registry auth entries: an auth-token line keyed
|
|
821
|
+
// by npm's registry auth key (scheme stripped, leading `//`, trailing `/`) plus a registry-routing
|
|
822
|
+
// line. A scope routes only that `@scope` to the registry (`@scope:registry=…`); without a scope
|
|
823
|
+
// the entry sets npm's default `registry=…` so an unscoped package spec (e.g. `npm:my-private-app`)
|
|
824
|
+
// or its transitive deps actually resolve against this registry rather than the public default.
|
|
825
|
+
// A scope-less entry therefore requires its registry to serve/proxy whatever npm needs to install;
|
|
826
|
+
// with multiple scope-less entries npm's last-value-wins applies to the default `registry`.
|
|
827
|
+
export function buildNpmrcContent(registryAuth: RegistryAuthEntry[]): string {
|
|
828
|
+
const lines: string[] = [];
|
|
829
|
+
for (const { registry, token, scope } of registryAuth) {
|
|
830
|
+
// Enforce the no-newline invariant at the injection point so it holds for every source. The
|
|
831
|
+
// ops validator already rejects CR/LF in a literal `token`, but a token resolved from an
|
|
832
|
+
// hdb_secret row bypasses that guard; without this a `\n` in a secret value would inject
|
|
833
|
+
// arbitrary .npmrc lines (admin-only per the threat model, but the literal path already
|
|
834
|
+
// defends this class).
|
|
835
|
+
if (/[\r\n]/.test(token)) {
|
|
836
|
+
throw new Error(`registry auth token for '${registry}' contains an illegal newline character`);
|
|
837
|
+
}
|
|
838
|
+
const registryUrl = normalizeRegistryUrl(registry);
|
|
839
|
+
const authKey = registryUrl.replace(/^https?:/i, '');
|
|
840
|
+
lines.push(`${authKey}:_authToken=${token}`);
|
|
841
|
+
lines.push(scope ? `${scope}:registry=${registryUrl}` : `registry=${registryUrl}`);
|
|
842
|
+
}
|
|
843
|
+
return lines.join('\n') + '\n';
|
|
844
|
+
}
|
|
845
|
+
|
|
678
846
|
/**
|
|
679
847
|
* Execute a command (using `spawn`) with stdin ignored.
|
|
680
848
|
*
|
|
@@ -734,7 +902,8 @@ export function nonInteractiveSpawn(
|
|
|
734
902
|
args: string[],
|
|
735
903
|
cwd: string,
|
|
736
904
|
timeoutMs: number = 60 * 60 * 1000,
|
|
737
|
-
onLine?: (stream: 'stdout' | 'stderr', line: string) => void
|
|
905
|
+
onLine?: (stream: 'stdout' | 'stderr', line: string) => void,
|
|
906
|
+
npmUserconfigPath?: string
|
|
738
907
|
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
739
908
|
return new Promise((resolve, reject) => {
|
|
740
909
|
logger
|
|
@@ -748,6 +917,18 @@ export function nonInteractiveSpawn(
|
|
|
748
917
|
env.GIT_SSH_COMMAND = gitSSHCommand;
|
|
749
918
|
}
|
|
750
919
|
|
|
920
|
+
// A deploy carrying transient registry auth points npm at a per-deploy `.npmrc` so
|
|
921
|
+
// `npm pack`/`install` can authenticate against a private registry without the token
|
|
922
|
+
// ever touching disk durably, the package reference, config, or hdb_deployment.
|
|
923
|
+
if (npmUserconfigPath) {
|
|
924
|
+
// On case-insensitive platforms (Windows) an inherited NPM_CONFIG_USERCONFIG would
|
|
925
|
+
// shadow the lowercase key we set, so drop any existing case variant first.
|
|
926
|
+
for (const key of Object.keys(env)) {
|
|
927
|
+
if (key.toLowerCase() === 'npm_config_userconfig') delete env[key];
|
|
928
|
+
}
|
|
929
|
+
env.npm_config_userconfig = npmUserconfigPath;
|
|
930
|
+
}
|
|
931
|
+
|
|
751
932
|
if (process.platform === 'win32' && command === 'npm') {
|
|
752
933
|
command = 'npm.cmd';
|
|
753
934
|
}
|
|
@@ -16,6 +16,8 @@ export class MissingDefaultFilesOptionError extends Error {
|
|
|
16
16
|
* This class is used to represent the application scope for the VM context used for loading modules within an application
|
|
17
17
|
*/
|
|
18
18
|
export class ApplicationScope {
|
|
19
|
+
/** Component identity (application directory name) — what secret grants are matched against. */
|
|
20
|
+
name: string;
|
|
19
21
|
logger: any;
|
|
20
22
|
resources: Resources;
|
|
21
23
|
server: Server;
|
|
@@ -25,6 +27,7 @@ export class ApplicationScope {
|
|
|
25
27
|
config: any;
|
|
26
28
|
moduleCache: any; // used by the loader to retain a cache of modules, type is an internal detail of the loader
|
|
27
29
|
constructor(name: string, resources: Resources, server: Server, isInternal = false) {
|
|
30
|
+
this.name = name;
|
|
28
31
|
this.logger = forComponent(name, !isInternal);
|
|
29
32
|
|
|
30
33
|
this.resources = resources;
|
package/components/Scope.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { FilesOption } from './deriveGlobOptions.ts';
|
|
|
12
12
|
import { requestRestart } from './requestRestart.ts';
|
|
13
13
|
import { resolveBaseURLPath } from './resolveBaseURLPath.ts';
|
|
14
14
|
import { ApplicationScope } from './ApplicationScope.ts';
|
|
15
|
+
import { getSecretsForComponent } from './componentSecrets.ts';
|
|
15
16
|
import { deployLifecycle } from './deployLifecycle.ts';
|
|
16
17
|
|
|
17
18
|
export class MissingDefaultFilesOptionError extends Error {
|
|
@@ -146,6 +147,17 @@ export class Scope extends EventEmitter<ScopeEventsMap> {
|
|
|
146
147
|
return this.#logger;
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The application's secrets view (#1550): hdb_secret rows granted to this application plus its
|
|
152
|
+
* declared global-tier env names. Frozen, enumerable, values decrypted at component load.
|
|
153
|
+
* Keyed by the ApplicationScope's name (the application directory name — the identity grants
|
|
154
|
+
* and env declarations use, and the same binding `import { secrets } from 'harper'` resolves);
|
|
155
|
+
* `#appName` can differ on paths like RUN_HDB_APP, where it is the full directory path.
|
|
156
|
+
*/
|
|
157
|
+
get secrets(): Readonly<Record<string, string>> {
|
|
158
|
+
return getSecretsForComponent(this.applicationScope?.name ?? this.#appName);
|
|
159
|
+
}
|
|
160
|
+
|
|
149
161
|
get appName(): string {
|
|
150
162
|
return this.#appName;
|
|
151
163
|
}
|
|
@@ -257,7 +257,10 @@ export class AnthropicBackend implements ModelBackend {
|
|
|
257
257
|
signal,
|
|
258
258
|
});
|
|
259
259
|
if (!res.ok) {
|
|
260
|
-
throw new AnthropicBackendError(
|
|
260
|
+
throw new AnthropicBackendError(
|
|
261
|
+
`Anthropic ${path} returned HTTP ${res.status}${await readErrorSuffix(res)}`,
|
|
262
|
+
res.status
|
|
263
|
+
);
|
|
261
264
|
}
|
|
262
265
|
return res;
|
|
263
266
|
}
|
|
@@ -283,9 +286,13 @@ export function registerAnthropicBackend(args: {
|
|
|
283
286
|
}
|
|
284
287
|
|
|
285
288
|
export class AnthropicBackendError extends ServerError {
|
|
286
|
-
|
|
289
|
+
/** HTTP status returned by the upstream provider, when the failure came from an HTTP response.
|
|
290
|
+
* Distinct from ServerError's statusCode, which is Harper's own response status (#1593). */
|
|
291
|
+
declare upstreamStatus?: number;
|
|
292
|
+
constructor(message: string, upstreamStatus?: number) {
|
|
287
293
|
super(message);
|
|
288
294
|
this.name = 'AnthropicBackendError';
|
|
295
|
+
if (upstreamStatus !== undefined) this.upstreamStatus = upstreamStatus;
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
298
|
|
|
@@ -256,6 +256,11 @@ export class BedrockBackend implements ModelBackend {
|
|
|
256
256
|
* Send a command with caller-supplied AbortSignal + optional per-call
|
|
257
257
|
* timeout composed via `AbortSignal.any`. The SDK accepts `abortSignal`
|
|
258
258
|
* in the request options bag.
|
|
259
|
+
*
|
|
260
|
+
* SDK `ServiceException`s are re-thrown as `BedrockBackendError` carrying the
|
|
261
|
+
* upstream HTTP status (`$metadata.httpStatusCode`) so the sanitized `@embed`
|
|
262
|
+
* error is diagnosable, matching the openai/anthropic/ollama backends (#1593).
|
|
263
|
+
* Aborts/timeouts are caller-driven, not upstream failures — propagated untouched.
|
|
259
264
|
*/
|
|
260
265
|
async #sendWithAbort(
|
|
261
266
|
client: { send: (cmd: object, options?: { abortSignal?: AbortSignal }) => Promise<unknown> },
|
|
@@ -263,7 +268,21 @@ export class BedrockBackend implements ModelBackend {
|
|
|
263
268
|
callerSignal?: AbortSignal
|
|
264
269
|
): Promise<unknown> {
|
|
265
270
|
const abortSignal = composeSignal(callerSignal, this.#requestTimeoutMs);
|
|
266
|
-
|
|
271
|
+
try {
|
|
272
|
+
return await client.send(command, abortSignal ? { abortSignal } : undefined);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
if ((err as any)?.name === 'AbortError' || abortSignal?.aborted) throw err;
|
|
275
|
+
const status = (err as any)?.$metadata?.httpStatusCode;
|
|
276
|
+
const sdkName = (err as any)?.name;
|
|
277
|
+
const wrapped = new BedrockBackendError(
|
|
278
|
+
`Bedrock request failed${sdkName ? ` (${sdkName})` : ''}`,
|
|
279
|
+
typeof status === 'number' ? status : undefined
|
|
280
|
+
);
|
|
281
|
+
// Keep the raw SDK error for the unsanitized server log; only class name +
|
|
282
|
+
// upstreamStatus surface to callers.
|
|
283
|
+
(wrapped as any).cause = err;
|
|
284
|
+
throw wrapped;
|
|
285
|
+
}
|
|
267
286
|
}
|
|
268
287
|
}
|
|
269
288
|
|
|
@@ -284,9 +303,13 @@ export function registerBedrockBackend(args: {
|
|
|
284
303
|
}
|
|
285
304
|
|
|
286
305
|
export class BedrockBackendError extends ServerError {
|
|
287
|
-
|
|
306
|
+
/** HTTP status returned by the upstream provider, when the failure came from an HTTP response.
|
|
307
|
+
* Distinct from ServerError's statusCode, which is Harper's own response status (#1593). */
|
|
308
|
+
declare upstreamStatus?: number;
|
|
309
|
+
constructor(message: string, upstreamStatus?: number) {
|
|
288
310
|
super(message);
|
|
289
311
|
this.name = 'BedrockBackendError';
|
|
312
|
+
if (upstreamStatus !== undefined) this.upstreamStatus = upstreamStatus;
|
|
290
313
|
}
|
|
291
314
|
}
|
|
292
315
|
|
|
@@ -23,7 +23,7 @@ import * as login from '../resources/login.ts';
|
|
|
23
23
|
import * as REST from '../server/REST.ts';
|
|
24
24
|
import * as staticFiles from '../server/static.ts';
|
|
25
25
|
import * as loadEnv from '../resources/loadEnv.ts';
|
|
26
|
-
import harperLogger from '../utility/logging/harper_logger.ts';
|
|
26
|
+
import harperLogger, { errorForLog } from '../utility/logging/harper_logger.ts';
|
|
27
27
|
import * as dataLoader from '../resources/dataLoader.ts';
|
|
28
28
|
import { restartWorkers, getWorkerIndex } from '../server/threads/manageThreads.js';
|
|
29
29
|
import { resetRestartNeeded, subscribeToRestartRequests } from './requestRestart.ts';
|
|
@@ -46,6 +46,7 @@ import * as mcpComponent from './mcp/index.ts';
|
|
|
46
46
|
import { Status } from '../server/status/index.ts';
|
|
47
47
|
import { lifecycle as componentLifecycle } from './status/index.ts';
|
|
48
48
|
import { DEFAULT_CONFIG } from './DEFAULT_CONFIG.ts';
|
|
49
|
+
import { materializeGlobalSecrets, processComponentEnv } from './componentSecrets.ts';
|
|
49
50
|
import { PluginModule } from './PluginModule.ts';
|
|
50
51
|
import { getEnvBuiltInComponents } from './Application.ts';
|
|
51
52
|
import { pathToFileURL } from 'node:url';
|
|
@@ -61,9 +62,14 @@ let resources;
|
|
|
61
62
|
* @param loadedPluginModules
|
|
62
63
|
* @param loadedResources
|
|
63
64
|
*/
|
|
64
|
-
export function loadComponentDirectories(loadedPluginModules?: Map<any, any>, loadedResources?: Resources) {
|
|
65
|
+
export async function loadComponentDirectories(loadedPluginModules?: Map<any, any>, loadedResources?: Resources) {
|
|
65
66
|
if (loadedResources) resources = loadedResources;
|
|
66
67
|
if (loadedPluginModules) loadedComponents = loadedPluginModules;
|
|
68
|
+
// Materialize hdb_secret global-tier rows into process.env and snapshot the scoped tier before
|
|
69
|
+
// any application loads (root components — including the Pro custody registration — have
|
|
70
|
+
// already loaded by this point). Re-runs on each reload cycle, which is how changed/late-custody
|
|
71
|
+
// secrets heal. Never throws.
|
|
72
|
+
await materializeGlobalSecrets();
|
|
67
73
|
const cfsLoaded: Promise<any>[] = [];
|
|
68
74
|
if (existsSync(CF_ROUTES_DIR)) {
|
|
69
75
|
const cfFolders = readdirSync(CF_ROUTES_DIR, { withFileTypes: true });
|
|
@@ -303,6 +309,11 @@ export interface LoadComponentOptions {
|
|
|
303
309
|
autoReload?: boolean;
|
|
304
310
|
providedLoadedComponents?: Map<any, any>;
|
|
305
311
|
appName?: string;
|
|
312
|
+
// When provided, every Scope created during this load is added to this set instead of being
|
|
313
|
+
// auto-closed on worker shutdown. The caller then owns closing them. Used by transient loads
|
|
314
|
+
// (e.g. the deploy pre-flight validation) so their deploy-lifecycle listeners don't accumulate
|
|
315
|
+
// across deploys (#1462).
|
|
316
|
+
collectScopes?: Set<Scope>;
|
|
306
317
|
}
|
|
307
318
|
|
|
308
319
|
/**
|
|
@@ -366,6 +377,34 @@ export async function loadComponent(
|
|
|
366
377
|
// per-component iteration below; built-in entries register synchronously.
|
|
367
378
|
if (isRoot) await bootstrapModels(config);
|
|
368
379
|
|
|
380
|
+
// The `env:` block declares the component's environment expectations (string literal →
|
|
381
|
+
// process.env; object → declaration satisfied from the hdb_secret store / process.env).
|
|
382
|
+
// Processed before any of the component's plugins load, so literals and the load-gate apply
|
|
383
|
+
// to everything below. A failed gate contains to this component — nothing of this
|
|
384
|
+
// component's is registered (its URL space is simply absent) and the instance keeps running.
|
|
385
|
+
if (config.env !== undefined) {
|
|
386
|
+
if (isRoot) {
|
|
387
|
+
harperLogger.warn(
|
|
388
|
+
`The 'env' config block is not supported in the root config; declare env expectations in each component's config`
|
|
389
|
+
);
|
|
390
|
+
} else {
|
|
391
|
+
const componentStatusName = basename(componentDirectory);
|
|
392
|
+
try {
|
|
393
|
+
// Refresh the store snapshot so out-of-cycle loads (e.g. deploy validation in a
|
|
394
|
+
// long-lived worker, after a set_secret/grant_secret since boot) gate against
|
|
395
|
+
// current data. Cheap (one small system-table scan per env-declaring component).
|
|
396
|
+
await materializeGlobalSecrets();
|
|
397
|
+
processComponentEnv(componentStatusName, config.env);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
error.message = `Could not load component '${componentStatusName}' due to: ${error.message}`;
|
|
400
|
+
errorReporter?.(error);
|
|
401
|
+
(getWorkerIndex() === 0 ? console : harperLogger).error(error);
|
|
402
|
+
componentLifecycle.failed(componentStatusName, error, `Could not load component '${componentStatusName}'`);
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
369
408
|
if (!isRoot) {
|
|
370
409
|
try {
|
|
371
410
|
await symlinkHarperModule(componentDirectory);
|
|
@@ -383,6 +422,7 @@ export async function loadComponent(
|
|
|
383
422
|
const componentFunctionality = {};
|
|
384
423
|
// iterate through the app handlers so they can each do their own loading process
|
|
385
424
|
for (const componentName in config) {
|
|
425
|
+
if (componentName === 'env') continue; // handled above — not a plugin
|
|
386
426
|
// For root components, use just the component name
|
|
387
427
|
// For application components, use applicationName.componentName format (directoryName.componentName)
|
|
388
428
|
const componentStatusName = isRoot ? componentName : `${basename(componentDirectory)}.${componentName}`;
|
|
@@ -442,6 +482,7 @@ export async function loadComponent(
|
|
|
442
482
|
applicationScope: subApplicationScope,
|
|
443
483
|
autoReload: false,
|
|
444
484
|
appName: appName || componentName,
|
|
485
|
+
collectScopes: options.collectScopes,
|
|
445
486
|
});
|
|
446
487
|
componentFunctionality[componentName] = true;
|
|
447
488
|
}
|
|
@@ -510,9 +551,17 @@ export async function loadComponent(
|
|
|
510
551
|
origin
|
|
511
552
|
);
|
|
512
553
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
554
|
+
if (options.collectScopes) {
|
|
555
|
+
// A transient/validation load owns these scopes and closes them itself once the
|
|
556
|
+
// load is validated (see operations.js deploy pre-flight). Skip the worker-shutdown
|
|
557
|
+
// auto-close so their deploy-lifecycle listeners — and this SHUTDOWN handler — don't
|
|
558
|
+
// accumulate across deploys (#1462).
|
|
559
|
+
options.collectScopes.add(scope);
|
|
560
|
+
} else {
|
|
561
|
+
// Track the close so the worker's shutdown path waits for it (and thus for any async
|
|
562
|
+
// native-runtime disposal, e.g. @harperfast/vite's dev server) before calling realExit.
|
|
563
|
+
onMessageByType(ITC_EVENT_TYPES.SHUTDOWN, () => trackScopeClose(scope.close()));
|
|
564
|
+
}
|
|
516
565
|
|
|
517
566
|
await sequentiallyHandleApplication(scope, extensionModule);
|
|
518
567
|
|
|
@@ -607,7 +656,7 @@ export async function loadComponent(
|
|
|
607
656
|
error.message
|
|
608
657
|
}`;
|
|
609
658
|
errorReporter?.(error);
|
|
610
|
-
(getWorkerIndex() === 0 ? console : harperLogger).error(error);
|
|
659
|
+
(getWorkerIndex() === 0 ? console : harperLogger).error(errorForLog(error));
|
|
611
660
|
resources.set(componentConfig.path || '/', new ErrorResource(error), null, true);
|
|
612
661
|
componentLifecycle.failed(componentStatusName, error, `Could not load component '${componentStatusName}'`);
|
|
613
662
|
}
|
|
@@ -683,7 +732,7 @@ export async function loadComponent(
|
|
|
683
732
|
);
|
|
684
733
|
}
|
|
685
734
|
} catch (error) {
|
|
686
|
-
console.error(`Could not load application directory ${componentDirectory}`, error);
|
|
735
|
+
console.error(`Could not load application directory ${componentDirectory}`, errorForLog(error));
|
|
687
736
|
error.message = `Could not load application due to ${error.message}`;
|
|
688
737
|
errorReporter?.(error);
|
|
689
738
|
resources.set('', new ErrorResource(error));
|