@nextclaw/kernel 0.6.28 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
10
10
  import { appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
11
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
12
12
  import { access, appendFile, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
13
- import { AppHomeService, AppInstallationService, AppManifestService, AppRegistryService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
13
+ import { AppHomeService, AppInstallationService, AppInstanceInventoryService, AppInstanceStorageService, AppManifestService, AppRegistryService, FileLockService, isAppComponentManifestBundle } from "@nextclaw/app-runtime";
14
14
  import { execFileSync, spawn } from "node:child_process";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
@@ -2686,6 +2686,52 @@ var AppPackageOperationManager = class {
2686
2686
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2687
2687
  };
2688
2688
  //#endregion
2689
+ //#region src/services/app-package-presentation.service.ts
2690
+ var AppPackagePresentationService = class {
2691
+ readManifest = async (manifestPath) => {
2692
+ const candidate = JSON.parse(await readFile(manifestPath, "utf8"));
2693
+ const rawIcon = typeof candidate.icon === "string" ? candidate.icon : void 0;
2694
+ return {
2695
+ ...typeof candidate.title === "string" ? { title: candidate.title } : {},
2696
+ ...typeof candidate.description === "string" ? { description: candidate.description } : {},
2697
+ ...rawIcon ? { icon: await this.resolveIcon(manifestPath, rawIcon) } : {},
2698
+ ...this.readLocalizedField(candidate, "nameI18n"),
2699
+ ...this.readLocalizedField(candidate, "titleI18n"),
2700
+ ...this.readLocalizedField(candidate, "descriptionI18n")
2701
+ };
2702
+ };
2703
+ readLocalizedField = (candidate, field) => {
2704
+ const value = candidate[field];
2705
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2706
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
2707
+ return entries.length > 0 ? { [field]: Object.fromEntries(entries) } : {};
2708
+ };
2709
+ resolveIcon = async (manifestPath, icon) => {
2710
+ if (this.isDirectIconReference(icon)) return icon;
2711
+ const manifestDirectory = path.dirname(manifestPath);
2712
+ const iconPath = path.resolve(manifestDirectory, icon);
2713
+ const relative = path.relative(manifestDirectory, iconPath);
2714
+ if (relative.startsWith("..") || path.isAbsolute(relative)) return icon;
2715
+ const mimeType = this.iconMimeType(path.extname(iconPath));
2716
+ if (!mimeType) return icon;
2717
+ const bytes = await readFile(iconPath);
2718
+ if (bytes.byteLength > 256 * 1024) return icon;
2719
+ return `data:${mimeType};base64,${bytes.toString("base64")}`;
2720
+ };
2721
+ isDirectIconReference = (icon) => icon.startsWith("data:") || icon.startsWith("http://") || icon.startsWith("https://") || icon.startsWith("/") || !icon.includes("/") && !icon.includes(".") && [...icon].length <= 8;
2722
+ iconMimeType = (extension) => {
2723
+ switch (extension.toLowerCase()) {
2724
+ case ".svg": return "image/svg+xml";
2725
+ case ".png": return "image/png";
2726
+ case ".jpg":
2727
+ case ".jpeg": return "image/jpeg";
2728
+ case ".webp": return "image/webp";
2729
+ case ".gif": return "image/gif";
2730
+ default: return;
2731
+ }
2732
+ };
2733
+ };
2734
+ //#endregion
2689
2735
  //#region src/types/app-package.types.ts
2690
2736
  var AppPackageError = class extends Error {
2691
2737
  constructor(code, message) {
@@ -2812,6 +2858,7 @@ var AppPackageManager = class {
2812
2858
  installationService;
2813
2859
  manifestService = new AppManifestService();
2814
2860
  operationManager;
2861
+ presentationService = new AppPackagePresentationService();
2815
2862
  registryService;
2816
2863
  runtimeHooks = EMPTY_RUNTIME_HOOKS;
2817
2864
  builtInBootstrapPromise;
@@ -2834,6 +2881,10 @@ var AppPackageManager = class {
2834
2881
  const records = await this.registryService.listApps();
2835
2882
  return { entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId)))) };
2836
2883
  };
2884
+ listInstalledDataOwners = async () => (await this.registryService.listApps()).map((record) => ({
2885
+ id: record.appId,
2886
+ name: record.name
2887
+ }));
2837
2888
  getPackage = async (appId) => {
2838
2889
  await this.ensureBuiltInPackages();
2839
2890
  try {
@@ -2845,7 +2896,9 @@ var AppPackageManager = class {
2845
2896
  };
2846
2897
  listActiveComponentSources = async () => {
2847
2898
  await this.ensureBuiltInPackages();
2848
- return (await this.registryService.listApps()).filter((record) => record.enabled).flatMap((record) => {
2899
+ const records = await this.registryService.listApps();
2900
+ return (await Promise.all(records.filter((record) => record.enabled).map(async (record) => {
2901
+ await this.installationService.assertVersionIntegrity(record.appId, record.activeVersion);
2849
2902
  const version = record.installedVersions[record.activeVersion];
2850
2903
  if (!version || version.manifestSchemaVersion !== 2) return [];
2851
2904
  return (version.components ?? []).map((component) => ({
@@ -2855,9 +2908,12 @@ var AppPackageManager = class {
2855
2908
  packageVersion: record.activeVersion,
2856
2909
  sourcePath: component.componentDirectory,
2857
2910
  manifestPath: component.manifestPath,
2858
- dataDirectory: record.dataDirectory
2911
+ dataDirectory: record.dataDirectory,
2912
+ instanceId: record.defaultInstance.id,
2913
+ storage: record.defaultInstance.storage,
2914
+ ...this.resolveSecurity(version, record.appId)
2859
2915
  }));
2860
- });
2916
+ }))).flat();
2861
2917
  };
2862
2918
  listOperations = async () => await this.operationManager.list();
2863
2919
  startOperation = async (input) => {
@@ -2873,66 +2929,115 @@ var AppPackageManager = class {
2873
2929
  return await this.getPackage(result.appId);
2874
2930
  };
2875
2931
  enable = async (appId) => {
2876
- const app = await this.getPackage(appId);
2877
- if (app.enabled) return app;
2878
- await this.assertEngineCompatibility(appId);
2879
- const sources = this.toComponentSources(app);
2880
- await this.runtimeHooks.assertCanActivate(sources);
2881
- await this.installationService.setEnabled(appId, true);
2882
- return await this.getPackage(appId);
2932
+ return await this.installationService.withAppOperation(appId, async () => {
2933
+ const app = await this.getPackage(appId);
2934
+ await this.installationService.assertVersionIntegrity(appId, app.activeVersion);
2935
+ if (app.enabled) return app;
2936
+ await this.assertEngineCompatibility(appId);
2937
+ const sources = this.toComponentSources(app);
2938
+ await this.runtimeHooks.assertCanActivate(sources);
2939
+ await this.installationService.setEnabled(appId, true);
2940
+ return await this.getPackage(appId);
2941
+ });
2883
2942
  };
2884
2943
  disable = async (appId) => {
2885
- const app = await this.getPackage(appId);
2886
- if (!app.enabled) return app;
2887
- await this.runtimeHooks.beforeDeactivate(this.toComponentSources(app));
2888
- await this.installationService.setEnabled(appId, false);
2889
- return await this.getPackage(appId);
2944
+ return await this.installationService.withAppOperation(appId, async () => {
2945
+ const app = await this.getPackage(appId);
2946
+ if (!app.enabled) return app;
2947
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(app));
2948
+ await this.installationService.setEnabled(appId, false);
2949
+ return await this.getPackage(appId);
2950
+ });
2890
2951
  };
2891
2952
  update = async (appId, options = {}) => {
2892
- const current = await this.getPackage(appId);
2893
- if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2894
- const result = await this.installationService.update(appId, options);
2895
- try {
2896
- await this.assertEngineCompatibility(appId);
2897
- const updated = await this.getPackage(appId);
2898
- if (updated.enabled) await this.runtimeHooks.assertCanActivate(this.toComponentSources(updated));
2899
- return {
2900
- package: updated,
2953
+ return await this.installationService.withAppOperation(appId, async () => {
2954
+ const current = await this.getPackage(appId);
2955
+ const result = await this.installationService.update(appId, {
2956
+ ...options,
2957
+ activate: false
2958
+ });
2959
+ if (!result.updated) return {
2960
+ package: current,
2901
2961
  result
2902
2962
  };
2903
- } catch (error) {
2904
- if (result.updated) await this.installationService.rollback(appId, result.previousVersion);
2905
- throw error;
2906
- }
2963
+ let activated = false;
2964
+ let deactivated = false;
2965
+ try {
2966
+ await this.assertEngineCompatibility(appId, result.version);
2967
+ const candidate = await this.toPackageView(await this.installationService.info(appId), result.version);
2968
+ if (current.enabled) {
2969
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2970
+ deactivated = true;
2971
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
2972
+ }
2973
+ activated = (await this.installationService.rollback(appId, result.version)).rolledBack;
2974
+ return {
2975
+ package: await this.getPackage(appId),
2976
+ result
2977
+ };
2978
+ } catch (error) {
2979
+ if (activated) await this.installationService.rollback(appId, result.previousVersion);
2980
+ if (deactivated) try {
2981
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(current));
2982
+ } catch (recoveryError) {
2983
+ throw new AggregateError([error, recoveryError], `应用 ${appId} 更新失败,且旧 runtime 恢复探测失败。`);
2984
+ }
2985
+ throw error;
2986
+ }
2987
+ });
2907
2988
  };
2908
2989
  rollback = async (appId, version) => {
2909
- const current = await this.getPackage(appId);
2910
- if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2911
- const result = await this.installationService.rollback(appId, version);
2912
- try {
2913
- await this.assertEngineCompatibility(appId);
2914
- const rolledBack = await this.getPackage(appId);
2915
- if (rolledBack.enabled) await this.runtimeHooks.assertCanActivate(this.toComponentSources(rolledBack));
2916
- return {
2917
- package: rolledBack,
2918
- result
2990
+ return await this.installationService.withAppOperation(appId, async () => {
2991
+ const current = await this.getPackage(appId);
2992
+ if (current.activeVersion === version) return {
2993
+ package: current,
2994
+ result: {
2995
+ appId,
2996
+ activeVersion: version,
2997
+ previousVersion: version,
2998
+ enabled: current.enabled,
2999
+ rolledBack: false
3000
+ }
2919
3001
  };
2920
- } catch (error) {
2921
- if (result.rolledBack) await this.installationService.rollback(appId, result.previousVersion);
2922
- throw error;
2923
- }
3002
+ await this.assertEngineCompatibility(appId, version);
3003
+ const candidate = await this.toPackageView(await this.installationService.info(appId), version);
3004
+ let deactivated = false;
3005
+ let result;
3006
+ try {
3007
+ if (current.enabled) {
3008
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
3009
+ deactivated = true;
3010
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
3011
+ }
3012
+ result = await this.installationService.rollback(appId, version);
3013
+ return {
3014
+ package: await this.getPackage(appId),
3015
+ result
3016
+ };
3017
+ } catch (error) {
3018
+ if (result?.rolledBack) await this.installationService.rollback(appId, result.previousVersion);
3019
+ if (deactivated) try {
3020
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(current));
3021
+ } catch (recoveryError) {
3022
+ throw new AggregateError([error, recoveryError], `应用 ${appId} 回滚失败,且旧 runtime 恢复探测失败。`);
3023
+ }
3024
+ throw error;
3025
+ }
3026
+ });
2924
3027
  };
2925
3028
  uninstall = async (appId, purgeData) => {
2926
- const current = await this.getPackage(appId);
2927
- if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, true);
2928
- try {
2929
- if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2930
- await this.runtimeHooks.beforeUninstall(this.toComponentSources(current));
2931
- return await this.installationService.uninstall(appId, purgeData);
2932
- } catch (error) {
2933
- if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, false);
2934
- throw error;
2935
- }
3029
+ return await this.installationService.withAppOperation(appId, async () => {
3030
+ const current = await this.getPackage(appId);
3031
+ if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, true);
3032
+ try {
3033
+ if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
3034
+ await this.runtimeHooks.beforeUninstall(this.toComponentSources(current));
3035
+ return await this.installationService.uninstall(appId, purgeData);
3036
+ } catch (error) {
3037
+ if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, false);
3038
+ throw error;
3039
+ }
3040
+ });
2936
3041
  };
2937
3042
  ensureBuiltInPackages = async () => {
2938
3043
  this.builtInBootstrapPromise ??= this.installBuiltInPackages();
@@ -2944,13 +3049,22 @@ var AppPackageManager = class {
2944
3049
  if (!isAppComponentManifestBundle(manifest)) continue;
2945
3050
  if (await this.registryService.isBuiltInSuppressed(manifest.manifest.id)) continue;
2946
3051
  if ((await this.registryService.getApp(manifest.manifest.id))?.installedVersions[manifest.manifest.version]) continue;
2947
- await this.installationService.install(appDirectory);
3052
+ await this.installationService.install(appDirectory, { trustedPublisher: {
3053
+ id: "nextclaw",
3054
+ name: "NextClaw",
3055
+ url: "https://nextclaw.io"
3056
+ } });
2948
3057
  }
2949
3058
  };
2950
- toPackageView = async (info) => {
2951
- const activeVersion = info.installedVersions.find((version) => version.version === info.activeVersion);
2952
- if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${info.appId} 缺少激活版本 ${info.activeVersion}。`);
2953
- const packagePresentation = await this.readManifestPresentation(path.join(activeVersion.installDirectory, "manifest.json"));
3059
+ toPackageView = async (info, selectedVersion = info.activeVersion) => {
3060
+ const activeVersion = info.installedVersions.find((version) => version.version === selectedVersion);
3061
+ if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${info.appId} 缺少版本 ${selectedVersion}。`);
3062
+ const packagePresentation = await this.presentationService.readManifest(path.join(activeVersion.installDirectory, "manifest.json"));
3063
+ const manifestBundle = await this.manifestService.load(activeVersion.installDirectory);
3064
+ const security = manifestBundle.manifest.schemaVersion === 2 ? this.manifestService.resolvePlatformSecurity(manifestBundle.manifest) : {
3065
+ runtimeProfile: "wasi",
3066
+ isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed"
3067
+ };
2954
3068
  return {
2955
3069
  id: info.appId,
2956
3070
  name: info.name,
@@ -2958,7 +3072,7 @@ var AppPackageManager = class {
2958
3072
  icon: packagePresentation.icon,
2959
3073
  nameI18n: packagePresentation.nameI18n,
2960
3074
  descriptionI18n: packagePresentation.descriptionI18n,
2961
- activeVersion: info.activeVersion,
3075
+ activeVersion: selectedVersion,
2962
3076
  installedVersions: info.installedVersions.map((version) => version.version),
2963
3077
  enabled: info.enabled,
2964
3078
  builtIn: await this.isBuiltInAppId(info.appId),
@@ -2967,43 +3081,50 @@ var AppPackageManager = class {
2967
3081
  kind: component.kind,
2968
3082
  id: component.id,
2969
3083
  packageId: info.appId,
2970
- packageVersion: info.activeVersion,
3084
+ packageVersion: selectedVersion,
2971
3085
  sourcePath: component.componentDirectory,
2972
3086
  manifestPath: component.manifestPath,
2973
3087
  dataDirectory: info.dataDirectory,
2974
- ...await this.readManifestPresentation(component.manifestPath)
3088
+ instanceId: info.instance.id,
3089
+ storage: info.storage,
3090
+ runtimeProfile: security.runtimeProfile,
3091
+ isolation: security.isolation,
3092
+ ...await this.presentationService.readManifest(component.manifestPath)
2975
3093
  }))),
2976
- dataDirectory: info.dataDirectory
3094
+ dataDirectory: info.dataDirectory,
3095
+ instanceId: info.instance.id,
3096
+ storage: info.storage,
3097
+ storageUsage: info.storageUsage,
3098
+ runtimeProfile: security.runtimeProfile,
3099
+ isolation: security.isolation
2977
3100
  };
2978
3101
  };
2979
- readManifestPresentation = async (manifestPath) => {
2980
- const candidate = JSON.parse(await readFile(manifestPath, "utf8"));
2981
- const rawIcon = typeof candidate.icon === "string" ? candidate.icon : void 0;
2982
- return {
2983
- ...typeof candidate.title === "string" ? { title: candidate.title } : {},
2984
- ...typeof candidate.description === "string" ? { description: candidate.description } : {},
2985
- ...rawIcon ? { icon: await this.resolvePresentationIcon(manifestPath, rawIcon) } : {},
2986
- ...this.readLocalizedField(candidate, "nameI18n"),
2987
- ...this.readLocalizedField(candidate, "titleI18n"),
2988
- ...this.readLocalizedField(candidate, "descriptionI18n")
3102
+ resolveSecurity = (version, appId) => {
3103
+ if (version.security) return {
3104
+ runtimeProfile: version.security.runtimeProfile,
3105
+ isolation: version.security.isolation
3106
+ };
3107
+ const hasService = version.components?.some((component) => component.kind === "service") ?? false;
3108
+ if (version.manifestSchemaVersion !== 2) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId} 仍使用 legacy schema,不能投影组件。`);
3109
+ return hasService ? {
3110
+ runtimeProfile: "native-process",
3111
+ isolation: "full-user"
3112
+ } : {
3113
+ runtimeProfile: "panel-only",
3114
+ isolation: "sandboxed"
2989
3115
  };
2990
- };
2991
- readLocalizedField = (candidate, field) => {
2992
- const value = candidate[field];
2993
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2994
- const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
2995
- return entries.length > 0 ? { [field]: Object.fromEntries(entries) } : {};
2996
3116
  };
2997
3117
  toComponentSources = (app) => app.components.map((component) => ({ ...component }));
2998
- assertEngineCompatibility = async (appId) => {
3118
+ assertEngineCompatibility = async (appId, selectedVersion) => {
2999
3119
  const productVersion = this.params.productVersion?.trim();
3000
3120
  if (!productVersion) return;
3001
3121
  const info = await this.installationService.info(appId);
3002
- const activeVersion = info.installedVersions.find((version) => version.version === info.activeVersion);
3003
- if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${appId} 缺少激活版本 ${info.activeVersion}。`);
3122
+ const targetVersion = selectedVersion ?? info.activeVersion;
3123
+ const activeVersion = info.installedVersions.find((version) => version.version === targetVersion);
3124
+ if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${appId} 缺少版本 ${targetVersion}。`);
3004
3125
  const manifestBundle = await this.manifestService.load(activeVersion.installDirectory);
3005
3126
  const engineRange = manifestBundle.manifest.schemaVersion === 2 ? manifestBundle.manifest.engines?.nextclaw?.trim() : void 0;
3006
- if (engineRange && !satisfiesAppEngineVersion(productVersion, engineRange)) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId}@${info.activeVersion} 要求 NextClaw ${engineRange},当前版本为 ${productVersion}。`);
3127
+ if (engineRange && !satisfiesAppEngineVersion(productVersion, engineRange)) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId}@${targetVersion} 要求 NextClaw ${engineRange},当前版本为 ${productVersion}。`);
3007
3128
  };
3008
3129
  listBuiltInDefinitions = async () => {
3009
3130
  if (!this.params.builtInAppsDirectory) return [];
@@ -3027,29 +3148,6 @@ var AppPackageManager = class {
3027
3148
  return await this.builtInDefinitionsPromise;
3028
3149
  };
3029
3150
  isBuiltInAppId = async (appId) => (await this.listBuiltInDefinitions()).some(({ manifest }) => manifest.manifest.id === appId);
3030
- resolvePresentationIcon = async (manifestPath, icon) => {
3031
- if (icon.startsWith("data:") || icon.startsWith("http://") || icon.startsWith("https://") || icon.startsWith("/") || !icon.includes("/") && !icon.includes(".") && [...icon].length <= 8) return icon;
3032
- const manifestDirectory = path.dirname(manifestPath);
3033
- const iconPath = path.resolve(manifestDirectory, icon);
3034
- const relative = path.relative(manifestDirectory, iconPath);
3035
- if (relative.startsWith("..") || path.isAbsolute(relative)) return icon;
3036
- const mimeType = this.iconMimeType(path.extname(iconPath));
3037
- if (!mimeType) return icon;
3038
- const bytes = await readFile(iconPath);
3039
- if (bytes.byteLength > 256 * 1024) return icon;
3040
- return `data:${mimeType};base64,${bytes.toString("base64")}`;
3041
- };
3042
- iconMimeType = (extension) => {
3043
- switch (extension.toLowerCase()) {
3044
- case ".svg": return "image/svg+xml";
3045
- case ".png": return "image/png";
3046
- case ".jpg":
3047
- case ".jpeg": return "image/jpeg";
3048
- case ".webp": return "image/webp";
3049
- case ".gif": return "image/gif";
3050
- default: return;
3051
- }
3052
- };
3053
3151
  executeOperation = async (input, report) => {
3054
3152
  if (input.action === "install") {
3055
3153
  const installed = await this.install(input.source, input.registryUrl, async (phase) => await report(phase));
@@ -3094,6 +3192,148 @@ var AppPackageManager = class {
3094
3192
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3095
3193
  };
3096
3194
  //#endregion
3195
+ //#region src/types/app-data.types.ts
3196
+ var AppDataError = class extends Error {
3197
+ constructor(code, message) {
3198
+ super(message);
3199
+ this.code = code;
3200
+ this.name = "AppDataError";
3201
+ }
3202
+ };
3203
+ function isAppDataError(error) {
3204
+ return error instanceof AppDataError;
3205
+ }
3206
+ //#endregion
3207
+ //#region src/managers/app-data.manager.ts
3208
+ const DATA_ID_PREFIX = "ad1.";
3209
+ const SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
3210
+ const WORKSPACE_SCOPE_PATTERN = /^[a-f0-9]{16}$/;
3211
+ var AppDataManager = class {
3212
+ appHomeService;
3213
+ fileLockService = new FileLockService();
3214
+ inventoryService = new AppInstanceInventoryService();
3215
+ constructor(params) {
3216
+ this.params = params;
3217
+ this.appHomeService = new AppHomeService(params.appHomeDirectory);
3218
+ }
3219
+ list = async () => {
3220
+ const workspacePath = path.resolve(this.params.getWorkspacePath());
3221
+ const workspaceInstancesRoot = this.getWorkspaceInstancesRoot(workspacePath);
3222
+ const [packageInventory, workspaceInventory, packageList, workspaceOwners] = await Promise.all([
3223
+ this.inventoryService.list(this.appHomeService.getInstancesDirectory()),
3224
+ this.inventoryService.list(workspaceInstancesRoot),
3225
+ this.params.listInstalledPackageOwners(),
3226
+ this.params.listWorkspaceDataOwners()
3227
+ ]);
3228
+ const packageOwners = new Map(packageList.map((entry) => [entry.id, entry]));
3229
+ const workspaceOwnerMap = new Map(workspaceOwners.map((entry) => [entry.id, entry]));
3230
+ const workspaceScope = this.workspaceScope(workspacePath);
3231
+ return {
3232
+ entries: [...packageInventory.entries.map((entry) => this.toEntry({
3233
+ entry,
3234
+ source: "package",
3235
+ displayName: packageOwners.get(entry.appId)?.name ?? entry.appId,
3236
+ active: packageOwners.has(entry.appId)
3237
+ })), ...workspaceInventory.entries.map((entry) => this.toEntry({
3238
+ entry,
3239
+ source: "workspace-service",
3240
+ displayName: workspaceOwnerMap.get(entry.appId)?.title ?? entry.appId,
3241
+ active: workspaceOwnerMap.has(entry.appId),
3242
+ scope: workspaceScope
3243
+ }))].sort((left, right) => left.displayName.localeCompare(right.displayName) || left.id.localeCompare(right.id)),
3244
+ diagnostics: [...packageInventory.diagnostics.map((entry) => ({
3245
+ ...entry,
3246
+ source: "package"
3247
+ })), ...workspaceInventory.diagnostics.map((entry) => ({
3248
+ ...entry,
3249
+ source: "workspace-service"
3250
+ }))]
3251
+ };
3252
+ };
3253
+ deleteRetained = async (dataId, confirmAppId) => {
3254
+ const payload = this.parseDataId(dataId);
3255
+ if (confirmAppId !== payload.appId) throw new AppDataError("APP_DATA_CONFIRMATION_MISMATCH", `确认的 App id 与目标不一致:${confirmAppId || "(empty)"}`);
3256
+ if (payload.source === "package") await this.deletePackageData(payload);
3257
+ else await this.deleteWorkspaceData(payload);
3258
+ return {
3259
+ deleted: true,
3260
+ id: dataId,
3261
+ appId: payload.appId,
3262
+ instanceId: payload.instanceId
3263
+ };
3264
+ };
3265
+ deletePackageData = async (payload) => {
3266
+ await this.fileLockService.withLock(this.appHomeService.getAppOperationLockPath(payload.appId), async () => {
3267
+ await this.inventoryService.purge({
3268
+ instancesRoot: this.appHomeService.getInstancesDirectory(),
3269
+ appId: payload.appId,
3270
+ instanceId: payload.instanceId,
3271
+ assertCanPurge: async () => {
3272
+ if (await this.isPackageActive(payload.appId)) throw new AppDataError("APP_DATA_ACTIVE", `应用 ${payload.appId} 仍已安装,请通过卸载入口处理其数据。`);
3273
+ }
3274
+ });
3275
+ }).catch((error) => this.rethrowMissing(error, payload));
3276
+ };
3277
+ deleteWorkspaceData = async (payload) => {
3278
+ const workspacePath = path.resolve(this.params.getWorkspacePath());
3279
+ if (payload.scope !== this.workspaceScope(workspacePath)) throw new AppDataError("APP_DATA_INVALID_ID", "App data id 不属于当前 workspace。");
3280
+ const lockPath = path.join(workspacePath, ".nextclaw", "locks", "service-apps", `${payload.appId}.lock`);
3281
+ await this.fileLockService.withLock(lockPath, async () => {
3282
+ await this.inventoryService.purge({
3283
+ instancesRoot: this.getWorkspaceInstancesRoot(workspacePath),
3284
+ appId: payload.appId,
3285
+ instanceId: payload.instanceId,
3286
+ assertCanPurge: async () => {
3287
+ if (await this.isWorkspaceServiceActive(payload.appId)) throw new AppDataError("APP_DATA_ACTIVE", `Service App ${payload.appId} 仍在 workspace 中,请通过 Service Apps 删除入口处理其数据。`);
3288
+ }
3289
+ });
3290
+ }).catch((error) => this.rethrowMissing(error, payload));
3291
+ };
3292
+ toEntry = (params) => {
3293
+ const { active, displayName, entry, scope, source } = params;
3294
+ return {
3295
+ id: this.createDataId({
3296
+ version: 1,
3297
+ source,
3298
+ appId: entry.appId,
3299
+ instanceId: entry.instanceId,
3300
+ scope
3301
+ }),
3302
+ appId: entry.appId,
3303
+ instanceId: entry.instanceId,
3304
+ publisherId: entry.publisherId,
3305
+ displayName,
3306
+ source,
3307
+ lifecycle: active ? "active" : "retained",
3308
+ storage: entry.storage,
3309
+ usage: entry.usage,
3310
+ createdAt: entry.createdAt,
3311
+ migratedAt: entry.migratedAt,
3312
+ actions: { deleteRetainedData: !active }
3313
+ };
3314
+ };
3315
+ createDataId = (payload) => `${DATA_ID_PREFIX}${Buffer.from(JSON.stringify(payload), "utf8").toString("base64url")}`;
3316
+ parseDataId = (dataId) => {
3317
+ try {
3318
+ if (!dataId.startsWith(DATA_ID_PREFIX)) throw new Error("prefix");
3319
+ const raw = JSON.parse(Buffer.from(dataId.slice(4), "base64url").toString("utf8"));
3320
+ if (raw.version !== 1 || raw.source !== "package" && raw.source !== "workspace-service" || typeof raw.appId !== "string" || !SAFE_ID_PATTERN.test(raw.appId) || typeof raw.instanceId !== "string" || !SAFE_ID_PATTERN.test(raw.instanceId) || raw.source === "package" && raw.scope !== void 0 || raw.source === "workspace-service" && (typeof raw.scope !== "string" || !WORKSPACE_SCOPE_PATTERN.test(raw.scope))) throw new Error("shape");
3321
+ return raw;
3322
+ } catch {
3323
+ throw new AppDataError("APP_DATA_INVALID_ID", "App data id 无效。");
3324
+ }
3325
+ };
3326
+ isPackageActive = async (appId) => (await this.params.listInstalledPackageOwners()).some((entry) => entry.id === appId);
3327
+ isWorkspaceServiceActive = async (appId) => (await this.params.listWorkspaceDataOwners()).some((entry) => entry.id === appId);
3328
+ getWorkspaceInstancesRoot = (workspacePath) => path.join(workspacePath, ".nextclaw", "app-instances");
3329
+ workspaceScope = (workspacePath) => createHash("sha256").update(workspacePath).digest("hex").slice(0, 16);
3330
+ rethrowMissing = (error, payload) => {
3331
+ if (error instanceof Error && (error.message.includes("metadata 不存在") || this.isMissingFileError(error))) throw new AppDataError("APP_DATA_NOT_FOUND", `未找到 App 数据:${payload.appId}/${payload.instanceId}`);
3332
+ throw error;
3333
+ };
3334
+ isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
3335
+ };
3336
+ //#endregion
3097
3337
  //#region src/managers/channel.manager.ts
3098
3338
  var ChannelManager = class {
3099
3339
  channels = {};
@@ -9731,7 +9971,7 @@ var McpServiceAppRuntimeService = class {
9731
9971
  toMcpServerRecord = (app, manifest) => ({
9732
9972
  name: app.id,
9733
9973
  definition: {
9734
- enabled: manifest.enabled,
9974
+ enabled: app.enabled,
9735
9975
  transport: {
9736
9976
  type: "stdio",
9737
9977
  command: manifest.command,
@@ -9752,7 +9992,16 @@ var McpServiceAppRuntimeService = class {
9752
9992
  });
9753
9993
  createAppRuntimeEnv = (app) => {
9754
9994
  const env = {};
9755
- if (app.dataDirectory) env.NEXTCLAW_APP_DATA_DIR = app.dataDirectory;
9995
+ if (app.storage) {
9996
+ env.NEXTCLAW_APP_INSTANCE_ID = app.storage.instanceId;
9997
+ env.NEXTCLAW_APP_COMPONENT_ID = app.id;
9998
+ env.NEXTCLAW_APP_DATA_DIR = app.storage.dataDirectory;
9999
+ env.NEXTCLAW_APP_CONFIG_DIR = app.storage.configDirectory;
10000
+ env.NEXTCLAW_APP_STATE_DIR = app.storage.stateDirectory;
10001
+ env.NEXTCLAW_APP_CACHE_DIR = app.storage.cacheDirectory;
10002
+ env.NEXTCLAW_APP_TMP_DIR = app.storage.temporaryDirectory;
10003
+ env.NEXTCLAW_APP_LOG_DIR = app.storage.logsDirectory;
10004
+ } else if (app.dataDirectory) env.NEXTCLAW_APP_DATA_DIR = app.dataDirectory;
9756
10005
  if (app.sourceKind !== "package" || !app.packageId || !app.packageVersion || !app.packageDirectory) return env;
9757
10006
  env.NEXTCLAW_APP_ID = app.packageId;
9758
10007
  env.NEXTCLAW_APP_VERSION = app.packageVersion;
@@ -9775,6 +10024,261 @@ var McpServiceAppRuntimeService = class {
9775
10024
  };
9776
10025
  };
9777
10026
  //#endregion
10027
+ //#region src/utils/service-app-manifest.utils.ts
10028
+ const SERVICE_APP_ID_PATTERN$1 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10029
+ const SERVICE_ACTION_RISKS = new Set([
10030
+ "read",
10031
+ "write",
10032
+ "external",
10033
+ "dangerous"
10034
+ ]);
10035
+ const SERVICE_APP_MANIFEST_FILE_NAME = "service-app.json";
10036
+ function getServiceAppManifestPath(dirPath) {
10037
+ return join(dirPath, SERVICE_APP_MANIFEST_FILE_NAME);
10038
+ }
10039
+ async function readServiceAppManifest(dirPath) {
10040
+ return parseServiceAppManifest(await readFile(getServiceAppManifestPath(dirPath), "utf8"));
10041
+ }
10042
+ function parseServiceAppManifest(raw) {
10043
+ let parsed;
10044
+ try {
10045
+ parsed = JSON.parse(raw);
10046
+ } catch (error) {
10047
+ throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
10048
+ }
10049
+ if (!isRecord$9(parsed)) throw new Error("service-app.json must contain an object.");
10050
+ const id = readRequiredString$6(parsed, "id");
10051
+ if (!SERVICE_APP_ID_PATTERN$1.test(id)) throw new Error("service app id must be kebab-case.");
10052
+ const protocol = readOptionalString$7(parsed, "protocol") ?? "mcp";
10053
+ if (protocol !== "mcp") throw new Error("service app protocol must be mcp.");
10054
+ return {
10055
+ id,
10056
+ title: readRequiredString$6(parsed, "title"),
10057
+ description: readOptionalString$7(parsed, "description"),
10058
+ enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
10059
+ protocol,
10060
+ command: readRequiredString$6(parsed, "command"),
10061
+ args: readStringArray(parsed.args, "args"),
10062
+ actions: readManifestActions(parsed.actions)
10063
+ };
10064
+ }
10065
+ function readManifestActions(value) {
10066
+ if (value === void 0) throw new Error("service app actions are required.");
10067
+ if (!isRecord$9(value)) throw new Error("service app actions must be an object.");
10068
+ if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
10069
+ const actions = {};
10070
+ for (const [name, action] of Object.entries(value)) {
10071
+ if (!name.trim()) throw new Error("service app action name cannot be empty.");
10072
+ if (!isRecord$9(action)) throw new Error(`service app action ${name} must be an object.`);
10073
+ const risk = readOptionalString$7(action, "risk");
10074
+ if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
10075
+ const inputSchema = action.inputSchema;
10076
+ if (inputSchema !== void 0 && !isRecord$9(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
10077
+ actions[name] = {
10078
+ risk,
10079
+ title: readOptionalString$7(action, "title"),
10080
+ description: readOptionalString$7(action, "description"),
10081
+ inputSchema
10082
+ };
10083
+ }
10084
+ return actions;
10085
+ }
10086
+ function readRequiredString$6(record, key) {
10087
+ const value = readOptionalString$7(record, key);
10088
+ if (!value) throw new Error(`service app ${key} is required.`);
10089
+ return value;
10090
+ }
10091
+ function readOptionalString$7(record, key) {
10092
+ return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
10093
+ }
10094
+ function readOptionalBoolean$1(record, key) {
10095
+ if (record[key] === void 0) return;
10096
+ if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
10097
+ return record[key];
10098
+ }
10099
+ function readStringArray(value, key) {
10100
+ if (value === void 0) return [];
10101
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
10102
+ return value;
10103
+ }
10104
+ function isRecord$9(value) {
10105
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10106
+ }
10107
+ //#endregion
10108
+ //#region src/services/service-app-record.service.ts
10109
+ var ServiceAppRecordService = class {
10110
+ instanceStorageService = new AppInstanceStorageService();
10111
+ constructor(params) {
10112
+ this.params = params;
10113
+ }
10114
+ buildWorkspaceRecord = async (serviceAppsPath, dirName) => {
10115
+ const dirPath = join(serviceAppsPath, dirName);
10116
+ try {
10117
+ const manifest = await readServiceAppManifest(dirPath);
10118
+ return this.fromManifest(dirPath, manifest, void 0, await this.materializeWorkspaceStorage(manifest.id));
10119
+ } catch (error) {
10120
+ if (this.isMissingFileError(error)) return null;
10121
+ return this.failedWorkspaceRecord(dirName, dirPath, error);
10122
+ }
10123
+ };
10124
+ buildPackageRecord = async (source) => {
10125
+ try {
10126
+ const manifest = await readServiceAppManifest(source.sourcePath);
10127
+ if (manifest.id !== source.id) throw new Error(`service component id mismatch: ${source.id}`);
10128
+ return this.fromManifest(source.sourcePath, manifest, source, source.storage);
10129
+ } catch (error) {
10130
+ return this.failedPackageRecord(source, error);
10131
+ }
10132
+ };
10133
+ listWorkspaceDataOwners = async (serviceAppsPath, dirNames) => {
10134
+ return (await Promise.all(dirNames.map(async (dirName) => {
10135
+ try {
10136
+ const manifest = await readServiceAppManifest(join(serviceAppsPath, dirName));
10137
+ return manifest.id === dirName ? {
10138
+ id: manifest.id,
10139
+ title: manifest.title
10140
+ } : null;
10141
+ } catch {
10142
+ return null;
10143
+ }
10144
+ }))).filter((entry) => Boolean(entry));
10145
+ };
10146
+ materializeWorkspaceStorage = async (serviceId) => {
10147
+ const instanceDirectory = join(this.params.getWorkspacePath(), ".nextclaw", "app-instances", serviceId, "default");
10148
+ return (await this.instanceStorageService.materialize({
10149
+ appId: serviceId,
10150
+ instanceId: "default",
10151
+ instanceDirectory
10152
+ })).storage;
10153
+ };
10154
+ fromManifest = (dirPath, manifest, packageSource, storage) => {
10155
+ const runtimeStatus = this.params.runtimeService.getStatus(manifest.id);
10156
+ return {
10157
+ id: manifest.id,
10158
+ title: manifest.title,
10159
+ description: manifest.description,
10160
+ dirPath,
10161
+ manifestPath: getServiceAppManifestPath(dirPath),
10162
+ command: manifest.command,
10163
+ args: manifest.args,
10164
+ cwd: dirPath,
10165
+ enabled: packageSource ? true : manifest.enabled,
10166
+ protocol: manifest.protocol,
10167
+ status: packageSource || manifest.enabled ? runtimeStatus.status : "stopped",
10168
+ lastError: runtimeStatus.lastError,
10169
+ lastStartedAt: runtimeStatus.lastStartedAt,
10170
+ lastReadyAt: runtimeStatus.lastReadyAt,
10171
+ lastFailedAt: runtimeStatus.lastFailedAt,
10172
+ sourceKind: packageSource ? "package" : "workspace",
10173
+ packageId: packageSource?.packageId,
10174
+ packageVersion: packageSource?.packageVersion,
10175
+ packageDirectory: packageSource ? join(packageSource.sourcePath, "..", "..") : void 0,
10176
+ dataDirectory: storage?.dataDirectory,
10177
+ instanceId: packageSource?.instanceId ?? storage?.instanceId,
10178
+ storage,
10179
+ isolation: packageSource?.isolation ?? "full-user"
10180
+ };
10181
+ };
10182
+ failedWorkspaceRecord = (id, dirPath, error) => ({
10183
+ id,
10184
+ title: this.toTitle(id),
10185
+ dirPath,
10186
+ manifestPath: getServiceAppManifestPath(dirPath),
10187
+ cwd: dirPath,
10188
+ enabled: false,
10189
+ protocol: "mcp",
10190
+ status: "failed",
10191
+ lastError: error instanceof Error ? error.message : String(error)
10192
+ });
10193
+ failedPackageRecord = (source, error) => ({
10194
+ id: source.id,
10195
+ title: this.toTitle(source.id),
10196
+ dirPath: source.sourcePath,
10197
+ manifestPath: source.manifestPath,
10198
+ cwd: source.sourcePath,
10199
+ enabled: false,
10200
+ protocol: "mcp",
10201
+ status: "failed",
10202
+ lastError: error instanceof Error ? error.message : String(error),
10203
+ sourceKind: "package",
10204
+ packageId: source.packageId,
10205
+ packageVersion: source.packageVersion,
10206
+ packageDirectory: join(source.sourcePath, "..", ".."),
10207
+ dataDirectory: source.dataDirectory,
10208
+ instanceId: source.instanceId,
10209
+ storage: source.storage,
10210
+ isolation: source.isolation
10211
+ });
10212
+ toTitle = (value) => basename(value).replace(/[-_]+/g, " ").trim() || value;
10213
+ isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
10214
+ };
10215
+ //#endregion
10216
+ //#region src/services/service-app-removal.service.ts
10217
+ var ServiceAppRemovalCleanupError = class extends Error {
10218
+ constructor(cause) {
10219
+ super(`临时目录清理失败:${cause instanceof Error ? cause.message : String(cause)}`);
10220
+ this.cause = cause;
10221
+ this.name = "ServiceAppRemovalCleanupError";
10222
+ }
10223
+ };
10224
+ var ServiceAppRemovalService = class {
10225
+ fileLockService = new FileLockService();
10226
+ remove = async (params) => await this.fileLockService.withLock(params.lockPath, async () => {
10227
+ const record = await params.loadRecord();
10228
+ await this.removeLocked({
10229
+ ...params,
10230
+ record
10231
+ });
10232
+ return record;
10233
+ });
10234
+ removeLocked = async (params) => {
10235
+ const { grantStore, purgeData, record, stopRuntime } = params;
10236
+ const grants = (await grantStore.list()).filter((grant) => grant.actionId.startsWith(`${record.id}.`));
10237
+ const stagedPaths = [];
10238
+ try {
10239
+ await stopRuntime(record);
10240
+ await this.stagePath(record.dirPath, stagedPaths);
10241
+ if (purgeData && record.storage) await this.stagePath(record.storage.instanceDirectory, stagedPaths);
10242
+ await grantStore.revokeActionsByPrefix(`${record.id}.`);
10243
+ } catch (error) {
10244
+ const recoveryErrors = [...await this.restoreStagedPaths(stagedPaths), ...await this.restoreGrants(grantStore, grants)];
10245
+ if (recoveryErrors.length > 0) throw new AggregateError([error, ...recoveryErrors], `Service App ${record.id} 删除失败,且恢复未完整完成。`);
10246
+ throw error;
10247
+ }
10248
+ try {
10249
+ await Promise.all(stagedPaths.map(async ({ stagedPath }) => await rm(stagedPath, { recursive: true })));
10250
+ } catch (error) {
10251
+ throw new ServiceAppRemovalCleanupError(error);
10252
+ }
10253
+ };
10254
+ stagePath = async (originalPath, stagedPaths) => {
10255
+ const stagedPath = `${originalPath}.deleting-${randomUUID()}`;
10256
+ await rename(originalPath, stagedPath);
10257
+ stagedPaths.push({
10258
+ originalPath,
10259
+ stagedPath
10260
+ });
10261
+ };
10262
+ restoreStagedPaths = async (stagedPaths) => {
10263
+ const errors = [];
10264
+ for (const entry of [...stagedPaths].reverse()) try {
10265
+ await rename(entry.stagedPath, entry.originalPath);
10266
+ } catch (error) {
10267
+ errors.push(error);
10268
+ }
10269
+ return errors;
10270
+ };
10271
+ restoreGrants = async (grantStore, grants) => {
10272
+ const errors = [];
10273
+ for (const grant of grants) try {
10274
+ await grantStore.grant(grant);
10275
+ } catch (error) {
10276
+ errors.push(error);
10277
+ }
10278
+ return errors;
10279
+ };
10280
+ };
10281
+ //#endregion
9778
10282
  //#region src/stores/service-action-grant.store.ts
9779
10283
  const EMPTY_GRANTS = {
9780
10284
  version: 1,
@@ -9784,9 +10288,9 @@ var ServiceActionGrantStore = class {
9784
10288
  constructor(storePath) {
9785
10289
  this.storePath = storePath;
9786
10290
  }
9787
- isGranted = async (caller, actionId) => {
9788
- const data = await this.load();
9789
- return Boolean(data.grants[getServiceActionCallerKey(caller)]?.actions[actionId]);
10291
+ isGranted = async (caller, actionId, currentRisk) => {
10292
+ const grant = (await this.load()).grants[getServiceActionCallerKey(caller)]?.actions[actionId];
10293
+ return Boolean(grant && (currentRisk === void 0 || grant.risk === currentRisk));
9790
10294
  };
9791
10295
  grant = async ({ actionId, caller, grantedAt, risk }) => {
9792
10296
  const data = await this.load();
@@ -9853,12 +10357,12 @@ var ServiceActionGrantStore = class {
9853
10357
  };
9854
10358
  };
9855
10359
  function normalizeStoreData(value) {
9856
- if (!isRecord$9(value) || value.version !== 1 || !isRecord$9(value.grants)) return structuredClone(EMPTY_GRANTS);
10360
+ if (!isRecord$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS);
9857
10361
  const grants = {};
9858
10362
  for (const [callerKey, callerValue] of Object.entries(value.grants)) {
9859
- if (!isRecord$9(callerValue) || !isRecord$9(callerValue.actions)) continue;
10363
+ if (!isRecord$8(callerValue) || !isRecord$8(callerValue.actions)) continue;
9860
10364
  const actions = {};
9861
- for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$9(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
10365
+ for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$8(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
9862
10366
  grantedAt: actionValue.grantedAt,
9863
10367
  risk: actionValue.risk
9864
10368
  };
@@ -9872,92 +10376,11 @@ function normalizeStoreData(value) {
9872
10376
  function isServiceActionRisk(value) {
9873
10377
  return value === "read" || value === "write" || value === "external" || value === "dangerous";
9874
10378
  }
9875
- function isRecord$9(value) {
10379
+ function isRecord$8(value) {
9876
10380
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9877
10381
  }
9878
10382
  function isMissingFileError(error) {
9879
- return isRecord$9(error) && error.code === "ENOENT";
9880
- }
9881
- //#endregion
9882
- //#region src/utils/service-app-manifest.utils.ts
9883
- const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
9884
- const SERVICE_ACTION_RISKS = new Set([
9885
- "read",
9886
- "write",
9887
- "external",
9888
- "dangerous"
9889
- ]);
9890
- const SERVICE_APP_MANIFEST_FILE_NAME = "service-app.json";
9891
- function getServiceAppManifestPath(dirPath) {
9892
- return join(dirPath, SERVICE_APP_MANIFEST_FILE_NAME);
9893
- }
9894
- async function readServiceAppManifest(dirPath) {
9895
- return parseServiceAppManifest(await readFile(getServiceAppManifestPath(dirPath), "utf8"));
9896
- }
9897
- function parseServiceAppManifest(raw) {
9898
- let parsed;
9899
- try {
9900
- parsed = JSON.parse(raw);
9901
- } catch (error) {
9902
- throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
9903
- }
9904
- if (!isRecord$8(parsed)) throw new Error("service-app.json must contain an object.");
9905
- const id = readRequiredString$6(parsed, "id");
9906
- if (!SERVICE_APP_ID_PATTERN.test(id)) throw new Error("service app id must be kebab-case.");
9907
- const protocol = readOptionalString$7(parsed, "protocol") ?? "mcp";
9908
- if (protocol !== "mcp") throw new Error("service app protocol must be mcp.");
9909
- return {
9910
- id,
9911
- title: readRequiredString$6(parsed, "title"),
9912
- description: readOptionalString$7(parsed, "description"),
9913
- enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
9914
- protocol,
9915
- command: readRequiredString$6(parsed, "command"),
9916
- args: readStringArray(parsed.args, "args"),
9917
- actions: readManifestActions(parsed.actions)
9918
- };
9919
- }
9920
- function readManifestActions(value) {
9921
- if (value === void 0) throw new Error("service app actions are required.");
9922
- if (!isRecord$8(value)) throw new Error("service app actions must be an object.");
9923
- if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
9924
- const actions = {};
9925
- for (const [name, action] of Object.entries(value)) {
9926
- if (!name.trim()) throw new Error("service app action name cannot be empty.");
9927
- if (!isRecord$8(action)) throw new Error(`service app action ${name} must be an object.`);
9928
- const risk = readOptionalString$7(action, "risk");
9929
- if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
9930
- const inputSchema = action.inputSchema;
9931
- if (inputSchema !== void 0 && !isRecord$8(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
9932
- actions[name] = {
9933
- risk,
9934
- title: readOptionalString$7(action, "title"),
9935
- description: readOptionalString$7(action, "description"),
9936
- inputSchema
9937
- };
9938
- }
9939
- return actions;
9940
- }
9941
- function readRequiredString$6(record, key) {
9942
- const value = readOptionalString$7(record, key);
9943
- if (!value) throw new Error(`service app ${key} is required.`);
9944
- return value;
9945
- }
9946
- function readOptionalString$7(record, key) {
9947
- return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
9948
- }
9949
- function readOptionalBoolean$1(record, key) {
9950
- if (record[key] === void 0) return;
9951
- if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
9952
- return record[key];
9953
- }
9954
- function readStringArray(value, key) {
9955
- if (value === void 0) return [];
9956
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
9957
- return value;
9958
- }
9959
- function isRecord$8(value) {
9960
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10383
+ return isRecord$8(error) && error.code === "ENOENT";
9961
10384
  }
9962
10385
  //#endregion
9963
10386
  //#region src/utils/service-app-runtime-action.utils.ts
@@ -9994,6 +10417,7 @@ function mergeServiceAppRuntimeActions({ record, manifest, runtimeActions }) {
9994
10417
  //#endregion
9995
10418
  //#region src/managers/service-app.manager.ts
9996
10419
  const SERVICE_ACTION_GRANTS_FILE_NAME = ".service-action-grants.json";
10420
+ const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
9997
10421
  var ServiceAppError = class extends Error {
9998
10422
  constructor(code, message) {
9999
10423
  super(message);
@@ -10005,18 +10429,24 @@ function isServiceAppError(error) {
10005
10429
  return error instanceof ServiceAppError;
10006
10430
  }
10007
10431
  var ServiceAppManager = class {
10432
+ removalService = new ServiceAppRemovalService();
10008
10433
  runtimeService;
10434
+ recordService;
10009
10435
  constructor(params) {
10010
10436
  this.params = params;
10011
10437
  this.runtimeService = params.runtimeService ?? new McpServiceAppRuntimeService({ getConfig: () => params.configManager.config });
10438
+ this.recordService = new ServiceAppRecordService({
10439
+ getWorkspacePath: this.getWorkspacePath,
10440
+ runtimeService: this.runtimeService
10441
+ });
10012
10442
  }
10013
10443
  listServiceApps = async () => {
10014
10444
  const workspacePath = this.getWorkspacePath();
10015
10445
  const serviceAppsPath = this.getServiceAppsPath(workspacePath);
10016
10446
  const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
10017
- const workspaceEntries = await Promise.all(dirNames.map((dirName) => this.buildServiceAppRecord(serviceAppsPath, dirName)));
10447
+ const workspaceEntries = await Promise.all(dirNames.map((dirName) => this.recordService.buildWorkspaceRecord(serviceAppsPath, dirName)));
10018
10448
  const packageSources = (await this.listPackageComponentSources()).filter((component) => component.kind === "service");
10019
- const packageEntries = await Promise.all(packageSources.map((source) => this.buildServiceAppRecordFromPackage(source)));
10449
+ const packageEntries = await Promise.all(packageSources.map((source) => this.recordService.buildPackageRecord(source)));
10020
10450
  return {
10021
10451
  workspacePath,
10022
10452
  serviceAppsPath,
@@ -10048,7 +10478,8 @@ var ServiceAppManager = class {
10048
10478
  const { manifest, record } = await this.requireServiceAppForAction(actionId);
10049
10479
  const actionName = getServiceActionName(actionId, record.id);
10050
10480
  if (!Object.hasOwn(manifest.actions, actionName)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
10051
- if (!await this.createGrantStore().isGranted(request.caller, actionId)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
10481
+ const declaredRisk = manifest.actions[actionName]?.risk ?? "dangerous";
10482
+ if (!await this.createGrantStore().isGranted(request.caller, actionId, declaredRisk)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
10052
10483
  return {
10053
10484
  actionId,
10054
10485
  result: await this.runtimeService.invokeAction({
@@ -10096,20 +10527,35 @@ var ServiceAppManager = class {
10096
10527
  await this.runtimeService.restart(record.id);
10097
10528
  return await this.getServiceApp(appId);
10098
10529
  };
10099
- deleteServiceApp = async (appId) => {
10100
- const { record } = await this.requireServiceApp(appId);
10101
- if (record.sourceKind === "package") throw new ServiceAppError("SERVICE_APP_MANAGED_SOURCE", `package service must be managed through Apps: ${record.packageId}`);
10102
- await this.runtimeService.restart(record.id);
10103
- await rm(record.dirPath, { recursive: true });
10104
- await this.createGrantStore().revokeActionsByPrefix(`${record.id}.`);
10530
+ listWorkspaceDataOwners = async () => await this.recordService.listWorkspaceDataOwners(this.getServiceAppsPath(this.getWorkspacePath()), await this.listServiceAppDirNames(this.getServiceAppsPath(this.getWorkspacePath())));
10531
+ deleteServiceApp = async (appId, purgeData = false) => {
10532
+ if (!SERVICE_APP_ID_PATTERN.test(appId)) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app id must use kebab-case");
10533
+ const workspacePath = this.getWorkspacePath();
10534
+ let record;
10535
+ try {
10536
+ record = await this.removalService.remove({
10537
+ grantStore: this.createGrantStore(),
10538
+ lockPath: join(workspacePath, ".nextclaw", "locks", "service-apps", `${appId}.lock`),
10539
+ purgeData,
10540
+ loadRecord: async () => {
10541
+ const { record } = await this.requireServiceApp(appId);
10542
+ if (record.sourceKind === "package") throw new ServiceAppError("SERVICE_APP_MANAGED_SOURCE", `package service must be managed through Apps: ${record.packageId}`);
10543
+ if (purgeData && !record.storage) throw new ServiceAppError("SERVICE_APP_READ_FAILED", `Service App ${appId} 缺少受管数据目录,已停止删除。`);
10544
+ return record;
10545
+ },
10546
+ stopRuntime: async (loaded) => await this.runtimeService.restart(loaded.id)
10547
+ });
10548
+ } catch (error) {
10549
+ if (error instanceof ServiceAppRemovalCleanupError) throw new ServiceAppError("SERVICE_APP_RUNTIME_FAILED", `Service App ${appId} 已从 workspace 移除,但${error.message}`);
10550
+ throw error;
10551
+ }
10105
10552
  return {
10106
10553
  deleted: true,
10107
- id: record.id
10554
+ id: record.id,
10555
+ dataRemoved: purgeData
10108
10556
  };
10109
10557
  };
10110
- dispose = async () => {
10111
- await this.runtimeService.dispose();
10112
- };
10558
+ dispose = async () => await this.runtimeService.dispose();
10113
10559
  assertCanActivatePackageComponents = async (components) => {
10114
10560
  const serviceComponents = components.filter((component) => component.kind === "service");
10115
10561
  if (serviceComponents.length === 0) return;
@@ -10125,6 +10571,31 @@ var ServiceAppManager = class {
10125
10571
  const conflictsWithPackage = activePackageSources.some((active) => active.kind === "service" && active.id === component.id && active.packageId !== component.packageId);
10126
10572
  if (workspaceIds.has(component.id) || conflictsWithPackage) throw new AppPackageError("APP_PACKAGE_CONFLICT", `Service component id 冲突:${component.id}`);
10127
10573
  }
10574
+ for (const component of serviceComponents) {
10575
+ let manifest;
10576
+ try {
10577
+ manifest = await readServiceAppManifest(component.sourcePath);
10578
+ } catch (error) {
10579
+ throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `Service component ${component.id} manifest 无效:${error instanceof Error ? error.message : String(error)}`);
10580
+ }
10581
+ try {
10582
+ const record = this.recordService.fromManifest(component.sourcePath, manifest, component, component.storage);
10583
+ const runtimeActions = await this.runtimeService.listActions({
10584
+ app: record,
10585
+ manifest
10586
+ });
10587
+ const runtimeStatus = this.runtimeService.getStatus(component.id);
10588
+ if (runtimeStatus.status === "failed") throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `Service component ${component.id} 启动探测失败:${runtimeStatus.lastError ?? "unknown error"}`);
10589
+ const invalidAction = mergeServiceAppRuntimeActions({
10590
+ record,
10591
+ manifest,
10592
+ runtimeActions
10593
+ }).find((action) => action.runtimeState === "missing" || action.runtimeState === "undeclared");
10594
+ if (invalidAction) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `Service component ${component.id} action 合同不一致:${invalidAction.id} (${invalidAction.runtimeState})`);
10595
+ } finally {
10596
+ await this.runtimeService.restart(component.id);
10597
+ }
10598
+ }
10128
10599
  };
10129
10600
  deactivatePackageComponents = async (components) => {
10130
10601
  const serviceIds = components.filter((component) => component.kind === "service").map((component) => component.id);
@@ -10137,7 +10608,7 @@ var ServiceAppManager = class {
10137
10608
  };
10138
10609
  withGrantState = async (action, params) => {
10139
10610
  if (!params.caller) return action;
10140
- const granted = await this.createGrantStore().isGranted(params.caller, action.id);
10611
+ const granted = await this.createGrantStore().isGranted(params.caller, action.id, action.risk);
10141
10612
  return {
10142
10613
  ...action,
10143
10614
  grantState: resolveServiceActionGrantState({
@@ -10175,7 +10646,7 @@ var ServiceAppManager = class {
10175
10646
  if (manifest.id !== appId) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app manifest id must match directory name");
10176
10647
  return {
10177
10648
  manifest,
10178
- record: this.toServiceAppRecord(dirPath, manifest, packageSource)
10649
+ record: this.recordService.fromManifest(dirPath, manifest, packageSource, packageSource?.storage ?? await this.recordService.materializeWorkspaceStorage(manifest.id))
10179
10650
  };
10180
10651
  } catch (error) {
10181
10652
  if (isServiceAppError(error)) throw error;
@@ -10193,7 +10664,7 @@ var ServiceAppManager = class {
10193
10664
  const manifest = await readServiceAppManifest(dirPath);
10194
10665
  return {
10195
10666
  manifest,
10196
- record: this.toServiceAppRecord(dirPath, manifest)
10667
+ record: this.recordService.fromManifest(dirPath, manifest, void 0, await this.recordService.materializeWorkspaceStorage(manifest.id))
10197
10668
  };
10198
10669
  } catch {
10199
10670
  return null;
@@ -10204,7 +10675,7 @@ var ServiceAppManager = class {
10204
10675
  const manifest = await readServiceAppManifest(component.sourcePath);
10205
10676
  return {
10206
10677
  manifest,
10207
- record: this.toServiceAppRecord(component.sourcePath, manifest, component)
10678
+ record: this.recordService.fromManifest(component.sourcePath, manifest, component, component.storage)
10208
10679
  };
10209
10680
  } catch {
10210
10681
  return null;
@@ -10212,51 +10683,6 @@ var ServiceAppManager = class {
10212
10683
  }));
10213
10684
  return [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry));
10214
10685
  };
10215
- buildServiceAppRecord = async (serviceAppsPath, dirName) => {
10216
- const dirPath = join(serviceAppsPath, dirName);
10217
- try {
10218
- const manifest = await readServiceAppManifest(dirPath);
10219
- return this.toServiceAppRecord(dirPath, manifest);
10220
- } catch (error) {
10221
- if (this.isMissingFileError(error)) return null;
10222
- return {
10223
- id: dirName,
10224
- title: toTitle(dirName),
10225
- dirPath,
10226
- manifestPath: getServiceAppManifestPath(dirPath),
10227
- cwd: dirPath,
10228
- enabled: false,
10229
- protocol: "mcp",
10230
- status: "failed",
10231
- lastError: error instanceof Error ? error.message : String(error)
10232
- };
10233
- }
10234
- };
10235
- toServiceAppRecord = (dirPath, manifest, packageSource) => {
10236
- const runtimeStatus = this.runtimeService.getStatus(manifest.id);
10237
- return {
10238
- id: manifest.id,
10239
- title: manifest.title,
10240
- description: manifest.description,
10241
- dirPath,
10242
- manifestPath: getServiceAppManifestPath(dirPath),
10243
- command: manifest.command,
10244
- args: manifest.args,
10245
- cwd: dirPath,
10246
- enabled: manifest.enabled,
10247
- protocol: manifest.protocol,
10248
- status: manifest.enabled ? runtimeStatus.status : "stopped",
10249
- lastError: runtimeStatus.lastError,
10250
- lastStartedAt: runtimeStatus.lastStartedAt,
10251
- lastReadyAt: runtimeStatus.lastReadyAt,
10252
- lastFailedAt: runtimeStatus.lastFailedAt,
10253
- sourceKind: packageSource ? "package" : "workspace",
10254
- packageId: packageSource?.packageId,
10255
- packageVersion: packageSource?.packageVersion,
10256
- packageDirectory: packageSource ? join(packageSource.sourcePath, "..", "..") : void 0,
10257
- dataDirectory: packageSource?.dataDirectory
10258
- };
10259
- };
10260
10686
  assertCaller = (caller) => {
10261
10687
  if (caller.surface !== "panel-app" || !caller.appId.trim()) throw new ServiceAppError("SERVICE_APP_INVALID_CALLER", "service action caller is invalid");
10262
10688
  };
@@ -10268,30 +10694,6 @@ var ServiceAppManager = class {
10268
10694
  getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
10269
10695
  createGrantStore = () => new ServiceActionGrantStore(join(this.getServiceAppsPath(this.getWorkspacePath()), SERVICE_ACTION_GRANTS_FILE_NAME));
10270
10696
  listPackageComponentSources = async () => await this.params.listPackageComponentSources?.() ?? [];
10271
- buildServiceAppRecordFromPackage = async (source) => {
10272
- try {
10273
- const manifest = await readServiceAppManifest(source.sourcePath);
10274
- if (manifest.id !== source.id) throw new Error(`service component id mismatch: ${source.id}`);
10275
- return this.toServiceAppRecord(source.sourcePath, manifest, source);
10276
- } catch (error) {
10277
- return {
10278
- id: source.id,
10279
- title: toTitle(source.id),
10280
- dirPath: source.sourcePath,
10281
- manifestPath: source.manifestPath,
10282
- cwd: source.sourcePath,
10283
- enabled: false,
10284
- protocol: "mcp",
10285
- status: "failed",
10286
- lastError: error instanceof Error ? error.message : String(error),
10287
- sourceKind: "package",
10288
- packageId: source.packageId,
10289
- packageVersion: source.packageVersion,
10290
- packageDirectory: join(source.sourcePath, "..", ".."),
10291
- dataDirectory: source.dataDirectory
10292
- };
10293
- }
10294
- };
10295
10697
  listServiceAppDirNames = async (serviceAppsPath) => {
10296
10698
  try {
10297
10699
  return (await readdir(serviceAppsPath, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
@@ -10302,9 +10704,6 @@ var ServiceAppManager = class {
10302
10704
  };
10303
10705
  isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
10304
10706
  };
10305
- function toTitle(value) {
10306
- return basename(value).replace(/[-_]+/g, " ").trim() || value;
10307
- }
10308
10707
  //#endregion
10309
10708
  //#region src/managers/session-run.manager.ts
10310
10709
  var MessageInbox = class {
@@ -15358,35 +15757,48 @@ var ToolProviderContribution = class {
15358
15757
  };
15359
15758
  };
15360
15759
  //#endregion
15361
- //#region src/app/nextclaw-kernel.ts
15760
+ //#region src/app/kernel-storage-paths.ts
15362
15761
  function resolveKernelAppHomeDirectory(options) {
15363
15762
  const homeDir = options.homeDir?.trim();
15364
15763
  return resolve(homeDir ? expandHome(homeDir) : getDataDir(), "apps");
15365
15764
  }
15366
15765
  function resolveKernelSessionsDir(options) {
15367
15766
  const homeDir = options.homeDir?.trim();
15368
- if (homeDir) return ensureDir(resolve(expandHome(homeDir), "sessions"));
15369
- return getSessionsPath();
15767
+ return homeDir ? ensureDir(resolve(expandHome(homeDir), "sessions")) : getSessionsPath();
15370
15768
  }
15371
15769
  function resolveKernelAutomationStorePath(options) {
15372
- const homeDir = options.homeDir?.trim();
15373
- if (homeDir) return resolve(expandHome(homeDir), "cron", "jobs.json");
15374
- return resolve(getDataDir(), "cron", "jobs.json");
15770
+ return resolveKernelDataPath(options, "cron", "jobs.json");
15375
15771
  }
15376
15772
  function resolveKernelPreferenceStorePath(options) {
15377
- const homeDir = options.homeDir?.trim();
15378
- if (homeDir) return resolve(expandHome(homeDir), "preferences", "preferences.json");
15379
- return resolve(getDataDir(), "preferences", "preferences.json");
15773
+ return resolveKernelDataPath(options, "preferences", "preferences.json");
15380
15774
  }
15381
15775
  function resolveKernelProjectStorePath(options) {
15382
- const homeDir = options.homeDir?.trim();
15383
- if (homeDir) return resolve(expandHome(homeDir), "projects", "projects.json");
15384
- return resolve(getDataDir(), "projects", "projects.json");
15776
+ return resolveKernelDataPath(options, "projects", "projects.json");
15385
15777
  }
15386
15778
  function resolveKernelInboxDeliveryStorePath(options) {
15779
+ return resolveKernelDataPath(options, "inbox", "deliveries.json");
15780
+ }
15781
+ function resolveKernelDataPath(options, ...segments) {
15387
15782
  const homeDir = options.homeDir?.trim();
15388
- if (homeDir) return resolve(expandHome(homeDir), "inbox", "deliveries.json");
15389
- return resolve(getDataDir(), "inbox", "deliveries.json");
15783
+ return resolve(homeDir ? expandHome(homeDir) : getDataDir(), ...segments);
15784
+ }
15785
+ //#endregion
15786
+ //#region src/app/nextclaw-kernel.ts
15787
+ function createKernelServiceAppManagers(params) {
15788
+ const { appHomeDirectory, appPackageManager, configManager } = params;
15789
+ const serviceAppManager = new ServiceAppManager({
15790
+ configManager,
15791
+ listPackageComponentSources: appPackageManager.listActiveComponentSources
15792
+ });
15793
+ return {
15794
+ serviceAppManager,
15795
+ appDataManager: new AppDataManager({
15796
+ appHomeDirectory,
15797
+ getWorkspacePath: () => getWorkspacePathFromConfig(configManager.config),
15798
+ listInstalledPackageOwners: appPackageManager.listInstalledDataOwners,
15799
+ listWorkspaceDataOwners: serviceAppManager.listWorkspaceDataOwners
15800
+ })
15801
+ };
15390
15802
  }
15391
15803
  var NextclawKernelControlManager = class {
15392
15804
  runtimeControl = null;
@@ -15412,6 +15824,7 @@ var NextclawKernel = class {
15412
15824
  skills;
15413
15825
  automation;
15414
15826
  appPackageManager;
15827
+ appDataManager;
15415
15828
  channels;
15416
15829
  sessionRequests;
15417
15830
  sessionSearch;
@@ -15489,10 +15902,11 @@ var NextclawKernel = class {
15489
15902
  listPackageComponentSources: this.appPackageManager.listActiveComponentSources
15490
15903
  });
15491
15904
  this.preferenceManager = new PreferenceManager({ storePath: resolveKernelPreferenceStorePath(options) });
15492
- this.serviceAppManager = new ServiceAppManager({
15493
- configManager: this.configManager,
15494
- listPackageComponentSources: this.appPackageManager.listActiveComponentSources
15495
- });
15905
+ ({appDataManager: this.appDataManager, serviceAppManager: this.serviceAppManager} = createKernelServiceAppManagers({
15906
+ appHomeDirectory: resolveKernelAppHomeDirectory(options),
15907
+ appPackageManager: this.appPackageManager,
15908
+ configManager: this.configManager
15909
+ }));
15496
15910
  this.installAppPackageRuntimeHooks();
15497
15911
  this.extensions = new ExtensionManager({
15498
15912
  configManager: this.configManager,
@@ -16149,6 +16563,6 @@ function resolveLegacyEventType(message) {
16149
16563
  return `message.${role || "other"}`;
16150
16564
  }
16151
16565
  //#endregion
16152
- export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, ExtensionManager, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawKernel, PANEL_APP_AGENT_CAPABILITIES, PROJECT_TEMPLATE_IDS, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppError, ServiceAppManager, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildServiceActionId, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getAutomaticUpdateCheckDelay, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, injectUiContentParamsBootstrap, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseServiceAppManifest, parseSkillFrontmatter, readContextCompactionCheckpoint, readContextWindowEventSessionId, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceAppManifest, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, waitForAgentRuntimeSessionReply };
16566
+ export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, ExtensionManager, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawKernel, PANEL_APP_AGENT_CAPABILITIES, PROJECT_TEMPLATE_IDS, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppError, ServiceAppManager, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildServiceActionId, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getAutomaticUpdateCheckDelay, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseServiceAppManifest, parseSkillFrontmatter, readContextCompactionCheckpoint, readContextWindowEventSessionId, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceAppManifest, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, waitForAgentRuntimeSessionReply };
16153
16567
 
16154
16568
  //# sourceMappingURL=index.js.map