@nextclaw/kernel 0.6.28 → 0.7.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, AppInstanceStorageService, AppManifestService, AppRegistryService, 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;
@@ -2845,7 +2892,9 @@ var AppPackageManager = class {
2845
2892
  };
2846
2893
  listActiveComponentSources = async () => {
2847
2894
  await this.ensureBuiltInPackages();
2848
- return (await this.registryService.listApps()).filter((record) => record.enabled).flatMap((record) => {
2895
+ const records = await this.registryService.listApps();
2896
+ return (await Promise.all(records.filter((record) => record.enabled).map(async (record) => {
2897
+ await this.installationService.assertVersionIntegrity(record.appId, record.activeVersion);
2849
2898
  const version = record.installedVersions[record.activeVersion];
2850
2899
  if (!version || version.manifestSchemaVersion !== 2) return [];
2851
2900
  return (version.components ?? []).map((component) => ({
@@ -2855,9 +2904,12 @@ var AppPackageManager = class {
2855
2904
  packageVersion: record.activeVersion,
2856
2905
  sourcePath: component.componentDirectory,
2857
2906
  manifestPath: component.manifestPath,
2858
- dataDirectory: record.dataDirectory
2907
+ dataDirectory: record.dataDirectory,
2908
+ instanceId: record.defaultInstance.id,
2909
+ storage: record.defaultInstance.storage,
2910
+ ...this.resolveSecurity(version, record.appId)
2859
2911
  }));
2860
- });
2912
+ }))).flat();
2861
2913
  };
2862
2914
  listOperations = async () => await this.operationManager.list();
2863
2915
  startOperation = async (input) => {
@@ -2873,66 +2925,115 @@ var AppPackageManager = class {
2873
2925
  return await this.getPackage(result.appId);
2874
2926
  };
2875
2927
  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);
2928
+ return await this.installationService.withAppOperation(appId, async () => {
2929
+ const app = await this.getPackage(appId);
2930
+ await this.installationService.assertVersionIntegrity(appId, app.activeVersion);
2931
+ if (app.enabled) return app;
2932
+ await this.assertEngineCompatibility(appId);
2933
+ const sources = this.toComponentSources(app);
2934
+ await this.runtimeHooks.assertCanActivate(sources);
2935
+ await this.installationService.setEnabled(appId, true);
2936
+ return await this.getPackage(appId);
2937
+ });
2883
2938
  };
2884
2939
  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);
2940
+ return await this.installationService.withAppOperation(appId, async () => {
2941
+ const app = await this.getPackage(appId);
2942
+ if (!app.enabled) return app;
2943
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(app));
2944
+ await this.installationService.setEnabled(appId, false);
2945
+ return await this.getPackage(appId);
2946
+ });
2890
2947
  };
2891
2948
  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,
2949
+ return await this.installationService.withAppOperation(appId, async () => {
2950
+ const current = await this.getPackage(appId);
2951
+ const result = await this.installationService.update(appId, {
2952
+ ...options,
2953
+ activate: false
2954
+ });
2955
+ if (!result.updated) return {
2956
+ package: current,
2901
2957
  result
2902
2958
  };
2903
- } catch (error) {
2904
- if (result.updated) await this.installationService.rollback(appId, result.previousVersion);
2905
- throw error;
2906
- }
2959
+ let activated = false;
2960
+ let deactivated = false;
2961
+ try {
2962
+ await this.assertEngineCompatibility(appId, result.version);
2963
+ const candidate = await this.toPackageView(await this.installationService.info(appId), result.version);
2964
+ if (current.enabled) {
2965
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2966
+ deactivated = true;
2967
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
2968
+ }
2969
+ activated = (await this.installationService.rollback(appId, result.version)).rolledBack;
2970
+ return {
2971
+ package: await this.getPackage(appId),
2972
+ result
2973
+ };
2974
+ } catch (error) {
2975
+ if (activated) await this.installationService.rollback(appId, result.previousVersion);
2976
+ if (deactivated) try {
2977
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(current));
2978
+ } catch (recoveryError) {
2979
+ throw new AggregateError([error, recoveryError], `应用 ${appId} 更新失败,且旧 runtime 恢复探测失败。`);
2980
+ }
2981
+ throw error;
2982
+ }
2983
+ });
2907
2984
  };
2908
2985
  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
2986
+ return await this.installationService.withAppOperation(appId, async () => {
2987
+ const current = await this.getPackage(appId);
2988
+ if (current.activeVersion === version) return {
2989
+ package: current,
2990
+ result: {
2991
+ appId,
2992
+ activeVersion: version,
2993
+ previousVersion: version,
2994
+ enabled: current.enabled,
2995
+ rolledBack: false
2996
+ }
2919
2997
  };
2920
- } catch (error) {
2921
- if (result.rolledBack) await this.installationService.rollback(appId, result.previousVersion);
2922
- throw error;
2923
- }
2998
+ await this.assertEngineCompatibility(appId, version);
2999
+ const candidate = await this.toPackageView(await this.installationService.info(appId), version);
3000
+ let deactivated = false;
3001
+ let result;
3002
+ try {
3003
+ if (current.enabled) {
3004
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
3005
+ deactivated = true;
3006
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(candidate));
3007
+ }
3008
+ result = await this.installationService.rollback(appId, version);
3009
+ return {
3010
+ package: await this.getPackage(appId),
3011
+ result
3012
+ };
3013
+ } catch (error) {
3014
+ if (result?.rolledBack) await this.installationService.rollback(appId, result.previousVersion);
3015
+ if (deactivated) try {
3016
+ await this.runtimeHooks.assertCanActivate(this.toComponentSources(current));
3017
+ } catch (recoveryError) {
3018
+ throw new AggregateError([error, recoveryError], `应用 ${appId} 回滚失败,且旧 runtime 恢复探测失败。`);
3019
+ }
3020
+ throw error;
3021
+ }
3022
+ });
2924
3023
  };
2925
3024
  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
- }
3025
+ return await this.installationService.withAppOperation(appId, async () => {
3026
+ const current = await this.getPackage(appId);
3027
+ if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, true);
3028
+ try {
3029
+ if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
3030
+ await this.runtimeHooks.beforeUninstall(this.toComponentSources(current));
3031
+ return await this.installationService.uninstall(appId, purgeData);
3032
+ } catch (error) {
3033
+ if (current.builtIn) await this.registryService.setBuiltInSuppressed(appId, false);
3034
+ throw error;
3035
+ }
3036
+ });
2936
3037
  };
2937
3038
  ensureBuiltInPackages = async () => {
2938
3039
  this.builtInBootstrapPromise ??= this.installBuiltInPackages();
@@ -2944,13 +3045,22 @@ var AppPackageManager = class {
2944
3045
  if (!isAppComponentManifestBundle(manifest)) continue;
2945
3046
  if (await this.registryService.isBuiltInSuppressed(manifest.manifest.id)) continue;
2946
3047
  if ((await this.registryService.getApp(manifest.manifest.id))?.installedVersions[manifest.manifest.version]) continue;
2947
- await this.installationService.install(appDirectory);
3048
+ await this.installationService.install(appDirectory, { trustedPublisher: {
3049
+ id: "nextclaw",
3050
+ name: "NextClaw",
3051
+ url: "https://nextclaw.io"
3052
+ } });
2948
3053
  }
2949
3054
  };
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"));
3055
+ toPackageView = async (info, selectedVersion = info.activeVersion) => {
3056
+ const activeVersion = info.installedVersions.find((version) => version.version === selectedVersion);
3057
+ if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${info.appId} 缺少版本 ${selectedVersion}。`);
3058
+ const packagePresentation = await this.presentationService.readManifest(path.join(activeVersion.installDirectory, "manifest.json"));
3059
+ const manifestBundle = await this.manifestService.load(activeVersion.installDirectory);
3060
+ const security = manifestBundle.manifest.schemaVersion === 2 ? this.manifestService.resolvePlatformSecurity(manifestBundle.manifest) : {
3061
+ runtimeProfile: "wasi",
3062
+ isolation: manifestBundle.manifest.main.kind === "wasi-http-component" ? "host-mediated" : "sandboxed"
3063
+ };
2954
3064
  return {
2955
3065
  id: info.appId,
2956
3066
  name: info.name,
@@ -2958,7 +3068,7 @@ var AppPackageManager = class {
2958
3068
  icon: packagePresentation.icon,
2959
3069
  nameI18n: packagePresentation.nameI18n,
2960
3070
  descriptionI18n: packagePresentation.descriptionI18n,
2961
- activeVersion: info.activeVersion,
3071
+ activeVersion: selectedVersion,
2962
3072
  installedVersions: info.installedVersions.map((version) => version.version),
2963
3073
  enabled: info.enabled,
2964
3074
  builtIn: await this.isBuiltInAppId(info.appId),
@@ -2967,43 +3077,50 @@ var AppPackageManager = class {
2967
3077
  kind: component.kind,
2968
3078
  id: component.id,
2969
3079
  packageId: info.appId,
2970
- packageVersion: info.activeVersion,
3080
+ packageVersion: selectedVersion,
2971
3081
  sourcePath: component.componentDirectory,
2972
3082
  manifestPath: component.manifestPath,
2973
3083
  dataDirectory: info.dataDirectory,
2974
- ...await this.readManifestPresentation(component.manifestPath)
3084
+ instanceId: info.instance.id,
3085
+ storage: info.storage,
3086
+ runtimeProfile: security.runtimeProfile,
3087
+ isolation: security.isolation,
3088
+ ...await this.presentationService.readManifest(component.manifestPath)
2975
3089
  }))),
2976
- dataDirectory: info.dataDirectory
2977
- };
2978
- };
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")
3090
+ dataDirectory: info.dataDirectory,
3091
+ instanceId: info.instance.id,
3092
+ storage: info.storage,
3093
+ storageUsage: info.storageUsage,
3094
+ runtimeProfile: security.runtimeProfile,
3095
+ isolation: security.isolation
3096
+ };
3097
+ };
3098
+ resolveSecurity = (version, appId) => {
3099
+ if (version.security) return {
3100
+ runtimeProfile: version.security.runtimeProfile,
3101
+ isolation: version.security.isolation
3102
+ };
3103
+ const hasService = version.components?.some((component) => component.kind === "service") ?? false;
3104
+ if (version.manifestSchemaVersion !== 2) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId} 仍使用 legacy schema,不能投影组件。`);
3105
+ return hasService ? {
3106
+ runtimeProfile: "native-process",
3107
+ isolation: "full-user"
3108
+ } : {
3109
+ runtimeProfile: "panel-only",
3110
+ isolation: "sandboxed"
2989
3111
  };
2990
3112
  };
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
- };
2997
3113
  toComponentSources = (app) => app.components.map((component) => ({ ...component }));
2998
- assertEngineCompatibility = async (appId) => {
3114
+ assertEngineCompatibility = async (appId, selectedVersion) => {
2999
3115
  const productVersion = this.params.productVersion?.trim();
3000
3116
  if (!productVersion) return;
3001
3117
  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}。`);
3118
+ const targetVersion = selectedVersion ?? info.activeVersion;
3119
+ const activeVersion = info.installedVersions.find((version) => version.version === targetVersion);
3120
+ if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${appId} 缺少版本 ${targetVersion}。`);
3004
3121
  const manifestBundle = await this.manifestService.load(activeVersion.installDirectory);
3005
3122
  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}。`);
3123
+ if (engineRange && !satisfiesAppEngineVersion(productVersion, engineRange)) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId}@${targetVersion} 要求 NextClaw ${engineRange},当前版本为 ${productVersion}。`);
3007
3124
  };
3008
3125
  listBuiltInDefinitions = async () => {
3009
3126
  if (!this.params.builtInAppsDirectory) return [];
@@ -3027,29 +3144,6 @@ var AppPackageManager = class {
3027
3144
  return await this.builtInDefinitionsPromise;
3028
3145
  };
3029
3146
  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
3147
  executeOperation = async (input, report) => {
3054
3148
  if (input.action === "install") {
3055
3149
  const installed = await this.install(input.source, input.registryUrl, async (phase) => await report(phase));
@@ -9731,7 +9825,7 @@ var McpServiceAppRuntimeService = class {
9731
9825
  toMcpServerRecord = (app, manifest) => ({
9732
9826
  name: app.id,
9733
9827
  definition: {
9734
- enabled: manifest.enabled,
9828
+ enabled: app.enabled,
9735
9829
  transport: {
9736
9830
  type: "stdio",
9737
9831
  command: manifest.command,
@@ -9752,7 +9846,16 @@ var McpServiceAppRuntimeService = class {
9752
9846
  });
9753
9847
  createAppRuntimeEnv = (app) => {
9754
9848
  const env = {};
9755
- if (app.dataDirectory) env.NEXTCLAW_APP_DATA_DIR = app.dataDirectory;
9849
+ if (app.storage) {
9850
+ env.NEXTCLAW_APP_INSTANCE_ID = app.storage.instanceId;
9851
+ env.NEXTCLAW_APP_COMPONENT_ID = app.id;
9852
+ env.NEXTCLAW_APP_DATA_DIR = app.storage.dataDirectory;
9853
+ env.NEXTCLAW_APP_CONFIG_DIR = app.storage.configDirectory;
9854
+ env.NEXTCLAW_APP_STATE_DIR = app.storage.stateDirectory;
9855
+ env.NEXTCLAW_APP_CACHE_DIR = app.storage.cacheDirectory;
9856
+ env.NEXTCLAW_APP_TMP_DIR = app.storage.temporaryDirectory;
9857
+ env.NEXTCLAW_APP_LOG_DIR = app.storage.logsDirectory;
9858
+ } else if (app.dataDirectory) env.NEXTCLAW_APP_DATA_DIR = app.dataDirectory;
9756
9859
  if (app.sourceKind !== "package" || !app.packageId || !app.packageVersion || !app.packageDirectory) return env;
9757
9860
  env.NEXTCLAW_APP_ID = app.packageId;
9758
9861
  env.NEXTCLAW_APP_VERSION = app.packageVersion;
@@ -9775,6 +9878,182 @@ var McpServiceAppRuntimeService = class {
9775
9878
  };
9776
9879
  };
9777
9880
  //#endregion
9881
+ //#region src/utils/service-app-manifest.utils.ts
9882
+ const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
9883
+ const SERVICE_ACTION_RISKS = new Set([
9884
+ "read",
9885
+ "write",
9886
+ "external",
9887
+ "dangerous"
9888
+ ]);
9889
+ const SERVICE_APP_MANIFEST_FILE_NAME = "service-app.json";
9890
+ function getServiceAppManifestPath(dirPath) {
9891
+ return join(dirPath, SERVICE_APP_MANIFEST_FILE_NAME);
9892
+ }
9893
+ async function readServiceAppManifest(dirPath) {
9894
+ return parseServiceAppManifest(await readFile(getServiceAppManifestPath(dirPath), "utf8"));
9895
+ }
9896
+ function parseServiceAppManifest(raw) {
9897
+ let parsed;
9898
+ try {
9899
+ parsed = JSON.parse(raw);
9900
+ } catch (error) {
9901
+ throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
9902
+ }
9903
+ if (!isRecord$9(parsed)) throw new Error("service-app.json must contain an object.");
9904
+ const id = readRequiredString$6(parsed, "id");
9905
+ if (!SERVICE_APP_ID_PATTERN.test(id)) throw new Error("service app id must be kebab-case.");
9906
+ const protocol = readOptionalString$7(parsed, "protocol") ?? "mcp";
9907
+ if (protocol !== "mcp") throw new Error("service app protocol must be mcp.");
9908
+ return {
9909
+ id,
9910
+ title: readRequiredString$6(parsed, "title"),
9911
+ description: readOptionalString$7(parsed, "description"),
9912
+ enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
9913
+ protocol,
9914
+ command: readRequiredString$6(parsed, "command"),
9915
+ args: readStringArray(parsed.args, "args"),
9916
+ actions: readManifestActions(parsed.actions)
9917
+ };
9918
+ }
9919
+ function readManifestActions(value) {
9920
+ if (value === void 0) throw new Error("service app actions are required.");
9921
+ if (!isRecord$9(value)) throw new Error("service app actions must be an object.");
9922
+ if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
9923
+ const actions = {};
9924
+ for (const [name, action] of Object.entries(value)) {
9925
+ if (!name.trim()) throw new Error("service app action name cannot be empty.");
9926
+ if (!isRecord$9(action)) throw new Error(`service app action ${name} must be an object.`);
9927
+ const risk = readOptionalString$7(action, "risk");
9928
+ if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
9929
+ const inputSchema = action.inputSchema;
9930
+ if (inputSchema !== void 0 && !isRecord$9(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
9931
+ actions[name] = {
9932
+ risk,
9933
+ title: readOptionalString$7(action, "title"),
9934
+ description: readOptionalString$7(action, "description"),
9935
+ inputSchema
9936
+ };
9937
+ }
9938
+ return actions;
9939
+ }
9940
+ function readRequiredString$6(record, key) {
9941
+ const value = readOptionalString$7(record, key);
9942
+ if (!value) throw new Error(`service app ${key} is required.`);
9943
+ return value;
9944
+ }
9945
+ function readOptionalString$7(record, key) {
9946
+ return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
9947
+ }
9948
+ function readOptionalBoolean$1(record, key) {
9949
+ if (record[key] === void 0) return;
9950
+ if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
9951
+ return record[key];
9952
+ }
9953
+ function readStringArray(value, key) {
9954
+ if (value === void 0) return [];
9955
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
9956
+ return value;
9957
+ }
9958
+ function isRecord$9(value) {
9959
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9960
+ }
9961
+ //#endregion
9962
+ //#region src/services/service-app-record.service.ts
9963
+ var ServiceAppRecordService = class {
9964
+ instanceStorageService = new AppInstanceStorageService();
9965
+ constructor(params) {
9966
+ this.params = params;
9967
+ }
9968
+ buildWorkspaceRecord = async (serviceAppsPath, dirName) => {
9969
+ const dirPath = join(serviceAppsPath, dirName);
9970
+ try {
9971
+ const manifest = await readServiceAppManifest(dirPath);
9972
+ return this.fromManifest(dirPath, manifest, void 0, await this.materializeWorkspaceStorage(manifest.id));
9973
+ } catch (error) {
9974
+ if (this.isMissingFileError(error)) return null;
9975
+ return this.failedWorkspaceRecord(dirName, dirPath, error);
9976
+ }
9977
+ };
9978
+ buildPackageRecord = async (source) => {
9979
+ try {
9980
+ const manifest = await readServiceAppManifest(source.sourcePath);
9981
+ if (manifest.id !== source.id) throw new Error(`service component id mismatch: ${source.id}`);
9982
+ return this.fromManifest(source.sourcePath, manifest, source, source.storage);
9983
+ } catch (error) {
9984
+ return this.failedPackageRecord(source, error);
9985
+ }
9986
+ };
9987
+ materializeWorkspaceStorage = async (serviceId) => {
9988
+ const instanceDirectory = join(this.params.getWorkspacePath(), ".nextclaw", "app-instances", serviceId, "default");
9989
+ return (await this.instanceStorageService.materialize({
9990
+ appId: serviceId,
9991
+ instanceId: "default",
9992
+ instanceDirectory
9993
+ })).storage;
9994
+ };
9995
+ fromManifest = (dirPath, manifest, packageSource, storage) => {
9996
+ const runtimeStatus = this.params.runtimeService.getStatus(manifest.id);
9997
+ return {
9998
+ id: manifest.id,
9999
+ title: manifest.title,
10000
+ description: manifest.description,
10001
+ dirPath,
10002
+ manifestPath: getServiceAppManifestPath(dirPath),
10003
+ command: manifest.command,
10004
+ args: manifest.args,
10005
+ cwd: dirPath,
10006
+ enabled: packageSource ? true : manifest.enabled,
10007
+ protocol: manifest.protocol,
10008
+ status: packageSource || manifest.enabled ? runtimeStatus.status : "stopped",
10009
+ lastError: runtimeStatus.lastError,
10010
+ lastStartedAt: runtimeStatus.lastStartedAt,
10011
+ lastReadyAt: runtimeStatus.lastReadyAt,
10012
+ lastFailedAt: runtimeStatus.lastFailedAt,
10013
+ sourceKind: packageSource ? "package" : "workspace",
10014
+ packageId: packageSource?.packageId,
10015
+ packageVersion: packageSource?.packageVersion,
10016
+ packageDirectory: packageSource ? join(packageSource.sourcePath, "..", "..") : void 0,
10017
+ dataDirectory: storage?.dataDirectory,
10018
+ instanceId: packageSource?.instanceId ?? storage?.instanceId,
10019
+ storage,
10020
+ isolation: packageSource?.isolation ?? "full-user"
10021
+ };
10022
+ };
10023
+ failedWorkspaceRecord = (id, dirPath, error) => ({
10024
+ id,
10025
+ title: this.toTitle(id),
10026
+ dirPath,
10027
+ manifestPath: getServiceAppManifestPath(dirPath),
10028
+ cwd: dirPath,
10029
+ enabled: false,
10030
+ protocol: "mcp",
10031
+ status: "failed",
10032
+ lastError: error instanceof Error ? error.message : String(error)
10033
+ });
10034
+ failedPackageRecord = (source, error) => ({
10035
+ id: source.id,
10036
+ title: this.toTitle(source.id),
10037
+ dirPath: source.sourcePath,
10038
+ manifestPath: source.manifestPath,
10039
+ cwd: source.sourcePath,
10040
+ enabled: false,
10041
+ protocol: "mcp",
10042
+ status: "failed",
10043
+ lastError: error instanceof Error ? error.message : String(error),
10044
+ sourceKind: "package",
10045
+ packageId: source.packageId,
10046
+ packageVersion: source.packageVersion,
10047
+ packageDirectory: join(source.sourcePath, "..", ".."),
10048
+ dataDirectory: source.dataDirectory,
10049
+ instanceId: source.instanceId,
10050
+ storage: source.storage,
10051
+ isolation: source.isolation
10052
+ });
10053
+ toTitle = (value) => basename(value).replace(/[-_]+/g, " ").trim() || value;
10054
+ isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
10055
+ };
10056
+ //#endregion
9778
10057
  //#region src/stores/service-action-grant.store.ts
9779
10058
  const EMPTY_GRANTS = {
9780
10059
  version: 1,
@@ -9784,9 +10063,9 @@ var ServiceActionGrantStore = class {
9784
10063
  constructor(storePath) {
9785
10064
  this.storePath = storePath;
9786
10065
  }
9787
- isGranted = async (caller, actionId) => {
9788
- const data = await this.load();
9789
- return Boolean(data.grants[getServiceActionCallerKey(caller)]?.actions[actionId]);
10066
+ isGranted = async (caller, actionId, currentRisk) => {
10067
+ const grant = (await this.load()).grants[getServiceActionCallerKey(caller)]?.actions[actionId];
10068
+ return Boolean(grant && (currentRisk === void 0 || grant.risk === currentRisk));
9790
10069
  };
9791
10070
  grant = async ({ actionId, caller, grantedAt, risk }) => {
9792
10071
  const data = await this.load();
@@ -9853,12 +10132,12 @@ var ServiceActionGrantStore = class {
9853
10132
  };
9854
10133
  };
9855
10134
  function normalizeStoreData(value) {
9856
- if (!isRecord$9(value) || value.version !== 1 || !isRecord$9(value.grants)) return structuredClone(EMPTY_GRANTS);
10135
+ if (!isRecord$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS);
9857
10136
  const grants = {};
9858
10137
  for (const [callerKey, callerValue] of Object.entries(value.grants)) {
9859
- if (!isRecord$9(callerValue) || !isRecord$9(callerValue.actions)) continue;
10138
+ if (!isRecord$8(callerValue) || !isRecord$8(callerValue.actions)) continue;
9860
10139
  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] = {
10140
+ for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$8(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
9862
10141
  grantedAt: actionValue.grantedAt,
9863
10142
  risk: actionValue.risk
9864
10143
  };
@@ -9872,92 +10151,11 @@ function normalizeStoreData(value) {
9872
10151
  function isServiceActionRisk(value) {
9873
10152
  return value === "read" || value === "write" || value === "external" || value === "dangerous";
9874
10153
  }
9875
- function isRecord$9(value) {
10154
+ function isRecord$8(value) {
9876
10155
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9877
10156
  }
9878
10157
  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);
10158
+ return isRecord$8(error) && error.code === "ENOENT";
9961
10159
  }
9962
10160
  //#endregion
9963
10161
  //#region src/utils/service-app-runtime-action.utils.ts
@@ -10006,17 +10204,22 @@ function isServiceAppError(error) {
10006
10204
  }
10007
10205
  var ServiceAppManager = class {
10008
10206
  runtimeService;
10207
+ recordService;
10009
10208
  constructor(params) {
10010
10209
  this.params = params;
10011
10210
  this.runtimeService = params.runtimeService ?? new McpServiceAppRuntimeService({ getConfig: () => params.configManager.config });
10211
+ this.recordService = new ServiceAppRecordService({
10212
+ getWorkspacePath: this.getWorkspacePath,
10213
+ runtimeService: this.runtimeService
10214
+ });
10012
10215
  }
10013
10216
  listServiceApps = async () => {
10014
10217
  const workspacePath = this.getWorkspacePath();
10015
10218
  const serviceAppsPath = this.getServiceAppsPath(workspacePath);
10016
10219
  const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
10017
- const workspaceEntries = await Promise.all(dirNames.map((dirName) => this.buildServiceAppRecord(serviceAppsPath, dirName)));
10220
+ const workspaceEntries = await Promise.all(dirNames.map((dirName) => this.recordService.buildWorkspaceRecord(serviceAppsPath, dirName)));
10018
10221
  const packageSources = (await this.listPackageComponentSources()).filter((component) => component.kind === "service");
10019
- const packageEntries = await Promise.all(packageSources.map((source) => this.buildServiceAppRecordFromPackage(source)));
10222
+ const packageEntries = await Promise.all(packageSources.map((source) => this.recordService.buildPackageRecord(source)));
10020
10223
  return {
10021
10224
  workspacePath,
10022
10225
  serviceAppsPath,
@@ -10048,7 +10251,8 @@ var ServiceAppManager = class {
10048
10251
  const { manifest, record } = await this.requireServiceAppForAction(actionId);
10049
10252
  const actionName = getServiceActionName(actionId, record.id);
10050
10253
  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}.`);
10254
+ const declaredRisk = manifest.actions[actionName]?.risk ?? "dangerous";
10255
+ if (!await this.createGrantStore().isGranted(request.caller, actionId, declaredRisk)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
10052
10256
  return {
10053
10257
  actionId,
10054
10258
  result: await this.runtimeService.invokeAction({
@@ -10125,6 +10329,31 @@ var ServiceAppManager = class {
10125
10329
  const conflictsWithPackage = activePackageSources.some((active) => active.kind === "service" && active.id === component.id && active.packageId !== component.packageId);
10126
10330
  if (workspaceIds.has(component.id) || conflictsWithPackage) throw new AppPackageError("APP_PACKAGE_CONFLICT", `Service component id 冲突:${component.id}`);
10127
10331
  }
10332
+ for (const component of serviceComponents) {
10333
+ let manifest;
10334
+ try {
10335
+ manifest = await readServiceAppManifest(component.sourcePath);
10336
+ } catch (error) {
10337
+ throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `Service component ${component.id} manifest 无效:${error instanceof Error ? error.message : String(error)}`);
10338
+ }
10339
+ try {
10340
+ const record = this.recordService.fromManifest(component.sourcePath, manifest, component, component.storage);
10341
+ const runtimeActions = await this.runtimeService.listActions({
10342
+ app: record,
10343
+ manifest
10344
+ });
10345
+ const runtimeStatus = this.runtimeService.getStatus(component.id);
10346
+ if (runtimeStatus.status === "failed") throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `Service component ${component.id} 启动探测失败:${runtimeStatus.lastError ?? "unknown error"}`);
10347
+ const invalidAction = mergeServiceAppRuntimeActions({
10348
+ record,
10349
+ manifest,
10350
+ runtimeActions
10351
+ }).find((action) => action.runtimeState === "missing" || action.runtimeState === "undeclared");
10352
+ if (invalidAction) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `Service component ${component.id} action 合同不一致:${invalidAction.id} (${invalidAction.runtimeState})`);
10353
+ } finally {
10354
+ await this.runtimeService.restart(component.id);
10355
+ }
10356
+ }
10128
10357
  };
10129
10358
  deactivatePackageComponents = async (components) => {
10130
10359
  const serviceIds = components.filter((component) => component.kind === "service").map((component) => component.id);
@@ -10137,7 +10366,7 @@ var ServiceAppManager = class {
10137
10366
  };
10138
10367
  withGrantState = async (action, params) => {
10139
10368
  if (!params.caller) return action;
10140
- const granted = await this.createGrantStore().isGranted(params.caller, action.id);
10369
+ const granted = await this.createGrantStore().isGranted(params.caller, action.id, action.risk);
10141
10370
  return {
10142
10371
  ...action,
10143
10372
  grantState: resolveServiceActionGrantState({
@@ -10175,7 +10404,7 @@ var ServiceAppManager = class {
10175
10404
  if (manifest.id !== appId) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app manifest id must match directory name");
10176
10405
  return {
10177
10406
  manifest,
10178
- record: this.toServiceAppRecord(dirPath, manifest, packageSource)
10407
+ record: this.recordService.fromManifest(dirPath, manifest, packageSource, packageSource?.storage ?? await this.recordService.materializeWorkspaceStorage(manifest.id))
10179
10408
  };
10180
10409
  } catch (error) {
10181
10410
  if (isServiceAppError(error)) throw error;
@@ -10193,7 +10422,7 @@ var ServiceAppManager = class {
10193
10422
  const manifest = await readServiceAppManifest(dirPath);
10194
10423
  return {
10195
10424
  manifest,
10196
- record: this.toServiceAppRecord(dirPath, manifest)
10425
+ record: this.recordService.fromManifest(dirPath, manifest, void 0, await this.recordService.materializeWorkspaceStorage(manifest.id))
10197
10426
  };
10198
10427
  } catch {
10199
10428
  return null;
@@ -10204,7 +10433,7 @@ var ServiceAppManager = class {
10204
10433
  const manifest = await readServiceAppManifest(component.sourcePath);
10205
10434
  return {
10206
10435
  manifest,
10207
- record: this.toServiceAppRecord(component.sourcePath, manifest, component)
10436
+ record: this.recordService.fromManifest(component.sourcePath, manifest, component, component.storage)
10208
10437
  };
10209
10438
  } catch {
10210
10439
  return null;
@@ -10212,51 +10441,6 @@ var ServiceAppManager = class {
10212
10441
  }));
10213
10442
  return [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry));
10214
10443
  };
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
10444
  assertCaller = (caller) => {
10261
10445
  if (caller.surface !== "panel-app" || !caller.appId.trim()) throw new ServiceAppError("SERVICE_APP_INVALID_CALLER", "service action caller is invalid");
10262
10446
  };
@@ -10268,30 +10452,6 @@ var ServiceAppManager = class {
10268
10452
  getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
10269
10453
  createGrantStore = () => new ServiceActionGrantStore(join(this.getServiceAppsPath(this.getWorkspacePath()), SERVICE_ACTION_GRANTS_FILE_NAME));
10270
10454
  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
10455
  listServiceAppDirNames = async (serviceAppsPath) => {
10296
10456
  try {
10297
10457
  return (await readdir(serviceAppsPath, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
@@ -10302,9 +10462,6 @@ var ServiceAppManager = class {
10302
10462
  };
10303
10463
  isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
10304
10464
  };
10305
- function toTitle(value) {
10306
- return basename(value).replace(/[-_]+/g, " ").trim() || value;
10307
- }
10308
10465
  //#endregion
10309
10466
  //#region src/managers/session-run.manager.ts
10310
10467
  var MessageInbox = class {