jishushell 0.6.5 → 0.7.3
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/apps/anythingllm-container.yaml +16 -170
- package/apps/browserless-chromium-container.yaml +16 -10
- package/apps/filebrowser-container.yaml +15 -9
- package/apps/hermes-container.yaml +20 -5
- package/apps/immich-container-lite.yaml +337 -0
- package/apps/immich-container.yaml +371 -0
- package/apps/jishu-kb-container.yaml +50 -177
- package/apps/ollama-binary.yaml +33 -28
- package/apps/ollama-cpu-container.yaml +6 -0
- package/apps/ollama-with-hollama-binary.yaml +35 -28
- package/apps/openclaw-binary.yaml +35 -15
- package/apps/openclaw-container.yaml +29 -11
- package/apps/openclaw-with-ollama-container.yaml +9 -2
- package/apps/openclaw-with-searxng-container.yaml +38 -6
- package/apps/searxng-container.yaml +31 -6
- package/apps/weknora-container.yaml +26 -21
- package/dependencies/jishushell-panel-0.7.3.tgz +0 -0
- package/dist/cli/app.js +244 -213
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/backup.js +15 -12
- package/dist/cli/backup.js.map +1 -1
- package/dist/cli/core.d.ts +4 -3
- package/dist/cli/core.js +392 -227
- package/dist/cli/core.js.map +1 -1
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.js +113 -10
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/job.js +62 -14
- package/dist/cli/job.js.map +1 -1
- package/dist/cli/llm.js +80 -11
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/managed-list.d.ts +1 -3
- package/dist/cli/managed-list.js +18 -16
- package/dist/cli/managed-list.js.map +1 -1
- package/dist/cli/migrate.d.ts +2 -0
- package/dist/cli/migrate.js +160 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +32 -20
- package/dist/config.js +132 -51
- package/dist/config.js.map +1 -1
- package/dist/control.d.ts +6 -6
- package/dist/control.js +31 -23
- package/dist/control.js.map +1 -1
- package/dist/core.d.ts +5 -5
- package/dist/core.js +5 -5
- package/dist/core.js.map +1 -1
- package/dist/install.d.ts +2 -2
- package/dist/install.js +78 -37
- package/dist/install.js.map +1 -1
- package/dist/routes/admin.d.ts +2 -0
- package/dist/routes/admin.js +72 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/apps.d.ts +1 -1
- package/dist/routes/apps.js +101 -193
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/auth.js +1 -1
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +1 -1
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/docker.d.ts +2 -0
- package/dist/routes/docker.js +58 -0
- package/dist/routes/docker.js.map +1 -0
- package/dist/routes/external-mounts.d.ts +1 -1
- package/dist/routes/external-mounts.js +1 -1
- package/dist/routes/external-mounts.js.map +1 -1
- package/dist/routes/file-mounts.d.ts +4 -3
- package/dist/routes/file-mounts.js +49 -31
- package/dist/routes/file-mounts.js.map +1 -1
- package/dist/routes/files-organize.d.ts +2 -2
- package/dist/routes/files-organize.js +5 -5
- package/dist/routes/files-organize.js.map +1 -1
- package/dist/routes/files.d.ts +1 -1
- package/dist/routes/files.js +1 -1
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/instances.d.ts +0 -8
- package/dist/routes/instances.js +202 -1560
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/integration-apps.d.ts +14 -0
- package/dist/routes/integration-apps.js +81 -0
- package/dist/routes/integration-apps.js.map +1 -0
- package/dist/routes/integrations.d.ts +9 -0
- package/dist/routes/integrations.js +12 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/llm-proxy.js +26 -3
- package/dist/routes/llm-proxy.js.map +1 -1
- package/dist/routes/setup.js +53 -38
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +108 -68
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +1 -1
- package/dist/routes/webdav.js +2 -2
- package/dist/routes/webdav.js.map +1 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.js +368 -233
- package/dist/server.js.map +1 -1
- package/dist/services/app-common/app-compiler.js +186 -0
- package/dist/services/app-common/app-compiler.js.map +1 -0
- package/dist/services/app-common/app-shared.d.ts +15 -0
- package/dist/services/app-common/app-shared.js +64 -0
- package/dist/services/app-common/app-shared.js.map +1 -0
- package/dist/services/app-common/capability-service.d.ts +45 -0
- package/dist/services/app-common/capability-service.js +331 -0
- package/dist/services/app-common/capability-service.js.map +1 -0
- package/dist/services/app-common/catalog-service.d.ts +59 -0
- package/dist/services/app-common/catalog-service.js +318 -0
- package/dist/services/app-common/catalog-service.js.map +1 -0
- package/dist/services/app-common/create-pipeline.d.ts +26 -0
- package/dist/services/app-common/create-pipeline.js +298 -0
- package/dist/services/app-common/create-pipeline.js.map +1 -0
- package/dist/services/app-common/delete-service.d.ts +5 -0
- package/dist/services/app-common/delete-service.js +109 -0
- package/dist/services/app-common/delete-service.js.map +1 -0
- package/dist/services/app-common/execution-owner.d.ts +23 -0
- package/dist/services/app-common/execution-owner.js +124 -0
- package/dist/services/app-common/execution-owner.js.map +1 -0
- package/dist/services/app-common/execution-service.d.ts +23 -0
- package/dist/services/app-common/execution-service.js +105 -0
- package/dist/services/app-common/execution-service.js.map +1 -0
- package/dist/services/app-common/id-normalizer.d.ts +31 -0
- package/dist/services/app-common/id-normalizer.js +83 -0
- package/dist/services/app-common/id-normalizer.js.map +1 -0
- package/dist/services/app-common/install-store.d.ts +34 -0
- package/dist/services/app-common/install-store.js +261 -0
- package/dist/services/app-common/install-store.js.map +1 -0
- package/dist/services/app-common/instance-store.d.ts +78 -0
- package/dist/services/app-common/instance-store.js +498 -0
- package/dist/services/app-common/instance-store.js.map +1 -0
- package/dist/services/app-common/integration-refs.d.ts +17 -0
- package/dist/services/app-common/integration-refs.js +47 -0
- package/dist/services/app-common/integration-refs.js.map +1 -0
- package/dist/services/app-common/lifecycle-pipeline.d.ts +62 -0
- package/dist/services/app-common/lifecycle-pipeline.js +317 -0
- package/dist/services/app-common/lifecycle-pipeline.js.map +1 -0
- package/dist/services/app-common/lifecycle-scripts.d.ts +38 -0
- package/dist/services/app-common/lifecycle-scripts.js +935 -0
- package/dist/services/app-common/lifecycle-scripts.js.map +1 -0
- package/dist/services/app-common/lifecycle-service.d.ts +68 -0
- package/dist/services/app-common/lifecycle-service.js +475 -0
- package/dist/services/app-common/lifecycle-service.js.map +1 -0
- package/dist/services/app-common/ownership.d.ts +3 -0
- package/dist/services/app-common/ownership.js +11 -0
- package/dist/services/app-common/ownership.js.map +1 -0
- package/dist/services/app-common/paths.d.ts +29 -0
- package/dist/services/app-common/paths.js +34 -0
- package/dist/services/app-common/paths.js.map +1 -0
- package/dist/services/app-common/platform-transform.d.ts +32 -0
- package/dist/services/app-common/platform-transform.js +65 -0
- package/dist/services/app-common/platform-transform.js.map +1 -0
- package/dist/services/app-common/provide-resolver.d.ts +29 -0
- package/dist/services/app-common/provide-resolver.js +129 -0
- package/dist/services/app-common/provide-resolver.js.map +1 -0
- package/dist/services/app-common/remote-spec.d.ts +14 -0
- package/dist/services/app-common/remote-spec.js +58 -0
- package/dist/services/app-common/remote-spec.js.map +1 -0
- package/dist/services/app-common/runtime-builder.d.ts +1 -0
- package/dist/services/app-common/runtime-builder.js +2 -0
- package/dist/services/app-common/runtime-builder.js.map +1 -0
- package/dist/services/app-common/runtime-facts.d.ts +19 -0
- package/dist/services/app-common/runtime-facts.js +128 -0
- package/dist/services/app-common/runtime-facts.js.map +1 -0
- package/dist/services/app-common/service.d.ts +9 -0
- package/dist/services/app-common/service.js +10 -0
- package/dist/services/app-common/service.js.map +1 -0
- package/dist/services/app-common/spec-materializer.d.ts +8 -0
- package/dist/services/app-common/spec-materializer.js +295 -0
- package/dist/services/app-common/spec-materializer.js.map +1 -0
- package/dist/services/app-common/status-refresh.d.ts +33 -0
- package/dist/services/app-common/status-refresh.js +771 -0
- package/dist/services/app-common/status-refresh.js.map +1 -0
- package/dist/services/app-common/task-service.d.ts +29 -0
- package/dist/services/app-common/task-service.js +93 -0
- package/dist/services/app-common/task-service.js.map +1 -0
- package/dist/services/app-common/terminal-session-manager.js +157 -0
- package/dist/services/app-common/terminal-session-manager.js.map +1 -0
- package/dist/services/app-modules/browserless/routes.d.ts +9 -0
- package/dist/services/app-modules/browserless/routes.js +519 -0
- package/dist/services/app-modules/browserless/routes.js.map +1 -0
- package/dist/services/app-modules/routes.d.ts +2 -0
- package/dist/services/app-modules/routes.js +5 -0
- package/dist/services/app-modules/routes.js.map +1 -0
- package/dist/services/backup/backup-admin.d.ts +95 -0
- package/dist/services/backup/backup-admin.js +246 -0
- package/dist/services/backup/backup-admin.js.map +1 -0
- package/dist/services/backup/backup-manager.d.ts +264 -0
- package/dist/services/backup/backup-manager.js +2318 -0
- package/dist/services/backup/backup-manager.js.map +1 -0
- package/dist/services/backup/backup-verify.js +240 -0
- package/dist/services/backup/backup-verify.js.map +1 -0
- package/dist/services/capabilities/browser-policy.d.ts +14 -0
- package/dist/services/capabilities/browser-policy.js +141 -0
- package/dist/services/capabilities/browser-policy.js.map +1 -0
- package/dist/services/capabilities/contract.d.ts +49 -0
- package/dist/services/capabilities/contract.js +119 -0
- package/dist/services/capabilities/contract.js.map +1 -0
- package/dist/services/capabilities/endpoint-validator.d.ts +42 -0
- package/dist/services/capabilities/endpoint-validator.js +113 -0
- package/dist/services/capabilities/endpoint-validator.js.map +1 -0
- package/dist/services/capabilities/health.d.ts +16 -0
- package/dist/services/capabilities/health.js +121 -0
- package/dist/services/capabilities/health.js.map +1 -0
- package/dist/services/capabilities/registry.d.ts +56 -0
- package/dist/services/capabilities/registry.js +222 -0
- package/dist/services/capabilities/registry.js.map +1 -0
- package/dist/services/capabilities/sync.d.ts +7 -0
- package/dist/services/capabilities/sync.js +223 -0
- package/dist/services/capabilities/sync.js.map +1 -0
- package/dist/services/capability-proxy/html-rewriters/browserless.d.ts +1 -0
- package/dist/services/capability-proxy/html-rewriters/browserless.js +83 -0
- package/dist/services/capability-proxy/html-rewriters/browserless.js.map +1 -0
- package/dist/services/capability-proxy/html-rewriters/index.d.ts +12 -0
- package/dist/services/capability-proxy/html-rewriters/index.js +25 -0
- package/dist/services/capability-proxy/html-rewriters/index.js.map +1 -0
- package/dist/services/capability-proxy/html-rewriters/jishukb.d.ts +1 -0
- package/dist/services/capability-proxy/html-rewriters/jishukb.js +161 -0
- package/dist/services/capability-proxy/html-rewriters/jishukb.js.map +1 -0
- package/dist/services/capability-proxy/http.d.ts +7 -0
- package/dist/services/capability-proxy/http.js +555 -0
- package/dist/services/capability-proxy/http.js.map +1 -0
- package/dist/services/capability-proxy/terminal.d.ts +4 -0
- package/dist/services/capability-proxy/terminal.js +179 -0
- package/dist/services/capability-proxy/terminal.js.map +1 -0
- package/dist/services/connections/admin.d.ts +80 -0
- package/dist/services/connections/admin.js +337 -0
- package/dist/services/connections/admin.js.map +1 -0
- package/dist/services/connections/apply.d.ts +104 -0
- package/dist/services/connections/apply.js +415 -0
- package/dist/services/connections/apply.js.map +1 -0
- package/dist/services/connections/resolver.d.ts +82 -0
- package/dist/services/connections/resolver.js +289 -0
- package/dist/services/connections/resolver.js.map +1 -0
- package/dist/services/connections/transactor.d.ts +39 -0
- package/dist/services/connections/transactor.js +307 -0
- package/dist/services/connections/transactor.js.map +1 -0
- package/dist/services/files/bootstrap.d.ts +7 -0
- package/dist/services/files/bootstrap.js +16 -0
- package/dist/services/files/bootstrap.js.map +1 -0
- package/dist/services/files/external-mounts.js +187 -0
- package/dist/services/files/external-mounts.js.map +1 -0
- package/dist/services/files/files-manager.d.ts +265 -0
- package/dist/services/files/files-manager.js +1189 -0
- package/dist/services/files/files-manager.js.map +1 -0
- package/dist/services/files/files-mounts.d.ts +42 -0
- package/dist/services/files/files-mounts.js +207 -0
- package/dist/services/files/files-mounts.js.map +1 -0
- package/dist/services/files/organize/applier.js +218 -0
- package/dist/services/files/organize/applier.js.map +1 -0
- package/dist/services/files/organize/rules.js +286 -0
- package/dist/services/files/organize/rules.js.map +1 -0
- package/dist/services/files/organize/scanner.js +366 -0
- package/dist/services/files/organize/scanner.js.map +1 -0
- package/dist/services/files/organize/store.js +82 -0
- package/dist/services/files/organize/store.js.map +1 -0
- package/dist/services/files/photos/upload-page.d.ts +2 -0
- package/dist/services/files/photos/upload-page.js +248 -0
- package/dist/services/files/photos/upload-page.js.map +1 -0
- package/dist/services/files/photos/upload-store.d.ts +74 -0
- package/dist/services/files/photos/upload-store.js +432 -0
- package/dist/services/files/photos/upload-store.js.map +1 -0
- package/dist/services/files/webdav/server.d.ts +47 -0
- package/dist/services/files/webdav/server.js +329 -0
- package/dist/services/files/webdav/server.js.map +1 -0
- package/dist/services/files/webdav/xml-builder.js.map +1 -0
- package/dist/services/http/proxy-utils.d.ts +7 -0
- package/dist/services/http/proxy-utils.js +29 -0
- package/dist/services/http/proxy-utils.js.map +1 -0
- package/dist/services/http/request-utils.d.ts +3 -0
- package/dist/services/http/request-utils.js +23 -0
- package/dist/services/http/request-utils.js.map +1 -0
- package/dist/services/instances/admin.d.ts +23 -0
- package/dist/services/instances/admin.js +218 -0
- package/dist/services/instances/admin.js.map +1 -0
- package/dist/services/instances/clone.d.ts +26 -0
- package/dist/services/instances/clone.js +78 -0
- package/dist/services/instances/clone.js.map +1 -0
- package/dist/services/instances/config-admin.d.ts +17 -0
- package/dist/services/instances/config-admin.js +181 -0
- package/dist/services/instances/config-admin.js.map +1 -0
- package/dist/services/instances/manager.d.ts +232 -0
- package/dist/services/instances/manager.js +1342 -0
- package/dist/services/instances/manager.js.map +1 -0
- package/dist/services/instances/pairing.d.ts +17 -0
- package/dist/services/instances/pairing.js +53 -0
- package/dist/services/instances/pairing.js.map +1 -0
- package/dist/services/instances/passwords.js +173 -0
- package/dist/services/instances/passwords.js.map +1 -0
- package/dist/services/instances/status.d.ts +2 -0
- package/dist/services/instances/status.js +11 -0
- package/dist/services/instances/status.js.map +1 -0
- package/dist/services/instances/types.d.ts +21 -0
- package/dist/services/instances/types.js +2 -0
- package/dist/services/instances/types.js.map +1 -0
- package/dist/services/integrations/anythingllm/integration.d.ts +25 -0
- package/dist/services/integrations/anythingllm/integration.js +251 -0
- package/dist/services/integrations/anythingllm/integration.js.map +1 -0
- package/dist/services/integrations/catalog.d.ts +3 -0
- package/dist/services/integrations/catalog.js +73 -0
- package/dist/services/integrations/catalog.js.map +1 -0
- package/dist/services/integrations/custom/integration.d.ts +28 -0
- package/dist/services/integrations/custom/integration.js +179 -0
- package/dist/services/integrations/custom/integration.js.map +1 -0
- package/dist/services/integrations/hermes/integration.d.ts +194 -0
- package/dist/services/integrations/hermes/integration.js +1668 -0
- package/dist/services/integrations/hermes/integration.js.map +1 -0
- package/dist/services/integrations/immich/client.d.ts +93 -0
- package/dist/services/integrations/immich/client.js +458 -0
- package/dist/services/integrations/immich/client.js.map +1 -0
- package/dist/services/integrations/immich/config.d.ts +15 -0
- package/dist/services/integrations/immich/config.js +178 -0
- package/dist/services/integrations/immich/config.js.map +1 -0
- package/dist/services/integrations/immich/discovery.d.ts +9 -0
- package/dist/services/integrations/immich/discovery.js +101 -0
- package/dist/services/integrations/immich/discovery.js.map +1 -0
- package/dist/services/integrations/immich/gallery-renderer.d.ts +5 -0
- package/dist/services/integrations/immich/gallery-renderer.js +150 -0
- package/dist/services/integrations/immich/gallery-renderer.js.map +1 -0
- package/dist/services/integrations/immich/immich-shim.d.ts +11 -0
- package/dist/services/integrations/immich/immich-shim.js +439 -0
- package/dist/services/integrations/immich/immich-shim.js.map +1 -0
- package/dist/services/integrations/immich/integration.d.ts +18 -0
- package/dist/services/integrations/immich/integration.js +64 -0
- package/dist/services/integrations/immich/integration.js.map +1 -0
- package/dist/services/integrations/immich/photo-library.d.ts +4 -0
- package/dist/services/integrations/immich/photo-library.js +63 -0
- package/dist/services/integrations/immich/photo-library.js.map +1 -0
- package/dist/services/integrations/immich/review-executor.d.ts +3 -0
- package/dist/services/integrations/immich/review-executor.js +41 -0
- package/dist/services/integrations/immich/review-executor.js.map +1 -0
- package/dist/services/integrations/immich/review-session-service.d.ts +27 -0
- package/dist/services/integrations/immich/review-session-service.js +206 -0
- package/dist/services/integrations/immich/review-session-service.js.map +1 -0
- package/dist/services/integrations/immich/review-store.d.ts +47 -0
- package/dist/services/integrations/immich/review-store.js +347 -0
- package/dist/services/integrations/immich/review-store.js.map +1 -0
- package/dist/services/integrations/immich/routes.d.ts +7 -0
- package/dist/services/integrations/immich/routes.js +363 -0
- package/dist/services/integrations/immich/routes.js.map +1 -0
- package/dist/services/integrations/immich/types.d.ts +186 -0
- package/dist/services/integrations/immich/types.js +2 -0
- package/dist/services/integrations/immich/types.js.map +1 -0
- package/dist/services/integrations/index.d.ts +41 -0
- package/dist/services/integrations/index.js +60 -0
- package/dist/services/integrations/index.js.map +1 -0
- package/dist/services/integrations/installable/catalog.d.ts +33 -0
- package/dist/services/integrations/installable/catalog.js +88 -0
- package/dist/services/integrations/installable/catalog.js.map +1 -0
- package/dist/services/integrations/installable/index.d.ts +35 -0
- package/dist/services/integrations/installable/index.js +170 -0
- package/dist/services/integrations/installable/index.js.map +1 -0
- package/dist/services/integrations/installable/installers/integration-probes.d.ts +50 -0
- package/dist/services/integrations/installable/installers/integration-probes.js +231 -0
- package/dist/services/integrations/installable/installers/integration-probes.js.map +1 -0
- package/dist/services/integrations/installable/installers/integration.d.ts +30 -0
- package/dist/services/integrations/installable/installers/integration.js +283 -0
- package/dist/services/integrations/installable/installers/integration.js.map +1 -0
- package/dist/services/integrations/installable/installers/registry-probe.js.map +1 -0
- package/dist/services/integrations/installable/installers/shell-script.d.ts +46 -0
- package/dist/services/integrations/installable/installers/shell-script.js +487 -0
- package/dist/services/integrations/installable/installers/shell-script.js.map +1 -0
- package/dist/services/integrations/installable/types.d.ts +130 -0
- package/dist/services/integrations/installable/types.js +19 -0
- package/dist/services/integrations/installable/types.js.map +1 -0
- package/dist/services/integrations/jishukb/integration.d.ts +24 -0
- package/dist/services/integrations/jishukb/integration.js +300 -0
- package/dist/services/integrations/jishukb/integration.js.map +1 -0
- package/dist/services/integrations/openclaw/anythingllm-shim.d.ts +46 -0
- package/dist/services/integrations/openclaw/anythingllm-shim.js +281 -0
- package/dist/services/integrations/openclaw/anythingllm-shim.js.map +1 -0
- package/dist/services/integrations/openclaw/drive-shim.js +490 -0
- package/dist/services/integrations/openclaw/drive-shim.js.map +1 -0
- package/dist/services/integrations/openclaw/integration.d.ts +438 -0
- package/dist/services/integrations/openclaw/integration.js +4629 -0
- package/dist/services/integrations/openclaw/integration.js.map +1 -0
- package/dist/services/integrations/openclaw/jishukb-native-mcp.d.ts +58 -0
- package/dist/services/integrations/openclaw/jishukb-native-mcp.js +373 -0
- package/dist/services/integrations/openclaw/jishukb-native-mcp.js.map +1 -0
- package/dist/services/integrations/openclaw/jishukb-shim.d.ts +52 -0
- package/dist/services/integrations/openclaw/jishukb-shim.js +1357 -0
- package/dist/services/integrations/openclaw/jishukb-shim.js.map +1 -0
- package/dist/services/integrations/openclaw/mcporter-lite.js +276 -0
- package/dist/services/integrations/openclaw/mcporter-lite.js.map +1 -0
- package/dist/services/integrations/openclaw/mcporter.d.ts +59 -0
- package/dist/services/integrations/openclaw/mcporter.js +143 -0
- package/dist/services/integrations/openclaw/mcporter.js.map +1 -0
- package/dist/services/integrations/openclaw/native-mcp.d.ts +48 -0
- package/dist/services/integrations/openclaw/native-mcp.js +125 -0
- package/dist/services/integrations/openclaw/native-mcp.js.map +1 -0
- package/dist/services/integrations/openclaw/routes.d.ts +21 -0
- package/dist/services/integrations/openclaw/routes.js +1194 -0
- package/dist/services/integrations/openclaw/routes.js.map +1 -0
- package/dist/services/integrations/registry.d.ts +17 -0
- package/dist/services/integrations/registry.js +36 -0
- package/dist/services/integrations/registry.js.map +1 -0
- package/dist/services/integrations/routes.d.ts +2 -0
- package/dist/services/integrations/routes.js +9 -0
- package/dist/services/integrations/routes.js.map +1 -0
- package/dist/services/integrations/types.d.ts +457 -0
- package/dist/services/integrations/types.js +2 -0
- package/dist/services/integrations/types.js.map +1 -0
- package/dist/services/legacy-migrator/classifier.d.ts +44 -0
- package/dist/services/legacy-migrator/classifier.js +309 -0
- package/dist/services/legacy-migrator/classifier.js.map +1 -0
- package/dist/services/legacy-migrator/executor.d.ts +42 -0
- package/dist/services/legacy-migrator/executor.js +637 -0
- package/dist/services/legacy-migrator/executor.js.map +1 -0
- package/dist/services/legacy-migrator/index.d.ts +31 -0
- package/dist/services/legacy-migrator/index.js +34 -0
- package/dist/services/legacy-migrator/index.js.map +1 -0
- package/dist/services/legacy-migrator/planner.d.ts +8 -0
- package/dist/services/legacy-migrator/planner.js +154 -0
- package/dist/services/legacy-migrator/planner.js.map +1 -0
- package/dist/services/legacy-migrator/provider-settings.d.ts +6 -0
- package/dist/services/legacy-migrator/provider-settings.js +72 -0
- package/dist/services/legacy-migrator/provider-settings.js.map +1 -0
- package/dist/services/legacy-migrator/report.d.ts +9 -0
- package/dist/services/legacy-migrator/report.js +99 -0
- package/dist/services/legacy-migrator/report.js.map +1 -0
- package/dist/services/legacy-migrator/scanner.d.ts +13 -0
- package/dist/services/legacy-migrator/scanner.js +157 -0
- package/dist/services/legacy-migrator/scanner.js.map +1 -0
- package/dist/services/legacy-migrator/types.d.ts +97 -0
- package/dist/services/legacy-migrator/types.js +23 -0
- package/dist/services/legacy-migrator/types.js.map +1 -0
- package/dist/services/llm-proxy/instance-proxy.d.ts +17 -1
- package/dist/services/llm-proxy/instance-proxy.js +171 -44
- package/dist/services/llm-proxy/instance-proxy.js.map +1 -1
- package/dist/services/llm-proxy/probe.js +5 -14
- package/dist/services/llm-proxy/probe.js.map +1 -1
- package/dist/services/llm-proxy/providers.js +23 -31
- package/dist/services/llm-proxy/providers.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.d.ts +11 -4
- package/dist/services/llm-proxy/ssrf.js +45 -7
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/llm-proxy/validate-key.js +16 -37
- package/dist/services/llm-proxy/validate-key.js.map +1 -1
- package/dist/services/repair/runtime-repair.d.ts +22 -0
- package/dist/services/repair/runtime-repair.js +374 -0
- package/dist/services/repair/runtime-repair.js.map +1 -0
- package/dist/services/runtime/docker-network.d.ts +8 -0
- package/dist/services/runtime/docker-network.js +123 -0
- package/dist/services/runtime/docker-network.js.map +1 -0
- package/dist/services/runtime/driver-registry.d.ts +25 -0
- package/dist/services/runtime/driver-registry.js +22 -0
- package/dist/services/runtime/driver-registry.js.map +1 -0
- package/dist/services/runtime/drivers/nomad.d.ts +261 -0
- package/dist/services/runtime/drivers/nomad.js +3122 -0
- package/dist/services/runtime/drivers/nomad.js.map +1 -0
- package/dist/services/runtime/errors.d.ts +3 -3
- package/dist/services/runtime/errors.js +3 -3
- package/dist/services/runtime/instance.d.ts +14 -16
- package/dist/services/runtime/instance.js +93 -123
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/job-id.d.ts +1 -1
- package/dist/services/runtime/job-id.js +1 -1
- package/dist/services/runtime/mcp-shims/firewall.d.ts +2 -2
- package/dist/services/runtime/mcp-shims/firewall.js +2 -2
- package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +3 -5
- package/dist/services/runtime/mcp-shims/searxng-shim.js +3 -5
- package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -1
- package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +20 -20
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js +16 -16
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -1
- package/dist/services/runtime/ownership-marker.d.ts +83 -0
- package/dist/services/runtime/ownership-marker.js +109 -0
- package/dist/services/runtime/ownership-marker.js.map +1 -0
- package/dist/services/runtime/service-manager.d.ts +2 -0
- package/dist/services/runtime/service-manager.js +18 -0
- package/dist/services/runtime/service-manager.js.map +1 -0
- package/dist/services/runtime/types.d.ts +23 -501
- package/dist/services/runtime/types.js +0 -12
- package/dist/services/runtime/types.js.map +1 -1
- package/dist/services/runtime/workload-compiler.d.ts +17 -0
- package/dist/services/runtime/workload-compiler.js +550 -0
- package/dist/services/runtime/workload-compiler.js.map +1 -0
- package/dist/services/runtime/workload-types.d.ts +11 -0
- package/dist/services/runtime/workload-types.js +2 -0
- package/dist/services/runtime/workload-types.js.map +1 -0
- package/dist/services/setup/core-manager.d.ts +50 -0
- package/dist/services/setup/core-manager.js +456 -0
- package/dist/services/setup/core-manager.js.map +1 -0
- package/dist/services/setup/plugin-installer.js +136 -0
- package/dist/services/setup/plugin-installer.js.map +1 -0
- package/dist/services/setup/setup-manager.d.ts +158 -0
- package/dist/services/setup/setup-manager.js +2724 -0
- package/dist/services/setup/setup-manager.js.map +1 -0
- package/dist/services/system/cli-command.d.ts +5 -0
- package/dist/services/system/cli-command.js +18 -0
- package/dist/services/system/cli-command.js.map +1 -0
- package/dist/services/system/macos-launchd.js +312 -0
- package/dist/services/system/macos-launchd.js.map +1 -0
- package/dist/services/system/repair-orchestrator.d.ts +71 -0
- package/dist/services/system/repair-orchestrator.js +412 -0
- package/dist/services/system/repair-orchestrator.js.map +1 -0
- package/dist/services/system/runtime-ownership.d.ts +36 -0
- package/dist/services/system/runtime-ownership.js +250 -0
- package/dist/services/system/runtime-ownership.js.map +1 -0
- package/dist/services/system/system-monitor.js +96 -0
- package/dist/services/system/system-monitor.js.map +1 -0
- package/dist/services/system/system-ollama-provider.d.ts +14 -0
- package/dist/services/system/system-ollama-provider.js +129 -0
- package/dist/services/system/system-ollama-provider.js.map +1 -0
- package/dist/services/system/system-reconciler.d.ts +59 -0
- package/dist/services/system/system-reconciler.js +763 -0
- package/dist/services/system/system-reconciler.js.map +1 -0
- package/dist/services/system/update-manager.d.ts +43 -0
- package/dist/services/system/update-manager.js +315 -0
- package/dist/services/system/update-manager.js.map +1 -0
- package/dist/services/system/upgrade-finalize.d.ts +80 -0
- package/dist/services/system/upgrade-finalize.js +507 -0
- package/dist/services/system/upgrade-finalize.js.map +1 -0
- package/dist/services/tasks/registry.d.ts +44 -0
- package/dist/services/tasks/registry.js +90 -0
- package/dist/services/tasks/registry.js.map +1 -0
- package/dist/services/telemetry/activation.d.ts +6 -2
- package/dist/services/telemetry/activation.js +6 -2
- package/dist/services/telemetry/activation.js.map +1 -1
- package/dist/services/telemetry/heartbeat.d.ts +6 -2
- package/dist/services/telemetry/heartbeat.js +6 -2
- package/dist/services/telemetry/heartbeat.js.map +1 -1
- package/dist/services/workspaces/builder.d.ts +29 -0
- package/dist/services/workspaces/builder.js +186 -0
- package/dist/services/workspaces/builder.js.map +1 -0
- package/dist/types.d.ts +350 -48
- package/dist/utils/instance-lock.d.ts +2 -2
- package/dist/utils/instance-lock.js +2 -2
- package/dist/utils/path-safety.js +1 -1
- package/dist/utils/service-user.d.ts +13 -0
- package/dist/utils/service-user.js +129 -0
- package/dist/utils/service-user.js.map +1 -0
- package/install/jishu-install.sh +107 -27
- package/install/jishu-uninstall.sh +8 -0
- package/install/post-install.sh +162 -185
- package/install/post-uninstall.sh +6 -0
- package/node_modules/@fastify/static/.github/workflows/ci.yml +1 -1
- package/node_modules/@fastify/static/.github/workflows/lock-threads.yml +19 -0
- package/node_modules/@fastify/static/LICENSE +1 -3
- package/node_modules/@fastify/static/example/server-benchmark.js +39 -0
- package/node_modules/@fastify/static/index.js +169 -23
- package/node_modules/@fastify/static/lib/dirList.js +20 -6
- package/node_modules/@fastify/static/package.json +10 -8
- package/node_modules/@fastify/static/test/dir-list.test.js +82 -0
- package/node_modules/@fastify/static/test/static.test.js +326 -4
- package/node_modules/@fastify/static/types/index.d.ts +0 -4
- package/node_modules/@fastify/static/types/index.test-d.ts +1 -1
- package/node_modules/brace-expansion/dist/commonjs/index.js +24 -14
- package/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -1
- package/node_modules/brace-expansion/dist/esm/index.js +24 -14
- package/node_modules/brace-expansion/dist/esm/index.js.map +1 -1
- package/node_modules/brace-expansion/package.json +2 -2
- package/node_modules/content-disposition/README.md +21 -22
- package/node_modules/content-disposition/index.js +122 -44
- package/node_modules/content-disposition/package.json +16 -20
- package/node_modules/fast-uri/index.js +1 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/security.test.js +28 -0
- package/node_modules/fastify/SECURITY.md +1 -1
- package/node_modules/fastify/SPONSORS.md +6 -4
- package/node_modules/fastify/docs/Guides/Database.md +0 -28
- package/node_modules/fastify/docs/Guides/Ecosystem.md +13 -2
- package/node_modules/fastify/docs/Guides/Serverless.md +2 -2
- package/node_modules/fastify/docs/Guides/Write-Plugin.md +1 -1
- package/node_modules/fastify/docs/Reference/Encapsulation.md +27 -26
- package/node_modules/fastify/docs/Reference/Errors.md +10 -4
- package/node_modules/fastify/docs/Reference/HTTP2.md +10 -10
- package/node_modules/fastify/docs/Reference/Hooks.md +4 -4
- package/node_modules/fastify/docs/Reference/Index.md +14 -16
- package/node_modules/fastify/docs/Reference/LTS.md +12 -13
- package/node_modules/fastify/docs/Reference/Lifecycle.md +9 -8
- package/node_modules/fastify/docs/Reference/Logging.md +44 -39
- package/node_modules/fastify/docs/Reference/Middleware.md +21 -25
- package/node_modules/fastify/docs/Reference/Principles.md +2 -2
- package/node_modules/fastify/docs/Reference/Reply.md +6 -1
- package/node_modules/fastify/docs/Reference/Request.md +27 -16
- package/node_modules/fastify/docs/Reference/Routes.md +5 -2
- package/node_modules/fastify/docs/Reference/Server.md +31 -3
- package/node_modules/fastify/docs/Reference/Type-Providers.md +29 -5
- package/node_modules/fastify/docs/Reference/Validation-and-Serialization.md +15 -2
- package/node_modules/fastify/docs/Reference/Warnings.md +7 -6
- package/node_modules/fastify/eslint.config.js +7 -2
- package/node_modules/fastify/fastify.d.ts +8 -3
- package/node_modules/fastify/fastify.js +43 -14
- package/node_modules/fastify/lib/content-type-parser.js +13 -1
- package/node_modules/fastify/lib/decorate.js +11 -3
- package/node_modules/fastify/lib/error-handler.js +4 -3
- package/node_modules/fastify/lib/error-serializer.js +59 -59
- package/node_modules/fastify/lib/errors.js +16 -1
- package/node_modules/fastify/lib/four-oh-four.js +14 -9
- package/node_modules/fastify/lib/handle-request.js +11 -5
- package/node_modules/fastify/lib/plugin-override.js +2 -1
- package/node_modules/fastify/lib/plugin-utils.js +5 -5
- package/node_modules/fastify/lib/reply.js +63 -8
- package/node_modules/fastify/lib/request.js +14 -4
- package/node_modules/fastify/lib/route.js +20 -6
- package/node_modules/fastify/lib/schema-controller.js +1 -1
- package/node_modules/fastify/lib/schemas.js +37 -30
- package/node_modules/fastify/lib/symbols.js +3 -1
- package/node_modules/fastify/lib/validation.js +1 -13
- package/node_modules/fastify/lib/warnings.js +3 -3
- package/node_modules/fastify/package.json +13 -15
- package/node_modules/fastify/scripts/validate-ecosystem-links.js +1 -0
- package/node_modules/fastify/test/bundler/esbuild/package.json +1 -1
- package/node_modules/fastify/test/close-pipelining.test.js +1 -2
- package/node_modules/fastify/test/custom-http-server.test.js +38 -0
- package/node_modules/fastify/test/decorator-instance-properties.test.js +63 -0
- package/node_modules/fastify/test/diagnostics-channel/async-error-handler.test.js +74 -0
- package/node_modules/fastify/test/hooks.test.js +23 -0
- package/node_modules/fastify/test/http-methods/get.test.js +1 -1
- package/node_modules/fastify/test/http2/plain.test.js +135 -0
- package/node_modules/fastify/test/http2/secure-with-fallback.test.js +1 -1
- package/node_modules/fastify/test/https/https.test.js +1 -2
- package/node_modules/fastify/test/internals/errors.test.js +31 -1
- package/node_modules/fastify/test/internals/plugin.test.js +3 -1
- package/node_modules/fastify/test/internals/request.test.js +27 -3
- package/node_modules/fastify/test/internals/schema-controller-perf.test.js +33 -0
- package/node_modules/fastify/test/logger/logging.test.js +18 -1
- package/node_modules/fastify/test/logger/options.test.js +38 -1
- package/node_modules/fastify/test/reply-error.test.js +1 -1
- package/node_modules/fastify/test/reply-trailers.test.js +70 -0
- package/node_modules/fastify/test/request-media-type.test.js +105 -0
- package/node_modules/fastify/test/route-prefix.test.js +34 -0
- package/node_modules/fastify/test/router-options.test.js +222 -11
- package/node_modules/fastify/test/schema-serialization.test.js +108 -0
- package/node_modules/fastify/test/schema-validation.test.js +24 -0
- package/node_modules/fastify/test/scripts/validate-ecosystem-links.test.js +40 -57
- package/node_modules/fastify/test/throw.test.js +14 -0
- package/node_modules/fastify/test/trust-proxy.test.js +21 -0
- package/node_modules/fastify/test/types/content-type-parser.tst.ts +70 -0
- package/node_modules/fastify/test/types/decorate-request-reply.tst.ts +18 -0
- package/node_modules/fastify/test/types/dummy-plugin.mts +9 -0
- package/node_modules/fastify/test/types/errors.tst.ts +91 -0
- package/node_modules/fastify/test/types/fastify.tst.ts +351 -0
- package/node_modules/fastify/test/types/hooks.tst.ts +578 -0
- package/node_modules/fastify/test/types/instance.tst.ts +597 -0
- package/node_modules/fastify/test/types/logger.tst.ts +276 -0
- package/node_modules/fastify/test/types/plugin.tst.ts +96 -0
- package/node_modules/fastify/test/types/register.tst.ts +245 -0
- package/node_modules/fastify/test/types/reply.tst.ts +297 -0
- package/node_modules/fastify/test/types/request.tst.ts +199 -0
- package/node_modules/fastify/test/types/route.tst.ts +576 -0
- package/node_modules/fastify/test/types/schema.tst.ts +135 -0
- package/node_modules/fastify/test/types/serverFactory.tst.ts +37 -0
- package/node_modules/fastify/test/types/tsconfig.json +9 -0
- package/node_modules/fastify/test/types/type-provider.tst.ts +1219 -0
- package/node_modules/fastify/test/types/using.tst.ts +14 -0
- package/node_modules/fastify/types/errors.d.ts +3 -0
- package/node_modules/fastify/types/request.d.ts +23 -2
- package/node_modules/glob/README.md +39 -130
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +4 -0
- package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +4 -0
- package/node_modules/glob/dist/esm/index.min.js.map +7 -0
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/glob/package.json +38 -37
- package/node_modules/jishushell-panel/README.md +4 -4
- package/node_modules/jishushell-panel/output/dist/server.js +17 -6
- package/node_modules/jishushell-panel/output/dist/server.js.map +1 -1
- package/node_modules/jishushell-panel/output/public/assets/ApiKeyField-Ce5d1xna.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/Dashboard-BXame3yg.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/HermesChatPanel-BHZtPCJd.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/HermesConfigForm-CB3GbNX9.js +4 -0
- package/node_modules/jishushell-panel/output/public/assets/InitPassword-Boab9F6g.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-DrIWCqo-.js +14 -0
- package/node_modules/jishushell-panel/output/public/assets/Login-CzpOkNau.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/NewInstance-CANXyCcL.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/ProviderRecommendations-BABo9VOC.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/Settings-CKp5XxFh.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/Setup-C7xVDPow.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/WeixinLoginPanel-B765Xz4C.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/api-C70Gt678.js +4 -0
- package/node_modules/jishushell-panel/output/public/assets/index-Bs6DSbiR.js +23 -0
- package/node_modules/jishushell-panel/output/public/assets/index-DnnqTf7s.css +1 -0
- package/node_modules/jishushell-panel/output/public/assets/registry-sWIZsIEF.js +2 -0
- package/node_modules/jishushell-panel/output/public/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/setup-task-q21GnI0E.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/usePolling-D4IDOQd_.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-Df8aUdv8.js +1 -0
- package/node_modules/jishushell-panel/output/public/assets/vendor-react-0L0rjmYG.js +8 -0
- package/node_modules/jishushell-panel/output/public/index.html +6 -4
- package/node_modules/jishushell-panel/package.json +2 -2
- package/node_modules/semver/classes/range.js +17 -4
- package/node_modules/semver/package.json +2 -2
- package/package.json +12 -64
- package/scripts/check-app-path-boundaries.mjs +121 -0
- package/scripts/check-app-spec.mjs +123 -29
- package/scripts/check-architecture-boundaries.mjs +178 -0
- package/scripts/check-colima-launchd.mjs +10 -8
- package/scripts/check-integration-isolation.ts +541 -0
- package/scripts/check-new-file-tests.mjs +11 -3
- package/scripts/check-open-core-boundaries.mjs +60 -10
- package/scripts/check-test-layering.sh +1 -1
- package/scripts/fixtures/instances/hermes-sample/instance.json +3 -2
- package/scripts/fixtures/instances/legacy-openclaw-sample/instance.json +1 -1
- package/scripts/local-web-upgrade-test.README +4 -3
- package/scripts/local-web-upgrade-test.example.env +2 -2
- package/scripts/local-web-upgrade-test.sh +14 -1
- package/scripts/pack-gui-and-send-pi.sh +43 -0
- package/scripts/perf/instances.js +1 -1
- package/scripts/prune-open-core-dist.mjs +89 -2
- package/scripts/smoke/hermes-bootstrap.sh +5 -5
- package/templates/hermes-entrypoint.sh +19 -29
- package/apps/openwebui-container.yaml +0 -97
- package/apps/playwright-container.yaml +0 -126
- package/dependencies/jishushell-panel-0.6.5.tgz +0 -0
- package/dist/crypto-shim.d.ts +0 -1
- package/dist/crypto-shim.js +0 -2
- package/dist/crypto-shim.js.map +0 -1
- package/dist/routes/agent-apps.d.ts +0 -14
- package/dist/routes/agent-apps.js +0 -77
- package/dist/routes/agent-apps.js.map +0 -1
- package/dist/routes/internal.d.ts +0 -2
- package/dist/routes/internal.js +0 -55
- package/dist/routes/internal.js.map +0 -1
- package/dist/routes/openclaw-routes.d.ts +0 -22
- package/dist/routes/openclaw-routes.js +0 -1020
- package/dist/routes/openclaw-routes.js.map +0 -1
- package/dist/routes/runtime.d.ts +0 -15
- package/dist/routes/runtime.js +0 -76
- package/dist/routes/runtime.js.map +0 -1
- package/dist/services/agent-apps/catalog.d.ts +0 -33
- package/dist/services/agent-apps/catalog.js +0 -88
- package/dist/services/agent-apps/catalog.js.map +0 -1
- package/dist/services/agent-apps/index.d.ts +0 -36
- package/dist/services/agent-apps/index.js +0 -171
- package/dist/services/agent-apps/index.js.map +0 -1
- package/dist/services/agent-apps/installers/adapter-probes.d.ts +0 -49
- package/dist/services/agent-apps/installers/adapter-probes.js +0 -230
- package/dist/services/agent-apps/installers/adapter-probes.js.map +0 -1
- package/dist/services/agent-apps/installers/adapter.d.ts +0 -30
- package/dist/services/agent-apps/installers/adapter.js +0 -171
- package/dist/services/agent-apps/installers/adapter.js.map +0 -1
- package/dist/services/agent-apps/installers/registry-probe.js.map +0 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +0 -47
- package/dist/services/agent-apps/installers/shell-script.js +0 -488
- package/dist/services/agent-apps/installers/shell-script.js.map +0 -1
- package/dist/services/agent-apps/types.d.ts +0 -128
- package/dist/services/agent-apps/types.js +0 -17
- package/dist/services/agent-apps/types.js.map +0 -1
- package/dist/services/app/app-compiler.js +0 -185
- package/dist/services/app/app-compiler.js.map +0 -1
- package/dist/services/app/app-manager.d.ts +0 -184
- package/dist/services/app/app-manager.js +0 -2933
- package/dist/services/app/app-manager.js.map +0 -1
- package/dist/services/app/custom-manager.d.ts +0 -27
- package/dist/services/app/custom-manager.js +0 -382
- package/dist/services/app/custom-manager.js.map +0 -1
- package/dist/services/app/hermes-agent-manager.d.ts +0 -20
- package/dist/services/app/hermes-agent-manager.js +0 -299
- package/dist/services/app/hermes-agent-manager.js.map +0 -1
- package/dist/services/app/id-normalizer.d.ts +0 -27
- package/dist/services/app/id-normalizer.js +0 -77
- package/dist/services/app/id-normalizer.js.map +0 -1
- package/dist/services/app/ollama-manager.d.ts +0 -18
- package/dist/services/app/ollama-manager.js +0 -224
- package/dist/services/app/ollama-manager.js.map +0 -1
- package/dist/services/app/openclaw-manager.d.ts +0 -63
- package/dist/services/app/openclaw-manager.js +0 -1215
- package/dist/services/app/openclaw-manager.js.map +0 -1
- package/dist/services/app/paths.d.ts +0 -27
- package/dist/services/app/paths.js +0 -40
- package/dist/services/app/paths.js.map +0 -1
- package/dist/services/app/platform-transform.d.ts +0 -32
- package/dist/services/app/platform-transform.js +0 -65
- package/dist/services/app/platform-transform.js.map +0 -1
- package/dist/services/app/provide-resolver.d.ts +0 -29
- package/dist/services/app/provide-resolver.js +0 -135
- package/dist/services/app/provide-resolver.js.map +0 -1
- package/dist/services/app/registry.d.ts +0 -17
- package/dist/services/app/registry.js +0 -31
- package/dist/services/app/registry.js.map +0 -1
- package/dist/services/app/remote-spec.d.ts +0 -14
- package/dist/services/app/remote-spec.js +0 -58
- package/dist/services/app/remote-spec.js.map +0 -1
- package/dist/services/app/terminal-session-manager.js +0 -157
- package/dist/services/app/terminal-session-manager.js.map +0 -1
- package/dist/services/app/types.d.ts +0 -74
- package/dist/services/app/types.js +0 -16
- package/dist/services/app/types.js.map +0 -1
- package/dist/services/app-config-admin.d.ts +0 -17
- package/dist/services/app-config-admin.js +0 -177
- package/dist/services/app-config-admin.js.map +0 -1
- package/dist/services/app-create-from-installed.d.ts +0 -23
- package/dist/services/app-create-from-installed.js +0 -75
- package/dist/services/app-create-from-installed.js.map +0 -1
- package/dist/services/app-passwords.js +0 -173
- package/dist/services/app-passwords.js.map +0 -1
- package/dist/services/backup-admin.d.ts +0 -101
- package/dist/services/backup-admin.js +0 -259
- package/dist/services/backup-admin.js.map +0 -1
- package/dist/services/backup-manager.d.ts +0 -264
- package/dist/services/backup-manager.js +0 -2263
- package/dist/services/backup-manager.js.map +0 -1
- package/dist/services/backup-verify.js +0 -240
- package/dist/services/backup-verify.js.map +0 -1
- package/dist/services/capability-endpoint-validator.d.ts +0 -41
- package/dist/services/capability-endpoint-validator.js +0 -114
- package/dist/services/capability-endpoint-validator.js.map +0 -1
- package/dist/services/capability-health.d.ts +0 -16
- package/dist/services/capability-health.js +0 -121
- package/dist/services/capability-health.js.map +0 -1
- package/dist/services/capability-registry.d.ts +0 -29
- package/dist/services/capability-registry.js +0 -176
- package/dist/services/capability-registry.js.map +0 -1
- package/dist/services/capability-sync.d.ts +0 -4
- package/dist/services/capability-sync.js +0 -220
- package/dist/services/capability-sync.js.map +0 -1
- package/dist/services/connection-admin.d.ts +0 -74
- package/dist/services/connection-admin.js +0 -287
- package/dist/services/connection-admin.js.map +0 -1
- package/dist/services/connection-apply.d.ts +0 -91
- package/dist/services/connection-apply.js +0 -471
- package/dist/services/connection-apply.js.map +0 -1
- package/dist/services/connection-resolver.d.ts +0 -65
- package/dist/services/connection-resolver.js +0 -281
- package/dist/services/connection-resolver.js.map +0 -1
- package/dist/services/connection-transactor.d.ts +0 -39
- package/dist/services/connection-transactor.js +0 -354
- package/dist/services/connection-transactor.js.map +0 -1
- package/dist/services/core-manager.d.ts +0 -50
- package/dist/services/core-manager.js +0 -411
- package/dist/services/core-manager.js.map +0 -1
- package/dist/services/external-mounts.js +0 -187
- package/dist/services/external-mounts.js.map +0 -1
- package/dist/services/files-manager.d.ts +0 -252
- package/dist/services/files-manager.js +0 -1156
- package/dist/services/files-manager.js.map +0 -1
- package/dist/services/files-mounts.d.ts +0 -42
- package/dist/services/files-mounts.js +0 -207
- package/dist/services/files-mounts.js.map +0 -1
- package/dist/services/instance-admin.d.ts +0 -26
- package/dist/services/instance-admin.js +0 -218
- package/dist/services/instance-admin.js.map +0 -1
- package/dist/services/instance-manager.d.ts +0 -192
- package/dist/services/instance-manager.js +0 -1289
- package/dist/services/instance-manager.js.map +0 -1
- package/dist/services/macos-launchd.js +0 -312
- package/dist/services/macos-launchd.js.map +0 -1
- package/dist/services/nomad-manager.d.ts +0 -307
- package/dist/services/nomad-manager.js +0 -3958
- package/dist/services/nomad-manager.js.map +0 -1
- package/dist/services/organize/applier.js +0 -218
- package/dist/services/organize/applier.js.map +0 -1
- package/dist/services/organize/rules.js +0 -286
- package/dist/services/organize/rules.js.map +0 -1
- package/dist/services/organize/scanner.js +0 -366
- package/dist/services/organize/scanner.js.map +0 -1
- package/dist/services/organize/store.js +0 -82
- package/dist/services/organize/store.js.map +0 -1
- package/dist/services/plugin-installer.js +0 -128
- package/dist/services/plugin-installer.js.map +0 -1
- package/dist/services/process-manager.d.ts +0 -25
- package/dist/services/process-manager.js +0 -568
- package/dist/services/process-manager.js.map +0 -1
- package/dist/services/runtime/adapters/custom.d.ts +0 -20
- package/dist/services/runtime/adapters/custom.js +0 -188
- package/dist/services/runtime/adapters/custom.js.map +0 -1
- package/dist/services/runtime/adapters/hermes.d.ts +0 -204
- package/dist/services/runtime/adapters/hermes.js +0 -1684
- package/dist/services/runtime/adapters/hermes.js.map +0 -1
- package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +0 -45
- package/dist/services/runtime/adapters/openclaw-mcporter.js +0 -108
- package/dist/services/runtime/adapters/openclaw-mcporter.js.map +0 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +0 -426
- package/dist/services/runtime/adapters/openclaw.js +0 -3975
- package/dist/services/runtime/adapters/openclaw.js.map +0 -1
- package/dist/services/runtime/index.d.ts +0 -34
- package/dist/services/runtime/index.js +0 -51
- package/dist/services/runtime/index.js.map +0 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +0 -46
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +0 -281
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +0 -1
- package/dist/services/runtime/mcp-shims/drive-shim.js +0 -490
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +0 -1
- package/dist/services/runtime/mcp-shims/jishukb-shim.d.ts +0 -48
- package/dist/services/runtime/mcp-shims/jishukb-shim.js +0 -723
- package/dist/services/runtime/mcp-shims/jishukb-shim.js.map +0 -1
- package/dist/services/runtime/mcp-shims/mcporter-lite.js +0 -276
- package/dist/services/runtime/mcp-shims/mcporter-lite.js.map +0 -1
- package/dist/services/runtime/migrations.d.ts +0 -23
- package/dist/services/runtime/migrations.js +0 -125
- package/dist/services/runtime/migrations.js.map +0 -1
- package/dist/services/runtime/registry.d.ts +0 -13
- package/dist/services/runtime/registry.js +0 -32
- package/dist/services/runtime/registry.js.map +0 -1
- package/dist/services/runtime-identity.d.ts +0 -13
- package/dist/services/runtime-identity.js +0 -166
- package/dist/services/runtime-identity.js.map +0 -1
- package/dist/services/runtime-repair.d.ts +0 -52
- package/dist/services/runtime-repair.js +0 -352
- package/dist/services/runtime-repair.js.map +0 -1
- package/dist/services/setup-manager.d.ts +0 -158
- package/dist/services/setup-manager.js +0 -2740
- package/dist/services/setup-manager.js.map +0 -1
- package/dist/services/suggestions.d.ts +0 -27
- package/dist/services/suggestions.js +0 -133
- package/dist/services/suggestions.js.map +0 -1
- package/dist/services/system-monitor.js +0 -79
- package/dist/services/system-monitor.js.map +0 -1
- package/dist/services/system-ollama-provider.d.ts +0 -14
- package/dist/services/system-ollama-provider.js +0 -125
- package/dist/services/system-ollama-provider.js.map +0 -1
- package/dist/services/system-reconciler.d.ts +0 -72
- package/dist/services/system-reconciler.js +0 -600
- package/dist/services/system-reconciler.js.map +0 -1
- package/dist/services/task-registry.d.ts +0 -44
- package/dist/services/task-registry.js +0 -76
- package/dist/services/task-registry.js.map +0 -1
- package/dist/services/types-shim.d.ts +0 -16
- package/dist/services/types-shim.js +0 -2
- package/dist/services/types-shim.js.map +0 -1
- package/dist/services/update-manager.d.ts +0 -47
- package/dist/services/update-manager.js +0 -351
- package/dist/services/update-manager.js.map +0 -1
- package/dist/services/webdav/server.d.ts +0 -24
- package/dist/services/webdav/server.js +0 -420
- package/dist/services/webdav/server.js.map +0 -1
- package/dist/services/webdav/xml-builder.js.map +0 -1
- package/dist/services/workspace-builder.d.ts +0 -29
- package/dist/services/workspace-builder.js +0 -188
- package/dist/services/workspace-builder.js.map +0 -1
- package/node_modules/@fastify/static/.github/stale.yml +0 -21
- package/node_modules/@isaacs/cliui/LICENSE.md +0 -63
- package/node_modules/@isaacs/cliui/README.md +0 -151
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.d.ts +0 -4
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.js +0 -16
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.d.ts +0 -34
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.js +0 -170
- package/node_modules/@isaacs/cliui/dist/commonjs/ansi-styles/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.d.ts +0 -6
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.js +0 -307
- package/node_modules/@isaacs/cliui/dist/commonjs/eastasianwidth/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.js +0 -7
- package/node_modules/@isaacs/cliui/dist/commonjs/emoji-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/index.d.ts +0 -41
- package/node_modules/@isaacs/cliui/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/index.js +0 -322
- package/node_modules/@isaacs/cliui/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/index.min.js +0 -12
- package/node_modules/@isaacs/cliui/dist/commonjs/index.min.js.map +0 -7
- package/node_modules/@isaacs/cliui/dist/commonjs/package.json +0 -3
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.d.ts +0 -5
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.js +0 -49
- package/node_modules/@isaacs/cliui/dist/commonjs/string-width/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.js +0 -8
- package/node_modules/@isaacs/cliui/dist/commonjs/strip-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.d.ts +0 -7
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.js +0 -176
- package/node_modules/@isaacs/cliui/dist/commonjs/wrap-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.d.ts +0 -4
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.js +0 -12
- package/node_modules/@isaacs/cliui/dist/esm/ansi-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.d.ts +0 -34
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.js +0 -167
- package/node_modules/@isaacs/cliui/dist/esm/ansi-styles/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.d.ts +0 -6
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.js +0 -299
- package/node_modules/@isaacs/cliui/dist/esm/eastasianwidth/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.js +0 -3
- package/node_modules/@isaacs/cliui/dist/esm/emoji-regex/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/index.d.ts +0 -41
- package/node_modules/@isaacs/cliui/dist/esm/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/index.js +0 -317
- package/node_modules/@isaacs/cliui/dist/esm/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/index.min.js +0 -12
- package/node_modules/@isaacs/cliui/dist/esm/index.min.js.map +0 -7
- package/node_modules/@isaacs/cliui/dist/esm/package.json +0 -3
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.d.ts +0 -5
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.js +0 -46
- package/node_modules/@isaacs/cliui/dist/esm/string-width/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.d.ts +0 -2
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.js +0 -4
- package/node_modules/@isaacs/cliui/dist/esm/strip-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.d.ts +0 -7
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.d.ts.map +0 -1
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.js +0 -172
- package/node_modules/@isaacs/cliui/dist/esm/wrap-ansi/index.js.map +0 -1
- package/node_modules/@isaacs/cliui/package.json +0 -163
- package/node_modules/content-disposition/HISTORY.md +0 -60
- package/node_modules/cross-spawn/LICENSE +0 -21
- package/node_modules/cross-spawn/README.md +0 -89
- package/node_modules/cross-spawn/index.js +0 -39
- package/node_modules/cross-spawn/lib/enoent.js +0 -59
- package/node_modules/cross-spawn/lib/parse.js +0 -91
- package/node_modules/cross-spawn/lib/util/escape.js +0 -47
- package/node_modules/cross-spawn/lib/util/readShebang.js +0 -23
- package/node_modules/cross-spawn/lib/util/resolveCommand.js +0 -52
- package/node_modules/cross-spawn/package.json +0 -73
- package/node_modules/fastify/test/types/content-type-parser.test-d.ts +0 -72
- package/node_modules/fastify/test/types/decorate-request-reply.test-d.ts +0 -18
- package/node_modules/fastify/test/types/dummy-plugin.ts +0 -9
- package/node_modules/fastify/test/types/errors.test-d.ts +0 -90
- package/node_modules/fastify/test/types/fastify.test-d.ts +0 -352
- package/node_modules/fastify/test/types/hooks.test-d.ts +0 -550
- package/node_modules/fastify/test/types/import.ts +0 -2
- package/node_modules/fastify/test/types/instance.test-d.ts +0 -588
- package/node_modules/fastify/test/types/logger.test-d.ts +0 -277
- package/node_modules/fastify/test/types/plugin.test-d.ts +0 -97
- package/node_modules/fastify/test/types/register.test-d.ts +0 -237
- package/node_modules/fastify/test/types/reply.test-d.ts +0 -254
- package/node_modules/fastify/test/types/request.test-d.ts +0 -188
- package/node_modules/fastify/test/types/route.test-d.ts +0 -553
- package/node_modules/fastify/test/types/schema.test-d.ts +0 -135
- package/node_modules/fastify/test/types/serverFactory.test-d.ts +0 -37
- package/node_modules/fastify/test/types/type-provider.test-d.ts +0 -1213
- package/node_modules/fastify/test/types/using.test-d.ts +0 -17
- package/node_modules/foreground-child/LICENSE +0 -15
- package/node_modules/foreground-child/README.md +0 -128
- package/node_modules/foreground-child/dist/commonjs/all-signals.d.ts +0 -2
- package/node_modules/foreground-child/dist/commonjs/all-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/all-signals.js +0 -58
- package/node_modules/foreground-child/dist/commonjs/all-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/index.d.ts +0 -58
- package/node_modules/foreground-child/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/index.js +0 -123
- package/node_modules/foreground-child/dist/commonjs/index.js.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/package.json +0 -3
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.d.ts +0 -6
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.js +0 -38
- package/node_modules/foreground-child/dist/commonjs/proxy-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/watchdog.d.ts +0 -10
- package/node_modules/foreground-child/dist/commonjs/watchdog.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/commonjs/watchdog.js +0 -50
- package/node_modules/foreground-child/dist/commonjs/watchdog.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/all-signals.d.ts +0 -2
- package/node_modules/foreground-child/dist/esm/all-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/all-signals.js +0 -52
- package/node_modules/foreground-child/dist/esm/all-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/index.d.ts +0 -58
- package/node_modules/foreground-child/dist/esm/index.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/index.js +0 -115
- package/node_modules/foreground-child/dist/esm/index.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/package.json +0 -3
- package/node_modules/foreground-child/dist/esm/proxy-signals.d.ts +0 -6
- package/node_modules/foreground-child/dist/esm/proxy-signals.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/proxy-signals.js +0 -34
- package/node_modules/foreground-child/dist/esm/proxy-signals.js.map +0 -1
- package/node_modules/foreground-child/dist/esm/watchdog.d.ts +0 -10
- package/node_modules/foreground-child/dist/esm/watchdog.d.ts.map +0 -1
- package/node_modules/foreground-child/dist/esm/watchdog.js +0 -46
- package/node_modules/foreground-child/dist/esm/watchdog.js.map +0 -1
- package/node_modules/foreground-child/package.json +0 -106
- package/node_modules/glob/dist/esm/bin.d.mts +0 -3
- package/node_modules/glob/dist/esm/bin.d.mts.map +0 -1
- package/node_modules/glob/dist/esm/bin.mjs +0 -346
- package/node_modules/glob/dist/esm/bin.mjs.map +0 -1
- package/node_modules/isexe/.npmignore +0 -2
- package/node_modules/isexe/LICENSE +0 -15
- package/node_modules/isexe/README.md +0 -51
- package/node_modules/isexe/index.js +0 -57
- package/node_modules/isexe/mode.js +0 -41
- package/node_modules/isexe/package.json +0 -31
- package/node_modules/isexe/test/basic.js +0 -221
- package/node_modules/isexe/windows.js +0 -42
- package/node_modules/jackspeak/LICENSE.md +0 -55
- package/node_modules/jackspeak/README.md +0 -394
- package/node_modules/jackspeak/dist/commonjs/index.d.ts +0 -323
- package/node_modules/jackspeak/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/jackspeak/dist/commonjs/index.js +0 -944
- package/node_modules/jackspeak/dist/commonjs/index.js.map +0 -1
- package/node_modules/jackspeak/dist/commonjs/index.min.js +0 -33
- package/node_modules/jackspeak/dist/commonjs/index.min.js.map +0 -7
- package/node_modules/jackspeak/dist/commonjs/package.json +0 -3
- package/node_modules/jackspeak/dist/esm/index.d.ts +0 -323
- package/node_modules/jackspeak/dist/esm/index.d.ts.map +0 -1
- package/node_modules/jackspeak/dist/esm/index.js +0 -936
- package/node_modules/jackspeak/dist/esm/index.js.map +0 -1
- package/node_modules/jackspeak/dist/esm/index.min.js +0 -33
- package/node_modules/jackspeak/dist/esm/index.min.js.map +0 -7
- package/node_modules/jackspeak/dist/esm/package.json +0 -3
- package/node_modules/jackspeak/package.json +0 -115
- package/node_modules/jishushell-panel/output/public/assets/ApiKeyField-D1i7zWXR.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/Dashboard-sWIvL43F.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/HermesChatPanel-DQ8RyvQY.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/HermesConfigForm-tIbPP1sB.js +0 -4
- package/node_modules/jishushell-panel/output/public/assets/InitPassword-C3Slq3Dd.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-7JqY9tq4.js +0 -92
- package/node_modules/jishushell-panel/output/public/assets/Login-BXLDJlQN.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/NewInstance-dLc5Xrpx.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/ProviderRecommendations-DIAXxesl.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/Settings-Bd5utbBh.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/Setup-Yn9_20FL.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/WeixinLoginPanel-C21doQTJ.js +0 -9
- package/node_modules/jishushell-panel/output/public/assets/index-CCkaIEjn.js +0 -20
- package/node_modules/jishushell-panel/output/public/assets/index-D7qxy-Vh.css +0 -1
- package/node_modules/jishushell-panel/output/public/assets/registry-B2ZQZXWL.js +0 -2
- package/node_modules/jishushell-panel/output/public/assets/usePolling-BFZm4do_.js +0 -1
- package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-DqPtOicc.js +0 -9
- package/node_modules/jishushell-panel/output/public/assets/vendor-react-DW5juQin.js +0 -59
- package/node_modules/package-json-from-dist/LICENSE.md +0 -63
- package/node_modules/package-json-from-dist/README.md +0 -110
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +0 -89
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +0 -1
- package/node_modules/package-json-from-dist/dist/commonjs/index.js +0 -134
- package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +0 -1
- package/node_modules/package-json-from-dist/dist/commonjs/package.json +0 -3
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts +0 -89
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +0 -1
- package/node_modules/package-json-from-dist/dist/esm/index.js +0 -129
- package/node_modules/package-json-from-dist/dist/esm/index.js.map +0 -1
- package/node_modules/package-json-from-dist/dist/esm/package.json +0 -3
- package/node_modules/package-json-from-dist/package.json +0 -68
- package/node_modules/path-key/index.d.ts +0 -40
- package/node_modules/path-key/index.js +0 -16
- package/node_modules/path-key/license +0 -9
- package/node_modules/path-key/package.json +0 -39
- package/node_modules/path-key/readme.md +0 -61
- package/node_modules/safe-buffer/LICENSE +0 -21
- package/node_modules/safe-buffer/README.md +0 -584
- package/node_modules/safe-buffer/index.d.ts +0 -187
- package/node_modules/safe-buffer/index.js +0 -65
- package/node_modules/safe-buffer/package.json +0 -51
- package/node_modules/shebang-command/index.js +0 -19
- package/node_modules/shebang-command/license +0 -9
- package/node_modules/shebang-command/package.json +0 -34
- package/node_modules/shebang-command/readme.md +0 -34
- package/node_modules/shebang-regex/index.d.ts +0 -22
- package/node_modules/shebang-regex/index.js +0 -2
- package/node_modules/shebang-regex/license +0 -9
- package/node_modules/shebang-regex/package.json +0 -35
- package/node_modules/shebang-regex/readme.md +0 -33
- package/node_modules/signal-exit/LICENSE.txt +0 -16
- package/node_modules/signal-exit/README.md +0 -74
- package/node_modules/signal-exit/dist/cjs/browser.d.ts +0 -12
- package/node_modules/signal-exit/dist/cjs/browser.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/cjs/browser.js +0 -10
- package/node_modules/signal-exit/dist/cjs/browser.js.map +0 -1
- package/node_modules/signal-exit/dist/cjs/index.d.ts +0 -48
- package/node_modules/signal-exit/dist/cjs/index.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/cjs/index.js +0 -279
- package/node_modules/signal-exit/dist/cjs/index.js.map +0 -1
- package/node_modules/signal-exit/dist/cjs/package.json +0 -3
- package/node_modules/signal-exit/dist/cjs/signals.d.ts +0 -29
- package/node_modules/signal-exit/dist/cjs/signals.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/cjs/signals.js +0 -42
- package/node_modules/signal-exit/dist/cjs/signals.js.map +0 -1
- package/node_modules/signal-exit/dist/mjs/browser.d.ts +0 -12
- package/node_modules/signal-exit/dist/mjs/browser.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/mjs/browser.js +0 -4
- package/node_modules/signal-exit/dist/mjs/browser.js.map +0 -1
- package/node_modules/signal-exit/dist/mjs/index.d.ts +0 -48
- package/node_modules/signal-exit/dist/mjs/index.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/mjs/index.js +0 -275
- package/node_modules/signal-exit/dist/mjs/index.js.map +0 -1
- package/node_modules/signal-exit/dist/mjs/package.json +0 -3
- package/node_modules/signal-exit/dist/mjs/signals.d.ts +0 -29
- package/node_modules/signal-exit/dist/mjs/signals.d.ts.map +0 -1
- package/node_modules/signal-exit/dist/mjs/signals.js +0 -39
- package/node_modules/signal-exit/dist/mjs/signals.js.map +0 -1
- package/node_modules/signal-exit/package.json +0 -106
- package/node_modules/which/CHANGELOG.md +0 -166
- package/node_modules/which/LICENSE +0 -15
- package/node_modules/which/README.md +0 -54
- package/node_modules/which/bin/node-which +0 -52
- package/node_modules/which/package.json +0 -43
- package/node_modules/which/which.js +0 -125
- package/scripts/check-adapter-isolation.ts +0 -293
- /package/dist/services/{app → app-common}/app-compiler.d.ts +0 -0
- /package/dist/services/{app → app-common}/terminal-session-manager.d.ts +0 -0
- /package/dist/services/{backup-verify.d.ts → backup/backup-verify.d.ts} +0 -0
- /package/dist/services/{external-mounts.d.ts → files/external-mounts.d.ts} +0 -0
- /package/dist/services/{organize → files/organize}/applier.d.ts +0 -0
- /package/dist/services/{organize → files/organize}/rules.d.ts +0 -0
- /package/dist/services/{organize → files/organize}/scanner.d.ts +0 -0
- /package/dist/services/{organize → files/organize}/store.d.ts +0 -0
- /package/dist/services/{webdav → files/webdav}/xml-builder.d.ts +0 -0
- /package/dist/services/{webdav → files/webdav}/xml-builder.js +0 -0
- /package/dist/services/{app-passwords.d.ts → instances/passwords.d.ts} +0 -0
- /package/dist/services/{agent-apps → integrations/installable}/installers/registry-probe.d.ts +0 -0
- /package/dist/services/{agent-apps → integrations/installable}/installers/registry-probe.js +0 -0
- /package/dist/services/{runtime/mcp-shims → integrations/openclaw}/drive-shim.d.ts +0 -0
- /package/dist/services/{runtime/mcp-shims → integrations/openclaw}/mcporter-lite.d.ts +0 -0
- /package/dist/services/{plugin-installer.d.ts → setup/plugin-installer.d.ts} +0 -0
- /package/dist/services/{macos-launchd.d.ts → system/macos-launchd.d.ts} +0 -0
- /package/dist/services/{system-monitor.d.ts → system/system-monitor.d.ts} +0 -0
|
@@ -1,2933 +0,0 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import { existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, chmodSync, chownSync, lstatSync, } from "fs";
|
|
3
|
-
import { homedir, tmpdir } from "os";
|
|
4
|
-
import { basename, extname, join, dirname } from "path";
|
|
5
|
-
import { spawn, spawnSync } from "child_process";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
import { parse, stringify } from "yaml";
|
|
8
|
-
import * as config from "../../config.js";
|
|
9
|
-
import { ensureDirHost } from "../../utils/fs.js";
|
|
10
|
-
import { safeReadJson, safeWriteJson } from "../../utils/safe-json.js";
|
|
11
|
-
import * as instanceServices from "../instance-manager.js";
|
|
12
|
-
import { invalidateRuntimeIdentity, resolveRuntimeIdentity } from "../runtime-identity.js";
|
|
13
|
-
import { withInstanceLock } from "../../utils/instance-lock.js";
|
|
14
|
-
import { createTask, emitTask, getRunningTasks, getTask } from "../task-registry.js";
|
|
15
|
-
import { compileTaskRuntime } from "./app-compiler.js";
|
|
16
|
-
import * as capabilityRegistry from "../capability-registry.js";
|
|
17
|
-
import { resolveProvideEndpoint } from "./provide-resolver.js";
|
|
18
|
-
import { platformTransformSpec } from "./platform-transform.js";
|
|
19
|
-
import { createInvalidSudoPasswordError, isSudoAuthenticationError, isSudoNoNewPrivilegesError, isSudoPasswordRequiredError, prepareSudoAskpassEnv, SUDO_PASSTHROUGH_ENV_KEYS, } from "../../utils/sudo-askpass.js";
|
|
20
|
-
const DEFAULT_LIFECYCLE_PATH_ENTRIES = [
|
|
21
|
-
"/opt/homebrew/bin",
|
|
22
|
-
"/opt/homebrew/sbin",
|
|
23
|
-
"/usr/local/bin",
|
|
24
|
-
"/usr/local/sbin",
|
|
25
|
-
"/usr/bin",
|
|
26
|
-
"/bin",
|
|
27
|
-
"/usr/sbin",
|
|
28
|
-
"/sbin",
|
|
29
|
-
];
|
|
30
|
-
const DEFAULT_LIFECYCLE_PATH = DEFAULT_LIFECYCLE_PATH_ENTRIES.join(":");
|
|
31
|
-
const MACOS_LIFECYCLE_PATH_PROBES = [
|
|
32
|
-
"/Applications/Docker.app/Contents/Resources/bin",
|
|
33
|
-
];
|
|
34
|
-
const ANONYMOUS_DOWNLOAD_IMAGE_ALLOWLIST = new Set([
|
|
35
|
-
"filebrowser/filebrowser:latest",
|
|
36
|
-
"ghcr.io/browserless/chromium:latest",
|
|
37
|
-
"ghcr.io/fmaclen/hollama:latest",
|
|
38
|
-
"ghcr.io/open-webui/open-webui:main",
|
|
39
|
-
"mcr.microsoft.com/playwright:v1.55.0-noble",
|
|
40
|
-
"mintplexlabs/anythingllm:latest",
|
|
41
|
-
"paradedb/paradedb:v0.22.2-pg17",
|
|
42
|
-
"redis:7.0-alpine",
|
|
43
|
-
"searxng/searxng:latest",
|
|
44
|
-
"wechatopenai/weknora-app:latest",
|
|
45
|
-
"wechatopenai/weknora-docreader:latest",
|
|
46
|
-
"wechatopenai/weknora-ui:latest",
|
|
47
|
-
]);
|
|
48
|
-
const ANONYMOUS_DOWNLOAD_IMAGE_REPOSITORY_ALLOWLIST = new Set([
|
|
49
|
-
"ghcr.io/x-aijishu/jishu-kb",
|
|
50
|
-
]);
|
|
51
|
-
function getConfigValue(name) {
|
|
52
|
-
return name in config ? config[name] : undefined;
|
|
53
|
-
}
|
|
54
|
-
function resolveConfigPath(value, fallback) {
|
|
55
|
-
return typeof value === "string" && value.trim() ? value : fallback;
|
|
56
|
-
}
|
|
57
|
-
const JISHUSHELL_HOME = resolveConfigPath(getConfigValue("JISHUSHELL_HOME"), join(process.env.HOME ?? homedir(), ".jishushell"));
|
|
58
|
-
const APPS_DIR = resolveConfigPath(getConfigValue("APPS_DIR"), join(JISHUSHELL_HOME, "apps"));
|
|
59
|
-
const INSTANCES_DIR = resolveConfigPath(getConfigValue("INSTANCES_DIR"), join(JISHUSHELL_HOME, "instances"));
|
|
60
|
-
const CURRENT_JISHUSHELL_VERSION = (() => {
|
|
61
|
-
try {
|
|
62
|
-
const pkg = JSON.parse(readFileSync(new URL("../../../package.json", import.meta.url), "utf-8"));
|
|
63
|
-
return String(pkg.version ?? "0.0.0");
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return "0.0.0";
|
|
67
|
-
}
|
|
68
|
-
})();
|
|
69
|
-
const BUILTIN_SPEC_FILE_RE = /\.ya?ml$/i;
|
|
70
|
-
function builtinAppsDirCandidates() {
|
|
71
|
-
return [
|
|
72
|
-
fileURLToPath(new URL("../../../apps/", import.meta.url)),
|
|
73
|
-
join(process.cwd(), "apps"),
|
|
74
|
-
];
|
|
75
|
-
}
|
|
76
|
-
function resolveBuiltinAppsDir() {
|
|
77
|
-
for (const candidate of builtinAppsDirCandidates()) {
|
|
78
|
-
if (existsSync(candidate))
|
|
79
|
-
return candidate;
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
function parseBuiltinTemplate(fileName, yamlText) {
|
|
84
|
-
let parsed = {};
|
|
85
|
-
try {
|
|
86
|
-
parsed = parse(yamlText) ?? {};
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
const tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
92
|
-
const serviceTasks = tasks.filter((task) => (task?.role ?? "service") === "service");
|
|
93
|
-
const serviceTask = serviceTasks[0] ?? tasks[0] ?? null;
|
|
94
|
-
const serviceRuntime = typeof serviceTask?.runtime === "string" && serviceTask.runtime.trim()
|
|
95
|
-
? serviceTask.runtime.trim()
|
|
96
|
-
: null;
|
|
97
|
-
const id = typeof parsed.id === "string" && parsed.id.trim()
|
|
98
|
-
? parsed.id.trim()
|
|
99
|
-
: basename(fileName, extname(fileName));
|
|
100
|
-
const name = typeof parsed.name === "string" && parsed.name.trim()
|
|
101
|
-
? parsed.name.trim()
|
|
102
|
-
: id;
|
|
103
|
-
const description = typeof parsed.description === "string" ? parsed.description : "";
|
|
104
|
-
const isOllamaTemplate = /^ollama(?:[-_]|$)/i.test(basename(fileName, extname(fileName))) || /^ollama(?:[-_]|$)/i.test(id);
|
|
105
|
-
const suggestedAppType = isOllamaTemplate ? "ollama" : "custom";
|
|
106
|
-
return {
|
|
107
|
-
id,
|
|
108
|
-
fileName,
|
|
109
|
-
name,
|
|
110
|
-
description,
|
|
111
|
-
serviceRuntime,
|
|
112
|
-
instanceCompatible: serviceTasks.length === 1
|
|
113
|
-
&& tasks.every((task) => {
|
|
114
|
-
const runtime = typeof task?.runtime === "string" ? task.runtime.trim() : "";
|
|
115
|
-
return runtime === "container" || runtime === "process";
|
|
116
|
-
})
|
|
117
|
-
&& (serviceRuntime === "container" || serviceRuntime === "process"),
|
|
118
|
-
suggestedAppType,
|
|
119
|
-
yaml: yamlText,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
export function listBuiltinAppSpecs() {
|
|
123
|
-
const dir = resolveBuiltinAppsDir();
|
|
124
|
-
if (!dir)
|
|
125
|
-
return [];
|
|
126
|
-
return readdirSync(dir)
|
|
127
|
-
.filter((entry) => BUILTIN_SPEC_FILE_RE.test(entry))
|
|
128
|
-
.sort((left, right) => left.localeCompare(right))
|
|
129
|
-
.map((fileName) => parseBuiltinTemplate(fileName, readFileSync(join(dir, fileName), "utf-8")))
|
|
130
|
-
.filter((entry) => entry != null);
|
|
131
|
-
}
|
|
132
|
-
function installedAppDir(instanceId) {
|
|
133
|
-
for (const rootDir of [APPS_DIR]) {
|
|
134
|
-
const dir = join(rootDir, instanceId);
|
|
135
|
-
if (existsSync(join(dir, "app-spec.yaml")) && existsSync(join(dir, "manifest.json"))) {
|
|
136
|
-
return dir;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
export function updateInstance(instanceId, name, description) {
|
|
142
|
-
const updatedMeta = instanceServices.updateInstance(instanceId, name, description);
|
|
143
|
-
const appData = getApp(instanceId);
|
|
144
|
-
const appDir = installedAppDir(instanceId);
|
|
145
|
-
if (appData && appDir) {
|
|
146
|
-
const nextSpec = {
|
|
147
|
-
...appData.spec,
|
|
148
|
-
...(name != null ? { name } : {}),
|
|
149
|
-
...(description != null ? { description } : {}),
|
|
150
|
-
};
|
|
151
|
-
const yamlPath = join(appDir, "app-spec.yaml");
|
|
152
|
-
const yamlTmp = yamlPath + ".tmp";
|
|
153
|
-
writeFileSync(yamlTmp, stringify(nextSpec), { mode: 0o644 });
|
|
154
|
-
renameSync(yamlTmp, yamlPath);
|
|
155
|
-
}
|
|
156
|
-
invalidateRuntimeIdentity(instanceId);
|
|
157
|
-
return instanceServices.getInstance(instanceId) ?? updatedMeta;
|
|
158
|
-
}
|
|
159
|
-
function parseComparableVersion(version, label) {
|
|
160
|
-
const normalized = version
|
|
161
|
-
.trim()
|
|
162
|
-
.replace(/^>=\s*/, "")
|
|
163
|
-
.replace(/^v/i, "")
|
|
164
|
-
.replace(/[-+].*$/, "");
|
|
165
|
-
const match = normalized.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
|
|
166
|
-
if (!match) {
|
|
167
|
-
throw new Error(`${label} '${version}' 格式无效,应为 x.y.z`);
|
|
168
|
-
}
|
|
169
|
-
return [
|
|
170
|
-
Number(match[1] ?? 0),
|
|
171
|
-
Number(match[2] ?? 0),
|
|
172
|
-
Number(match[3] ?? 0),
|
|
173
|
-
];
|
|
174
|
-
}
|
|
175
|
-
function compareVersions(left, right) {
|
|
176
|
-
for (let index = 0; index < 3; index++) {
|
|
177
|
-
if (left[index] > right[index])
|
|
178
|
-
return 1;
|
|
179
|
-
if (left[index] < right[index])
|
|
180
|
-
return -1;
|
|
181
|
-
}
|
|
182
|
-
return 0;
|
|
183
|
-
}
|
|
184
|
-
function requiredJishuShellVersion(spec) {
|
|
185
|
-
const required = spec.jishushell?.min_version?.trim();
|
|
186
|
-
if (!required) {
|
|
187
|
-
throw new Error(`App '${spec.id}' 缺少 jishushell.min_version,请先在 YAML 中声明支持的最低 JishuShell 版本`);
|
|
188
|
-
}
|
|
189
|
-
return required;
|
|
190
|
-
}
|
|
191
|
-
function ensureCompatibleJishuShellVersion(spec) {
|
|
192
|
-
const required = requiredJishuShellVersion(spec);
|
|
193
|
-
const current = parseComparableVersion(CURRENT_JISHUSHELL_VERSION, "当前 JishuShell 版本");
|
|
194
|
-
const minimum = parseComparableVersion(required, "jishushell.min_version");
|
|
195
|
-
if (compareVersions(current, minimum) < 0) {
|
|
196
|
-
throw new Error(`当前 JishuShell 版本 ${CURRENT_JISHUSHELL_VERSION} 低于应用要求 ${required},请先升级 JishuShell`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function expandPath(p) {
|
|
200
|
-
if (/^~\/\.jishushell(?:\/|$)/.test(p)) {
|
|
201
|
-
return p.replace(/^~\/\.jishushell(?=\/|$)/, JISHUSHELL_HOME);
|
|
202
|
-
}
|
|
203
|
-
return p.replace(/^~(?=\/|$)/, process.env.HOME ?? homedir());
|
|
204
|
-
}
|
|
205
|
-
function buildDeterministicPath(basePath, extraPaths = []) {
|
|
206
|
-
return [basePath ?? "", DEFAULT_LIFECYCLE_PATH, dirname(process.execPath), ...extraPaths]
|
|
207
|
-
.flatMap((entry) => entry.split(":"))
|
|
208
|
-
.map((entry) => entry.trim())
|
|
209
|
-
.filter(Boolean)
|
|
210
|
-
.filter((entry, index, entries) => entries.indexOf(entry) === index)
|
|
211
|
-
.join(":");
|
|
212
|
-
}
|
|
213
|
-
function buildLifecycleEnv() {
|
|
214
|
-
const extraPaths = process.platform === "darwin"
|
|
215
|
-
? MACOS_LIFECYCLE_PATH_PROBES.filter((entry) => existsSync(entry))
|
|
216
|
-
: [];
|
|
217
|
-
const mergedPath = buildDeterministicPath(process.env.PATH, extraPaths);
|
|
218
|
-
// Surface core callback hooks to lifecycle scripts. post_start can curl
|
|
219
|
-
// ${JISHUSHELL_CORE_URL}/api/internal/* with the internal token to read
|
|
220
|
-
// core-managed state (default provider creds etc.) and self-configure
|
|
221
|
-
// without going through user JWT. Best-effort: if the token file or
|
|
222
|
-
// port lookup fails we just omit the vars and the script gets a clean
|
|
223
|
-
// "missing env" failure path.
|
|
224
|
-
const coreHooks = {};
|
|
225
|
-
try {
|
|
226
|
-
coreHooks.JISHUSHELL_CORE_URL = `http://127.0.0.1:${config.getCorePort()}`;
|
|
227
|
-
coreHooks.JISHUSHELL_INTERNAL_TOKEN = config.getInternalMcpToken();
|
|
228
|
-
// LAN host that *other* services (and the post_start script's curls into
|
|
229
|
-
// its own service) can reach. AnythingLLM binds eth0 via Nomad's
|
|
230
|
-
// `external` host_network, so the panel-host loopback (127.0.0.1) won't
|
|
231
|
-
// reach 18097 — post_start needs to hit the LAN IP to check its own
|
|
232
|
-
// health. getCoreLanHost() returns the same IP Nomad publishes ports on.
|
|
233
|
-
coreHooks.JISHUSHELL_LAN_HOST = config.getCoreLanHost();
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
// tolerate — only post_start cares
|
|
237
|
-
}
|
|
238
|
-
return {
|
|
239
|
-
...process.env,
|
|
240
|
-
HOME: process.env.HOME ?? homedir(),
|
|
241
|
-
PATH: mergedPath,
|
|
242
|
-
...coreHooks,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
function buildSudoWrappedCommand(cmd, args, env, execOptions) {
|
|
246
|
-
const sudoArgs = execOptions?.sudoPassword ? ["-k", "-A"] : ["-n"];
|
|
247
|
-
const envArgs = SUDO_PASSTHROUGH_ENV_KEYS.flatMap((key) => {
|
|
248
|
-
const value = env[key];
|
|
249
|
-
return typeof value === "string" && value.length > 0 ? [`${key}=${value}`] : [];
|
|
250
|
-
});
|
|
251
|
-
return {
|
|
252
|
-
command: "sudo",
|
|
253
|
-
args: [...sudoArgs, "--", "env", ...envArgs, cmd, ...args],
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
function createLifecycleSudoError(stderr, fallbackDisplay, hasPassword) {
|
|
257
|
-
const message = sanitizeTaskLine(stderr).trim();
|
|
258
|
-
if (isSudoNoNewPrivilegesError(message)) {
|
|
259
|
-
return createNoNewPrivilegesSudoError();
|
|
260
|
-
}
|
|
261
|
-
if (isSudoAuthenticationError(message)) {
|
|
262
|
-
return createInvalidSudoPasswordError();
|
|
263
|
-
}
|
|
264
|
-
if (!hasPassword && isSudoPasswordRequiredError(message)) {
|
|
265
|
-
return new Error("该生命周期步骤需要 sudo 密码;请在页面弹窗中输入后重试。");
|
|
266
|
-
}
|
|
267
|
-
if (message) {
|
|
268
|
-
return new Error(message);
|
|
269
|
-
}
|
|
270
|
-
return new Error(`lifecycle sudo step failed: ${fallbackDisplay}`);
|
|
271
|
-
}
|
|
272
|
-
function coreSystemdServicePath() {
|
|
273
|
-
const override = process.env.JISHUSHELL_CORE_SYSTEMD_SERVICE_PATH?.trim();
|
|
274
|
-
return override || "/etc/systemd/system/jishushell.service";
|
|
275
|
-
}
|
|
276
|
-
function isLikelySystemdServiceProcess() {
|
|
277
|
-
return Boolean(process.env.INVOCATION_ID
|
|
278
|
-
|| process.env.JOURNAL_STREAM
|
|
279
|
-
|| process.env.NOTIFY_SOCKET
|
|
280
|
-
|| process.env.JISHUSHELL_CORE_SYSTEMD_SERVICE_PATH?.trim());
|
|
281
|
-
}
|
|
282
|
-
function maybeRepairCoreAutostartNoNewPrivileges() {
|
|
283
|
-
if (!isLikelySystemdServiceProcess())
|
|
284
|
-
return null;
|
|
285
|
-
const servicePath = coreSystemdServicePath();
|
|
286
|
-
if (!existsSync(servicePath))
|
|
287
|
-
return null;
|
|
288
|
-
let unitText = "";
|
|
289
|
-
try {
|
|
290
|
-
unitText = readFileSync(servicePath, "utf-8");
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
return { servicePath, detected: true, updated: false };
|
|
294
|
-
}
|
|
295
|
-
if (!/^\s*NoNewPrivileges\s*=\s*true\s*$/mi.test(unitText)) {
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
const nextText = unitText.replace(/^\s*NoNewPrivileges\s*=\s*true\s*\n?/gim, "");
|
|
299
|
-
if (nextText === unitText) {
|
|
300
|
-
return { servicePath, detected: true, updated: false };
|
|
301
|
-
}
|
|
302
|
-
try {
|
|
303
|
-
writeFileSync(servicePath, nextText);
|
|
304
|
-
return { servicePath, detected: true, updated: true };
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
307
|
-
return { servicePath, detected: true, updated: false };
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
function manualInstallCommandForSpec(spec) {
|
|
311
|
-
if (spec.id === "ollama-binary") {
|
|
312
|
-
return "jishushell app install ollama";
|
|
313
|
-
}
|
|
314
|
-
const builtin = listBuiltinAppSpecs().find((entry) => entry.id === spec.id);
|
|
315
|
-
if (!builtin)
|
|
316
|
-
return null;
|
|
317
|
-
return `jishushell app install ${spec.id}`;
|
|
318
|
-
}
|
|
319
|
-
function createNoNewPrivilegesSudoError(manualInstallCommand) {
|
|
320
|
-
const repair = maybeRepairCoreAutostartNoNewPrivileges();
|
|
321
|
-
const restartCommand = "sudo systemctl daemon-reload && sudo systemctl restart jishushell";
|
|
322
|
-
const parts = ["当前运行环境禁止 sudo 提权(no new privileges),面板内无法继续后续安装。"];
|
|
323
|
-
if (repair?.updated) {
|
|
324
|
-
parts.push(`已从自启文件 ${repair.servicePath} 移除 NoNewPrivileges=true。请在系统终端执行以下命令后重试:\n${restartCommand}`);
|
|
325
|
-
}
|
|
326
|
-
else if (repair?.detected) {
|
|
327
|
-
parts.push(`检测到自启文件 ${repair.servicePath} 仍包含 NoNewPrivileges=true。请在系统终端删除该行后执行:\n${restartCommand}`);
|
|
328
|
-
}
|
|
329
|
-
if (manualInstallCommand) {
|
|
330
|
-
parts.push(`当前安装已停止。你也可以在系统终端手动执行 ${manualInstallCommand}。`);
|
|
331
|
-
}
|
|
332
|
-
return new Error(parts.join("\n"));
|
|
333
|
-
}
|
|
334
|
-
function decorateInstallError(error, spec) {
|
|
335
|
-
const original = error instanceof Error ? error : new Error(String(error));
|
|
336
|
-
if (!isSudoNoNewPrivilegesError(original.message) && !/NoNewPrivileges=true/i.test(original.message)) {
|
|
337
|
-
return original;
|
|
338
|
-
}
|
|
339
|
-
return createNoNewPrivilegesSudoError(manualInstallCommandForSpec(spec) ?? undefined);
|
|
340
|
-
}
|
|
341
|
-
export async function validateSudoPassword(sudoPassword) {
|
|
342
|
-
if (!sudoPassword) {
|
|
343
|
-
throw new Error("请输入 sudo 密码");
|
|
344
|
-
}
|
|
345
|
-
if (typeof process.getuid === "function" && process.getuid() === 0) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const preparedEnv = prepareLifecycleExecEnv({ sudoPassword });
|
|
349
|
-
try {
|
|
350
|
-
await new Promise((resolve, reject) => {
|
|
351
|
-
const child = spawn("sudo", ["-k", "-A", "true"], {
|
|
352
|
-
stdio: ["ignore", "ignore", "pipe"],
|
|
353
|
-
env: preparedEnv.env,
|
|
354
|
-
timeout: 15_000,
|
|
355
|
-
});
|
|
356
|
-
let stderr = "";
|
|
357
|
-
child.stderr?.on("data", (chunk) => {
|
|
358
|
-
stderr += chunk.toString("utf-8");
|
|
359
|
-
});
|
|
360
|
-
child.on("close", (code) => {
|
|
361
|
-
if (code === 0) {
|
|
362
|
-
resolve();
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
const message = sanitizeTaskLine(stderr).trim();
|
|
366
|
-
if (isSudoNoNewPrivilegesError(message)) {
|
|
367
|
-
reject(createNoNewPrivilegesSudoError());
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (isSudoAuthenticationError(message)) {
|
|
371
|
-
reject(createInvalidSudoPasswordError());
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
resolve();
|
|
375
|
-
});
|
|
376
|
-
child.on("error", (_error) => {
|
|
377
|
-
resolve();
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
finally {
|
|
382
|
-
preparedEnv.cleanup();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
function prepareLifecycleExecEnv(execOptions, envOverrides) {
|
|
386
|
-
const env = {
|
|
387
|
-
...buildLifecycleEnv(),
|
|
388
|
-
...(envOverrides ?? {}),
|
|
389
|
-
};
|
|
390
|
-
const sudoPassword = execOptions?.sudoPassword;
|
|
391
|
-
if (!sudoPassword) {
|
|
392
|
-
return { env, cleanup: () => undefined };
|
|
393
|
-
}
|
|
394
|
-
return prepareSudoAskpassEnv(sudoPassword, env);
|
|
395
|
-
}
|
|
396
|
-
function shouldBypassDockerCredentialHelperForDownloadImage(image) {
|
|
397
|
-
const trimmedImage = image.trim();
|
|
398
|
-
return ANONYMOUS_DOWNLOAD_IMAGE_ALLOWLIST.has(trimmedImage)
|
|
399
|
-
|| ANONYMOUS_DOWNLOAD_IMAGE_REPOSITORY_ALLOWLIST.has(normalizeDockerImageRepository(trimmedImage));
|
|
400
|
-
}
|
|
401
|
-
function normalizeDockerImageRepository(image) {
|
|
402
|
-
const digestIndex = image.indexOf("@");
|
|
403
|
-
const imageWithoutDigest = digestIndex >= 0 ? image.slice(0, digestIndex) : image;
|
|
404
|
-
const lastSlashIndex = imageWithoutDigest.lastIndexOf("/");
|
|
405
|
-
const lastColonIndex = imageWithoutDigest.lastIndexOf(":");
|
|
406
|
-
return lastColonIndex > lastSlashIndex
|
|
407
|
-
? imageWithoutDigest.slice(0, lastColonIndex)
|
|
408
|
-
: imageWithoutDigest;
|
|
409
|
-
}
|
|
410
|
-
function createAnonymousDockerConfig() {
|
|
411
|
-
const dockerConfigDir = mkdtempSync(join(tmpdir(), "jishushell-docker-config-"));
|
|
412
|
-
writeFileSync(join(dockerConfigDir, "config.json"), `${JSON.stringify({ auths: {} }, null, 2)}\n`, { mode: 0o600 });
|
|
413
|
-
return {
|
|
414
|
-
envOverrides: {
|
|
415
|
-
DOCKER_CONFIG: dockerConfigDir,
|
|
416
|
-
},
|
|
417
|
-
cleanup: () => {
|
|
418
|
-
try {
|
|
419
|
-
rmSync(dockerConfigDir, { recursive: true, force: true });
|
|
420
|
-
}
|
|
421
|
-
catch {
|
|
422
|
-
// best effort cleanup for one-shot anonymous docker config files
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
const ANSI_ESCAPE_RE = /\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
428
|
-
function sanitizeTaskLine(line) {
|
|
429
|
-
return line
|
|
430
|
-
.replace(ANSI_ESCAPE_RE, "")
|
|
431
|
-
.replace(/[\u0000-\u0008\u000B-\u001F\u007F]/g, "")
|
|
432
|
-
.trimEnd();
|
|
433
|
-
}
|
|
434
|
-
function emitInstallTaskLog(task, message) {
|
|
435
|
-
if (!task)
|
|
436
|
-
return;
|
|
437
|
-
const line = sanitizeTaskLine(message).trim();
|
|
438
|
-
if (!line)
|
|
439
|
-
return;
|
|
440
|
-
emitTask(task, { type: "log", message: line });
|
|
441
|
-
}
|
|
442
|
-
function emitAppTaskProgress(task, message, progress) {
|
|
443
|
-
if (!task)
|
|
444
|
-
return;
|
|
445
|
-
const line = sanitizeTaskLine(message).trim();
|
|
446
|
-
if (!line)
|
|
447
|
-
return;
|
|
448
|
-
emitTask(task, { type: "progress", message: line, ...(typeof progress === "number" ? { progress } : {}) });
|
|
449
|
-
}
|
|
450
|
-
function normalizePortVisibility(visibility) {
|
|
451
|
-
if (!visibility || visibility === "external" || visibility === "public") {
|
|
452
|
-
return "external";
|
|
453
|
-
}
|
|
454
|
-
if (visibility === "internal") {
|
|
455
|
-
return visibility;
|
|
456
|
-
}
|
|
457
|
-
throw new Error(`port visibility '${visibility}' 仅支持 external 或 internal`);
|
|
458
|
-
}
|
|
459
|
-
function normalizeAppSpec(spec) {
|
|
460
|
-
const normalizedProvides = (spec.provides ?? []).map((provide) => {
|
|
461
|
-
if (spec.id === "browserless-chromium-container"
|
|
462
|
-
&& provide.capability === BROWSERLESS_DEBUGGER_CAPABILITY
|
|
463
|
-
&& (provide.path === "/" || provide.path === "/debugger")) {
|
|
464
|
-
return { ...provide, path: "/debugger/" };
|
|
465
|
-
}
|
|
466
|
-
if (spec.id === "browserless-chromium-container"
|
|
467
|
-
&& provide.capability === BROWSERLESS_DOCS_CAPABILITY
|
|
468
|
-
&& provide.path === "/docs") {
|
|
469
|
-
return { ...provide, path: "/docs/" };
|
|
470
|
-
}
|
|
471
|
-
if (spec.id === "browserless-chromium-container"
|
|
472
|
-
&& provide.capability === BROWSERLESS_API_CAPABILITY
|
|
473
|
-
&& provide.path !== "/") {
|
|
474
|
-
return { ...provide, path: "/" };
|
|
475
|
-
}
|
|
476
|
-
if (spec.id === "jishu-kb-container"
|
|
477
|
-
&& provide.capability === "web-jishukb"
|
|
478
|
-
&& provide.embedded !== "proxy") {
|
|
479
|
-
return { ...provide, embedded: "proxy" };
|
|
480
|
-
}
|
|
481
|
-
return provide;
|
|
482
|
-
});
|
|
483
|
-
// Inject browserless-api capability for legacy installed specs that lack it.
|
|
484
|
-
if (spec.id === "browserless-chromium-container"
|
|
485
|
-
&& normalizedProvides.length > 0
|
|
486
|
-
&& !normalizedProvides.some((p) => p.capability === BROWSERLESS_API_CAPABILITY)) {
|
|
487
|
-
const debugger_ = normalizedProvides.find((p) => p.capability === BROWSERLESS_DEBUGGER_CAPABILITY);
|
|
488
|
-
if (debugger_) {
|
|
489
|
-
normalizedProvides.push({
|
|
490
|
-
capability: BROWSERLESS_API_CAPABILITY,
|
|
491
|
-
port: debugger_.port ?? 3000,
|
|
492
|
-
path: "/",
|
|
493
|
-
protocol: "http",
|
|
494
|
-
description: "Browserless 根 API(供调试器页面连接 ws 与 sessions 接口)",
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
const normalizedTasks = (spec.tasks ?? []).map((task) => {
|
|
499
|
-
const rawTask = { ...task };
|
|
500
|
-
if (!rawTask.role) {
|
|
501
|
-
rawTask.role = "service";
|
|
502
|
-
}
|
|
503
|
-
if (!rawTask.command && rawTask.binary) {
|
|
504
|
-
rawTask.command = rawTask.binary;
|
|
505
|
-
}
|
|
506
|
-
if (Array.isArray(rawTask.ports)) {
|
|
507
|
-
rawTask.ports = rawTask.ports.map((port) => ({
|
|
508
|
-
...port,
|
|
509
|
-
visibility: normalizePortVisibility(port.visibility),
|
|
510
|
-
}));
|
|
511
|
-
}
|
|
512
|
-
if (spec.id === "jishu-kb-container" && rawTask.name === "jishukb") {
|
|
513
|
-
const env = rawTask.env && typeof rawTask.env === "object"
|
|
514
|
-
? { ...rawTask.env }
|
|
515
|
-
: {};
|
|
516
|
-
const frameAncestors = typeof env.JISHU_KB_FRAME_ANCESTORS === "string"
|
|
517
|
-
? env.JISHU_KB_FRAME_ANCESTORS.trim()
|
|
518
|
-
: "";
|
|
519
|
-
// Keep legacy KB specs iframe-safe even though the embedded UI now runs
|
|
520
|
-
// through the capability proxy. Users can still open the direct port
|
|
521
|
-
// manually, and older installs may still rely on permissive upstream
|
|
522
|
-
// framing until they restart onto the proxied path.
|
|
523
|
-
// Repair legacy installed specs here (not via Nomad extraEnv) because
|
|
524
|
-
// task.env wins over extraEnv during job materialization.
|
|
525
|
-
if (!frameAncestors || frameAncestors === "'self'") {
|
|
526
|
-
env.JISHU_KB_FRAME_ANCESTORS = JISHU_KB_FRAME_ANCESTORS_ALLOW_ALL;
|
|
527
|
-
}
|
|
528
|
-
rawTask.env = env;
|
|
529
|
-
}
|
|
530
|
-
return rawTask;
|
|
531
|
-
});
|
|
532
|
-
let normalizedLifecycle = spec.lifecycle ? { ...spec.lifecycle } : undefined;
|
|
533
|
-
if (spec.id === "jishu-kb-container" && normalizedLifecycle?.post_start?.length) {
|
|
534
|
-
const postStart = normalizedLifecycle.post_start.map((step) => {
|
|
535
|
-
if (typeof step?.run !== "string")
|
|
536
|
-
return step;
|
|
537
|
-
const run = normalizeJishuKbPostStartRun(step.run);
|
|
538
|
-
return run === step.run
|
|
539
|
-
? step
|
|
540
|
-
: { ...step, run };
|
|
541
|
-
});
|
|
542
|
-
normalizedLifecycle = {
|
|
543
|
-
...(normalizedLifecycle ?? {}),
|
|
544
|
-
post_start: postStart,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
if (spec.id === "browserless-chromium-container") {
|
|
548
|
-
const browserlessDataDir = `~/.jishushell/apps/${spec.app_id || spec.id}/data`;
|
|
549
|
-
const browserlessDataDirTemplate = "~/.jishushell/apps/${app_id}/data";
|
|
550
|
-
const browserlessTask = normalizedTasks.find((task) => task.name === "browserless");
|
|
551
|
-
if (browserlessTask) {
|
|
552
|
-
const dataDirTarget = "/tmp/browserless-data";
|
|
553
|
-
const existingVolumes = Array.isArray(browserlessTask.volumes) ? [...browserlessTask.volumes] : [];
|
|
554
|
-
const hasDataDirVolume = existingVolumes.some((volume) => typeof volume === "object" && volume !== null && volume.target === dataDirTarget);
|
|
555
|
-
if (!hasDataDirVolume) {
|
|
556
|
-
existingVolumes.push({ source: browserlessDataDir, target: dataDirTarget });
|
|
557
|
-
}
|
|
558
|
-
browserlessTask.volumes = existingVolumes;
|
|
559
|
-
}
|
|
560
|
-
const install = [...(normalizedLifecycle?.install ?? [])];
|
|
561
|
-
if (!install.some((step) => "mkdir" in step && (step.mkdir === browserlessDataDir || step.mkdir === browserlessDataDirTemplate))) {
|
|
562
|
-
install.push({ mkdir: browserlessDataDir });
|
|
563
|
-
}
|
|
564
|
-
const preStart = [...(normalizedLifecycle?.pre_start ?? [])];
|
|
565
|
-
if (!preStart.some((step) => "mkdir" in step && (step.mkdir === browserlessDataDir || step.mkdir === browserlessDataDirTemplate))) {
|
|
566
|
-
preStart.push({ mkdir: browserlessDataDir });
|
|
567
|
-
}
|
|
568
|
-
normalizedLifecycle = {
|
|
569
|
-
...(normalizedLifecycle ?? {}),
|
|
570
|
-
install,
|
|
571
|
-
pre_start: preStart,
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
function normalizeJishuKbPostStartRun(run) {
|
|
575
|
-
if (run.includes("fetch_default_provider_json()"))
|
|
576
|
-
return run;
|
|
577
|
-
const marker = 'echo "[post_start] fetching core default provider ..."';
|
|
578
|
-
const readyLine = `READY=$(printf '%s' "$DP_JSON" | jget ready)`;
|
|
579
|
-
if (!run.includes(marker)
|
|
580
|
-
|| !run.includes('"$JISHUSHELL_CORE_URL/api/internal/default-provider"')
|
|
581
|
-
|| !run.includes(`|| echo '{"ready":false,"reason":"core_unreachable"}'`)) {
|
|
582
|
-
return run;
|
|
583
|
-
}
|
|
584
|
-
const start = run.indexOf(marker);
|
|
585
|
-
const end = run.indexOf(readyLine, start);
|
|
586
|
-
if (start < 0 || end < 0)
|
|
587
|
-
return run;
|
|
588
|
-
return `${run.slice(0, start)}${JISHU_KB_DEFAULT_PROVIDER_RETRY_BLOCK}${run.slice(end + readyLine.length)}`;
|
|
589
|
-
}
|
|
590
|
-
return {
|
|
591
|
-
...spec,
|
|
592
|
-
...(spec.provides ? { provides: normalizedProvides } : {}),
|
|
593
|
-
tasks: normalizedTasks,
|
|
594
|
-
...(normalizedLifecycle ? { lifecycle: normalizedLifecycle } : {}),
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
function imageReferencedByOtherInstalledApps(currentAppId, imagePath) {
|
|
598
|
-
return listApps().some((app) => app.manifest.id !== currentAppId && (app.spec.tasks.some((task) => task.image === imagePath)
|
|
599
|
-
|| app.manifest.artifacts?.some((artifact) => artifact.type === "image" && artifact.path === imagePath)
|
|
600
|
-
|| app.spec.lifecycle?.install?.some((step) => "downloadImage" in step && step.downloadImage === imagePath)));
|
|
601
|
-
}
|
|
602
|
-
function shouldDeleteImageForApp(appData, imagePath) {
|
|
603
|
-
if (appData.spec.singleInstance === false)
|
|
604
|
-
return false;
|
|
605
|
-
return !imageReferencedByOtherInstalledApps(appData.manifest.id, imagePath);
|
|
606
|
-
}
|
|
607
|
-
function selectUninstallLifecycleSteps(appData) {
|
|
608
|
-
if (!appData?.spec.lifecycle?.uninstall?.length)
|
|
609
|
-
return undefined;
|
|
610
|
-
return appData.spec.lifecycle.uninstall.filter((step) => !("deleteImage" in step) || shouldDeleteImageForApp(appData, step.deleteImage));
|
|
611
|
-
}
|
|
612
|
-
function selectCleanupArtifacts(appData) {
|
|
613
|
-
if (!appData?.manifest.artifacts?.length)
|
|
614
|
-
return [];
|
|
615
|
-
const lifecycleImageDeletes = new Set((selectUninstallLifecycleSteps(appData) ?? [])
|
|
616
|
-
.flatMap((step) => ("deleteImage" in step ? [step.deleteImage] : [])));
|
|
617
|
-
return appData.manifest.artifacts.filter((artifact) => {
|
|
618
|
-
if (artifact.type !== "image")
|
|
619
|
-
return true;
|
|
620
|
-
if (lifecycleImageDeletes.has(artifact.path))
|
|
621
|
-
return false;
|
|
622
|
-
return shouldDeleteImageForApp(appData, artifact.path);
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
async function deleteLinkedInstances(appId) {
|
|
626
|
-
const warnings = [];
|
|
627
|
-
const instanceManager = await import("../instance-manager.js");
|
|
628
|
-
const linkedInstances = instanceManager
|
|
629
|
-
.listInstances()
|
|
630
|
-
.filter((instance) => instance?.id && instance.id !== appId && instance.app_id === appId);
|
|
631
|
-
if (linkedInstances.length === 0)
|
|
632
|
-
return warnings;
|
|
633
|
-
const { stopNomadJobInstance: stopInstance } = await import("../nomad-manager.js");
|
|
634
|
-
let processManager = null;
|
|
635
|
-
try {
|
|
636
|
-
processManager = await import("../process-manager.js");
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
processManager = null;
|
|
640
|
-
}
|
|
641
|
-
for (const instance of linkedInstances) {
|
|
642
|
-
const instanceId = String(instance.id);
|
|
643
|
-
let stopFailed = false;
|
|
644
|
-
try {
|
|
645
|
-
const stopped = await stopInstance(instanceId, true);
|
|
646
|
-
if (!stopped.ok) {
|
|
647
|
-
const fallback = await stopInstance(instanceId);
|
|
648
|
-
if (!fallback.ok)
|
|
649
|
-
stopFailed = true;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
catch {
|
|
653
|
-
stopFailed = true;
|
|
654
|
-
}
|
|
655
|
-
if (processManager) {
|
|
656
|
-
try {
|
|
657
|
-
if ((await processManager.getLegacyStatus(instanceId)).status === "running") {
|
|
658
|
-
await processManager.stopInstance(instanceId);
|
|
659
|
-
stopFailed = false;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
catch {
|
|
663
|
-
// best effort
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
const result = await instanceManager.deleteInstance(instanceId);
|
|
667
|
-
if (!result.ok) {
|
|
668
|
-
warnings.push(`关联实例 '${instanceId}' 删除失败`);
|
|
669
|
-
}
|
|
670
|
-
if (result.warnings?.length) {
|
|
671
|
-
warnings.push(...result.warnings.map((warning) => `关联实例 '${instanceId}': ${warning}`));
|
|
672
|
-
}
|
|
673
|
-
if (stopFailed) {
|
|
674
|
-
warnings.push(`关联实例 '${instanceId}' 停止失败,可能残留进程`);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
return warnings;
|
|
678
|
-
}
|
|
679
|
-
function getProvidePort(spec, provide) {
|
|
680
|
-
if (typeof provide.url === "string" && provide.url.trim()) {
|
|
681
|
-
return null;
|
|
682
|
-
}
|
|
683
|
-
if (typeof provide.port === "number") {
|
|
684
|
-
return provide.port;
|
|
685
|
-
}
|
|
686
|
-
const firstPort = spec.tasks.find((task) => task.role === "service")?.ports?.[0];
|
|
687
|
-
if (!firstPort)
|
|
688
|
-
return null;
|
|
689
|
-
const p = firstPort.host_port ?? firstPort.port;
|
|
690
|
-
return typeof p === "number" && p > 0 ? p : null;
|
|
691
|
-
}
|
|
692
|
-
function getProvideUrl(provide) {
|
|
693
|
-
const raw = typeof provide.url === "string" ? provide.url.trim() : "";
|
|
694
|
-
if (!raw)
|
|
695
|
-
return null;
|
|
696
|
-
try {
|
|
697
|
-
const parsed = new URL(raw);
|
|
698
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
699
|
-
return null;
|
|
700
|
-
}
|
|
701
|
-
return parsed.toString();
|
|
702
|
-
}
|
|
703
|
-
catch {
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
function buildCapabilityAddress(port, path) {
|
|
708
|
-
const host = port > 0 ? instanceServices.getAdvertisedHostForPort(port) : "127.0.0.1";
|
|
709
|
-
if (!path) {
|
|
710
|
-
return `${host}:${port}`;
|
|
711
|
-
}
|
|
712
|
-
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
713
|
-
return `${host}:${port}${normalizedPath}`;
|
|
714
|
-
}
|
|
715
|
-
// docker0 bridge gateway (host.docker.internal target). A port published only
|
|
716
|
-
// on this address is reachable by peer containers but NOT by a remote browser,
|
|
717
|
-
// so it must never be advertised as the host for an embedded UI iframe.
|
|
718
|
-
function isDockerBridgeHost(host) {
|
|
719
|
-
return host === "172.17.0.1";
|
|
720
|
-
}
|
|
721
|
-
function normalizeProvideProtocol(protocol) {
|
|
722
|
-
const raw = typeof protocol === "string" ? protocol.trim().toLowerCase() : "";
|
|
723
|
-
return raw || "http";
|
|
724
|
-
}
|
|
725
|
-
function resolveProvideProtocol(provide) {
|
|
726
|
-
const url = getProvideUrl(provide);
|
|
727
|
-
if (url) {
|
|
728
|
-
try {
|
|
729
|
-
return new URL(url).protocol.replace(/:$/, "").toLowerCase();
|
|
730
|
-
}
|
|
731
|
-
catch {
|
|
732
|
-
return normalizeProvideProtocol(provide.protocol);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return normalizeProvideProtocol(provide.protocol);
|
|
736
|
-
}
|
|
737
|
-
function collectDeclaredPorts(spec) {
|
|
738
|
-
return spec.tasks.flatMap((task) => (task.ports ?? [])
|
|
739
|
-
.map((port) => port.host_port ?? port.port)
|
|
740
|
-
.filter((port) => Number.isInteger(port) && port > 0 && port <= 65535));
|
|
741
|
-
}
|
|
742
|
-
function buildPortShiftMap(spec, offset) {
|
|
743
|
-
const portShiftMap = new Map();
|
|
744
|
-
for (const port of collectDeclaredPorts(spec)) {
|
|
745
|
-
const shiftedPort = port + offset;
|
|
746
|
-
if (shiftedPort < 1 || shiftedPort > 65535) {
|
|
747
|
-
throw new Error(`App '${spec.id}' 没有可用端口,端口递增已超出 65535`);
|
|
748
|
-
}
|
|
749
|
-
portShiftMap.set(port, shiftedPort);
|
|
750
|
-
}
|
|
751
|
-
return portShiftMap;
|
|
752
|
-
}
|
|
753
|
-
function replaceAppScopedPaths(value, baseId, appId) {
|
|
754
|
-
if (baseId === appId)
|
|
755
|
-
return value;
|
|
756
|
-
const homeDir = homedir();
|
|
757
|
-
const replacements = [
|
|
758
|
-
[`~/.jishushell/apps/${baseId}`, `~/.jishushell/apps/${appId}`],
|
|
759
|
-
[`$HOME/.jishushell/apps/${baseId}`, `$HOME/.jishushell/apps/${appId}`],
|
|
760
|
-
[`${homeDir}/.jishushell/apps/${baseId}`, `${homeDir}/.jishushell/apps/${appId}`],
|
|
761
|
-
[`\${HOME}/.jishushell/apps/${baseId}`, `\${HOME}/.jishushell/apps/${appId}`],
|
|
762
|
-
];
|
|
763
|
-
let rewritten = value;
|
|
764
|
-
for (const [from, to] of replacements) {
|
|
765
|
-
rewritten = rewritten.split(from).join(to);
|
|
766
|
-
}
|
|
767
|
-
return rewritten;
|
|
768
|
-
}
|
|
769
|
-
function replaceAppIdTokens(value, appId) {
|
|
770
|
-
return value
|
|
771
|
-
.replace(/\$\{app_id\}/g, appId)
|
|
772
|
-
.replace(/\$\{app\.id\}/g, appId);
|
|
773
|
-
}
|
|
774
|
-
function replacePortTokens(value, portShiftMap) {
|
|
775
|
-
let rewritten = value;
|
|
776
|
-
const replacements = [...portShiftMap.entries()].sort((left, right) => right[0] - left[0]);
|
|
777
|
-
for (const [fromPort, toPort] of replacements) {
|
|
778
|
-
const pattern = new RegExp(`(^|[^0-9A-Za-z])${fromPort}($|[^0-9A-Za-z])`, "g");
|
|
779
|
-
rewritten = rewritten.replace(pattern, (_match, prefix, suffix) => `${prefix}${toPort}${suffix}`);
|
|
780
|
-
}
|
|
781
|
-
return rewritten;
|
|
782
|
-
}
|
|
783
|
-
function rewriteInstalledSpecValue(value, baseId, appId, portShiftMap) {
|
|
784
|
-
if (typeof value === "string") {
|
|
785
|
-
const hasAppIdToken = value.includes("${app_id}") || value.includes("${app.id}");
|
|
786
|
-
return replacePortTokens(hasAppIdToken
|
|
787
|
-
? replaceAppIdTokens(value, appId)
|
|
788
|
-
: replaceAppScopedPaths(value, baseId, appId), portShiftMap);
|
|
789
|
-
}
|
|
790
|
-
if (typeof value === "number") {
|
|
791
|
-
return portShiftMap.get(value) ?? value;
|
|
792
|
-
}
|
|
793
|
-
if (Array.isArray(value)) {
|
|
794
|
-
return value.map((entry) => rewriteInstalledSpecValue(entry, baseId, appId, portShiftMap));
|
|
795
|
-
}
|
|
796
|
-
if (value && typeof value === "object") {
|
|
797
|
-
const rewritten = {};
|
|
798
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
799
|
-
rewritten[key] = rewriteInstalledSpecValue(entry, baseId, appId, portShiftMap);
|
|
800
|
-
}
|
|
801
|
-
return rewritten;
|
|
802
|
-
}
|
|
803
|
-
return value;
|
|
804
|
-
}
|
|
805
|
-
function materializeInstalledSpec(spec, appId, offset) {
|
|
806
|
-
const portShiftMap = buildPortShiftMap(spec, offset);
|
|
807
|
-
const rewritten = rewriteInstalledSpecValue(spec, spec.id, appId, portShiftMap);
|
|
808
|
-
const derivedName = deriveInstalledDisplayName(spec, appId);
|
|
809
|
-
const normalized = normalizeAppSpec({
|
|
810
|
-
...rewritten,
|
|
811
|
-
id: spec.id,
|
|
812
|
-
...(derivedName ? { name: derivedName } : {}),
|
|
813
|
-
});
|
|
814
|
-
// Final step: drop spec fields that work on the spec author's Linux
|
|
815
|
-
// baseline but break on the host platform (e.g. host_network:
|
|
816
|
-
// docker_bridge / user: "host" on darwin). Identity pass-through on
|
|
817
|
-
// Linux — see platform-transform.ts for the rule list.
|
|
818
|
-
return platformTransformSpec(normalized);
|
|
819
|
-
}
|
|
820
|
-
function deriveInstalledDisplayName(spec, appId) {
|
|
821
|
-
const baseName = typeof spec.name === "string" && spec.name.trim() ? spec.name.trim() : spec.id;
|
|
822
|
-
if (!baseName)
|
|
823
|
-
return appId;
|
|
824
|
-
if (appId === spec.id)
|
|
825
|
-
return baseName;
|
|
826
|
-
if (!appId.startsWith(`${spec.id}-`))
|
|
827
|
-
return baseName;
|
|
828
|
-
const suffix = appId.slice(spec.id.length + 1).trim();
|
|
829
|
-
return suffix ? `${baseName}-${suffix}` : baseName;
|
|
830
|
-
}
|
|
831
|
-
function formatInstalledAppId(baseId, instancePrefix, offset) {
|
|
832
|
-
if (offset === 0)
|
|
833
|
-
return baseId;
|
|
834
|
-
return `${baseId}-${instancePrefix}${offset}`;
|
|
835
|
-
}
|
|
836
|
-
// Delegate to the canonical multi-locus probe in instance-manager so macOS
|
|
837
|
-
// loopback-only listeners (e.g. a natively-installed openclaw bound to
|
|
838
|
-
// 127.0.0.1:18789) are visible to AppSpec port conflict detection as well.
|
|
839
|
-
// Wrap in a thunk so the lookup happens at call time — some test suites
|
|
840
|
-
// partially mock instance-manager without re-exporting this symbol.
|
|
841
|
-
function isPortInUse(port) {
|
|
842
|
-
return instanceServices.isPortInUse(port);
|
|
843
|
-
}
|
|
844
|
-
async function resolveInstallTarget(spec, originalSpecYaml, requestedAppId) {
|
|
845
|
-
const baseId = spec.id;
|
|
846
|
-
const multiInstance = spec.singleInstance === false;
|
|
847
|
-
const explicitAppId = requestedAppId?.trim();
|
|
848
|
-
if (explicitAppId && !APP_ID_RE.test(explicitAppId)) {
|
|
849
|
-
throw new Error(`App id '${explicitAppId}' 格式无效,必须符合 /^[a-z0-9][a-z0-9-]{0,62}$/`);
|
|
850
|
-
}
|
|
851
|
-
const installedSameSpec = listApps().find((app) => app.spec.id === baseId);
|
|
852
|
-
if (!multiInstance) {
|
|
853
|
-
if (installedSameSpec) {
|
|
854
|
-
const err = new Error(`App '${baseId}' 已安装为 '${installedSameSpec.manifest.id}',如需多实例请设置 singleInstance: false`);
|
|
855
|
-
err.code = 409;
|
|
856
|
-
throw err;
|
|
857
|
-
}
|
|
858
|
-
const appId = explicitAppId ?? baseId;
|
|
859
|
-
if (appId !== baseId && resolveAppDir(appId)) {
|
|
860
|
-
const err = new Error(`App '${appId}' 已安装`);
|
|
861
|
-
err.code = 409;
|
|
862
|
-
throw err;
|
|
863
|
-
}
|
|
864
|
-
const installedSpec = materializeInstalledSpec(spec, appId, 0);
|
|
865
|
-
return {
|
|
866
|
-
appId,
|
|
867
|
-
installedSpec,
|
|
868
|
-
installedSpecYaml: stringify(installedSpec),
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
const usedPorts = new Set();
|
|
872
|
-
for (const installedApp of listApps()) {
|
|
873
|
-
for (const port of collectDeclaredPorts(installedApp.spec)) {
|
|
874
|
-
usedPorts.add(port);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
const instancePrefix = spec.instancePrefix ?? "";
|
|
878
|
-
const declaredPorts = collectDeclaredPorts(spec);
|
|
879
|
-
const maxBasePort = declaredPorts.length > 0 ? Math.max(...declaredPorts) : 0;
|
|
880
|
-
const maxOffset = declaredPorts.length > 0 ? 65535 - maxBasePort : 9999;
|
|
881
|
-
if (explicitAppId) {
|
|
882
|
-
if (resolveAppDir(explicitAppId)) {
|
|
883
|
-
const err = new Error(`App '${explicitAppId}' 已安装`);
|
|
884
|
-
err.code = 409;
|
|
885
|
-
throw err;
|
|
886
|
-
}
|
|
887
|
-
for (let offset = 0; offset <= maxOffset; offset++) {
|
|
888
|
-
const installedSpec = materializeInstalledSpec(spec, explicitAppId, offset);
|
|
889
|
-
const candidatePorts = collectDeclaredPorts(installedSpec);
|
|
890
|
-
let portConflict = false;
|
|
891
|
-
for (const port of candidatePorts) {
|
|
892
|
-
if (usedPorts.has(port) || await isPortInUse(port)) {
|
|
893
|
-
portConflict = true;
|
|
894
|
-
break;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
if (portConflict)
|
|
898
|
-
continue;
|
|
899
|
-
return {
|
|
900
|
-
appId: explicitAppId,
|
|
901
|
-
installedSpec,
|
|
902
|
-
installedSpecYaml: stringify(installedSpec),
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
throw new Error(`App '${baseId}' 使用指定 id '${explicitAppId}' 时没有可用端口槽位`);
|
|
906
|
-
}
|
|
907
|
-
for (let offset = 0; offset <= maxOffset; offset++) {
|
|
908
|
-
const appId = formatInstalledAppId(baseId, instancePrefix, offset);
|
|
909
|
-
if (!APP_ID_RE.test(appId)) {
|
|
910
|
-
throw new Error(`自动生成的 App id '${appId}' 超出格式限制,请缩短 id 或 instancePrefix`);
|
|
911
|
-
}
|
|
912
|
-
if (existsSync(appDirForId(appId)))
|
|
913
|
-
continue;
|
|
914
|
-
const installedSpec = materializeInstalledSpec(spec, appId, offset);
|
|
915
|
-
const candidatePorts = collectDeclaredPorts(installedSpec);
|
|
916
|
-
let portConflict = false;
|
|
917
|
-
for (const port of candidatePorts) {
|
|
918
|
-
if (usedPorts.has(port) || await isPortInUse(port)) {
|
|
919
|
-
portConflict = true;
|
|
920
|
-
break;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
if (portConflict)
|
|
924
|
-
continue;
|
|
925
|
-
return {
|
|
926
|
-
appId,
|
|
927
|
-
installedSpec,
|
|
928
|
-
// Always re-serialize from `installedSpec` so the cached yaml
|
|
929
|
-
// reflects every transform `materializeInstalledSpec` ran —
|
|
930
|
-
// including the platform pass that strips host_network/user fields
|
|
931
|
-
// on darwin. Using `originalSpecYaml` for offset=0 (the previous
|
|
932
|
-
// behavior) bypassed the transformer for multi-instance apps'
|
|
933
|
-
// first install slot and re-introduced the Linux-only fields on
|
|
934
|
-
// disk; subsequent core restarts then re-read the raw source via
|
|
935
|
-
// `loadInstalledAppSpec` and broke macOS placement again.
|
|
936
|
-
installedSpecYaml: stringify(installedSpec),
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
throw new Error(`App '${baseId}' 没有可用安装槽位,目录名或端口已全部占用`);
|
|
940
|
-
}
|
|
941
|
-
function spawnStep(label, display, cmd, args, task) {
|
|
942
|
-
return spawnStepWithTimeout(label, display, display, cmd, args, 300_000, task);
|
|
943
|
-
}
|
|
944
|
-
const DOCKER_PULL_RETRY_ATTEMPTS = 3;
|
|
945
|
-
// spawnStep's 5-min default is fine for small CLI calls but starves real-world
|
|
946
|
-
// image pulls on ARM + SD-card hardware. Playwright v1.55.0-noble is ~2.3 GB
|
|
947
|
-
// compressed (bundles Chromium + Firefox + WebKit), Hermes ~2.4 GB — neither
|
|
948
|
-
// extracts in 5 min on a Raspberry Pi. 30 min clears both with headroom while
|
|
949
|
-
// still capping runaway failures (total retry budget 90 min).
|
|
950
|
-
const DOCKER_PULL_TIMEOUT_MS = 1_800_000;
|
|
951
|
-
// Separate from the total timeout above: if docker pull stops producing any
|
|
952
|
-
// stdout/stderr for long enough, treat it as stalled and retry rather than
|
|
953
|
-
// waiting the full 30 minutes.
|
|
954
|
-
//
|
|
955
|
-
// Why 600s (not 180s): on slow links (especially Docker Hub from China to
|
|
956
|
-
// edge devices), large single layers — AnythingLLM ~500MB, Playwright/Hermes
|
|
957
|
-
// ~2.3GB — can spend 5-8 minutes between progress lines without TTY. 180s
|
|
958
|
-
// idle was killing pulls that were actually making progress, then rolling
|
|
959
|
-
// back the partially-completed image. Layer cache is preserved across
|
|
960
|
-
// retries (docker dedupes by layer sha), so a higher idle ceiling lets each
|
|
961
|
-
// big layer finish without throwing away the layers already on disk.
|
|
962
|
-
const DOCKER_PULL_IDLE_TIMEOUT_MS = 600_000;
|
|
963
|
-
async function pullDockerImageStep(label, image, display, task, timeoutMs = DOCKER_PULL_TIMEOUT_MS, idleTimeoutMs) {
|
|
964
|
-
if (await dockerImageExists(image)) {
|
|
965
|
-
const skipMessage = `[lifecycle:${label}] docker image '${image}' already exists locally; skipping pull`;
|
|
966
|
-
process.stdout.write(` ${skipMessage}\n`);
|
|
967
|
-
emitInstallTaskLog(task, skipMessage);
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
const anonymousDockerConfig = shouldBypassDockerCredentialHelperForDownloadImage(image)
|
|
971
|
-
? createAnonymousDockerConfig()
|
|
972
|
-
: null;
|
|
973
|
-
let lastError;
|
|
974
|
-
try {
|
|
975
|
-
for (let attempt = 1; attempt <= DOCKER_PULL_RETRY_ATTEMPTS; attempt++) {
|
|
976
|
-
try {
|
|
977
|
-
await spawnStepWithTimeout(label, display, display, "docker", ["pull", image], timeoutMs, task, undefined, undefined, {
|
|
978
|
-
idleTimeoutMs: idleTimeoutMs ?? Math.min(DOCKER_PULL_IDLE_TIMEOUT_MS, timeoutMs),
|
|
979
|
-
envOverrides: anonymousDockerConfig?.envOverrides,
|
|
980
|
-
stallMessageHint: "Docker credential resolution (for example docker-credential-desktop) may be involved.",
|
|
981
|
-
});
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
catch (error) {
|
|
985
|
-
if (await dockerImageExists(image)) {
|
|
986
|
-
const recoveredMessage = `[lifecycle:${label}] docker image '${image}' is present locally after pull failure/timeout; treating step as successful`;
|
|
987
|
-
process.stdout.write(` ${recoveredMessage}\n`);
|
|
988
|
-
emitInstallTaskLog(task, recoveredMessage);
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
lastError = error;
|
|
992
|
-
if (attempt === DOCKER_PULL_RETRY_ATTEMPTS) {
|
|
993
|
-
break;
|
|
994
|
-
}
|
|
995
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
996
|
-
const retryMessage = `[lifecycle:${label}] docker pull failed for ${image} (attempt ${attempt}/${DOCKER_PULL_RETRY_ATTEMPTS}): ${reason}; retrying`;
|
|
997
|
-
process.stdout.write(` ${retryMessage}\n`);
|
|
998
|
-
emitInstallTaskLog(task, retryMessage);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
finally {
|
|
1003
|
-
anonymousDockerConfig?.cleanup();
|
|
1004
|
-
}
|
|
1005
|
-
throw (lastError instanceof Error ? lastError : new Error(String(lastError)));
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* In-flight background pulls keyed by image tag. Used by
|
|
1009
|
-
* `pullDockerImageInBackground` to dedup concurrent requests so repeated
|
|
1010
|
-
* UI clicks for the same not-yet-local image don't queue duplicate pulls.
|
|
1011
|
-
*/
|
|
1012
|
-
const inFlightBackgroundPulls = new Map();
|
|
1013
|
-
/**
|
|
1014
|
-
* Fire-and-forget background docker pull. Used by `*-manager.ts`
|
|
1015
|
-
* (custom / hermes / ollama) when an instance is requested with an image
|
|
1016
|
-
* that's not yet local: kicks off a pull in the background and the caller
|
|
1017
|
-
* asks the user to retry later (HTTP 202).
|
|
1018
|
-
*
|
|
1019
|
-
* Wraps the same `pullDockerImageStep` used by full installs, so it
|
|
1020
|
-
* inherits the same protections — total timeout (30 min), idle timeout
|
|
1021
|
-
* (10 min of silence on the pull's stdout/stderr terminates it), 3
|
|
1022
|
-
* retries, and a post-failure `dockerImageExists` recovery check — that
|
|
1023
|
-
* a bare `exec("docker pull ...", { timeout: 0 })` lacks. Docker Desktop
|
|
1024
|
-
* on macOS can hang the CLI for tens of minutes after the image has been
|
|
1025
|
-
* fully downloaded (the final manifest/tag write step occasionally
|
|
1026
|
-
* deadlocks); the idle timer kills the hung CLI and the recovery check
|
|
1027
|
-
* picks up the image if the tag did make it through.
|
|
1028
|
-
*
|
|
1029
|
-
* Concurrent calls for the same image dedup onto a single in-flight pull
|
|
1030
|
-
* so repeated UI clicks don't waste retries.
|
|
1031
|
-
*
|
|
1032
|
-
* Always resolves — errors are logged via console.warn so the
|
|
1033
|
-
* fire-and-forget caller doesn't have to attach a `.catch` handler.
|
|
1034
|
-
*/
|
|
1035
|
-
export function pullDockerImageInBackground(label, image) {
|
|
1036
|
-
const existing = inFlightBackgroundPulls.get(image);
|
|
1037
|
-
if (existing)
|
|
1038
|
-
return existing;
|
|
1039
|
-
const promise = (async () => {
|
|
1040
|
-
try {
|
|
1041
|
-
await pullDockerImageStep(`${label}-background`, image, `docker pull ${image} (background)`);
|
|
1042
|
-
}
|
|
1043
|
-
catch (err) {
|
|
1044
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1045
|
-
console.warn(`[${label}] Background pull of ${image} failed: ${reason}`);
|
|
1046
|
-
}
|
|
1047
|
-
finally {
|
|
1048
|
-
inFlightBackgroundPulls.delete(image);
|
|
1049
|
-
}
|
|
1050
|
-
})();
|
|
1051
|
-
inFlightBackgroundPulls.set(image, promise);
|
|
1052
|
-
return promise;
|
|
1053
|
-
}
|
|
1054
|
-
async function dockerImageExists(image) {
|
|
1055
|
-
return new Promise((resolve) => {
|
|
1056
|
-
const child = spawn("docker", ["image", "inspect", image], {
|
|
1057
|
-
stdio: "ignore",
|
|
1058
|
-
env: buildLifecycleEnv(),
|
|
1059
|
-
});
|
|
1060
|
-
let settled = false;
|
|
1061
|
-
const finish = (value) => {
|
|
1062
|
-
if (settled)
|
|
1063
|
-
return;
|
|
1064
|
-
settled = true;
|
|
1065
|
-
resolve(value);
|
|
1066
|
-
};
|
|
1067
|
-
// `docker image inspect` is a local-only daemon API call that should
|
|
1068
|
-
// return in milliseconds. In practice, when dockerd itself wedges
|
|
1069
|
-
// (the same failure mode that causes `docker pull` to hang), `docker
|
|
1070
|
-
// image inspect` hangs too. Without this timeout the inspect call
|
|
1071
|
-
// ties up the install lifecycle's post-failure recovery check, which
|
|
1072
|
-
// in turn keeps the install lock held indefinitely. 10s is generous
|
|
1073
|
-
// for a healthy daemon and short enough that a wedged daemon doesn't
|
|
1074
|
-
// block install retry/recovery.
|
|
1075
|
-
const watchdog = setTimeout(() => {
|
|
1076
|
-
try {
|
|
1077
|
-
child.kill("SIGKILL");
|
|
1078
|
-
}
|
|
1079
|
-
catch {
|
|
1080
|
-
/* already exited */
|
|
1081
|
-
}
|
|
1082
|
-
finish(false);
|
|
1083
|
-
}, 10_000);
|
|
1084
|
-
watchdog.unref?.();
|
|
1085
|
-
child.on("close", (code) => {
|
|
1086
|
-
clearTimeout(watchdog);
|
|
1087
|
-
finish(code === 0);
|
|
1088
|
-
});
|
|
1089
|
-
child.on("error", () => {
|
|
1090
|
-
clearTimeout(watchdog);
|
|
1091
|
-
finish(false);
|
|
1092
|
-
});
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
/**
|
|
1096
|
-
* Force-terminate a spawned child: send `SIGTERM`, then escalate to
|
|
1097
|
-
* `SIGKILL` after a grace period if the process is still alive.
|
|
1098
|
-
*
|
|
1099
|
-
* Needed because some CLIs (notably `docker` on macOS when its daemon
|
|
1100
|
-
* has wedged) block on a daemon round-trip and ignore `SIGTERM` until
|
|
1101
|
-
* the daemon responds. Without `SIGKILL` escalation the parent
|
|
1102
|
-
* `child.on("close")` handler never fires, the lifecycle promise never
|
|
1103
|
-
* resolves, and the install task hangs forever holding its lock.
|
|
1104
|
-
*
|
|
1105
|
-
* The grace period must be long enough for well-behaved children to
|
|
1106
|
-
* flush buffers and exit cleanly on SIGTERM (avoid corrupt artifacts in
|
|
1107
|
-
* the common case) but short enough that a hung child doesn't waste a
|
|
1108
|
-
* meaningful slice of the parent's overall budget. 30s is the sweet
|
|
1109
|
-
* spot in practice for docker-class tooling.
|
|
1110
|
-
*/
|
|
1111
|
-
function forceTerminateChild(child, label) {
|
|
1112
|
-
try {
|
|
1113
|
-
child.kill("SIGTERM");
|
|
1114
|
-
}
|
|
1115
|
-
catch {
|
|
1116
|
-
/* already exited */
|
|
1117
|
-
}
|
|
1118
|
-
// Unref'd so it doesn't keep the event loop alive. If the child
|
|
1119
|
-
// exited cleanly under SIGTERM before this fires, `kill("SIGKILL")`
|
|
1120
|
-
// on a dead process is a harmless no-op (and we try/catch around it
|
|
1121
|
-
// anyway). We deliberately do NOT register an extra `on("close")`
|
|
1122
|
-
// listener to cancel this timer — some lifecycle paths use bare
|
|
1123
|
-
// mock children whose `on` is single-slot and a second listener
|
|
1124
|
-
// would clobber the parent's reject handler.
|
|
1125
|
-
const killTimer = setTimeout(() => {
|
|
1126
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
1127
|
-
return;
|
|
1128
|
-
process.stdout.write(` [lifecycle:${label}] child ignored SIGTERM after 30s; escalating to SIGKILL\n`);
|
|
1129
|
-
try {
|
|
1130
|
-
child.kill("SIGKILL");
|
|
1131
|
-
}
|
|
1132
|
-
catch {
|
|
1133
|
-
/* already exited */
|
|
1134
|
-
}
|
|
1135
|
-
}, 30_000);
|
|
1136
|
-
killTimer.unref?.();
|
|
1137
|
-
}
|
|
1138
|
-
function spawnStepWithTimeout(label, display, taskDisplay, cmd, args, timeoutMs, task, execOptions, sudo, runOptions) {
|
|
1139
|
-
process.stdout.write(` [lifecycle:${label}] ${display}\n`);
|
|
1140
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] ${taskDisplay}`);
|
|
1141
|
-
return new Promise((resolve, reject) => {
|
|
1142
|
-
const preparedEnv = prepareLifecycleExecEnv(sudo ? execOptions : undefined, runOptions?.envOverrides);
|
|
1143
|
-
let cleaned = false;
|
|
1144
|
-
let heartbeatTimer = null;
|
|
1145
|
-
let idleTimer = null;
|
|
1146
|
-
let stdoutPending = "";
|
|
1147
|
-
let stderrPending = "";
|
|
1148
|
-
let capturedStderr = "";
|
|
1149
|
-
let forcedError = null;
|
|
1150
|
-
let totalTimeoutTimer = null;
|
|
1151
|
-
const cleanupPreparedEnv = () => {
|
|
1152
|
-
if (cleaned)
|
|
1153
|
-
return;
|
|
1154
|
-
cleaned = true;
|
|
1155
|
-
if (heartbeatTimer) {
|
|
1156
|
-
clearInterval(heartbeatTimer);
|
|
1157
|
-
heartbeatTimer = null;
|
|
1158
|
-
}
|
|
1159
|
-
if (idleTimer) {
|
|
1160
|
-
clearTimeout(idleTimer);
|
|
1161
|
-
idleTimer = null;
|
|
1162
|
-
}
|
|
1163
|
-
if (totalTimeoutTimer) {
|
|
1164
|
-
clearTimeout(totalTimeoutTimer);
|
|
1165
|
-
totalTimeoutTimer = null;
|
|
1166
|
-
}
|
|
1167
|
-
preparedEnv.cleanup();
|
|
1168
|
-
};
|
|
1169
|
-
const spawnTarget = sudo
|
|
1170
|
-
? buildSudoWrappedCommand(cmd, args, preparedEnv.env, execOptions)
|
|
1171
|
-
: { command: cmd, args };
|
|
1172
|
-
const captureOutput = Boolean(task) || Boolean(sudo) || Boolean(runOptions?.idleTimeoutMs);
|
|
1173
|
-
const child = spawn(spawnTarget.command, spawnTarget.args, {
|
|
1174
|
-
stdio: captureOutput ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
1175
|
-
// Node's spawn `timeout` sends SIGTERM at the deadline but has no
|
|
1176
|
-
// SIGKILL escalation, so a child that ignores SIGTERM (docker CLI
|
|
1177
|
-
// on macOS when its daemon is wedged) still blocks the parent's
|
|
1178
|
-
// `child.on("close")`. We keep this here so callers / tests can
|
|
1179
|
-
// observe the configured budget on the spawn options, and add the
|
|
1180
|
-
// `totalKillEscalationTimer` below to SIGKILL after a grace.
|
|
1181
|
-
timeout: timeoutMs,
|
|
1182
|
-
env: preparedEnv.env,
|
|
1183
|
-
});
|
|
1184
|
-
// SIGKILL escalation for the total-timeout case. Node's `timeout`
|
|
1185
|
-
// option above will have sent SIGTERM at `timeoutMs`; if the child
|
|
1186
|
-
// hasn't exited within `timeoutMs + 30s` we force it. Independent
|
|
1187
|
-
// of the idle timer's escalation (which `forceTerminateChild`
|
|
1188
|
-
// handles inline) — covers the path where output keeps flowing but
|
|
1189
|
-
// the total budget is blown.
|
|
1190
|
-
totalTimeoutTimer = setTimeout(() => {
|
|
1191
|
-
if (child.exitCode !== null || child.signalCode !== null)
|
|
1192
|
-
return;
|
|
1193
|
-
const totalSeconds = Math.max(1, Math.round(timeoutMs / 1000));
|
|
1194
|
-
const timeoutMessage = `[lifecycle:${label}] child ignored spawn-timeout SIGTERM ${totalSeconds}s+30s ago; escalating to SIGKILL`;
|
|
1195
|
-
process.stdout.write(` ${timeoutMessage}\n`);
|
|
1196
|
-
emitInstallTaskLog(task, timeoutMessage);
|
|
1197
|
-
if (!forcedError) {
|
|
1198
|
-
forcedError = new Error(`lifecycle '${label}' step exceeded ${totalSeconds}s total budget: ${display}`);
|
|
1199
|
-
}
|
|
1200
|
-
try {
|
|
1201
|
-
child.kill("SIGKILL");
|
|
1202
|
-
}
|
|
1203
|
-
catch {
|
|
1204
|
-
/* already exited */
|
|
1205
|
-
}
|
|
1206
|
-
}, timeoutMs + 30_000);
|
|
1207
|
-
totalTimeoutTimer.unref?.();
|
|
1208
|
-
const resetIdleTimer = () => {
|
|
1209
|
-
if (!runOptions?.idleTimeoutMs)
|
|
1210
|
-
return;
|
|
1211
|
-
if (idleTimer)
|
|
1212
|
-
clearTimeout(idleTimer);
|
|
1213
|
-
idleTimer = setTimeout(() => {
|
|
1214
|
-
const idleSeconds = Math.max(1, Math.round(runOptions.idleTimeoutMs / 1000));
|
|
1215
|
-
const stallMessageSuffix = runOptions.stallMessageHint ? ` ${runOptions.stallMessageHint}` : "";
|
|
1216
|
-
const stallMessage = `[lifecycle:${label}] no output for ${idleSeconds}s; terminating stalled step: ${taskDisplay}${stallMessageSuffix}`;
|
|
1217
|
-
process.stdout.write(` ${stallMessage}\n`);
|
|
1218
|
-
emitInstallTaskLog(task, stallMessage);
|
|
1219
|
-
forcedError = new Error(`lifecycle '${label}' step stalled after ${idleSeconds}s with no output: ${display}${stallMessageSuffix}`);
|
|
1220
|
-
forceTerminateChild(child, label);
|
|
1221
|
-
}, runOptions.idleTimeoutMs);
|
|
1222
|
-
idleTimer.unref?.();
|
|
1223
|
-
};
|
|
1224
|
-
resetIdleTimer();
|
|
1225
|
-
if (captureOutput) {
|
|
1226
|
-
const startedAt = Date.now();
|
|
1227
|
-
const flushPendingLine = (line) => {
|
|
1228
|
-
if (!task)
|
|
1229
|
-
return;
|
|
1230
|
-
emitInstallTaskLog(task, line);
|
|
1231
|
-
};
|
|
1232
|
-
const handleChunk = (chunk, stream) => {
|
|
1233
|
-
resetIdleTimer();
|
|
1234
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
1235
|
-
if (stream === "stderr") {
|
|
1236
|
-
capturedStderr += text;
|
|
1237
|
-
}
|
|
1238
|
-
if (!task) {
|
|
1239
|
-
if (stream === "stdout")
|
|
1240
|
-
process.stdout.write(text);
|
|
1241
|
-
else
|
|
1242
|
-
process.stderr.write(text);
|
|
1243
|
-
return;
|
|
1244
|
-
}
|
|
1245
|
-
const normalized = `${stream === "stdout" ? stdoutPending : stderrPending}${text}`
|
|
1246
|
-
.replace(/\r\n/g, "\n")
|
|
1247
|
-
.replace(/\r/g, "\n");
|
|
1248
|
-
const lines = normalized.split("\n");
|
|
1249
|
-
const pending = lines.pop() ?? "";
|
|
1250
|
-
if (stream === "stdout")
|
|
1251
|
-
stdoutPending = pending;
|
|
1252
|
-
else
|
|
1253
|
-
stderrPending = pending;
|
|
1254
|
-
for (const line of lines) {
|
|
1255
|
-
flushPendingLine(line);
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
child.stdout?.on("data", (data) => handleChunk(data, "stdout"));
|
|
1259
|
-
child.stderr?.on("data", (data) => handleChunk(data, "stderr"));
|
|
1260
|
-
if (task) {
|
|
1261
|
-
heartbeatTimer = setInterval(() => {
|
|
1262
|
-
const elapsedSeconds = Math.max(1, Math.round((Date.now() - startedAt) / 1000));
|
|
1263
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] still running (${elapsedSeconds}s): ${taskDisplay}`);
|
|
1264
|
-
}, 10_000);
|
|
1265
|
-
child.on("close", () => {
|
|
1266
|
-
flushPendingLine(stdoutPending);
|
|
1267
|
-
flushPendingLine(stderrPending);
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
child.on("close", (code) => {
|
|
1272
|
-
cleanupPreparedEnv();
|
|
1273
|
-
if (forcedError)
|
|
1274
|
-
reject(forcedError);
|
|
1275
|
-
else if (code === 0)
|
|
1276
|
-
resolve();
|
|
1277
|
-
else if (sudo)
|
|
1278
|
-
reject(createLifecycleSudoError(capturedStderr, display, Boolean(execOptions?.sudoPassword)));
|
|
1279
|
-
else
|
|
1280
|
-
reject(new Error(`lifecycle '${label}' step failed (exit ${code ?? 1}): ${display}`));
|
|
1281
|
-
});
|
|
1282
|
-
child.on("error", (err) => {
|
|
1283
|
-
cleanupPreparedEnv();
|
|
1284
|
-
if (forcedError) {
|
|
1285
|
-
reject(forcedError);
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
if (sudo && err.code === "ENOENT") {
|
|
1289
|
-
reject(new Error("当前环境未检测到 sudo,无法执行需要 sudo 的生命周期步骤。请以 root 身份重试。"));
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
reject(new Error(`lifecycle '${label}' step error: ${err.message}`));
|
|
1293
|
-
});
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
function lifecycleRunStepDisplay(label, index) {
|
|
1297
|
-
if (label === "pre_install") {
|
|
1298
|
-
return `run step ${index + 1}`;
|
|
1299
|
-
}
|
|
1300
|
-
return "$";
|
|
1301
|
-
}
|
|
1302
|
-
async function commandExists(command) {
|
|
1303
|
-
return new Promise((resolve) => {
|
|
1304
|
-
const quoted = command.replace(/'/g, "'\\''");
|
|
1305
|
-
const child = spawn("sh", ["-c", `command -v '${quoted}' > /dev/null 2>&1`], {
|
|
1306
|
-
stdio: "ignore",
|
|
1307
|
-
env: buildLifecycleEnv(),
|
|
1308
|
-
});
|
|
1309
|
-
child.on("close", (code) => resolve(code === 0));
|
|
1310
|
-
child.on("error", () => resolve(false));
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
// Recursively chown a path. Owner format is "uid:gid" (numeric only, e.g.
|
|
1314
|
-
// "0:0" or "1000:1000"). Used by container apps whose images run as a
|
|
1315
|
-
// different uid than the service user — without this, bind-mounted data
|
|
1316
|
-
// dirs end up unwritable for the in-container process and fail with
|
|
1317
|
-
// SQLite "readonly database" or chroma init errors.
|
|
1318
|
-
function parseOwnerSpec(owner) {
|
|
1319
|
-
const m = /^(\d+):(\d+)$/.exec(owner);
|
|
1320
|
-
if (!m)
|
|
1321
|
-
throw new Error(`chown owner must be "uid:gid" (numeric), got "${owner}"`);
|
|
1322
|
-
return { uid: Number(m[1]), gid: Number(m[2]) };
|
|
1323
|
-
}
|
|
1324
|
-
function chownRecursive(path, uid, gid) {
|
|
1325
|
-
chownSync(path, uid, gid);
|
|
1326
|
-
let stat;
|
|
1327
|
-
try {
|
|
1328
|
-
stat = lstatSync(path);
|
|
1329
|
-
}
|
|
1330
|
-
catch {
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
if (!stat.isDirectory())
|
|
1334
|
-
return;
|
|
1335
|
-
for (const entry of readdirSync(path)) {
|
|
1336
|
-
chownRecursive(join(path, entry), uid, gid);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
/**
|
|
1340
|
-
* Try chowning via direct fs syscall first; on EPERM (core runs as a
|
|
1341
|
-
* non-root user with no CAP_CHOWN) fall back to `sudo -n chown`. The
|
|
1342
|
-
* fallback only succeeds where passwordless sudo is configured for the
|
|
1343
|
-
* service user (the canonical Pi setup); on other hosts the original
|
|
1344
|
-
* EPERM bubbles up as a clear error.
|
|
1345
|
-
*/
|
|
1346
|
-
function chownWithSudoFallback(path, uid, gid, recursive) {
|
|
1347
|
-
try {
|
|
1348
|
-
if (recursive)
|
|
1349
|
-
chownRecursive(path, uid, gid);
|
|
1350
|
-
else
|
|
1351
|
-
chownSync(path, uid, gid);
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
catch (e) {
|
|
1355
|
-
if (e?.code !== "EPERM" && e?.code !== "EACCES")
|
|
1356
|
-
throw e;
|
|
1357
|
-
}
|
|
1358
|
-
const args = [
|
|
1359
|
-
"-n",
|
|
1360
|
-
"chown",
|
|
1361
|
-
...(recursive ? ["-R"] : []),
|
|
1362
|
-
`${uid}:${gid}`,
|
|
1363
|
-
path,
|
|
1364
|
-
];
|
|
1365
|
-
const r = spawnSync("sudo", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
1366
|
-
if (r.status !== 0) {
|
|
1367
|
-
const stderr = r.stderr ? r.stderr.toString().trim() : "";
|
|
1368
|
-
throw new Error(`chown ${recursive ? "-R " : ""}${uid}:${gid} ${path} failed: service user lacks CAP_CHOWN and passwordless sudo also failed${stderr ? `: ${stderr}` : ""}`);
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
async function downloadBinaryStep(label, url, dest, chmod, task) {
|
|
1372
|
-
const expanded = expandPath(dest);
|
|
1373
|
-
process.stdout.write(` [lifecycle:${label}] downloadBinary: ${url} → ${expanded}\n`);
|
|
1374
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] downloadBinary: ${url} -> ${expanded}`);
|
|
1375
|
-
mkdirSync(dirname(expanded), { recursive: true });
|
|
1376
|
-
const tmp = expanded + ".tmp";
|
|
1377
|
-
const res = await fetch(url);
|
|
1378
|
-
if (!res.ok)
|
|
1379
|
-
throw new Error(`downloadBinary: HTTP ${res.status} ${url}`);
|
|
1380
|
-
const buf = await res.arrayBuffer();
|
|
1381
|
-
writeFileSync(tmp, Buffer.from(buf), { mode: 0o644 });
|
|
1382
|
-
renameSync(tmp, expanded);
|
|
1383
|
-
if (chmod)
|
|
1384
|
-
chmodSync(expanded, parseInt(chmod, 8));
|
|
1385
|
-
}
|
|
1386
|
-
async function runLifecycleSteps(steps, label, artifacts, task, execOptions) {
|
|
1387
|
-
if (!steps || steps.length === 0)
|
|
1388
|
-
return;
|
|
1389
|
-
for (const [index, step] of steps.entries()) {
|
|
1390
|
-
if ("run" in step) {
|
|
1391
|
-
if (step.ifFileExists && !existsSync(expandPath(step.ifFileExists))) {
|
|
1392
|
-
continue;
|
|
1393
|
-
}
|
|
1394
|
-
const timeoutMs = step.timeout_ms ?? 300_000;
|
|
1395
|
-
const display = label === "pre_install"
|
|
1396
|
-
? lifecycleRunStepDisplay(label, index)
|
|
1397
|
-
: `${lifecycleRunStepDisplay(label, index)} ${step.run}`;
|
|
1398
|
-
const taskDisplay = `run step ${index + 1}`;
|
|
1399
|
-
try {
|
|
1400
|
-
await spawnStepWithTimeout(label, display, taskDisplay, "sh", ["-c", step.run], timeoutMs, task, execOptions, step.sudo === true);
|
|
1401
|
-
}
|
|
1402
|
-
catch (error) {
|
|
1403
|
-
if (step.successIfCommandExists && await commandExists(step.successIfCommandExists)) {
|
|
1404
|
-
process.stdout.write(` [lifecycle:${label}] command '${step.successIfCommandExists}' detected after failure/timeout; treating step as successful\n`);
|
|
1405
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] command '${step.successIfCommandExists}' detected after failure/timeout; treating step as successful`);
|
|
1406
|
-
continue;
|
|
1407
|
-
}
|
|
1408
|
-
throw error;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
else if ("checkNotCommand" in step) {
|
|
1412
|
-
const cmd = step.checkNotCommand;
|
|
1413
|
-
const found = await commandExists(cmd);
|
|
1414
|
-
if (found) {
|
|
1415
|
-
throw new Error(step.message ??
|
|
1416
|
-
`系统中已检测到 '${cmd}',建议直接使用系统版本,无需重复安装。如需由 Nomad 统一管理,请先卸载系统版本`);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
else if ("downloadImage" in step) {
|
|
1420
|
-
await pullDockerImageStep(label, step.downloadImage, `downloadImage: ${step.downloadImage}`, task, step.timeout_ms, step.idle_timeout_ms);
|
|
1421
|
-
artifacts?.push({ type: "image", path: step.downloadImage });
|
|
1422
|
-
}
|
|
1423
|
-
else if ("deleteImage" in step) {
|
|
1424
|
-
await spawnStep(label, `deleteImage: ${step.deleteImage}`, "sh", [
|
|
1425
|
-
"-c",
|
|
1426
|
-
`docker rmi -f ${step.deleteImage} 2>/dev/null || true`,
|
|
1427
|
-
], task);
|
|
1428
|
-
}
|
|
1429
|
-
else if ("downloadBinary" in step) {
|
|
1430
|
-
const { url, dest, chmod } = step.downloadBinary;
|
|
1431
|
-
await downloadBinaryStep(label, url, dest, chmod, task);
|
|
1432
|
-
artifacts?.push({ type: "binary", path: expandPath(dest) });
|
|
1433
|
-
}
|
|
1434
|
-
else if ("deleteBinary" in step) {
|
|
1435
|
-
const p = expandPath(step.deleteBinary);
|
|
1436
|
-
process.stdout.write(` [lifecycle:${label}] deleteBinary: ${p}\n`);
|
|
1437
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] deleteBinary: ${p}`);
|
|
1438
|
-
try {
|
|
1439
|
-
unlinkSync(p);
|
|
1440
|
-
}
|
|
1441
|
-
catch (e) {
|
|
1442
|
-
if (e.code !== "ENOENT")
|
|
1443
|
-
throw e;
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
else if ("mkdir" in step) {
|
|
1447
|
-
const p = expandPath(step.mkdir);
|
|
1448
|
-
process.stdout.write(` [lifecycle:${label}] mkdir: ${p}\n`);
|
|
1449
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] mkdir: ${p}`);
|
|
1450
|
-
mkdirSync(p, { recursive: true });
|
|
1451
|
-
artifacts?.push({ type: "dir", path: p });
|
|
1452
|
-
}
|
|
1453
|
-
else if ("chown" in step) {
|
|
1454
|
-
const p = expandPath(step.chown.path);
|
|
1455
|
-
const { uid, gid } = parseOwnerSpec(step.chown.owner);
|
|
1456
|
-
const recursive = step.chown.recursive !== false;
|
|
1457
|
-
const tag = recursive ? "chown -R" : "chown";
|
|
1458
|
-
process.stdout.write(` [lifecycle:${label}] ${tag} ${uid}:${gid} ${p}\n`);
|
|
1459
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] ${tag} ${uid}:${gid} ${p}`);
|
|
1460
|
-
if (!existsSync(p)) {
|
|
1461
|
-
// chown only makes sense if the target exists; surface a clear
|
|
1462
|
-
// error rather than letting fs throw an opaque ENOENT later.
|
|
1463
|
-
throw new Error(`chown target does not exist: ${p}`);
|
|
1464
|
-
}
|
|
1465
|
-
try {
|
|
1466
|
-
chownWithSudoFallback(p, uid, gid, recursive);
|
|
1467
|
-
}
|
|
1468
|
-
catch (chownErr) {
|
|
1469
|
-
// In pre_start, chown failures are non-fatal: on macOS Docker/Colima
|
|
1470
|
-
// the VM handles UID mapping for bind-mounts, so the container can
|
|
1471
|
-
// write even without matching ownership. Failing fatally here blocks
|
|
1472
|
-
// any non-root service user from starting the app.
|
|
1473
|
-
if (label === "pre_start") {
|
|
1474
|
-
const msg = `[lifecycle:${label}] ${tag} ${uid}:${gid} ${p} failed (non-fatal): ${chownErr.message}`;
|
|
1475
|
-
process.stdout.write(` ${msg}\n`);
|
|
1476
|
-
emitInstallTaskLog(task, msg);
|
|
1477
|
-
}
|
|
1478
|
-
else {
|
|
1479
|
-
throw chownErr;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
else if ("deleteDir" in step) {
|
|
1484
|
-
const p = expandPath(step.deleteDir);
|
|
1485
|
-
process.stdout.write(` [lifecycle:${label}] deleteDir: ${p}\n`);
|
|
1486
|
-
emitInstallTaskLog(task, `[lifecycle:${label}] deleteDir: ${p}`);
|
|
1487
|
-
rmSync(p, { recursive: true, force: true });
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
function cleanupArtifacts(artifacts, task) {
|
|
1492
|
-
for (let i = artifacts.length - 1; i >= 0; i--) {
|
|
1493
|
-
const a = artifacts[i];
|
|
1494
|
-
try {
|
|
1495
|
-
switch (a.type) {
|
|
1496
|
-
case "image":
|
|
1497
|
-
process.stdout.write(` [cleanup] removing image: ${a.path}\n`);
|
|
1498
|
-
emitInstallTaskLog(task, `[cleanup] removing image: ${a.path}`);
|
|
1499
|
-
spawn("docker", ["rmi", "-f", a.path], { stdio: "ignore" }).unref();
|
|
1500
|
-
break;
|
|
1501
|
-
case "binary":
|
|
1502
|
-
process.stdout.write(` [cleanup] removing binary: ${a.path}\n`);
|
|
1503
|
-
emitInstallTaskLog(task, `[cleanup] removing binary: ${a.path}`);
|
|
1504
|
-
unlinkSync(a.path);
|
|
1505
|
-
break;
|
|
1506
|
-
case "dir":
|
|
1507
|
-
process.stdout.write(` [cleanup] removing dir: ${a.path}\n`);
|
|
1508
|
-
emitInstallTaskLog(task, `[cleanup] removing dir: ${a.path}`);
|
|
1509
|
-
rmSync(a.path, { recursive: true, force: true });
|
|
1510
|
-
break;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
catch {
|
|
1514
|
-
// best-effort
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
const DOCKER_IMAGE_RE = /^[a-zA-Z0-9][a-zA-Z0-9\-_.:/@]*$/;
|
|
1519
|
-
const APP_ID_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
1520
|
-
const _REGISTRY_PATH = join(APPS_DIR, "capability-registry.json");
|
|
1521
|
-
const INSTALL_LOCK_FILENAME = "install.lock";
|
|
1522
|
-
// ── Directory helpers ─────────────────────────────────────────────────────
|
|
1523
|
-
function appDirForId(appId) {
|
|
1524
|
-
return join(APPS_DIR, appId);
|
|
1525
|
-
}
|
|
1526
|
-
function installLockPathForDir(appDir) {
|
|
1527
|
-
return join(appDir, INSTALL_LOCK_FILENAME);
|
|
1528
|
-
}
|
|
1529
|
-
function hasInstallLock(appDir) {
|
|
1530
|
-
return existsSync(installLockPathForDir(appDir));
|
|
1531
|
-
}
|
|
1532
|
-
function readInstallLockMetadata(appDir) {
|
|
1533
|
-
const lockPath = installLockPathForDir(appDir);
|
|
1534
|
-
if (!existsSync(lockPath))
|
|
1535
|
-
return null;
|
|
1536
|
-
try {
|
|
1537
|
-
const parsed = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
1538
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1539
|
-
}
|
|
1540
|
-
catch {
|
|
1541
|
-
return null;
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
function installDirLooksComplete(appDir) {
|
|
1545
|
-
return (existsSync(join(appDir, "app-spec.yaml"))
|
|
1546
|
-
&& existsSync(join(appDir, "manifest.json"))
|
|
1547
|
-
&& existsSync(join(appDir, "instance.json")));
|
|
1548
|
-
}
|
|
1549
|
-
function isProcessAlive(pid) {
|
|
1550
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
1551
|
-
return false;
|
|
1552
|
-
try {
|
|
1553
|
-
process.kill(pid, 0);
|
|
1554
|
-
return true;
|
|
1555
|
-
}
|
|
1556
|
-
catch {
|
|
1557
|
-
return false;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
function isStaleInstallLock(appDir) {
|
|
1561
|
-
if (!hasInstallLock(appDir))
|
|
1562
|
-
return false;
|
|
1563
|
-
const metadata = readInstallLockMetadata(appDir);
|
|
1564
|
-
if (!metadata)
|
|
1565
|
-
return true;
|
|
1566
|
-
const taskId = typeof metadata.taskId === "string" ? metadata.taskId.trim() : "";
|
|
1567
|
-
if (taskId) {
|
|
1568
|
-
return getTask(taskId)?.status !== "running";
|
|
1569
|
-
}
|
|
1570
|
-
if (typeof metadata.pid === "number") {
|
|
1571
|
-
if (isProcessAlive(metadata.pid)) {
|
|
1572
|
-
const specId = typeof metadata.specId === "string" ? metadata.specId.trim() : "";
|
|
1573
|
-
if (installDirLooksComplete(appDir) && (!specId || getRunningTasks(`app-install-${specId}`).length === 0)) {
|
|
1574
|
-
return true;
|
|
1575
|
-
}
|
|
1576
|
-
return false;
|
|
1577
|
-
}
|
|
1578
|
-
return !isProcessAlive(metadata.pid);
|
|
1579
|
-
}
|
|
1580
|
-
const specId = typeof metadata.specId === "string" ? metadata.specId.trim() : "";
|
|
1581
|
-
return specId ? getRunningTasks(`app-install-${specId}`).length === 0 : true;
|
|
1582
|
-
}
|
|
1583
|
-
function cleanupStaleInstallDir(appDir) {
|
|
1584
|
-
if (!isStaleInstallLock(appDir))
|
|
1585
|
-
return false;
|
|
1586
|
-
if (installDirLooksComplete(appDir)) {
|
|
1587
|
-
process.stdout.write(` [app-manager] removing stale install lock: ${appDir}\n`);
|
|
1588
|
-
removeInstallLock(appDir);
|
|
1589
|
-
return false;
|
|
1590
|
-
}
|
|
1591
|
-
process.stdout.write(` [app-manager] removing stale install dir: ${appDir}\n`);
|
|
1592
|
-
rmSync(appDir, { recursive: true, force: true });
|
|
1593
|
-
return true;
|
|
1594
|
-
}
|
|
1595
|
-
function createInstallLock(appDir, appId, specId, task) {
|
|
1596
|
-
writeFileSync(installLockPathForDir(appDir), JSON.stringify({ appId, specId, started_at: new Date().toISOString(), pid: process.pid, taskId: task?.id }, null, 2) + "\n", { mode: 0o644 });
|
|
1597
|
-
}
|
|
1598
|
-
function removeInstallLock(appDir) {
|
|
1599
|
-
const lockPath = installLockPathForDir(appDir);
|
|
1600
|
-
if (existsSync(lockPath)) {
|
|
1601
|
-
unlinkSync(lockPath);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
function appTaskName(kind, appId) {
|
|
1605
|
-
return `app-${kind}:${appId}`;
|
|
1606
|
-
}
|
|
1607
|
-
function findRunningInstallTask(appDir) {
|
|
1608
|
-
const metadata = readInstallLockMetadata(appDir);
|
|
1609
|
-
const taskId = typeof metadata?.taskId === "string" ? metadata.taskId.trim() : "";
|
|
1610
|
-
if (!taskId)
|
|
1611
|
-
return undefined;
|
|
1612
|
-
const status = getTask(taskId)?.status;
|
|
1613
|
-
if (status !== "running")
|
|
1614
|
-
return undefined;
|
|
1615
|
-
return { id: taskId, kind: "install", status };
|
|
1616
|
-
}
|
|
1617
|
-
function findRunningNamedTask(taskName, kind) {
|
|
1618
|
-
const running = getRunningTasks(taskName)[0];
|
|
1619
|
-
if (!running)
|
|
1620
|
-
return undefined;
|
|
1621
|
-
const status = getTask(running.id)?.status;
|
|
1622
|
-
if (status !== "running")
|
|
1623
|
-
return undefined;
|
|
1624
|
-
return { id: running.id, kind, status };
|
|
1625
|
-
}
|
|
1626
|
-
export function getRunningAppTask(appId, appDir) {
|
|
1627
|
-
const resolvedAppDir = appDir ?? resolveAppLocation(appId)?.dir;
|
|
1628
|
-
if (resolvedAppDir && hasInstallLock(resolvedAppDir)) {
|
|
1629
|
-
const installTask = findRunningInstallTask(resolvedAppDir);
|
|
1630
|
-
if (installTask)
|
|
1631
|
-
return installTask;
|
|
1632
|
-
}
|
|
1633
|
-
for (const kind of ["uninstall", "restart", "stop", "start"]) {
|
|
1634
|
-
const task = findRunningNamedTask(appTaskName(kind, appId), kind);
|
|
1635
|
-
if (task)
|
|
1636
|
-
return task;
|
|
1637
|
-
}
|
|
1638
|
-
return undefined;
|
|
1639
|
-
}
|
|
1640
|
-
function startAppLifecycleTask(appId, kind, startMessage, doneMessage, action) {
|
|
1641
|
-
const currentTask = getRunningAppTask(appId);
|
|
1642
|
-
if (currentTask) {
|
|
1643
|
-
if (currentTask.kind === kind) {
|
|
1644
|
-
return { ok: true, taskId: currentTask.id, reused: true, kind };
|
|
1645
|
-
}
|
|
1646
|
-
return {
|
|
1647
|
-
ok: false,
|
|
1648
|
-
error: `App '${appId}' 正在执行 ${currentTask.kind} 操作,请等待完成后再试`,
|
|
1649
|
-
code: "TASK_BUSY",
|
|
1650
|
-
kind,
|
|
1651
|
-
};
|
|
1652
|
-
}
|
|
1653
|
-
const task = createTask(appTaskName(kind, appId));
|
|
1654
|
-
emitAppTaskProgress(task, startMessage, 0);
|
|
1655
|
-
void (async () => {
|
|
1656
|
-
try {
|
|
1657
|
-
await action();
|
|
1658
|
-
task.status = "done";
|
|
1659
|
-
emitTask(task, { type: "done", message: doneMessage, progress: 100 });
|
|
1660
|
-
}
|
|
1661
|
-
catch (e) {
|
|
1662
|
-
task.status = "error";
|
|
1663
|
-
emitTask(task, {
|
|
1664
|
-
type: "error",
|
|
1665
|
-
message: e?.message || `App '${appId}' ${kind} 失败`,
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1668
|
-
})();
|
|
1669
|
-
return { ok: true, taskId: task.id, kind };
|
|
1670
|
-
}
|
|
1671
|
-
function instanceAppDirForId(appId) {
|
|
1672
|
-
return join(INSTANCES_DIR, appId);
|
|
1673
|
-
}
|
|
1674
|
-
function resolveAppLocation(appId) {
|
|
1675
|
-
const candidates = [
|
|
1676
|
-
{ dir: appDirForId(appId), installMode: "app-dir" },
|
|
1677
|
-
{ dir: instanceAppDirForId(appId), installMode: "instance-dir" },
|
|
1678
|
-
];
|
|
1679
|
-
for (const candidate of candidates) {
|
|
1680
|
-
cleanupStaleInstallDir(candidate.dir);
|
|
1681
|
-
if (existsSync(join(candidate.dir, "app-spec.yaml"))
|
|
1682
|
-
&& existsSync(join(candidate.dir, "manifest.json"))) {
|
|
1683
|
-
return candidate;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
return null;
|
|
1687
|
-
}
|
|
1688
|
-
function isInstanceBackedApp(appData) {
|
|
1689
|
-
return appData?.manifest.install_mode === "instance-dir";
|
|
1690
|
-
}
|
|
1691
|
-
function getSingleServiceTask(spec) {
|
|
1692
|
-
const serviceTasks = spec.tasks.filter((task) => (task.role ?? "service") === "service");
|
|
1693
|
-
if (spec.tasks.length !== 1 || serviceTasks.length !== 1)
|
|
1694
|
-
return null;
|
|
1695
|
-
return serviceTasks[0];
|
|
1696
|
-
}
|
|
1697
|
-
function inferLegacyAppType(spec) {
|
|
1698
|
-
return /ollama/i.test(`${spec.id} ${spec.name ?? ""}`) ? "ollama" : "custom";
|
|
1699
|
-
}
|
|
1700
|
-
function rewriteInstanceScopedPathText(value, appId) {
|
|
1701
|
-
return value
|
|
1702
|
-
.split(`~/.jishushell/apps/${appId}`).join(`~/.jishushell/instances/${appId}`)
|
|
1703
|
-
.split(`$HOME/.jishushell/apps/${appId}`).join(`$HOME/.jishushell/instances/${appId}`)
|
|
1704
|
-
.split(`${homedir()}/.jishushell/apps/${appId}`).join(`${homedir()}/.jishushell/instances/${appId}`);
|
|
1705
|
-
}
|
|
1706
|
-
function rewriteInstanceScopedPaths(value, appId) {
|
|
1707
|
-
if (typeof value === "string") {
|
|
1708
|
-
return rewriteInstanceScopedPathText(value, appId);
|
|
1709
|
-
}
|
|
1710
|
-
if (Array.isArray(value)) {
|
|
1711
|
-
return value.map((entry) => rewriteInstanceScopedPaths(entry, appId));
|
|
1712
|
-
}
|
|
1713
|
-
if (value && typeof value === "object") {
|
|
1714
|
-
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
1715
|
-
key,
|
|
1716
|
-
rewriteInstanceScopedPaths(entry, appId),
|
|
1717
|
-
]));
|
|
1718
|
-
}
|
|
1719
|
-
return value;
|
|
1720
|
-
}
|
|
1721
|
-
function buildGenericInstanceBackedMeta(appId, spec) {
|
|
1722
|
-
return {
|
|
1723
|
-
id: appId,
|
|
1724
|
-
name: spec.name || appId,
|
|
1725
|
-
description: spec.description || "",
|
|
1726
|
-
// See buildGenericAppMeta for the dual-write rationale: legacy
|
|
1727
|
-
// `app_type` keeps V1 dispatch alive, `app_spec_ref` is the V2
|
|
1728
|
-
// discriminator recognized by resolveAgentType.
|
|
1729
|
-
app_type: inferLegacyAppType(spec),
|
|
1730
|
-
app_spec_ref: spec.id,
|
|
1731
|
-
...(spec.singleInstance ? { singleInstance: true } : {}),
|
|
1732
|
-
runtime: {},
|
|
1733
|
-
created_at: new Date().toISOString(),
|
|
1734
|
-
app_id: spec.app_id ?? appId,
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
function explicitAgentTypeForSpec(spec) {
|
|
1738
|
-
if (typeof spec.agentType === "string" && spec.agentType.trim()) {
|
|
1739
|
-
return spec.agentType.trim();
|
|
1740
|
-
}
|
|
1741
|
-
if (typeof spec._engine?.agentType === "string" && spec._engine.agentType.trim()) {
|
|
1742
|
-
return spec._engine.agentType.trim();
|
|
1743
|
-
}
|
|
1744
|
-
return "";
|
|
1745
|
-
}
|
|
1746
|
-
function buildGenericAppRuntime(spec, appId) {
|
|
1747
|
-
const serviceTask = spec.tasks.find((task) => (task.role ?? "service") === "service");
|
|
1748
|
-
if (!serviceTask)
|
|
1749
|
-
return {};
|
|
1750
|
-
const runtime = compileTaskRuntime(serviceTask, appId);
|
|
1751
|
-
// Record EVERY external port across ALL tasks, each tagged with its owning
|
|
1752
|
-
// taskName. Multi-service-task apps (e.g. WeKnora's `app` + `ui`, both with a
|
|
1753
|
-
// port named "http") otherwise lose the secondary task's ports downstream,
|
|
1754
|
-
// letting name-only matching collapse two distinct host ports into one.
|
|
1755
|
-
// Ordering matters: the primary service task's ports come first so
|
|
1756
|
-
// extractGatewayPort / single-service `runtime.ports[0]` semantics are
|
|
1757
|
-
// byte-for-byte preserved.
|
|
1758
|
-
const orderedTasks = [serviceTask, ...spec.tasks.filter((task) => task !== serviceTask)];
|
|
1759
|
-
const allPorts = orderedTasks.flatMap((task) => (task.ports ?? [])
|
|
1760
|
-
.filter((port) => (port.visibility ?? "external") !== "internal")
|
|
1761
|
-
.map((port) => ({
|
|
1762
|
-
taskName: task.name,
|
|
1763
|
-
name: port.name,
|
|
1764
|
-
containerPort: port.container_port ?? port.port,
|
|
1765
|
-
hostPort: port.host_port ?? port.port,
|
|
1766
|
-
visibility: port.visibility ?? "external",
|
|
1767
|
-
...(port.host_network ? { host_network: port.host_network } : {}),
|
|
1768
|
-
})));
|
|
1769
|
-
if (allPorts.length > 0) {
|
|
1770
|
-
runtime.ports = allPorts;
|
|
1771
|
-
}
|
|
1772
|
-
if (serviceTask.health?.http) {
|
|
1773
|
-
const healthPortEntry = serviceTask.ports?.find((port) => port.port === serviceTask.health?.http?.port
|
|
1774
|
-
|| port.host_port === serviceTask.health?.http?.port
|
|
1775
|
-
|| port.container_port === serviceTask.health?.http?.port);
|
|
1776
|
-
runtime.health = {
|
|
1777
|
-
type: "http",
|
|
1778
|
-
path: serviceTask.health.http.path,
|
|
1779
|
-
port: healthPortEntry?.host_port ?? healthPortEntry?.port ?? serviceTask.health.http.port,
|
|
1780
|
-
interval: serviceTask.health.interval,
|
|
1781
|
-
timeout: serviceTask.health.timeout,
|
|
1782
|
-
retries: serviceTask.health.retries,
|
|
1783
|
-
};
|
|
1784
|
-
}
|
|
1785
|
-
if (Array.isArray(serviceTask.volumes)) {
|
|
1786
|
-
runtime.volumes = serviceTask.volumes.map((volume) => ({
|
|
1787
|
-
hostPath: expandPath(String(volume.source ?? "")),
|
|
1788
|
-
containerPath: String(volume.target ?? ""),
|
|
1789
|
-
mode: volume.readonly ? "ro" : "rw",
|
|
1790
|
-
}));
|
|
1791
|
-
}
|
|
1792
|
-
return runtime;
|
|
1793
|
-
}
|
|
1794
|
-
function buildGenericAppMeta(appId, spec, createdAt) {
|
|
1795
|
-
const explicitAgentType = explicitAgentTypeForSpec(spec);
|
|
1796
|
-
return {
|
|
1797
|
-
id: appId,
|
|
1798
|
-
name: spec.name || appId,
|
|
1799
|
-
description: spec.description || "",
|
|
1800
|
-
runtime: buildGenericAppRuntime(spec, appId),
|
|
1801
|
-
created_at: createdAt,
|
|
1802
|
-
app_id: spec.app_id ?? appId,
|
|
1803
|
-
...(explicitAgentType
|
|
1804
|
-
? { agentType: explicitAgentType }
|
|
1805
|
-
: {
|
|
1806
|
-
// `app_type: "custom"` is the V1 legacy marker; `app_spec_ref`
|
|
1807
|
-
// is the V2 discriminator that resolveAgentType (runtime/
|
|
1808
|
-
// instance.ts) uses to route to the custom adapter. Writing
|
|
1809
|
-
// both makes dispatch survive the eventual removal of the
|
|
1810
|
-
// resolveLegacyAppType bridge without touching each instance.
|
|
1811
|
-
app_type: inferLegacyAppType(spec),
|
|
1812
|
-
app_spec_ref: spec.id,
|
|
1813
|
-
...(spec.singleInstance ? { singleInstance: true } : {}),
|
|
1814
|
-
}),
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
async function bootstrapAdapterManagedApp(appId, spec, options) {
|
|
1818
|
-
const agentType = explicitAgentTypeForSpec(spec);
|
|
1819
|
-
if (!agentType)
|
|
1820
|
-
return false;
|
|
1821
|
-
const { hasAdapter, getAdapter } = await import("../runtime/index.js");
|
|
1822
|
-
if (!hasAdapter(agentType))
|
|
1823
|
-
return false;
|
|
1824
|
-
const adapter = getAdapter(agentType);
|
|
1825
|
-
if (!adapter.createInstance)
|
|
1826
|
-
return false;
|
|
1827
|
-
await adapter.createInstance({
|
|
1828
|
-
instanceId: appId,
|
|
1829
|
-
name: options.bootstrap?.name ?? spec.name ?? appId,
|
|
1830
|
-
description: options.bootstrap?.description ?? spec.description ?? "",
|
|
1831
|
-
cloneFrom: options.bootstrap?.cloneFrom,
|
|
1832
|
-
agentHome: options.bootstrap?.agentHome,
|
|
1833
|
-
cloneOptions: options.bootstrap?.cloneOptions,
|
|
1834
|
-
appSpec: spec,
|
|
1835
|
-
});
|
|
1836
|
-
return true;
|
|
1837
|
-
}
|
|
1838
|
-
function getAdapterManagedAgentType(appData) {
|
|
1839
|
-
if (!appData || appData.manifest.install_mode !== "app-dir")
|
|
1840
|
-
return null;
|
|
1841
|
-
const identity = resolveRuntimeIdentity(appData.manifest.id);
|
|
1842
|
-
return identity?.driver === "runtime-adapter" ? identity.agentType ?? null : null;
|
|
1843
|
-
}
|
|
1844
|
-
async function installIntoInstanceDir(spec, specYaml, requestedAppId, options = {}) {
|
|
1845
|
-
if (options.preferInstanceDir !== true)
|
|
1846
|
-
return null;
|
|
1847
|
-
const serviceTask = getSingleServiceTask(spec);
|
|
1848
|
-
if (!serviceTask)
|
|
1849
|
-
return null;
|
|
1850
|
-
const explicitAgentType = typeof spec.agentType === "string" && spec.agentType.trim()
|
|
1851
|
-
? spec.agentType.trim()
|
|
1852
|
-
: typeof spec._engine?.agentType === "string" && spec._engine.agentType.trim()
|
|
1853
|
-
? spec._engine.agentType.trim()
|
|
1854
|
-
: "";
|
|
1855
|
-
const { hasAdapter, getAdapter } = await import("../runtime/index.js");
|
|
1856
|
-
let installTarget = null;
|
|
1857
|
-
if (explicitAgentType) {
|
|
1858
|
-
if (hasAdapter(explicitAgentType) && getAdapter(explicitAgentType).createInstance) {
|
|
1859
|
-
installTarget = { kind: "adapter", agentType: explicitAgentType };
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
else {
|
|
1863
|
-
const hintText = `${spec.id} ${spec.name ?? ""}`.toLowerCase();
|
|
1864
|
-
for (const hintedAgentType of ["openclaw", "hermes"]) {
|
|
1865
|
-
if (hintText.includes(hintedAgentType) && hasAdapter(hintedAgentType) && getAdapter(hintedAgentType).createInstance) {
|
|
1866
|
-
installTarget = { kind: "adapter", agentType: hintedAgentType };
|
|
1867
|
-
break;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
if (!installTarget && serviceTask.runtime === "container") {
|
|
1871
|
-
installTarget = { kind: "legacy", appType: inferLegacyAppType(spec) };
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
if (!installTarget)
|
|
1875
|
-
return null;
|
|
1876
|
-
const { appId, installedSpec, } = await resolveInstallTarget(spec, specYaml, requestedAppId);
|
|
1877
|
-
let instanceSpec = rewriteInstanceScopedPaths(installedSpec, appId);
|
|
1878
|
-
// PR 3 sub-step 3c: switch from legacy `resolveRequires(spec)` to the new
|
|
1879
|
-
// resolveConnections in preCreate mode. Legacy single-candidate fallback
|
|
1880
|
-
// still materializes its env (so meta-apps stay "open the box and it
|
|
1881
|
-
// works"); category-prefix requires + missing required producers fall
|
|
1882
|
-
// into `pending` for UI display via the install task event (PR 4 wires
|
|
1883
|
-
// task.event.affectedConsumers etc.). install never fails here — even if
|
|
1884
|
-
// a required capability is unavailable; start time will surface the
|
|
1885
|
-
// error. The previous `ensureRequiredCapabilitiesAvailable` block has
|
|
1886
|
-
// been removed for the same reason.
|
|
1887
|
-
const { resolveConnections, resolvedToLegacyEnv } = await import("../connection-resolver.js");
|
|
1888
|
-
const { resolved, pending } = resolveConnections(installedSpec, { connections: {} }, "preCreate");
|
|
1889
|
-
if (pending.length > 0) {
|
|
1890
|
-
console.log(`[install] ${appId}: ${pending.length} pending connection(s): ` +
|
|
1891
|
-
pending.map((p) => `${p.slot} (${p.capability}, ${p.reason})`).join(", "));
|
|
1892
|
-
}
|
|
1893
|
-
const resolvedRequires = resolvedToLegacyEnv(resolved);
|
|
1894
|
-
if (Object.keys(resolvedRequires).length > 0) {
|
|
1895
|
-
instanceSpec = {
|
|
1896
|
-
...installedSpec,
|
|
1897
|
-
tasks: installedSpec.tasks.map((task) => (task.role ?? "service") === "service"
|
|
1898
|
-
? { ...task, env: { ...task.env, ...resolvedRequires } }
|
|
1899
|
-
: task),
|
|
1900
|
-
};
|
|
1901
|
-
instanceSpec = rewriteInstanceScopedPaths(instanceSpec, appId);
|
|
1902
|
-
}
|
|
1903
|
-
const persistedSpecYaml = stringify(instanceSpec);
|
|
1904
|
-
const instanceDir = instanceAppDirForId(appId);
|
|
1905
|
-
const manifest = {
|
|
1906
|
-
id: appId,
|
|
1907
|
-
installed_at: new Date().toISOString(),
|
|
1908
|
-
spec_hash: createHash("sha256").update(persistedSpecYaml).digest("hex"),
|
|
1909
|
-
install_mode: "instance-dir",
|
|
1910
|
-
};
|
|
1911
|
-
const { getAppManager } = await import("./registry.js");
|
|
1912
|
-
const instanceName = instanceSpec.name || appId;
|
|
1913
|
-
const instanceDescription = instanceSpec.description || "";
|
|
1914
|
-
const artifacts = [];
|
|
1915
|
-
try {
|
|
1916
|
-
if (installTarget?.kind === "adapter") {
|
|
1917
|
-
const adapter = getAdapter(installTarget.agentType);
|
|
1918
|
-
if (!adapter.createInstance)
|
|
1919
|
-
return null;
|
|
1920
|
-
await adapter.createInstance({
|
|
1921
|
-
instanceId: appId,
|
|
1922
|
-
name: instanceName,
|
|
1923
|
-
description: instanceDescription,
|
|
1924
|
-
appSpec: instanceSpec,
|
|
1925
|
-
});
|
|
1926
|
-
}
|
|
1927
|
-
else if (installTarget?.kind === "legacy") {
|
|
1928
|
-
await getAppManager(installTarget.appType).createInstance(appId, instanceName, instanceDescription, { appSpec: instanceSpec });
|
|
1929
|
-
}
|
|
1930
|
-
else {
|
|
1931
|
-
ensureDirHost(instanceDir);
|
|
1932
|
-
safeWriteJson(join(instanceDir, "instance.json"), buildGenericInstanceBackedMeta(appId, instanceSpec), true);
|
|
1933
|
-
}
|
|
1934
|
-
ensureDirHost(instanceDir);
|
|
1935
|
-
const yamlPath = join(instanceDir, "app-spec.yaml");
|
|
1936
|
-
const yamlTmp = yamlPath + ".tmp";
|
|
1937
|
-
writeFileSync(yamlTmp, persistedSpecYaml, { mode: 0o644 });
|
|
1938
|
-
renameSync(yamlTmp, yamlPath);
|
|
1939
|
-
safeWriteJson(join(instanceDir, "manifest.json"), manifest, true);
|
|
1940
|
-
await runLifecycleSteps(instanceSpec.lifecycle?.pre_install, "pre_install", artifacts, options.task, options.exec);
|
|
1941
|
-
}
|
|
1942
|
-
catch (e) {
|
|
1943
|
-
cleanupArtifacts(artifacts, options.task);
|
|
1944
|
-
try {
|
|
1945
|
-
const instanceManager = await import("../instance-manager.js");
|
|
1946
|
-
await instanceManager.deleteInstance(appId);
|
|
1947
|
-
}
|
|
1948
|
-
catch {
|
|
1949
|
-
rmSync(instanceDir, { recursive: true, force: true });
|
|
1950
|
-
}
|
|
1951
|
-
throw decorateInstallError(e, instanceSpec);
|
|
1952
|
-
}
|
|
1953
|
-
try {
|
|
1954
|
-
await runLifecycleSteps(instanceSpec.lifecycle?.install, "install", artifacts, options.task, options.exec);
|
|
1955
|
-
const pulledImages = new Set(artifacts.filter((artifact) => artifact.type === "image").map((artifact) => artifact.path));
|
|
1956
|
-
const imagesToPull = [...new Set(instanceSpec.tasks.filter((task) => task.image).map((task) => task.image))];
|
|
1957
|
-
for (const image of imagesToPull) {
|
|
1958
|
-
if (!pulledImages.has(image)) {
|
|
1959
|
-
await pullDockerImageStep("install", image, `docker pull ${image}`, options.task);
|
|
1960
|
-
artifacts.push({ type: "image", path: image });
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
catch (e) {
|
|
1965
|
-
try {
|
|
1966
|
-
await runLifecycleSteps(instanceSpec.lifecycle?.uninstall, "rollback_uninstall", undefined, options.task, options.exec);
|
|
1967
|
-
}
|
|
1968
|
-
catch (rollbackError) {
|
|
1969
|
-
process.stdout.write(` [app-manager] rollback uninstall failed: ${rollbackError.message}\n`);
|
|
1970
|
-
emitInstallTaskLog(options.task, `[app-manager] rollback uninstall failed: ${rollbackError.message}`);
|
|
1971
|
-
}
|
|
1972
|
-
cleanupArtifacts(artifacts, options.task);
|
|
1973
|
-
try {
|
|
1974
|
-
const instanceManager = await import("../instance-manager.js");
|
|
1975
|
-
await instanceManager.deleteInstance(appId);
|
|
1976
|
-
}
|
|
1977
|
-
catch {
|
|
1978
|
-
rmSync(instanceDir, { recursive: true, force: true });
|
|
1979
|
-
}
|
|
1980
|
-
throw decorateInstallError(e, instanceSpec);
|
|
1981
|
-
}
|
|
1982
|
-
if (artifacts.length > 0) {
|
|
1983
|
-
manifest.artifacts = artifacts;
|
|
1984
|
-
safeWriteJson(join(instanceDir, "manifest.json"), manifest, true);
|
|
1985
|
-
}
|
|
1986
|
-
invalidateRuntimeIdentity(appId);
|
|
1987
|
-
return { spec: instanceSpec, manifest };
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Check whether an app with this id is installed (has manifest.json + app-spec.yaml).
|
|
1991
|
-
* Exported so nomad-manager can use it for routing without the old prefix convention.
|
|
1992
|
-
*/
|
|
1993
|
-
export function isAppInstalled(appId) {
|
|
1994
|
-
const location = resolveAppLocation(appId);
|
|
1995
|
-
return location != null && !hasInstallLock(location.dir);
|
|
1996
|
-
}
|
|
1997
|
-
function resolveAppDir(appId) {
|
|
1998
|
-
return resolveAppLocation(appId)?.dir ?? null;
|
|
1999
|
-
}
|
|
2000
|
-
function readAppFromDir(appDir, appId, installModeHint = "app-dir") {
|
|
2001
|
-
if (cleanupStaleInstallDir(appDir))
|
|
2002
|
-
return null;
|
|
2003
|
-
const yamlPath = join(appDir, "app-spec.yaml");
|
|
2004
|
-
const manifestPath = join(appDir, "manifest.json");
|
|
2005
|
-
if (!existsSync(yamlPath) || !existsSync(manifestPath))
|
|
2006
|
-
return null;
|
|
2007
|
-
try {
|
|
2008
|
-
const spec = normalizeAppSpec(parse(readFileSync(yamlPath, "utf-8")));
|
|
2009
|
-
const manifest = safeReadJson(manifestPath, `app:${appId}`);
|
|
2010
|
-
if (!manifest)
|
|
2011
|
-
return null;
|
|
2012
|
-
return {
|
|
2013
|
-
spec,
|
|
2014
|
-
manifest: {
|
|
2015
|
-
...manifest,
|
|
2016
|
-
install_mode: manifest.install_mode ?? installModeHint,
|
|
2017
|
-
},
|
|
2018
|
-
install_state: hasInstallLock(appDir) ? "installing" : "installed",
|
|
2019
|
-
...(getRunningAppTask(appId, appDir) ? { current_task: getRunningAppTask(appId, appDir) } : {}),
|
|
2020
|
-
};
|
|
2021
|
-
}
|
|
2022
|
-
catch {
|
|
2023
|
-
return null;
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
export function getAppInstallState(appId) {
|
|
2027
|
-
const location = resolveAppLocation(appId);
|
|
2028
|
-
if (!location)
|
|
2029
|
-
return null;
|
|
2030
|
-
return hasInstallLock(location.dir) ? "installing" : "installed";
|
|
2031
|
-
}
|
|
2032
|
-
const BROWSERLESS_DEBUGGER_CAPABILITY = "browserless-debugger";
|
|
2033
|
-
const BROWSERLESS_API_CAPABILITY = "browserless-api";
|
|
2034
|
-
const BROWSERLESS_DOCS_CAPABILITY = "browserless-docs";
|
|
2035
|
-
const JISHU_KB_FRAME_ANCESTORS_ALLOW_ALL = "*";
|
|
2036
|
-
const JISHU_KB_DEFAULT_PROVIDER_RETRY_BLOCK = `echo "[post_start] fetching core default provider ..."
|
|
2037
|
-
fetch_default_provider_json() {
|
|
2038
|
-
curl -sf --max-time 8 \
|
|
2039
|
-
-H "X-Jishushell-Internal-Token: $JISHUSHELL_INTERNAL_TOKEN" \
|
|
2040
|
-
"$JISHUSHELL_CORE_URL/api/internal/default-provider"
|
|
2041
|
-
}
|
|
2042
|
-
DP_JSON=""
|
|
2043
|
-
for i in $(seq 1 30); do
|
|
2044
|
-
DP_JSON=$(fetch_default_provider_json || true)
|
|
2045
|
-
READY=$(printf '%s' "$DP_JSON" | jget ready)
|
|
2046
|
-
REASON=$(printf '%s' "$DP_JSON" | jget reason)
|
|
2047
|
-
if [ "$READY" = "true" ]; then
|
|
2048
|
-
if [ "$i" -gt 1 ]; then
|
|
2049
|
-
echo "[post_start] core default provider became ready after $i attempts"
|
|
2050
|
-
fi
|
|
2051
|
-
break
|
|
2052
|
-
fi
|
|
2053
|
-
case "$REASON" in
|
|
2054
|
-
no_provider|no_key)
|
|
2055
|
-
break
|
|
2056
|
-
;;
|
|
2057
|
-
esac
|
|
2058
|
-
DP_JSON='{"ready":false,"reason":"core_unreachable"}'
|
|
2059
|
-
if [ "$i" -lt 30 ]; then
|
|
2060
|
-
echo "[post_start] core default provider not ready yet (attempt $i/30); retrying in 2s"
|
|
2061
|
-
sleep 2
|
|
2062
|
-
fi
|
|
2063
|
-
done
|
|
2064
|
-
READY=$(printf '%s' "$DP_JSON" | jget ready)`;
|
|
2065
|
-
// `ensureRequiredCapabilitiesAvailable` was removed in PR 3 sub-step 3c.
|
|
2066
|
-
// install never blocks on missing required providers any more —
|
|
2067
|
-
// resolveConnections(..., "preCreate") collects them into the `pending`
|
|
2068
|
-
// list and the UI surfaces them after install completes.
|
|
2069
|
-
// `listProvidedCapabilities()` does a full filesystem app scan (listApps →
|
|
2070
|
-
// readdir + per-app spec parse) plus a registry read. `augmentInstanceMetadata`
|
|
2071
|
-
// calls it ~2× per instance (getProvidedCapabilitiesForApp +
|
|
2072
|
-
// getEmbeddedUiHintForApp), so `GET /api/instances` over N instances did ~2N
|
|
2073
|
-
// full disk scans per request — the dashboard warm-path cost.
|
|
2074
|
-
//
|
|
2075
|
-
// Cache the result, but key validity on a CHEAP fingerprint of the
|
|
2076
|
-
// underlying state, not on time alone. A time-only TTL left a staleness
|
|
2077
|
-
// window where a freshly installed/uninstalled app (or created/deleted
|
|
2078
|
-
// instance — INSTANCES_DIR also feeds listApps) was missing from / lingered
|
|
2079
|
-
// in the list until the TTL expired, which broke the Connections UI and the
|
|
2080
|
-
// embedded-UI hint. The fingerprint (sorted dir entries of APPS_DIR +
|
|
2081
|
-
// INSTANCES_DIR, plus the registry file mtime) changes the instant the app
|
|
2082
|
-
// set or registry changes — including external mutations — so the cache
|
|
2083
|
-
// self-busts without having to hook every mutation site. It costs two
|
|
2084
|
-
// readdir + one stat, still vastly cheaper than re-parsing every app spec.
|
|
2085
|
-
// The short TTL stays only as a safety net for in-place spec edits that
|
|
2086
|
-
// don't change the dir set; the explicit register/unregister/markStopped
|
|
2087
|
-
// invalidations remain as a precise fast-path for sub-millisecond registry
|
|
2088
|
-
// writes that could share an mtime tick.
|
|
2089
|
-
const PROVIDED_CAPS_TTL_MS = 5_000;
|
|
2090
|
-
let _providedCapsEntry = null;
|
|
2091
|
-
function providedCapabilitiesFingerprint() {
|
|
2092
|
-
const dirEntries = (dir) => {
|
|
2093
|
-
try {
|
|
2094
|
-
return existsSync(dir) ? readdirSync(dir).sort().join(",") : "";
|
|
2095
|
-
}
|
|
2096
|
-
catch {
|
|
2097
|
-
return "";
|
|
2098
|
-
}
|
|
2099
|
-
};
|
|
2100
|
-
let registryMtime = 0;
|
|
2101
|
-
try {
|
|
2102
|
-
registryMtime = lstatSync(_REGISTRY_PATH).mtimeMs;
|
|
2103
|
-
}
|
|
2104
|
-
catch {
|
|
2105
|
-
/* registry file not written yet → 0 */
|
|
2106
|
-
}
|
|
2107
|
-
return `${dirEntries(APPS_DIR)}|${registryMtime}`;
|
|
2108
|
-
}
|
|
2109
|
-
export function invalidateProvidedCapabilitiesCache() {
|
|
2110
|
-
_providedCapsEntry = null;
|
|
2111
|
-
}
|
|
2112
|
-
export function listProvidedCapabilities() {
|
|
2113
|
-
const fp = providedCapabilitiesFingerprint();
|
|
2114
|
-
const now = Date.now();
|
|
2115
|
-
if (_providedCapsEntry &&
|
|
2116
|
-
_providedCapsEntry.fp === fp &&
|
|
2117
|
-
now - _providedCapsEntry.ts < PROVIDED_CAPS_TTL_MS) {
|
|
2118
|
-
return _providedCapsEntry.data;
|
|
2119
|
-
}
|
|
2120
|
-
const data = computeProvidedCapabilities();
|
|
2121
|
-
_providedCapsEntry = { data, fp, ts: now };
|
|
2122
|
-
return data;
|
|
2123
|
-
}
|
|
2124
|
-
function computeProvidedCapabilities() {
|
|
2125
|
-
const reg = capabilityRegistry.snapshot();
|
|
2126
|
-
return listApps().flatMap((app) => (app.spec.provides ?? []).map((provide) => {
|
|
2127
|
-
const url = getProvideUrl(provide) ?? undefined;
|
|
2128
|
-
// Report the RUNTIME port the capability actually listens on (the
|
|
2129
|
-
// resolver consults the instance's recorded port allocation and only
|
|
2130
|
-
// falls back to the declared spec port when no runtime exists yet).
|
|
2131
|
-
// Declared spec ports go stale once a sibling task is remapped onto a
|
|
2132
|
-
// different host port (multi-service-task apps like WeKnora).
|
|
2133
|
-
const port = resolveRuntimeCapabilityPort(app.manifest.id, provide.capability) ?? undefined;
|
|
2134
|
-
const address = !url && typeof port === "number" ? buildCapabilityAddress(port, provide.path) : undefined;
|
|
2135
|
-
const providers = reg.providersByCapability?.[provide.capability] ?? [];
|
|
2136
|
-
const registered = providers.find((e) => e.instanceId === app.manifest.id)
|
|
2137
|
-
?? providers.find((e) => e.status === "running")
|
|
2138
|
-
?? providers[0];
|
|
2139
|
-
const protocol = resolveProvideProtocol(provide);
|
|
2140
|
-
return {
|
|
2141
|
-
appId: app.manifest.id,
|
|
2142
|
-
capability: provide.capability,
|
|
2143
|
-
...(typeof port === "number" ? { port } : {}),
|
|
2144
|
-
...(provide.path ? { path: provide.path } : {}),
|
|
2145
|
-
...(url ? { url } : {}),
|
|
2146
|
-
protocol,
|
|
2147
|
-
...(provide.visibility ? { visibility: provide.visibility } : {}),
|
|
2148
|
-
...(provide.description ? { description: provide.description } : {}),
|
|
2149
|
-
...(provide.terminal ? { terminal: provide.terminal } : {}),
|
|
2150
|
-
...(provide.embedded ? { embedded: provide.embedded } : {}),
|
|
2151
|
-
...(address ? { address } : {}),
|
|
2152
|
-
registered: Boolean(registered),
|
|
2153
|
-
...(registered?.address ? { registeredAddress: registered.address } : {}),
|
|
2154
|
-
...(registered?.instanceId ? { providerInstanceId: registered.instanceId } : {}),
|
|
2155
|
-
};
|
|
2156
|
-
}));
|
|
2157
|
-
}
|
|
2158
|
-
export function getProvidedCapabilitiesForApp(appId) {
|
|
2159
|
-
return listProvidedCapabilities().filter((entry) => entry.appId === appId);
|
|
2160
|
-
}
|
|
2161
|
-
/**
|
|
2162
|
-
* Resolve the runtime endpoint (host + port) for a given app's capability.
|
|
2163
|
-
* Returns the runtime-resolved port when available, falling back to the
|
|
2164
|
-
* declared AppSpec port. This is used by the capability proxy to target the
|
|
2165
|
-
* correct live endpoint after potential port reallocation.
|
|
2166
|
-
*/
|
|
2167
|
-
export function resolveRuntimeCapabilityPort(appId, capabilityName) {
|
|
2168
|
-
const app = getApp(appId);
|
|
2169
|
-
if (!app)
|
|
2170
|
-
return null;
|
|
2171
|
-
const provide = (app.spec.provides ?? []).find((p) => p.capability === capabilityName);
|
|
2172
|
-
if (!provide)
|
|
2173
|
-
return null;
|
|
2174
|
-
const resolved = resolveProvideEndpoint(appId, app.spec, provide);
|
|
2175
|
-
return resolved?.hostPort ?? getProvidePort(app.spec, provide);
|
|
2176
|
-
}
|
|
2177
|
-
export function getEmbeddedUiHintForApp(appId) {
|
|
2178
|
-
const app = getApp(appId);
|
|
2179
|
-
if (!app)
|
|
2180
|
-
return null;
|
|
2181
|
-
// OpenClaw should always render through its dedicated gateway-launch iframe
|
|
2182
|
-
// path, not through the generic app-provides embedded UI chooser. App-dir
|
|
2183
|
-
// OpenClaw specs expose `openclaw-dashboard`, which would otherwise be
|
|
2184
|
-
// advertised as a direct LAN URL (e.g. http://10.x.x.x:18793) and hijack the
|
|
2185
|
-
// panel into a browser-direct iframe that bypasses gateway auth/proxy logic.
|
|
2186
|
-
// Returning null here lets InstanceDetail fall back to
|
|
2187
|
-
// `capabilities.gateway.chatPanel === "iframe"` → `/gateway-launch`.
|
|
2188
|
-
if (app.spec?.agentType === "openclaw")
|
|
2189
|
-
return null;
|
|
2190
|
-
const provides = getProvidedCapabilitiesForApp(appId);
|
|
2191
|
-
if (!provides.length)
|
|
2192
|
-
return null;
|
|
2193
|
-
const isUiCapability = (capability) => capability.endsWith("-ui") || capability.startsWith("web-") || capability.endsWith("-web");
|
|
2194
|
-
// Selection priority for which provide becomes the embedded UI:
|
|
2195
|
-
// 1. browserless-debugger (special — dev console, not the API)
|
|
2196
|
-
// 2. any capability whose name looks like a Web UI slot:
|
|
2197
|
-
// `*-ui`, `web-*`, or `*-web`. Apps like AnythingLLM provide both an
|
|
2198
|
-
// API capability (`knowledge-anythingllm`, path `/api/v1`) AND a UI
|
|
2199
|
-
// capability (`anythingllm-ui`, path `/`). Jishu KB uses `web-jishukb`
|
|
2200
|
-
// while OpenWebUI/Hollama use `*-web`. Without this preference the
|
|
2201
|
-
// first-iterated API provide can win and the iframe lands on JSON / a
|
|
2202
|
-
// direct upstream URL instead of the intended Web UI.
|
|
2203
|
-
// 3. fall back to natural order for legacy single-capability apps.
|
|
2204
|
-
const browserlessPreferred = provides.find((provide) => provide.capability === BROWSERLESS_DEBUGGER_CAPABILITY);
|
|
2205
|
-
const uiPreferred = provides.find((provide) => isUiCapability(provide.capability));
|
|
2206
|
-
const preferred = browserlessPreferred ?? uiPreferred ?? null;
|
|
2207
|
-
const orderedProvides = preferred
|
|
2208
|
-
? [preferred, ...provides.filter((provide) => provide !== preferred)]
|
|
2209
|
-
: provides;
|
|
2210
|
-
for (const provide of orderedProvides) {
|
|
2211
|
-
const protocol = normalizeProvideProtocol(provide.protocol);
|
|
2212
|
-
if (provide.visibility === "internal")
|
|
2213
|
-
continue;
|
|
2214
|
-
if (protocol !== "http" && protocol !== "https")
|
|
2215
|
-
continue;
|
|
2216
|
-
if (typeof provide.url === "string" && provide.url.trim()) {
|
|
2217
|
-
const url = provide.url.trim();
|
|
2218
|
-
return {
|
|
2219
|
-
capability: provide.capability,
|
|
2220
|
-
protocol,
|
|
2221
|
-
url,
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
// Resolve the RUNTIME port for this capability, not the declared spec
|
|
2225
|
-
// port. For multi-service-task apps the declared port can be a dead value
|
|
2226
|
-
// once another task has been remapped onto a different host port; the
|
|
2227
|
-
// runtime resolver returns the port the capability actually listens on
|
|
2228
|
-
// (falling back to the declared port when no runtime is recorded yet).
|
|
2229
|
-
const resolvedPort = resolveRuntimeCapabilityPort(appId, provide.capability) ?? provide.port;
|
|
2230
|
-
if (typeof resolvedPort !== "number" || resolvedPort < 1)
|
|
2231
|
-
continue;
|
|
2232
|
-
// Honor explicit `embedded` opt-in/out on the provide before the
|
|
2233
|
-
// auto-detection logic runs. `"proxy"` short-circuits to the
|
|
2234
|
-
// same-origin proxy path (needed when upstream is firewall-blocked,
|
|
2235
|
-
// emits X-Frame-Options, or otherwise can't be reached by the
|
|
2236
|
-
// browser directly). `"direct"` forces the direct URL even when
|
|
2237
|
-
// listening only on loopback — caller asserts they know what
|
|
2238
|
-
// they're doing.
|
|
2239
|
-
const embeddedMode = provide.embedded ?? "auto";
|
|
2240
|
-
// Prefer a direct upstream URL when the container port is published to
|
|
2241
|
-
// a LAN-reachable address (Pi with host_network "external", etc.). The
|
|
2242
|
-
// same-origin reverse-proxy path is necessary only when the container
|
|
2243
|
-
// is bound to 127.0.0.1 (macOS+Colima, dev laptops without LAN
|
|
2244
|
-
// exposure) — there it's the only way for a remote browser to reach
|
|
2245
|
-
// the iframe content. Going through the proxy when the upstream is
|
|
2246
|
-
// already public causes path-collision bugs for apps that fetch
|
|
2247
|
-
// absolute URLs starting with `/api/...` (e.g. OpenWebUI), because
|
|
2248
|
-
// those calls bypass `<base href>` and hit the core API instead.
|
|
2249
|
-
const listeningHost = instanceServices.getListeningHostForPort(resolvedPort);
|
|
2250
|
-
const isLoopback = listeningHost === "127.0.0.1" || listeningHost === "::1";
|
|
2251
|
-
const directlyReachable = embeddedMode !== "proxy"
|
|
2252
|
-
&& listeningHost
|
|
2253
|
-
&& (!isLoopback || embeddedMode === "direct");
|
|
2254
|
-
if (directlyReachable) {
|
|
2255
|
-
// Advertise a browser-reachable host. getAdvertisedHostForPort already
|
|
2256
|
-
// prefers the external/LAN address and only ever falls back to the
|
|
2257
|
-
// primary IPv4 — but when the port is published on the docker_bridge
|
|
2258
|
-
// (docker0 IP 172.17.0.1) it is internal-only and unreachable from a
|
|
2259
|
-
// remote browser, so override it with the LAN address for the embedded
|
|
2260
|
-
// UI slot.
|
|
2261
|
-
const rawAdvertised = instanceServices.getAdvertisedHostForPort(resolvedPort);
|
|
2262
|
-
const advertised = isDockerBridgeHost(rawAdvertised)
|
|
2263
|
-
? instanceServices.getPrimaryIpv4Address()
|
|
2264
|
-
: rawAdvertised;
|
|
2265
|
-
const directUrl = `${protocol}://${advertised}:${resolvedPort}${provide.path ?? ""}`;
|
|
2266
|
-
return {
|
|
2267
|
-
capability: provide.capability,
|
|
2268
|
-
protocol,
|
|
2269
|
-
port: resolvedPort,
|
|
2270
|
-
url: directUrl,
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
|
-
// Use same-origin reverse-proxy path so the frontend iframe works for
|
|
2274
|
-
// remote browsers and macOS+Colima environments where the container
|
|
2275
|
-
// port is only published to 127.0.0.1.
|
|
2276
|
-
// Root-path UIs (path omitted) still need a trailing slash so the
|
|
2277
|
-
// browser treats the iframe src as a directory URL. Without it, some
|
|
2278
|
-
// SPA runtimes compute relative URLs from `/.../provides/<capability>`
|
|
2279
|
-
// as if `<capability>` were a file segment, which breaks boot under the
|
|
2280
|
-
// proxy even when the HTML/base rewrite succeeded.
|
|
2281
|
-
const normalizedProvidePath = typeof provide.path === "string" ? provide.path.trim() : "";
|
|
2282
|
-
const needsTrailingSlash = !normalizedProvidePath || normalizedProvidePath.endsWith("/");
|
|
2283
|
-
const proxyPath = `/api/instances/${encodeURIComponent(appId)}/provides/${encodeURIComponent(provide.capability)}${needsTrailingSlash ? "/" : ""}`;
|
|
2284
|
-
return {
|
|
2285
|
-
capability: provide.capability,
|
|
2286
|
-
protocol,
|
|
2287
|
-
port: resolvedPort,
|
|
2288
|
-
url: proxyPath,
|
|
2289
|
-
};
|
|
2290
|
-
}
|
|
2291
|
-
return null;
|
|
2292
|
-
}
|
|
2293
|
-
export async function installApp(specYaml, requestedAppId, options = {}) {
|
|
2294
|
-
let spec;
|
|
2295
|
-
try {
|
|
2296
|
-
spec = normalizeAppSpec(parse(specYaml));
|
|
2297
|
-
}
|
|
2298
|
-
catch (e) {
|
|
2299
|
-
throw new Error(`YAML 解析失败: ${e.message}`);
|
|
2300
|
-
}
|
|
2301
|
-
if (!spec || !spec.id || !APP_ID_RE.test(spec.id)) {
|
|
2302
|
-
throw new Error(`App id '${spec?.id}' 格式无效,必须符合 /^[a-z0-9][a-z0-9-]{0,62}$/`);
|
|
2303
|
-
}
|
|
2304
|
-
ensureCompatibleJishuShellVersion(spec);
|
|
2305
|
-
if (!Array.isArray(spec.tasks) || spec.tasks.length === 0) {
|
|
2306
|
-
throw new Error("tasks 不能为空");
|
|
2307
|
-
}
|
|
2308
|
-
const hasService = spec.tasks.some((t) => t.role === "service");
|
|
2309
|
-
if (!hasService) {
|
|
2310
|
-
throw new Error("tasks 中至少需要一个 role 为 'service' 的任务");
|
|
2311
|
-
}
|
|
2312
|
-
for (const task of spec.tasks) {
|
|
2313
|
-
if (task.runtime === "vm") {
|
|
2314
|
-
throw new Error(`runtime 'vm' 暂不支持,请使用 runtime: container 或 process`);
|
|
2315
|
-
}
|
|
2316
|
-
if (task.runtime === "container" && !task.image) {
|
|
2317
|
-
throw new Error(`container task '${task.name}' 需要指定 image 字段`);
|
|
2318
|
-
}
|
|
2319
|
-
if (task.runtime === "process" && !task.command) {
|
|
2320
|
-
throw new Error(`process task '${task.name}' 需要指定 command 或 binary 字段`);
|
|
2321
|
-
}
|
|
2322
|
-
if (task.image && !DOCKER_IMAGE_RE.test(task.image)) {
|
|
2323
|
-
throw new Error(`task '${task.name}' 的 image '${task.image}' 格式无效`);
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
// PR 3 sub-step 3c: removed the legacy `ensureRequiredCapabilitiesAvailable`
|
|
2327
|
-
// hard-stop. install now never blocks on missing required providers —
|
|
2328
|
-
// resolveConnections(..., "preCreate") records them as `pending` on the
|
|
2329
|
-
// install task event so the UI can prompt the user; start time surfaces
|
|
2330
|
-
// the error if still unresolved.
|
|
2331
|
-
const instanceBackedInstall = await installIntoInstanceDir(spec, specYaml, requestedAppId, options);
|
|
2332
|
-
if (instanceBackedInstall) {
|
|
2333
|
-
invalidateRuntimeIdentity(instanceBackedInstall.manifest.id);
|
|
2334
|
-
return instanceBackedInstall;
|
|
2335
|
-
}
|
|
2336
|
-
const { appId, installedSpec, installedSpecYaml, } = await resolveInstallTarget(spec, specYaml, requestedAppId);
|
|
2337
|
-
const appDir = appDirForId(appId);
|
|
2338
|
-
ensureDirHost(appDir);
|
|
2339
|
-
createInstallLock(appDir, appId, spec.id, options.task);
|
|
2340
|
-
emitInstallTaskLog(options.task, `[app-manager] created ${INSTALL_LOCK_FILENAME} for ${appId}`);
|
|
2341
|
-
const yamlPath = join(appDir, "app-spec.yaml");
|
|
2342
|
-
const yamlTmp = yamlPath + ".tmp";
|
|
2343
|
-
writeFileSync(yamlTmp, installedSpecYaml, { mode: 0o644 });
|
|
2344
|
-
renameSync(yamlTmp, yamlPath);
|
|
2345
|
-
const manifest = {
|
|
2346
|
-
id: appId,
|
|
2347
|
-
installed_at: new Date().toISOString(),
|
|
2348
|
-
spec_hash: createHash("sha256").update(installedSpecYaml).digest("hex"),
|
|
2349
|
-
install_mode: "app-dir",
|
|
2350
|
-
};
|
|
2351
|
-
safeWriteJson(join(appDir, "manifest.json"), manifest, true);
|
|
2352
|
-
const artifacts = [];
|
|
2353
|
-
try {
|
|
2354
|
-
await runLifecycleSteps(installedSpec.lifecycle?.pre_install, "pre_install", artifacts, options.task, options.exec);
|
|
2355
|
-
}
|
|
2356
|
-
catch (e) {
|
|
2357
|
-
cleanupArtifacts(artifacts, options.task);
|
|
2358
|
-
rmSync(appDir, { recursive: true, force: true });
|
|
2359
|
-
throw decorateInstallError(e, installedSpec);
|
|
2360
|
-
}
|
|
2361
|
-
try {
|
|
2362
|
-
await runLifecycleSteps(installedSpec.lifecycle?.install, "install", artifacts, options.task, options.exec);
|
|
2363
|
-
// Auto-pull docker images declared in tasks (deduplicated, skip already-pulled by lifecycle steps)
|
|
2364
|
-
const pulledImages = new Set(artifacts.filter(a => a.type === "image").map(a => a.path));
|
|
2365
|
-
const imagesToPull = [...new Set(installedSpec.tasks.filter(t => t.image).map(t => t.image))];
|
|
2366
|
-
for (const image of imagesToPull) {
|
|
2367
|
-
if (!pulledImages.has(image)) {
|
|
2368
|
-
await pullDockerImageStep("install", image, `docker pull ${image}`, options.task);
|
|
2369
|
-
artifacts.push({ type: "image", path: image });
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
const bootstrappedByAdapter = await bootstrapAdapterManagedApp(appId, installedSpec, options);
|
|
2373
|
-
if (!bootstrappedByAdapter) {
|
|
2374
|
-
safeWriteJson(join(appDir, "instance.json"), buildGenericAppMeta(appId, installedSpec, manifest.installed_at), true);
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
catch (e) {
|
|
2378
|
-
try {
|
|
2379
|
-
await runLifecycleSteps(installedSpec.lifecycle?.uninstall, "rollback_uninstall", undefined, options.task, options.exec);
|
|
2380
|
-
}
|
|
2381
|
-
catch (rollbackError) {
|
|
2382
|
-
process.stdout.write(` [app-manager] rollback uninstall failed: ${rollbackError.message}\n`);
|
|
2383
|
-
emitInstallTaskLog(options.task, `[app-manager] rollback uninstall failed: ${rollbackError.message}`);
|
|
2384
|
-
}
|
|
2385
|
-
cleanupArtifacts(artifacts, options.task);
|
|
2386
|
-
rmSync(appDir, { recursive: true, force: true });
|
|
2387
|
-
throw decorateInstallError(e, installedSpec);
|
|
2388
|
-
}
|
|
2389
|
-
if (artifacts.length > 0) {
|
|
2390
|
-
manifest.artifacts = artifacts;
|
|
2391
|
-
safeWriteJson(join(appDir, "manifest.json"), manifest, true);
|
|
2392
|
-
}
|
|
2393
|
-
removeInstallLock(appDir);
|
|
2394
|
-
emitInstallTaskLog(options.task, `[app-manager] removed ${INSTALL_LOCK_FILENAME} for ${appId}`);
|
|
2395
|
-
invalidateRuntimeIdentity(appId);
|
|
2396
|
-
return { spec: installedSpec, manifest };
|
|
2397
|
-
}
|
|
2398
|
-
export function listApps() {
|
|
2399
|
-
const results = [];
|
|
2400
|
-
for (const { rootDir, installMode } of [
|
|
2401
|
-
{ rootDir: APPS_DIR, installMode: "app-dir" },
|
|
2402
|
-
]) {
|
|
2403
|
-
if (!existsSync(rootDir))
|
|
2404
|
-
continue;
|
|
2405
|
-
let entries;
|
|
2406
|
-
try {
|
|
2407
|
-
entries = readdirSync(rootDir, { withFileTypes: true });
|
|
2408
|
-
}
|
|
2409
|
-
catch {
|
|
2410
|
-
continue;
|
|
2411
|
-
}
|
|
2412
|
-
for (const entry of entries) {
|
|
2413
|
-
if (!entry.isDirectory())
|
|
2414
|
-
continue;
|
|
2415
|
-
const appId = entry.name;
|
|
2416
|
-
const appDir = join(rootDir, appId);
|
|
2417
|
-
try {
|
|
2418
|
-
const appData = readAppFromDir(appDir, appId, installMode);
|
|
2419
|
-
if (!appData)
|
|
2420
|
-
continue;
|
|
2421
|
-
results.push(appData);
|
|
2422
|
-
}
|
|
2423
|
-
catch (e) {
|
|
2424
|
-
console.warn(`[app-manager] Skipping app '${appId}': ${e.message}`);
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
const deduped = new Map();
|
|
2429
|
-
for (const item of results) {
|
|
2430
|
-
deduped.set(item.manifest.id, item);
|
|
2431
|
-
}
|
|
2432
|
-
return [...deduped.values()];
|
|
2433
|
-
}
|
|
2434
|
-
export function getApp(id) {
|
|
2435
|
-
if (!APP_ID_RE.test(id))
|
|
2436
|
-
return null;
|
|
2437
|
-
const location = resolveAppLocation(id);
|
|
2438
|
-
if (!location)
|
|
2439
|
-
return null;
|
|
2440
|
-
return readAppFromDir(location.dir, id, location.installMode);
|
|
2441
|
-
}
|
|
2442
|
-
function writeAppManagerWarnings(warnings) {
|
|
2443
|
-
for (const warning of warnings) {
|
|
2444
|
-
process.stdout.write(` [app-manager] ${warning}\n`);
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
export async function uninstallApp(id, options = {}) {
|
|
2448
|
-
if (!APP_ID_RE.test(id))
|
|
2449
|
-
return;
|
|
2450
|
-
const appData = getApp(id);
|
|
2451
|
-
const warnings = [];
|
|
2452
|
-
if (isInstanceBackedApp(appData)) {
|
|
2453
|
-
const { stopNomadJobInstance } = await import("../nomad-manager.js");
|
|
2454
|
-
const stopResult = await stopNomadJobInstance(id, true);
|
|
2455
|
-
if (!stopResult.ok && stopResult.error && !stopResult.error.includes("not found") && !stopResult.error.includes("not running")) {
|
|
2456
|
-
warnings.push(`应用 '${id}' 停止失败: ${stopResult.error}`);
|
|
2457
|
-
}
|
|
2458
|
-
if (appData) {
|
|
2459
|
-
// For multi-instance container apps the uninstalled app is the base
|
|
2460
|
-
// template and may have spawned children via copyApp. Children carry
|
|
2461
|
-
// `app_id === id`; stop + delete them here so they don't outlive the
|
|
2462
|
-
// template that described their lifecycle.
|
|
2463
|
-
if (appData.spec.singleInstance === false) {
|
|
2464
|
-
warnings.push(...await deleteLinkedInstances(appData.manifest.id));
|
|
2465
|
-
}
|
|
2466
|
-
const uninstallSteps = selectUninstallLifecycleSteps(appData);
|
|
2467
|
-
if (uninstallSteps?.length) {
|
|
2468
|
-
try {
|
|
2469
|
-
await runLifecycleSteps(uninstallSteps, "uninstall", undefined, undefined, options.exec);
|
|
2470
|
-
}
|
|
2471
|
-
catch (e) {
|
|
2472
|
-
writeAppManagerWarnings(warnings);
|
|
2473
|
-
throw new Error(`卸载生命周期执行失败: ${e.message}`);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
const cleanupList = selectCleanupArtifacts(appData);
|
|
2477
|
-
if (cleanupList.length > 0) {
|
|
2478
|
-
cleanupArtifacts(cleanupList);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
const instanceManager = await import("../instance-manager.js");
|
|
2482
|
-
const deleteResult = await instanceManager.deleteInstance(id);
|
|
2483
|
-
if (!deleteResult.ok && deleteResult.warnings?.length) {
|
|
2484
|
-
warnings.push(...deleteResult.warnings);
|
|
2485
|
-
}
|
|
2486
|
-
await syncCapabilitiesForApp(id);
|
|
2487
|
-
invalidateRuntimeIdentity(id);
|
|
2488
|
-
writeAppManagerWarnings(warnings);
|
|
2489
|
-
return;
|
|
2490
|
-
}
|
|
2491
|
-
const adapterManagedAgentType = getAdapterManagedAgentType(appData);
|
|
2492
|
-
if (adapterManagedAgentType) {
|
|
2493
|
-
const { stopNomadJobInstance } = await import("../nomad-manager.js");
|
|
2494
|
-
const stopResult = await stopNomadJobInstance(id, true);
|
|
2495
|
-
if (!stopResult.ok && stopResult.error && !stopResult.error.includes("not found") && !stopResult.error.includes("not running")) {
|
|
2496
|
-
warnings.push(`应用 '${id}' 停止失败: ${stopResult.error}`);
|
|
2497
|
-
}
|
|
2498
|
-
if (appData) {
|
|
2499
|
-
warnings.push(...await deleteLinkedInstances(appData.manifest.id));
|
|
2500
|
-
const uninstallSteps = selectUninstallLifecycleSteps(appData);
|
|
2501
|
-
if (uninstallSteps?.length) {
|
|
2502
|
-
try {
|
|
2503
|
-
await runLifecycleSteps(uninstallSteps, "uninstall", undefined, undefined, options.exec);
|
|
2504
|
-
}
|
|
2505
|
-
catch (e) {
|
|
2506
|
-
writeAppManagerWarnings(warnings);
|
|
2507
|
-
throw new Error(`卸载生命周期执行失败: ${e.message}`);
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
const cleanupList = selectCleanupArtifacts(appData);
|
|
2511
|
-
if (cleanupList.length > 0) {
|
|
2512
|
-
cleanupArtifacts(cleanupList);
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
const deleteResult = await instanceServices.deleteInstance(id);
|
|
2516
|
-
if (!deleteResult.ok && deleteResult.warnings?.length) {
|
|
2517
|
-
warnings.push(...deleteResult.warnings);
|
|
2518
|
-
}
|
|
2519
|
-
await syncCapabilitiesForApp(id);
|
|
2520
|
-
invalidateRuntimeIdentity(id);
|
|
2521
|
-
writeAppManagerWarnings(warnings);
|
|
2522
|
-
return;
|
|
2523
|
-
}
|
|
2524
|
-
const stopResult = await stopApp(id, true);
|
|
2525
|
-
if (stopResult.ok) {
|
|
2526
|
-
process.stdout.write(` [app-manager] Stopped running job for "${id}"\n`);
|
|
2527
|
-
}
|
|
2528
|
-
else if (stopResult.error && !stopResult.error.includes("not found") && !stopResult.error.includes("not running")) {
|
|
2529
|
-
warnings.push(`应用 '${id}' 停止失败: ${stopResult.error}`);
|
|
2530
|
-
}
|
|
2531
|
-
if (appData) {
|
|
2532
|
-
warnings.push(...await deleteLinkedInstances(appData.manifest.id));
|
|
2533
|
-
const uninstallSteps = selectUninstallLifecycleSteps(appData);
|
|
2534
|
-
if (uninstallSteps?.length) {
|
|
2535
|
-
try {
|
|
2536
|
-
await runLifecycleSteps(uninstallSteps, "uninstall", undefined, undefined, options.exec);
|
|
2537
|
-
}
|
|
2538
|
-
catch (e) {
|
|
2539
|
-
writeAppManagerWarnings(warnings);
|
|
2540
|
-
throw new Error(`卸载生命周期执行失败: ${e.message}`);
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
const cleanupList = selectCleanupArtifacts(appData);
|
|
2544
|
-
if (cleanupList.length > 0) {
|
|
2545
|
-
cleanupArtifacts(cleanupList);
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
const appDir = resolveAppDir(id) ?? appDirForId(id);
|
|
2549
|
-
rmSync(appDir, { recursive: true, force: true });
|
|
2550
|
-
await syncCapabilitiesForApp(id);
|
|
2551
|
-
invalidateRuntimeIdentity(id);
|
|
2552
|
-
writeAppManagerWarnings(warnings);
|
|
2553
|
-
}
|
|
2554
|
-
export function uninstallAppTask(id, exec) {
|
|
2555
|
-
if (!getApp(id)) {
|
|
2556
|
-
return { ok: false, error: `App '${id}' not found`, kind: "uninstall" };
|
|
2557
|
-
}
|
|
2558
|
-
return startAppLifecycleTask(id, "uninstall", `开始卸载应用 ${id}...`, `应用 ${id} 卸载完成`, async () => {
|
|
2559
|
-
await uninstallApp(id, { exec });
|
|
2560
|
-
});
|
|
2561
|
-
}
|
|
2562
|
-
export async function runPostStartSteps(spec) {
|
|
2563
|
-
await runLifecycleSteps(spec.lifecycle?.post_start, "post_start");
|
|
2564
|
-
}
|
|
2565
|
-
async function runPostStartStepsAfterStart(appId, spec) {
|
|
2566
|
-
if (!spec.lifecycle?.post_start?.length)
|
|
2567
|
-
return;
|
|
2568
|
-
if (spec.provides?.length || spec.lifecycle.post_start.length) {
|
|
2569
|
-
await waitForAppRuntimeRunning(appId, spec.lifecycle.post_start.length ? 120_000 : 30_000);
|
|
2570
|
-
}
|
|
2571
|
-
await runPostStartSteps(spec);
|
|
2572
|
-
}
|
|
2573
|
-
export async function waitForAppRuntimeRunning(appId, timeoutMs = 30_000, pollIntervalMs = 1_000) {
|
|
2574
|
-
const deadline = Date.now() + timeoutMs;
|
|
2575
|
-
while (Date.now() < deadline) {
|
|
2576
|
-
const status = await getAppRuntimeStatus(appId);
|
|
2577
|
-
if (status.status === "running")
|
|
2578
|
-
return true;
|
|
2579
|
-
if (status.status === "dead" || status.status === "failed")
|
|
2580
|
-
return false;
|
|
2581
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2582
|
-
}
|
|
2583
|
-
return false;
|
|
2584
|
-
}
|
|
2585
|
-
export async function syncCapabilitiesForApp(appId) {
|
|
2586
|
-
const { syncCapabilitiesForApp: sync } = await import("../capability-sync.js");
|
|
2587
|
-
await sync(appId);
|
|
2588
|
-
}
|
|
2589
|
-
export async function refreshCapabilityRegistry(options = {}) {
|
|
2590
|
-
const { refreshCapabilityRegistry: refresh } = await import("../capability-sync.js");
|
|
2591
|
-
await refresh(options);
|
|
2592
|
-
}
|
|
2593
|
-
export function resolveRequires(spec) {
|
|
2594
|
-
if (!spec.requires || spec.requires.length === 0)
|
|
2595
|
-
return {};
|
|
2596
|
-
const result = {};
|
|
2597
|
-
for (const req of spec.requires) {
|
|
2598
|
-
const entry = capabilityRegistry
|
|
2599
|
-
.listProviders(req.capability)
|
|
2600
|
-
.find((provider) => provider.status === "running");
|
|
2601
|
-
if (entry) {
|
|
2602
|
-
result[req.inject_as] = entry.address;
|
|
2603
|
-
}
|
|
2604
|
-
else if (req.required !== false) {
|
|
2605
|
-
throw new Error(`Required capability '${req.capability}' is not registered`);
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
return result;
|
|
2609
|
-
}
|
|
2610
|
-
// ── App Lifecycle (delegates to nomad-manager) ───────────────────
|
|
2611
|
-
/**
|
|
2612
|
-
* Read `instance.json` for a generic container app. Returns the parsed
|
|
2613
|
-
* record or `null` if missing/unreadable. Adapter-managed consumers
|
|
2614
|
-
* (OpenClaw, Hermes) keep their state elsewhere; this is the generic
|
|
2615
|
-
* app-dir layout under `~/.jishushell/apps/<appId>/`.
|
|
2616
|
-
*/
|
|
2617
|
-
function readAppInstanceJson(appId) {
|
|
2618
|
-
try {
|
|
2619
|
-
const path = join(APPS_DIR, appId, "instance.json");
|
|
2620
|
-
return safeReadJson(path, `app-instance:${appId}`) ?? null;
|
|
2621
|
-
}
|
|
2622
|
-
catch (e) {
|
|
2623
|
-
console.warn(`[app-instance] read failed for ${appId}: ${e?.message ?? e}`);
|
|
2624
|
-
return null;
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
|
-
/**
|
|
2628
|
-
* Read `instance.json["connections-env"]` for a generic container app and
|
|
2629
|
-
* return a copy of the persisted env vars. Adapter-managed consumers
|
|
2630
|
-
* (OpenClaw, Hermes) route connections through `applyConnectionEnv` and
|
|
2631
|
-
* don't write to this field. Best-effort — failures return empty object
|
|
2632
|
-
* so they never block startup.
|
|
2633
|
-
*/
|
|
2634
|
-
function loadConnectionsEnv(appId) {
|
|
2635
|
-
const inst = readAppInstanceJson(appId);
|
|
2636
|
-
const env = inst?.["connections-env"];
|
|
2637
|
-
if (env && typeof env === "object" && !Array.isArray(env)) {
|
|
2638
|
-
const out = {};
|
|
2639
|
-
for (const [k, v] of Object.entries(env)) {
|
|
2640
|
-
if (typeof v === "string")
|
|
2641
|
-
out[k] = v;
|
|
2642
|
-
}
|
|
2643
|
-
return out;
|
|
2644
|
-
}
|
|
2645
|
-
return {};
|
|
2646
|
-
}
|
|
2647
|
-
export async function startApp(appId) {
|
|
2648
|
-
// Serialize against PUT /connections, stopApp and concurrent startApp on
|
|
2649
|
-
// the same instance — see `utils/instance-lock.ts` and §10.3 of the
|
|
2650
|
-
// app-interconnect design.
|
|
2651
|
-
return withInstanceLock(appId, () => startAppImpl(appId));
|
|
2652
|
-
}
|
|
2653
|
-
async function startAppImpl(appId) {
|
|
2654
|
-
const appData = getApp(appId);
|
|
2655
|
-
if (!appData) {
|
|
2656
|
-
return { ok: false, error: `App '${appId}' not found` };
|
|
2657
|
-
}
|
|
2658
|
-
if (appData.install_state === "installing") {
|
|
2659
|
-
return { ok: false, error: `App '${appId}' is still installing` };
|
|
2660
|
-
}
|
|
2661
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2662
|
-
if (appData.spec.lifecycle?.pre_start?.length) {
|
|
2663
|
-
await runLifecycleSteps(appData.spec.lifecycle.pre_start, "pre_start");
|
|
2664
|
-
}
|
|
2665
|
-
const { startNomadJobInstance } = await import("../nomad-manager.js");
|
|
2666
|
-
const result = await startNomadJobInstance(appId);
|
|
2667
|
-
if (!result.ok)
|
|
2668
|
-
return result;
|
|
2669
|
-
await runPostStartStepsAfterStart(appId, appData.spec);
|
|
2670
|
-
await syncCapabilitiesForApp(appId);
|
|
2671
|
-
return result;
|
|
2672
|
-
}
|
|
2673
|
-
// Resolve requires through the v3 connection-resolver in runtime mode so
|
|
2674
|
-
// missing-required / ambiguous-prefix / invalid-binding all surface with
|
|
2675
|
-
// the structured 412/409/400 codes (§6.4 Phase 4 of the app-interconnect
|
|
2676
|
-
// design). The legacy `resolveRequires` path threw a bare Error which the
|
|
2677
|
-
// route handler could only forward as a generic 400 — losing the bind-vs-
|
|
2678
|
-
// start-vs-pick distinction the UI needs.
|
|
2679
|
-
const instJson = readAppInstanceJson(appId);
|
|
2680
|
-
const persistedEnv = loadConnectionsEnv(appId);
|
|
2681
|
-
let extraEnv = {};
|
|
2682
|
-
try {
|
|
2683
|
-
await refreshCapabilityRegistry();
|
|
2684
|
-
const { resolveConnections, resolvedToLegacyEnv } = await import("../connection-resolver.js");
|
|
2685
|
-
const { renderRuntimeConnectionsEnv } = await import("../connection-apply.js");
|
|
2686
|
-
const { resolved } = resolveConnections(appData.spec, { connections: instJson?.connections ?? {} }, "runtime");
|
|
2687
|
-
const runtimeEnv = await renderRuntimeConnectionsEnv(appData.spec, {
|
|
2688
|
-
id: appId,
|
|
2689
|
-
connections: instJson?.connections,
|
|
2690
|
-
});
|
|
2691
|
-
// Frozen `connections-env` is the lowest-priority fallback for legacy
|
|
2692
|
-
// apps that pre-date resolveConnections. Runtime-rendered env wins so
|
|
2693
|
-
// provider port/IP changes propagate without re-binding.
|
|
2694
|
-
extraEnv = { ...persistedEnv, ...resolvedToLegacyEnv(resolved), ...runtimeEnv };
|
|
2695
|
-
}
|
|
2696
|
-
catch (e) {
|
|
2697
|
-
return {
|
|
2698
|
-
ok: false,
|
|
2699
|
-
error: e.message,
|
|
2700
|
-
...(e.code ? { code: e.code } : {}),
|
|
2701
|
-
...(typeof e.statusCode === "number" ? { statusCode: e.statusCode } : {}),
|
|
2702
|
-
};
|
|
2703
|
-
}
|
|
2704
|
-
const { startAppJob: nomadStart, checkDependencies } = await import("../nomad-manager.js");
|
|
2705
|
-
const depCheck = await checkDependencies(appData.spec);
|
|
2706
|
-
if (!depCheck.ok) {
|
|
2707
|
-
return { ok: false, error: depCheck.errors.join("; ") };
|
|
2708
|
-
}
|
|
2709
|
-
// Run pre_start steps right before submitting the Nomad job — gives
|
|
2710
|
-
// apps a place to enforce per-start invariants (e.g. chown the
|
|
2711
|
-
// bind-mount source so the container's runtime uid can write).
|
|
2712
|
-
if (appData.spec.lifecycle?.pre_start?.length) {
|
|
2713
|
-
await runLifecycleSteps(appData.spec.lifecycle.pre_start, "pre_start");
|
|
2714
|
-
}
|
|
2715
|
-
const result = await nomadStart(appData.spec, appId, extraEnv);
|
|
2716
|
-
if (!result.ok) {
|
|
2717
|
-
return result;
|
|
2718
|
-
}
|
|
2719
|
-
await runPostStartStepsAfterStart(appId, appData.spec);
|
|
2720
|
-
await syncCapabilitiesForApp(appId);
|
|
2721
|
-
return result;
|
|
2722
|
-
}
|
|
2723
|
-
export function startAppTask(appId) {
|
|
2724
|
-
if (!getApp(appId)) {
|
|
2725
|
-
return { ok: false, error: `App '${appId}' not found`, kind: "start" };
|
|
2726
|
-
}
|
|
2727
|
-
return startAppLifecycleTask(appId, "start", `开始启动应用 ${appId}...`, `应用 ${appId} 已启动`, async () => {
|
|
2728
|
-
const result = await startApp(appId);
|
|
2729
|
-
if (!result.ok) {
|
|
2730
|
-
throw new Error(result.error || `App '${appId}' start failed`);
|
|
2731
|
-
}
|
|
2732
|
-
});
|
|
2733
|
-
}
|
|
2734
|
-
export async function stopApp(appId, purge = false) {
|
|
2735
|
-
return withInstanceLock(appId, () => stopAppImpl(appId, purge));
|
|
2736
|
-
}
|
|
2737
|
-
async function stopAppImpl(appId, purge) {
|
|
2738
|
-
const appData = getApp(appId);
|
|
2739
|
-
if (appData?.install_state === "installing") {
|
|
2740
|
-
return { ok: false, error: `App '${appId}' is still installing` };
|
|
2741
|
-
}
|
|
2742
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2743
|
-
const { stopNomadJobInstance } = await import("../nomad-manager.js");
|
|
2744
|
-
const result = await stopNomadJobInstance(appId, purge);
|
|
2745
|
-
if (result.ok || result.error?.includes("not running") || result.error?.includes("not found")) {
|
|
2746
|
-
await syncCapabilitiesForApp(appId);
|
|
2747
|
-
}
|
|
2748
|
-
return result;
|
|
2749
|
-
}
|
|
2750
|
-
const { stopAppJob } = await import("../nomad-manager.js");
|
|
2751
|
-
const result = await stopAppJob(appId, purge);
|
|
2752
|
-
if (result.ok || result.error?.includes("not running") || result.error?.includes("not found")) {
|
|
2753
|
-
await syncCapabilitiesForApp(appId);
|
|
2754
|
-
}
|
|
2755
|
-
return result;
|
|
2756
|
-
}
|
|
2757
|
-
export function stopAppTask(appId, purge = false) {
|
|
2758
|
-
if (!getApp(appId)) {
|
|
2759
|
-
return { ok: false, error: `App '${appId}' not found`, kind: "stop" };
|
|
2760
|
-
}
|
|
2761
|
-
return startAppLifecycleTask(appId, "stop", `开始停止应用 ${appId}${purge ? "(purge)" : ""}...`, `应用 ${appId} 已停止${purge ? "(purged)" : ""}`, async () => {
|
|
2762
|
-
const result = await stopApp(appId, purge);
|
|
2763
|
-
if (!result.ok) {
|
|
2764
|
-
throw new Error(result.error || `App '${appId}' stop failed`);
|
|
2765
|
-
}
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
export async function restartApp(appId) {
|
|
2769
|
-
// Hold the instance lock for the entire restart sequence so a concurrent
|
|
2770
|
-
// PUT /connections / startApp / stopApp on the same id can't observe a
|
|
2771
|
-
// half-restarted state. Inner stop/start calls reuse the same lock id;
|
|
2772
|
-
// we route them through the *Impl helpers to avoid re-acquiring.
|
|
2773
|
-
return withInstanceLock(appId, async () => {
|
|
2774
|
-
const appData = getApp(appId);
|
|
2775
|
-
if (appData?.install_state === "installing") {
|
|
2776
|
-
return { ok: false, error: `App '${appId}' is still installing` };
|
|
2777
|
-
}
|
|
2778
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2779
|
-
const { restartNomadJobInstance } = await import("../nomad-manager.js");
|
|
2780
|
-
const result = await restartNomadJobInstance(appId);
|
|
2781
|
-
if (!result.ok)
|
|
2782
|
-
return result;
|
|
2783
|
-
if (appData) {
|
|
2784
|
-
await runPostStartStepsAfterStart(appId, appData.spec);
|
|
2785
|
-
}
|
|
2786
|
-
await syncCapabilitiesForApp(appId);
|
|
2787
|
-
return result;
|
|
2788
|
-
}
|
|
2789
|
-
const stopResult = await stopAppImpl(appId, true);
|
|
2790
|
-
if (!stopResult.ok && !stopResult.error?.includes("not found")) {
|
|
2791
|
-
return stopResult;
|
|
2792
|
-
}
|
|
2793
|
-
return startAppImpl(appId);
|
|
2794
|
-
});
|
|
2795
|
-
}
|
|
2796
|
-
export function restartAppTask(appId) {
|
|
2797
|
-
if (!getApp(appId)) {
|
|
2798
|
-
return { ok: false, error: `App '${appId}' not found`, kind: "restart" };
|
|
2799
|
-
}
|
|
2800
|
-
return startAppLifecycleTask(appId, "restart", `开始重启应用 ${appId}...`, `应用 ${appId} 已重启`, async () => {
|
|
2801
|
-
const result = await restartApp(appId);
|
|
2802
|
-
if (!result.ok) {
|
|
2803
|
-
throw new Error(result.error || `App '${appId}' restart failed`);
|
|
2804
|
-
}
|
|
2805
|
-
});
|
|
2806
|
-
}
|
|
2807
|
-
export async function getAppRuntimeStatus(appId) {
|
|
2808
|
-
const appData = getApp(appId);
|
|
2809
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2810
|
-
const { getInstanceStatus } = await import("../nomad-manager.js");
|
|
2811
|
-
const st = await getInstanceStatus(appId);
|
|
2812
|
-
return {
|
|
2813
|
-
status: st.status,
|
|
2814
|
-
uptime: st.uptime ?? undefined,
|
|
2815
|
-
memory_mb: st.memory_mb ?? undefined,
|
|
2816
|
-
};
|
|
2817
|
-
}
|
|
2818
|
-
const { getAppStatus, isBinaryRunning } = await import("../nomad-manager.js");
|
|
2819
|
-
const st = await getAppStatus(appId);
|
|
2820
|
-
// For process-runtime apps: if Nomad can't place the job (pending/unplaced due
|
|
2821
|
-
// to missing raw_exec driver) but the binary is already running on the host,
|
|
2822
|
-
// report the real status as "running" so the UI doesn't show a failure.
|
|
2823
|
-
if (st.status === "pending" || st.status === "stopped") {
|
|
2824
|
-
const appData = getApp(appId);
|
|
2825
|
-
if (appData) {
|
|
2826
|
-
const processTask = appData.spec.tasks.find((t) => t.runtime === "process" && (t.role ?? "service") === "service" && t.command);
|
|
2827
|
-
if (processTask) {
|
|
2828
|
-
const running = await isBinaryRunning(processTask.command);
|
|
2829
|
-
if (running) {
|
|
2830
|
-
return { status: "running" };
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
return {
|
|
2836
|
-
status: st.status,
|
|
2837
|
-
uptime: st.uptime ?? undefined,
|
|
2838
|
-
memory_mb: st.memory_mb ?? undefined,
|
|
2839
|
-
tasks: st.tasks,
|
|
2840
|
-
error: st.error,
|
|
2841
|
-
};
|
|
2842
|
-
}
|
|
2843
|
-
export async function getAppStatus(appId) {
|
|
2844
|
-
const appData = getApp(appId);
|
|
2845
|
-
if (appData?.install_state === "installing") {
|
|
2846
|
-
return {
|
|
2847
|
-
status: "installing",
|
|
2848
|
-
tasks: {},
|
|
2849
|
-
pid: null,
|
|
2850
|
-
uptime: null,
|
|
2851
|
-
memory_mb: null,
|
|
2852
|
-
cpu_percent: null,
|
|
2853
|
-
restarts: 0,
|
|
2854
|
-
error: undefined,
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2858
|
-
const { getInstanceStatus } = await import("../nomad-manager.js");
|
|
2859
|
-
const st = await getInstanceStatus(appId);
|
|
2860
|
-
return {
|
|
2861
|
-
status: st.status,
|
|
2862
|
-
tasks: {},
|
|
2863
|
-
pid: null,
|
|
2864
|
-
uptime: st.uptime,
|
|
2865
|
-
memory_mb: st.memory_mb,
|
|
2866
|
-
cpu_percent: st.cpu_percent,
|
|
2867
|
-
restarts: 0,
|
|
2868
|
-
error: undefined,
|
|
2869
|
-
};
|
|
2870
|
-
}
|
|
2871
|
-
const { getAppStatus: nomadGetAppStatus } = await import("../nomad-manager.js");
|
|
2872
|
-
return nomadGetAppStatus(appId);
|
|
2873
|
-
}
|
|
2874
|
-
export async function getAppLogs(appId, taskName, lines = 200, logType = "stderr") {
|
|
2875
|
-
const appData = getApp(appId);
|
|
2876
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2877
|
-
const { getInstanceLogs } = await import("../nomad-manager.js");
|
|
2878
|
-
return getInstanceLogs(appId, lines, logType);
|
|
2879
|
-
}
|
|
2880
|
-
const { getAppLogs: nomadGetAppLogs } = await import("../nomad-manager.js");
|
|
2881
|
-
return nomadGetAppLogs(appId, taskName, lines, logType);
|
|
2882
|
-
}
|
|
2883
|
-
export async function execInApp(appId, command, timeoutMs) {
|
|
2884
|
-
const appData = getApp(appId);
|
|
2885
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2886
|
-
const { execInInstance } = await import("../nomad-manager.js");
|
|
2887
|
-
return execInInstance(appId, command, timeoutMs ?? 120_000);
|
|
2888
|
-
}
|
|
2889
|
-
const { execInApp: nomadExecInApp } = await import("../nomad-manager.js");
|
|
2890
|
-
return nomadExecInApp(appId, "", command, timeoutMs ?? 120_000);
|
|
2891
|
-
}
|
|
2892
|
-
export async function streamExecInApp(appId, command, handlers, options) {
|
|
2893
|
-
const timeoutMs = options?.timeoutMs ?? 120_000;
|
|
2894
|
-
const taskName = options?.taskName ?? "";
|
|
2895
|
-
const appData = getApp(appId);
|
|
2896
|
-
if (isInstanceBackedApp(appData) || getAdapterManagedAgentType(appData)) {
|
|
2897
|
-
const { streamExecInInstance } = await import("../nomad-manager.js");
|
|
2898
|
-
return streamExecInInstance(appId, command, handlers, timeoutMs, taskName);
|
|
2899
|
-
}
|
|
2900
|
-
const { streamExecInApp: nomadStreamExecInApp } = await import("../nomad-manager.js");
|
|
2901
|
-
return nomadStreamExecInApp(appId, taskName, command, handlers, timeoutMs);
|
|
2902
|
-
}
|
|
2903
|
-
export async function copyApp(sourceId) {
|
|
2904
|
-
const source = getApp(sourceId);
|
|
2905
|
-
if (!source)
|
|
2906
|
-
throw new Error(`App '${sourceId}' not found`);
|
|
2907
|
-
if (source.spec.singleInstance)
|
|
2908
|
-
throw new Error(`App '${sourceId}' is singleInstance — cannot copy`);
|
|
2909
|
-
// A faithful copy requires re-materialization: ${app_id} tokens, port
|
|
2910
|
-
// shifts, and app-scoped paths all need to be recomputed for the new
|
|
2911
|
-
// slot. Naively swapping the `id:` line leaves lifecycle scripts and
|
|
2912
|
-
// volume sources pointing at the source's settings dir, which then
|
|
2913
|
-
// collides on start and leaks state across instances.
|
|
2914
|
-
//
|
|
2915
|
-
// The base app's on-disk yaml for offset=0 is written as the pristine
|
|
2916
|
-
// originalSpecYaml (see resolveInstallTarget → installedSpecYaml), so
|
|
2917
|
-
// reading from the base dir recovers the template that installApp can
|
|
2918
|
-
// re-materialize. The source's own yaml is already baked to its own
|
|
2919
|
-
// offset when source is itself a copy, and installApp cannot cleanly
|
|
2920
|
-
// un-do that substitution from a string diff alone.
|
|
2921
|
-
const baseId = source.spec.id;
|
|
2922
|
-
const baseDir = resolveAppDir(baseId);
|
|
2923
|
-
if (!baseDir) {
|
|
2924
|
-
throw new Error(`App '${sourceId}' base '${baseId}' is not installed; cannot copy without the pristine spec. Reinstall the base app first.`);
|
|
2925
|
-
}
|
|
2926
|
-
const baseYaml = readFileSync(join(baseDir, "app-spec.yaml"), "utf-8");
|
|
2927
|
-
// Delegate: installApp runs resolveInstallTarget (which picks the next
|
|
2928
|
-
// free <baseId>-<prefix><n> slot and applies a fresh port shift) and
|
|
2929
|
-
// executes the install lifecycle in the new app dir.
|
|
2930
|
-
return installApp(baseYaml);
|
|
2931
|
-
}
|
|
2932
|
-
export { onConfigChange, notifyConfigChange, instanceDir, instanceMetaPath, defaultModelEnvFile, normalizePath, extractGatewayPort, isPortInUse, allocateGatewayPort, releasePendingPort, getResolvedOpenclawBin, resolveServiceUser, chownToServiceUser, parseEnvFile, updateEnvFile, inferProviderApiKeyEnvName, listInstances, getInstance, updateInstanceMeta, deleteInstance, getConfig, getStoredConfig, saveConfig, CHANNEL_PLUGIN_MAP, isChannelPluginInstalled, createInstance, getOpenclawHome, saveFeishuCredentials, saveWeixinCredentials, getWeixinAccounts, getOpenclawConfigPath, getLegacyOpenclawConfigPath, getInstanceRuntime, getRuntimeEnvFiles, getGatewayPort, getGatewayHost, getListeningHostForPort, getHostForAppPort, urlHost, findInstancesSharingOpenclawHome, reallocateGatewayPort, findInstancesSharingGatewayPort, getRuntimeEnv, defaultGatewayPort, releasePort, } from "../instance-manager.js";
|
|
2933
|
-
//# sourceMappingURL=app-manager.js.map
|