@nextclaw/kernel 0.6.23 → 0.6.25

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
@@ -3,20 +3,22 @@ import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t
3
3
  import { createRequire } from "node:module";
4
4
  import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderModelDiscoveryService, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SILENT_REPLY_TOKEN, SessionProjectContextResolver, SessionSearchService, SkillsLoader, THINKING_LEVELS, ViewImageTool, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createRuntimeChildEnv, createTypingStopControlMessage, diffConfigPaths, ensureDir, estimateInputTokens, evaluateSilentReply, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, isNextclawControlMessage, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeAgentProfileId, normalizeInlineSecretRefs, normalizeModelThinkingCapability, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveNextclawSelfManageGuidePaths, resolveProviderRuntime, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, safeFilename, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
5
5
  import { NCP_AI_EXECUTION_METADATA_KEY, NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, createUnavailableNcpAiExecutionMetadata, isHiddenNcpMessage, normalizeAssistantText, readNcpAiExecutionMetadata, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
6
- import { CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY, CHAT_CONVERSATION_EXCERPT_TOKEN_KIND, CHAT_INLINE_TOKENS_METADATA_KEY, CHAT_INLINE_TOKENS_SCHEMA_VERSION, CHAT_PROJECT_TOKEN_KIND, CHAT_SESSION_MATERIALIZATION_METADATA_KEY, CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND, CHAT_WORKSPACE_EXCERPT_TOKEN_KIND, CHAT_WORKSPACE_FILE_TOKEN_KIND, EventBus, INBOX_DELIVERY_SESSION_METADATA_KEY, Ingress, PANEL_APP_INLINE_HOST_CONTRACT, PANEL_APP_SCROLL_RESTORATION_CONTRACT, UI_CONTENT_PARAMS_HOST_CONTRACT, eventKeys, ingressKeys, isRuntimeDefaultModelValue, isSilentReplyNcpMessage, normalizeRuntimeModelSelectionMode, readInlineContentHeight, readUiContentParams } from "@nextclaw/shared";
7
- import { LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
6
+ import { CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY, CHAT_CONVERSATION_EXCERPT_TOKEN_KIND, CHAT_INLINE_TOKENS_METADATA_KEY, CHAT_INLINE_TOKENS_SCHEMA_VERSION, CHAT_PROJECT_TOKEN_KIND, CHAT_SESSION_MATERIALIZATION_METADATA_KEY, CHAT_SYSTEM_OBJECT_TOKEN_KIND, CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND, CHAT_WORKSPACE_EXCERPT_TOKEN_KIND, CHAT_WORKSPACE_FILE_TOKEN_KIND, EventBus, Ingress, PANEL_APP_INLINE_HOST_CONTRACT, PANEL_APP_SCROLL_RESTORATION_CONTRACT, SYSTEM_OBJECT_REFERENCE_DEFAULT_LIMIT, SYSTEM_OBJECT_REFERENCE_MAX_LIMIT, SYSTEM_OBJECT_TYPE_CRON_JOB, SYSTEM_OBJECT_TYPE_INBOX_DELIVERY, UI_CONTENT_PARAMS_HOST_CONTRACT, createSystemObjectReferenceUri, eventKeys, ingressKeys, isRuntimeDefaultModelValue, isSilentReplyNcpMessage, normalizeRuntimeModelSelectionMode, parseSystemObjectReferenceUri, readInlineContentHeight, readSystemObjectResolvedReference, readUiContentParams } from "@nextclaw/shared";
7
+ import { LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, isTextLikeAsset, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
8
8
  import { createHash, createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
9
9
  import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
10
- import { appendFileSync, chmodSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
- import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
12
- import { execFileSync, spawn } from "node:child_process";
10
+ import { appendFileSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
+ import path, { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
13
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";
14
+ import { execFileSync, spawn } from "node:child_process";
14
15
  import { fileURLToPath } from "node:url";
15
16
  import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
16
17
  import { McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
17
18
  import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
18
19
  import { DefaultNcpAgentConversationStateManager, insertMessageByTimeline } from "@nextclaw/ncp-toolkit";
19
20
  import { parse } from "yaml";
21
+ import { createInterface } from "node:readline";
20
22
  import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
21
23
  import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRuntime } from "@nextclaw/nextclaw-ncp-runtime-stdio-client";
22
24
  import { DefaultNcpAgentRuntime } from "@nextclaw/ncp-agent-runtime-next";
@@ -1231,7 +1233,7 @@ const SESSION_ACTIVITY_PREVIEW_STATUS_KINDS = new Set([
1231
1233
  "run-failed",
1232
1234
  "run-interrupted"
1233
1235
  ]);
1234
- function isRecord$15(value) {
1236
+ function isRecord$16(value) {
1235
1237
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1236
1238
  }
1237
1239
  function readOptionalString$12(value) {
@@ -1240,7 +1242,7 @@ function readOptionalString$12(value) {
1240
1242
  return trimmed.length > 0 ? trimmed : void 0;
1241
1243
  }
1242
1244
  function readSessionActivityPreviewMetadata(value) {
1243
- if (!isRecord$15(value)) return null;
1245
+ if (!isRecord$16(value)) return null;
1244
1246
  const state = value.state;
1245
1247
  const timestamp = readOptionalString$12(value.timestamp);
1246
1248
  if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
@@ -1334,7 +1336,7 @@ var SessionActivityPreviewEventService = class {
1334
1336
  //#endregion
1335
1337
  //#region src/managers/agent-run-session-command.manager.ts
1336
1338
  const CONTINUATION_PROMPT = "Continue from where you stopped. Preserve completed work and avoid repeating it.";
1337
- function isRecord$14(value) {
1339
+ function isRecord$15(value) {
1338
1340
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1339
1341
  }
1340
1342
  function hasSendableMessageContent(message) {
@@ -1342,7 +1344,7 @@ function hasSendableMessageContent(message) {
1342
1344
  }
1343
1345
  function readSessionActivityState(metadata) {
1344
1346
  const preview = metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY];
1345
- return isRecord$14(preview) && typeof preview.state === "string" ? preview.state : null;
1347
+ return isRecord$15(preview) && typeof preview.state === "string" ? preview.state : null;
1346
1348
  }
1347
1349
  var AgentRunSessionCommandManager = class {
1348
1350
  pendingCommands = /* @__PURE__ */ new Map();
@@ -2503,6 +2505,318 @@ var AutomationManager = class extends CronService {
2503
2505
  }
2504
2506
  };
2505
2507
  //#endregion
2508
+ //#region src/types/app-package.types.ts
2509
+ var AppPackageError = class extends Error {
2510
+ constructor(code, message) {
2511
+ super(message);
2512
+ this.code = code;
2513
+ this.name = "AppPackageError";
2514
+ }
2515
+ };
2516
+ function isAppPackageError(error) {
2517
+ return error instanceof AppPackageError;
2518
+ }
2519
+ //#endregion
2520
+ //#region src/utils/app-engine-version.utils.ts
2521
+ function satisfiesAppEngineVersion(version, range) {
2522
+ const parsedVersion = parseVersion(version);
2523
+ const normalizedRange = range.trim();
2524
+ if (!parsedVersion || !normalizedRange) return false;
2525
+ return normalizedRange.split(/\s*\|\|\s*/).some((alternative) => satisfiesAlternative(parsedVersion, alternative.trim()));
2526
+ }
2527
+ function satisfiesAlternative(version, range) {
2528
+ const hyphenRange = /^(\S+)\s+-\s+(\S+)$/.exec(range);
2529
+ if (hyphenRange) {
2530
+ const lower = parseVersion(hyphenRange[1] ?? "");
2531
+ const upper = parseVersion(hyphenRange[2] ?? "");
2532
+ return Boolean(lower && upper && compareVersions(version, lower) >= 0 && compareVersions(version, upper) <= 0);
2533
+ }
2534
+ const comparators = range.split(/\s+/).filter(Boolean);
2535
+ return comparators.length > 0 && comparators.every((comparator) => satisfiesComparator(version, comparator));
2536
+ }
2537
+ function satisfiesComparator(version, comparator) {
2538
+ if (comparator === "*" || comparator.toLowerCase() === "x") return true;
2539
+ if (comparator.startsWith("^")) {
2540
+ const lower = parseVersion(comparator.slice(1));
2541
+ if (!lower) return false;
2542
+ const upper = lower.major > 0 ? {
2543
+ major: lower.major + 1,
2544
+ minor: 0,
2545
+ patch: 0,
2546
+ prerelease: []
2547
+ } : lower.minor > 0 ? {
2548
+ major: 0,
2549
+ minor: lower.minor + 1,
2550
+ patch: 0,
2551
+ prerelease: []
2552
+ } : {
2553
+ major: 0,
2554
+ minor: 0,
2555
+ patch: lower.patch + 1,
2556
+ prerelease: []
2557
+ };
2558
+ return compareVersions(version, lower) >= 0 && compareVersions(version, upper) < 0;
2559
+ }
2560
+ if (comparator.startsWith("~")) {
2561
+ const lower = parseVersion(comparator.slice(1));
2562
+ if (!lower) return false;
2563
+ const upper = {
2564
+ major: lower.major,
2565
+ minor: lower.minor + 1,
2566
+ patch: 0,
2567
+ prerelease: []
2568
+ };
2569
+ return compareVersions(version, lower) >= 0 && compareVersions(version, upper) < 0;
2570
+ }
2571
+ if (/[xX*]/.test(comparator)) {
2572
+ const parts = comparator.replace(/^v/, "").split(".");
2573
+ const expected = [
2574
+ version.major,
2575
+ version.minor,
2576
+ version.patch
2577
+ ];
2578
+ return parts.every((part, index) => /^(x|\*)$/i.test(part) || Number(part) === expected[index]);
2579
+ }
2580
+ const match = /^(>=|<=|>|<|=)?\s*(.+)$/.exec(comparator);
2581
+ const target = parseVersion(match?.[2] ?? "");
2582
+ if (!target) return false;
2583
+ const compared = compareVersions(version, target);
2584
+ switch (match?.[1] ?? "=") {
2585
+ case ">=": return compared >= 0;
2586
+ case "<=": return compared <= 0;
2587
+ case ">": return compared > 0;
2588
+ case "<": return compared < 0;
2589
+ default: return compared === 0;
2590
+ }
2591
+ }
2592
+ function parseVersion(raw) {
2593
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(raw.trim());
2594
+ if (!match) return null;
2595
+ return {
2596
+ major: Number(match[1]),
2597
+ minor: Number(match[2]),
2598
+ patch: Number(match[3]),
2599
+ prerelease: match[4] ? match[4].split(".").map((part) => /^\d+$/.test(part) ? Number(part) : part) : []
2600
+ };
2601
+ }
2602
+ function compareVersions(left, right) {
2603
+ for (const field of [
2604
+ "major",
2605
+ "minor",
2606
+ "patch"
2607
+ ]) if (left[field] !== right[field]) return left[field] > right[field] ? 1 : -1;
2608
+ if (left.prerelease.length === 0 || right.prerelease.length === 0) return Number(left.prerelease.length === 0) - Number(right.prerelease.length === 0);
2609
+ const length = Math.max(left.prerelease.length, right.prerelease.length);
2610
+ for (let index = 0; index < length; index += 1) {
2611
+ const leftPart = left.prerelease[index];
2612
+ const rightPart = right.prerelease[index];
2613
+ if (leftPart === void 0 || rightPart === void 0) return leftPart === void 0 ? -1 : 1;
2614
+ if (leftPart === rightPart) continue;
2615
+ if (typeof leftPart === "number" && typeof rightPart === "number") return leftPart > rightPart ? 1 : -1;
2616
+ if (typeof leftPart === "number") return -1;
2617
+ if (typeof rightPart === "number") return 1;
2618
+ return leftPart.localeCompare(rightPart);
2619
+ }
2620
+ return 0;
2621
+ }
2622
+ //#endregion
2623
+ //#region src/managers/app-package.manager.ts
2624
+ const EMPTY_RUNTIME_HOOKS = {
2625
+ assertCanActivate: async () => void 0,
2626
+ beforeDeactivate: async () => void 0,
2627
+ beforeUninstall: async () => void 0
2628
+ };
2629
+ var AppPackageManager = class {
2630
+ installationService;
2631
+ manifestService = new AppManifestService();
2632
+ registryService;
2633
+ runtimeHooks = EMPTY_RUNTIME_HOOKS;
2634
+ builtInBootstrapPromise;
2635
+ constructor(params) {
2636
+ this.params = params;
2637
+ const appHomeService = new AppHomeService(params.appHomeDirectory);
2638
+ this.installationService = new AppInstallationService(appHomeService);
2639
+ this.registryService = new AppRegistryService(appHomeService);
2640
+ }
2641
+ installRuntimeHooks = (hooks) => {
2642
+ this.runtimeHooks = hooks;
2643
+ };
2644
+ listPackages = async () => {
2645
+ await this.ensureBuiltInPackages();
2646
+ const records = await this.registryService.listApps();
2647
+ return { entries: await Promise.all(records.map(async (record) => await this.toPackageView(await this.installationService.info(record.appId)))) };
2648
+ };
2649
+ getPackage = async (appId) => {
2650
+ await this.ensureBuiltInPackages();
2651
+ try {
2652
+ return await this.toPackageView(await this.installationService.info(appId));
2653
+ } catch (error) {
2654
+ if (error instanceof Error && error.message.includes("未找到已安装应用")) throw new AppPackageError("APP_PACKAGE_NOT_FOUND", error.message);
2655
+ throw error;
2656
+ }
2657
+ };
2658
+ listActiveComponentSources = async () => {
2659
+ await this.ensureBuiltInPackages();
2660
+ return (await this.registryService.listApps()).filter((record) => record.enabled).flatMap((record) => {
2661
+ const version = record.installedVersions[record.activeVersion];
2662
+ if (!version || version.manifestSchemaVersion !== 2) return [];
2663
+ return (version.components ?? []).map((component) => ({
2664
+ kind: component.kind,
2665
+ id: component.id,
2666
+ packageId: record.appId,
2667
+ packageVersion: record.activeVersion,
2668
+ sourcePath: component.componentDirectory,
2669
+ manifestPath: component.manifestPath,
2670
+ dataDirectory: record.dataDirectory
2671
+ }));
2672
+ });
2673
+ };
2674
+ install = async (source, registryUrl) => {
2675
+ const result = await this.installationService.install(source, { registryUrl });
2676
+ return await this.getPackage(result.appId);
2677
+ };
2678
+ enable = async (appId) => {
2679
+ const app = await this.getPackage(appId);
2680
+ if (app.enabled) return app;
2681
+ await this.assertEngineCompatibility(appId);
2682
+ const sources = this.toComponentSources(app);
2683
+ await this.runtimeHooks.assertCanActivate(sources);
2684
+ await this.installationService.setEnabled(appId, true);
2685
+ return await this.getPackage(appId);
2686
+ };
2687
+ disable = async (appId) => {
2688
+ const app = await this.getPackage(appId);
2689
+ if (!app.enabled) return app;
2690
+ await this.runtimeHooks.beforeDeactivate(this.toComponentSources(app));
2691
+ await this.installationService.setEnabled(appId, false);
2692
+ return await this.getPackage(appId);
2693
+ };
2694
+ update = async (appId, options = {}) => {
2695
+ const current = await this.getPackage(appId);
2696
+ if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2697
+ const result = await this.installationService.update(appId, options);
2698
+ try {
2699
+ await this.assertEngineCompatibility(appId);
2700
+ const updated = await this.getPackage(appId);
2701
+ if (updated.enabled) await this.runtimeHooks.assertCanActivate(this.toComponentSources(updated));
2702
+ return {
2703
+ package: updated,
2704
+ result
2705
+ };
2706
+ } catch (error) {
2707
+ if (result.updated) await this.installationService.rollback(appId, result.previousVersion);
2708
+ throw error;
2709
+ }
2710
+ };
2711
+ rollback = async (appId, version) => {
2712
+ const current = await this.getPackage(appId);
2713
+ if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2714
+ const result = await this.installationService.rollback(appId, version);
2715
+ try {
2716
+ await this.assertEngineCompatibility(appId);
2717
+ const rolledBack = await this.getPackage(appId);
2718
+ if (rolledBack.enabled) await this.runtimeHooks.assertCanActivate(this.toComponentSources(rolledBack));
2719
+ return {
2720
+ package: rolledBack,
2721
+ result
2722
+ };
2723
+ } catch (error) {
2724
+ if (result.rolledBack) await this.installationService.rollback(appId, result.previousVersion);
2725
+ throw error;
2726
+ }
2727
+ };
2728
+ uninstall = async (appId, purgeData) => {
2729
+ const current = await this.getPackage(appId);
2730
+ if (current.enabled) await this.runtimeHooks.beforeDeactivate(this.toComponentSources(current));
2731
+ await this.runtimeHooks.beforeUninstall(this.toComponentSources(current));
2732
+ return await this.installationService.uninstall(appId, purgeData);
2733
+ };
2734
+ ensureBuiltInPackages = async () => {
2735
+ if (!this.params.builtInAppsDirectory) return;
2736
+ this.builtInBootstrapPromise ??= this.installBuiltInPackages();
2737
+ await this.builtInBootstrapPromise;
2738
+ };
2739
+ installBuiltInPackages = async () => {
2740
+ const builtInDirectory = path.resolve(this.params.builtInAppsDirectory);
2741
+ let entries;
2742
+ try {
2743
+ entries = await readdir(builtInDirectory, { withFileTypes: true });
2744
+ } catch (error) {
2745
+ if (this.isMissingFileError(error)) return;
2746
+ throw error;
2747
+ }
2748
+ for (const entry of entries.filter((item) => item.isDirectory() && !item.name.startsWith("."))) {
2749
+ const appDirectory = path.join(builtInDirectory, entry.name);
2750
+ const manifest = await this.manifestService.load(appDirectory);
2751
+ if (!isAppComponentManifestBundle(manifest)) continue;
2752
+ if ((await this.registryService.getApp(manifest.manifest.id))?.installedVersions[manifest.manifest.version]) continue;
2753
+ await this.installationService.install(appDirectory);
2754
+ }
2755
+ };
2756
+ toPackageView = async (info) => {
2757
+ const activeVersion = info.installedVersions.find((version) => version.version === info.activeVersion);
2758
+ if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${info.appId} 缺少激活版本 ${info.activeVersion}。`);
2759
+ const packagePresentation = await this.readManifestPresentation(path.join(activeVersion.installDirectory, "manifest.json"));
2760
+ return {
2761
+ id: info.appId,
2762
+ name: info.name,
2763
+ description: info.description,
2764
+ nameI18n: packagePresentation.nameI18n,
2765
+ descriptionI18n: packagePresentation.descriptionI18n,
2766
+ activeVersion: info.activeVersion,
2767
+ installedVersions: info.installedVersions.map((version) => version.version),
2768
+ enabled: info.enabled,
2769
+ builtIn: this.isBuiltInSource(activeVersion.sourceRef),
2770
+ primaryPanelId: activeVersion.primaryPanelId,
2771
+ components: await Promise.all((activeVersion.components ?? []).map(async (component) => ({
2772
+ kind: component.kind,
2773
+ id: component.id,
2774
+ packageId: info.appId,
2775
+ packageVersion: info.activeVersion,
2776
+ sourcePath: component.componentDirectory,
2777
+ manifestPath: component.manifestPath,
2778
+ dataDirectory: info.dataDirectory,
2779
+ ...await this.readManifestPresentation(component.manifestPath)
2780
+ }))),
2781
+ dataDirectory: info.dataDirectory
2782
+ };
2783
+ };
2784
+ readManifestPresentation = async (manifestPath) => {
2785
+ const candidate = JSON.parse(await readFile(manifestPath, "utf8"));
2786
+ return {
2787
+ ...typeof candidate.title === "string" ? { title: candidate.title } : {},
2788
+ ...typeof candidate.description === "string" ? { description: candidate.description } : {},
2789
+ ...typeof candidate.icon === "string" ? { icon: candidate.icon } : {},
2790
+ ...this.readLocalizedField(candidate, "nameI18n"),
2791
+ ...this.readLocalizedField(candidate, "titleI18n"),
2792
+ ...this.readLocalizedField(candidate, "descriptionI18n")
2793
+ };
2794
+ };
2795
+ readLocalizedField = (candidate, field) => {
2796
+ const value = candidate[field];
2797
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2798
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
2799
+ return entries.length > 0 ? { [field]: Object.fromEntries(entries) } : {};
2800
+ };
2801
+ toComponentSources = (app) => app.components.map((component) => ({ ...component }));
2802
+ assertEngineCompatibility = async (appId) => {
2803
+ const productVersion = this.params.productVersion?.trim();
2804
+ if (!productVersion) return;
2805
+ const info = await this.installationService.info(appId);
2806
+ const activeVersion = info.installedVersions.find((version) => version.version === info.activeVersion);
2807
+ if (!activeVersion) throw new AppPackageError("APP_PACKAGE_OPERATION_FAILED", `应用 ${appId} 缺少激活版本 ${info.activeVersion}。`);
2808
+ const manifestBundle = await this.manifestService.load(activeVersion.installDirectory);
2809
+ const engineRange = manifestBundle.manifest.schemaVersion === 2 ? manifestBundle.manifest.engines?.nextclaw?.trim() : void 0;
2810
+ if (engineRange && !satisfiesAppEngineVersion(productVersion, engineRange)) throw new AppPackageError("APP_PACKAGE_INCOMPATIBLE", `应用 ${appId}@${info.activeVersion} 要求 NextClaw ${engineRange},当前版本为 ${productVersion}。`);
2811
+ };
2812
+ isBuiltInSource = (sourceRef) => {
2813
+ if (!this.params.builtInAppsDirectory) return false;
2814
+ const relative = path.relative(path.resolve(this.params.builtInAppsDirectory), path.resolve(sourceRef));
2815
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
2816
+ };
2817
+ isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2818
+ };
2819
+ //#endregion
2506
2820
  //#region src/managers/channel.manager.ts
2507
2821
  var ChannelManager = class {
2508
2822
  channels = {};
@@ -2901,9 +3215,9 @@ var ConfigManager = class {
2901
3215
  required: true,
2902
3216
  automatic: false,
2903
3217
  changedPaths: [...plan.restartRequired],
2904
- message: `Config saved. Restart manually to apply: ${plan.restartRequired.join(", ")}.`
3218
+ message: `Config saved. Run nextclaw restart in an external terminal to apply: ${plan.restartRequired.join(", ")}.`
2905
3219
  } : null;
2906
- const message = changedPaths.length === 0 ? "Config already matched the requested state." : pendingRestart ? changedPaths.length > plan.restartRequired.length ? "Config saved. Supported changes were applied immediately; restart manually to apply the rest." : "Config saved. Restart manually to apply changes." : "Config saved and applied.";
3220
+ const message = changedPaths.length === 0 ? "Config already matched the requested state." : pendingRestart ? changedPaths.length > plan.restartRequired.length ? "Config saved. Supported changes were applied immediately; run nextclaw restart in an external terminal to apply the rest." : "Config saved. Run nextclaw restart in an external terminal to apply changes." : "Config saved and applied.";
2907
3221
  return {
2908
3222
  ok: true,
2909
3223
  note: note ?? null,
@@ -4550,11 +4864,11 @@ var ExtensionManager = class {
4550
4864
  //#endregion
4551
4865
  //#region src/utils/model-message-vision.utils.ts
4552
4866
  const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
4553
- function isRecord$13(value) {
4867
+ function isRecord$14(value) {
4554
4868
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4555
4869
  }
4556
4870
  function isImageContentPart(value) {
4557
- if (!isRecord$13(value)) return false;
4871
+ if (!isRecord$14(value)) return false;
4558
4872
  const type = value.type;
4559
4873
  return type === "image_url" || type === "input_image";
4560
4874
  }
@@ -4570,7 +4884,7 @@ function normalizeContentWithoutVision(content) {
4570
4884
  };
4571
4885
  });
4572
4886
  if (!sawImage) return content;
4573
- const textParts = parts.filter((part) => isRecord$13(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
4887
+ const textParts = parts.filter((part) => isRecord$14(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
4574
4888
  if (textParts.length === parts.length) return textParts.join("\n\n");
4575
4889
  return parts;
4576
4890
  }
@@ -5205,7 +5519,8 @@ var LlmUsageManager = class {
5205
5519
  };
5206
5520
  //#endregion
5207
5521
  //#region src/stores/inbox-delivery.store.ts
5208
- const INBOX_DELIVERY_STORE_VERSION = 1;
5522
+ const INBOX_DELIVERY_STORE_VERSION = 2;
5523
+ const LEGACY_INBOX_DELIVERY_STORE_VERSION = 1;
5209
5524
  var InboxDeliveryStoreError = class extends Error {
5210
5525
  constructor(message) {
5211
5526
  super(message);
@@ -5242,15 +5557,19 @@ var InboxDeliveryStore = class {
5242
5557
  };
5243
5558
  parseStoreFile = (source) => {
5244
5559
  const value = JSON.parse(source);
5245
- if (!this.isRecord(value) || value.version !== INBOX_DELIVERY_STORE_VERSION || !Array.isArray(value.deliveries) || !value.deliveries.every(this.isDelivery)) throw new InboxDeliveryStoreError("inbox delivery store has an unsupported structure");
5560
+ if (!this.isRecord(value) || value.version !== INBOX_DELIVERY_STORE_VERSION && value.version !== LEGACY_INBOX_DELIVERY_STORE_VERSION || !Array.isArray(value.deliveries) || !value.deliveries.every((delivery) => this.isDelivery(delivery, value.version === LEGACY_INBOX_DELIVERY_STORE_VERSION))) throw new InboxDeliveryStoreError("inbox delivery store has an unsupported structure");
5246
5561
  return {
5247
5562
  version: INBOX_DELIVERY_STORE_VERSION,
5248
- deliveries: value.deliveries.map((delivery) => structuredClone(delivery))
5563
+ deliveries: value.deliveries.map((delivery) => this.toCurrentDelivery(delivery))
5249
5564
  };
5250
5565
  };
5251
- isDelivery = (value) => {
5566
+ isDelivery = (value, legacy) => {
5252
5567
  if (!this.isRecord(value) || !this.isSource(value.source)) return false;
5253
- return typeof value.id === "string" && typeof value.title === "string" && (value.summary === null || typeof value.summary === "string") && typeof value.content === "string" && (value.contentType === "markdown" || value.contentType === "html") && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && this.isOptionalTimestamp(value.presentedAt) && this.isOptionalTimestamp(value.readAt) && this.isOptionalTimestamp(value.archivedAt) && (value.conversationSessionId === null || typeof value.conversationSessionId === "string");
5568
+ return typeof value.id === "string" && typeof value.title === "string" && (value.summary === null || typeof value.summary === "string") && typeof value.content === "string" && (value.contentType === "markdown" || value.contentType === "html") && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && this.isOptionalTimestamp(value.presentedAt) && this.isOptionalTimestamp(value.readAt) && this.isOptionalTimestamp(value.archivedAt) && (!legacy || value.conversationSessionId === null || typeof value.conversationSessionId === "string");
5569
+ };
5570
+ toCurrentDelivery = (delivery) => {
5571
+ const { conversationSessionId: _legacySessionId, ...current } = delivery;
5572
+ return structuredClone(current);
5254
5573
  };
5255
5574
  isSource = (value) => this.isRecord(value) && value.kind === "agent" && this.isOptionalString(value.agentId) && this.isOptionalString(value.sessionId) && this.isOptionalString(value.toolCallId) && this.isOptionalString(value.filePath);
5256
5575
  isOptionalString = (value) => value === null || typeof value === "string";
@@ -5308,8 +5627,7 @@ var InboxDeliveryManager = class {
5308
5627
  updatedAt: now,
5309
5628
  presentedAt: null,
5310
5629
  readAt: null,
5311
- archivedAt: null,
5312
- conversationSessionId: null
5630
+ archivedAt: null
5313
5631
  };
5314
5632
  const deliveries = await this.store.list();
5315
5633
  await this.store.save([delivery, ...deliveries]);
@@ -5336,41 +5654,6 @@ var InboxDeliveryManager = class {
5336
5654
  this.publishChange(deliveryId, "delete");
5337
5655
  return true;
5338
5656
  });
5339
- continueInChat = async (deliveryId) => await this.mutate(async () => {
5340
- const deliveries = await this.store.list();
5341
- const index = deliveries.findIndex(({ id }) => id === deliveryId);
5342
- if (index < 0) throw this.notFound(deliveryId);
5343
- const current = deliveries[index];
5344
- const existingSession = current.conversationSessionId ? await this.options.sessionManager.getSessionRecord(current.conversationSessionId) : null;
5345
- let sessionId = current.conversationSessionId;
5346
- let created = false;
5347
- if (!sessionId || !existingSession) {
5348
- sessionId = (await this.options.sessionManager.createSession({
5349
- sourceSessionMetadata: {},
5350
- metadataOverrides: { [INBOX_DELIVERY_SESSION_METADATA_KEY]: current.id },
5351
- task: `Continue discussing inbox delivery: ${current.title}`,
5352
- title: current.title,
5353
- agentId: current.source.agentId ?? void 0
5354
- })).sessionId;
5355
- created = true;
5356
- }
5357
- const now = (/* @__PURE__ */ new Date()).toISOString();
5358
- const delivery = {
5359
- ...current,
5360
- updatedAt: now,
5361
- presentedAt: current.presentedAt ?? now,
5362
- readAt: current.readAt ?? now,
5363
- conversationSessionId: sessionId
5364
- };
5365
- deliveries[index] = delivery;
5366
- await this.store.save(deliveries);
5367
- this.publishChange(deliveryId, "upsert");
5368
- return {
5369
- delivery: structuredClone(delivery),
5370
- sessionId,
5371
- created
5372
- };
5373
- });
5374
5657
  applyStateAction = (delivery, action, now) => {
5375
5658
  switch (action) {
5376
5659
  case "present": return {
@@ -5426,6 +5709,300 @@ var InboxDeliveryManager = class {
5426
5709
  };
5427
5710
  };
5428
5711
  //#endregion
5712
+ //#region src/managers/system-object-reference.manager.ts
5713
+ const MAX_SYSTEM_OBJECT_SNAPSHOT_BYTES = 1024 * 1024;
5714
+ var SystemObjectReferenceError = class extends Error {
5715
+ constructor(code, message) {
5716
+ super(message);
5717
+ this.code = code;
5718
+ this.name = "SystemObjectReferenceError";
5719
+ }
5720
+ };
5721
+ function isSystemObjectReferenceError(error) {
5722
+ return error instanceof SystemObjectReferenceError;
5723
+ }
5724
+ function normalizedSearchText(value) {
5725
+ return value.trim().toLocaleLowerCase();
5726
+ }
5727
+ function scoreSearchItem(item, query) {
5728
+ if (!query) return 1;
5729
+ const fields = [
5730
+ item.label,
5731
+ item.objectId,
5732
+ item.description ?? ""
5733
+ ].map(normalizedSearchText).filter(Boolean);
5734
+ if (fields.some((field) => field === query)) return 400;
5735
+ if (fields.some((field) => field.startsWith(query))) return 300;
5736
+ if (fields.some((field) => field.split(/\s+/).some((word) => word.startsWith(query)))) return 200;
5737
+ return fields.some((field) => field.includes(query)) ? 100 : 0;
5738
+ }
5739
+ function normalizeLimit(limit) {
5740
+ if (limit === void 0) return SYSTEM_OBJECT_REFERENCE_DEFAULT_LIMIT;
5741
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > SYSTEM_OBJECT_REFERENCE_MAX_LIMIT) throw new SystemObjectReferenceError("SYSTEM_OBJECT_INVALID_REFERENCE", `limit must be an integer between 1 and ${SYSTEM_OBJECT_REFERENCE_MAX_LIMIT}`);
5742
+ return limit;
5743
+ }
5744
+ const SYSTEM_OBJECT_GROUP_ICONS = new Set([
5745
+ "calendar-clock",
5746
+ "file",
5747
+ "inbox"
5748
+ ]);
5749
+ function normalizeDisplayText(value, field) {
5750
+ const defaultText = value.default.trim();
5751
+ if (!defaultText) throw new Error(`System object provider group ${field} must be non-empty.`);
5752
+ const translations = Object.fromEntries(Object.entries(value.translations ?? {}).map(([language, text]) => [language.trim(), text.trim()]).filter(([language, text]) => language && text));
5753
+ return {
5754
+ default: defaultText,
5755
+ ...Object.keys(translations).length > 0 ? { translations } : {}
5756
+ };
5757
+ }
5758
+ function normalizeProviderGroup(group) {
5759
+ const objectType = group.objectType.trim();
5760
+ if (!objectType || !SYSTEM_OBJECT_GROUP_ICONS.has(group.icon) || !Number.isSafeInteger(group.order)) throw new Error(`System object provider group is invalid: ${objectType}`);
5761
+ return {
5762
+ objectType,
5763
+ label: normalizeDisplayText(group.label, "label"),
5764
+ description: normalizeDisplayText(group.description, "description"),
5765
+ icon: group.icon,
5766
+ order: group.order
5767
+ };
5768
+ }
5769
+ function safeSnapshotFileName(label, suffix) {
5770
+ return `${label.trim().replace(/[^\p{L}\p{N}._-]+/gu, "-").replace(/^-+|-+$/g, "") || "system-object"}.${suffix}`;
5771
+ }
5772
+ function truncate(value, maxLength = 180) {
5773
+ const normalized = value?.replace(/\s+/g, " ").trim() ?? "";
5774
+ if (!normalized) return null;
5775
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1)}…`;
5776
+ }
5777
+ function toIsoTime(value) {
5778
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
5779
+ const date = new Date(value);
5780
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
5781
+ }
5782
+ var SystemObjectReferenceManager = class {
5783
+ providers = /* @__PURE__ */ new Map();
5784
+ resolvedCache = /* @__PURE__ */ new Map();
5785
+ constructor(assetStore, providers = []) {
5786
+ this.assetStore = assetStore;
5787
+ providers.forEach((provider) => this.registerProvider(provider));
5788
+ }
5789
+ registerProvider = (provider) => {
5790
+ const group = normalizeProviderGroup(provider.group);
5791
+ const objectType = group.objectType;
5792
+ if (this.providers.has(objectType)) throw new Error(`System object provider is invalid or already registered: ${objectType}`);
5793
+ const registeredProvider = {
5794
+ ...provider,
5795
+ group
5796
+ };
5797
+ this.providers.set(objectType, registeredProvider);
5798
+ return () => {
5799
+ if (this.providers.get(objectType) === registeredProvider) this.providers.delete(objectType);
5800
+ };
5801
+ };
5802
+ listReferences = async (params = {}) => {
5803
+ const query = normalizedSearchText(params.query ?? "");
5804
+ const limit = normalizeLimit(params.limit);
5805
+ const requestedObjectType = params.objectType?.trim();
5806
+ if (params.objectType !== void 0 && !requestedObjectType) throw new SystemObjectReferenceError("SYSTEM_OBJECT_INVALID_REFERENCE", "objectType must be a non-empty registered system object type");
5807
+ const requestedProvider = requestedObjectType ? this.providers.get(requestedObjectType) : void 0;
5808
+ if (requestedObjectType && !requestedProvider) throw new SystemObjectReferenceError("SYSTEM_OBJECT_INVALID_REFERENCE", `system object provider is not registered: ${requestedObjectType}`);
5809
+ const providers = requestedProvider ? [requestedProvider] : [...this.providers.values()];
5810
+ const collator = new Intl.Collator(void 0, {
5811
+ numeric: true,
5812
+ sensitivity: "base"
5813
+ });
5814
+ const shouldIncludeItems = Boolean(requestedObjectType || query);
5815
+ const visibleGroups = (await Promise.all(providers.map(async (provider) => {
5816
+ const matches = (await provider.list()).map((item) => {
5817
+ this.assertListItemContract(item, provider.group.objectType);
5818
+ return {
5819
+ item,
5820
+ score: scoreSearchItem(item, query)
5821
+ };
5822
+ }).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || right.item.updatedAt.localeCompare(left.item.updatedAt) || collator.compare(left.item.label, right.item.label));
5823
+ return {
5824
+ ...structuredClone(provider.group),
5825
+ items: shouldIncludeItems ? matches.slice(0, limit).map(({ item }) => structuredClone(item)) : [],
5826
+ total: matches.length
5827
+ };
5828
+ }))).filter((group) => !query || group.total > 0).sort((left, right) => left.order - right.order || collator.compare(left.label.default, right.label.default));
5829
+ return {
5830
+ groups: visibleGroups,
5831
+ total: visibleGroups.reduce((total, group) => total + group.total, 0)
5832
+ };
5833
+ };
5834
+ resolveReference = async (uri) => {
5835
+ const parsed = parseSystemObjectReferenceUri(uri);
5836
+ if (!parsed) throw new SystemObjectReferenceError("SYSTEM_OBJECT_INVALID_REFERENCE", `invalid system object reference: ${uri}`);
5837
+ const provider = this.providers.get(parsed.objectType);
5838
+ if (!provider) throw new SystemObjectReferenceError("SYSTEM_OBJECT_INVALID_REFERENCE", `system object provider is not registered: ${parsed.objectType}`);
5839
+ const source = await provider.resolve(parsed.objectId);
5840
+ if (!source) throw new SystemObjectReferenceError("SYSTEM_OBJECT_NOT_FOUND", `system object not found: ${uri}`);
5841
+ this.assertSnapshotContract(source, parsed);
5842
+ const bytes = new TextEncoder().encode(source.content);
5843
+ if (bytes.byteLength > MAX_SYSTEM_OBJECT_SNAPSHOT_BYTES) throw new SystemObjectReferenceError("SYSTEM_OBJECT_PROVIDER_CONTRACT", `system object snapshot exceeds ${MAX_SYSTEM_OBJECT_SNAPSHOT_BYTES} bytes: ${uri}`);
5844
+ const version = createHash("sha256").update(bytes).digest("hex");
5845
+ const cacheKey = `${uri}@${version}`;
5846
+ const cached = this.resolvedCache.get(cacheKey);
5847
+ if (cached && await this.assetStore.statRecord(cached.assetUri)) return structuredClone(cached);
5848
+ const asset = await this.assetStore.putBytes({
5849
+ bytes,
5850
+ fileName: source.fileName,
5851
+ mimeType: source.mimeType
5852
+ });
5853
+ const resolved = {
5854
+ ...structuredClone(source.item),
5855
+ version,
5856
+ assetUri: asset.uri,
5857
+ fileName: asset.fileName,
5858
+ mimeType: asset.mimeType,
5859
+ sizeBytes: asset.sizeBytes
5860
+ };
5861
+ this.resolvedCache.set(cacheKey, resolved);
5862
+ return structuredClone(resolved);
5863
+ };
5864
+ assertSnapshotContract = (source, parsed) => {
5865
+ const expectedUri = createSystemObjectReferenceUri(parsed.objectType, parsed.objectId);
5866
+ if (source.item.uri !== expectedUri || source.item.objectType !== parsed.objectType || source.item.objectId !== parsed.objectId || !source.item.label.trim() || !source.fileName.trim() || !source.content.trim() || !isTextLikeAsset({
5867
+ mimeType: source.mimeType,
5868
+ fileName: source.fileName
5869
+ })) throw new SystemObjectReferenceError("SYSTEM_OBJECT_PROVIDER_CONTRACT", `system object provider returned an invalid snapshot: ${expectedUri}`);
5870
+ };
5871
+ assertListItemContract = (item, objectType) => {
5872
+ const expectedUri = createSystemObjectReferenceUri(objectType, item.objectId);
5873
+ if (item.objectType !== objectType || item.uri !== expectedUri || !item.label.trim() || !item.updatedAt.trim()) throw new SystemObjectReferenceError("SYSTEM_OBJECT_PROVIDER_CONTRACT", `system object provider returned an invalid list item: ${item.uri}`);
5874
+ };
5875
+ };
5876
+ function createInboxDeliverySystemObjectProvider(manager) {
5877
+ const toItem = (delivery) => ({
5878
+ uri: createSystemObjectReferenceUri(SYSTEM_OBJECT_TYPE_INBOX_DELIVERY, delivery.id),
5879
+ objectType: SYSTEM_OBJECT_TYPE_INBOX_DELIVERY,
5880
+ objectId: delivery.id,
5881
+ label: delivery.title,
5882
+ description: truncate(delivery.summary),
5883
+ updatedAt: delivery.updatedAt
5884
+ });
5885
+ return {
5886
+ group: {
5887
+ objectType: SYSTEM_OBJECT_TYPE_INBOX_DELIVERY,
5888
+ label: {
5889
+ default: "Inbox Reports",
5890
+ translations: {
5891
+ en: "Inbox Reports",
5892
+ zh: "收件箱报告"
5893
+ }
5894
+ },
5895
+ description: {
5896
+ default: "Reference reports and delivered content saved by NextClaw.",
5897
+ translations: {
5898
+ en: "Reference reports and delivered content saved by NextClaw.",
5899
+ zh: "引用由 NextClaw 保存的报告和送达内容。"
5900
+ }
5901
+ },
5902
+ icon: "inbox",
5903
+ order: 100
5904
+ },
5905
+ list: async () => (await manager.listDeliveries()).deliveries.map((delivery) => toItem(delivery)),
5906
+ resolve: async (objectId) => {
5907
+ const delivery = await manager.getDelivery(objectId);
5908
+ if (!delivery) return null;
5909
+ const sourceLines = [
5910
+ `# ${delivery.title}`,
5911
+ "",
5912
+ `- Object: ${createSystemObjectReferenceUri(SYSTEM_OBJECT_TYPE_INBOX_DELIVERY, delivery.id)}`,
5913
+ `- Created: ${delivery.createdAt}`,
5914
+ `- Source agent: ${delivery.source.agentId ?? "unknown"}`
5915
+ ];
5916
+ if (delivery.summary) sourceLines.push("", "## Summary", "", delivery.summary);
5917
+ sourceLines.push("", "## Content", "", delivery.content);
5918
+ return {
5919
+ item: toItem(delivery),
5920
+ content: sourceLines.join("\n"),
5921
+ fileName: safeSnapshotFileName(delivery.title, "md"),
5922
+ mimeType: "text/markdown"
5923
+ };
5924
+ }
5925
+ };
5926
+ }
5927
+ function createCronJobSystemObjectProvider(automation) {
5928
+ const toItem = (job) => ({
5929
+ uri: createSystemObjectReferenceUri(SYSTEM_OBJECT_TYPE_CRON_JOB, job.id),
5930
+ objectType: SYSTEM_OBJECT_TYPE_CRON_JOB,
5931
+ objectId: job.id,
5932
+ label: job.name,
5933
+ description: truncate(job.payload.message),
5934
+ updatedAt: new Date(job.updatedAtMs).toISOString()
5935
+ });
5936
+ return {
5937
+ group: {
5938
+ objectType: SYSTEM_OBJECT_TYPE_CRON_JOB,
5939
+ label: {
5940
+ default: "Scheduled Tasks",
5941
+ translations: {
5942
+ en: "Scheduled Tasks",
5943
+ zh: "定时任务"
5944
+ }
5945
+ },
5946
+ description: {
5947
+ default: "Reference task schedules, instructions, and recent run state.",
5948
+ translations: {
5949
+ en: "Reference task schedules, instructions, and recent run state.",
5950
+ zh: "引用任务计划、指令和最近运行状态。"
5951
+ }
5952
+ },
5953
+ icon: "calendar-clock",
5954
+ order: 200
5955
+ },
5956
+ list: () => automation.listJobs(true).map(toItem),
5957
+ resolve: (objectId) => {
5958
+ const job = automation.listJobs(true).find(({ id }) => id === objectId);
5959
+ if (!job) return null;
5960
+ const content = [
5961
+ `# ${job.name}`,
5962
+ "",
5963
+ `- Object: ${createSystemObjectReferenceUri(SYSTEM_OBJECT_TYPE_CRON_JOB, job.id)}`,
5964
+ `- Enabled: ${job.enabled ? "yes" : "no"}`,
5965
+ `- Delete after run: ${job.deleteAfterRun ? "yes" : "no"}`,
5966
+ `- Created: ${new Date(job.createdAtMs).toISOString()}`,
5967
+ `- Updated: ${new Date(job.updatedAtMs).toISOString()}`,
5968
+ `- Next run: ${toIsoTime(job.state.nextRunAtMs) ?? "none"}`,
5969
+ `- Last run: ${toIsoTime(job.state.lastRunAtMs) ?? "none"}`,
5970
+ `- Last status: ${job.state.lastStatus ?? "none"}`,
5971
+ "",
5972
+ "## Schedule",
5973
+ "",
5974
+ "```json",
5975
+ JSON.stringify(job.schedule, null, 2),
5976
+ "```",
5977
+ "",
5978
+ "## Payload",
5979
+ "",
5980
+ job.payload.message,
5981
+ "",
5982
+ "```json",
5983
+ JSON.stringify({
5984
+ kind: job.payload.kind ?? "agent_turn",
5985
+ agentId: job.payload.agentId ?? null,
5986
+ sessionId: job.payload.sessionId ?? null
5987
+ }, null, 2),
5988
+ "```",
5989
+ ...job.state.lastError ? [
5990
+ "",
5991
+ "## Last error",
5992
+ "",
5993
+ job.state.lastError
5994
+ ] : []
5995
+ ].join("\n");
5996
+ return {
5997
+ item: toItem(job),
5998
+ content,
5999
+ fileName: safeSnapshotFileName(job.name, "md"),
6000
+ mimeType: "text/markdown"
6001
+ };
6002
+ }
6003
+ };
6004
+ }
6005
+ //#endregion
5429
6006
  //#region src/managers/mcp.manager.ts
5430
6007
  var McpManager = class {
5431
6008
  currentMcpConfig;
@@ -5520,7 +6097,7 @@ function safeNcpSessionFilename(value) {
5520
6097
  function normalizeNcpAgentId(agentId) {
5521
6098
  return agentId?.trim().toLowerCase() || void 0;
5522
6099
  }
5523
- function isRecord$12(value) {
6100
+ function isRecord$13(value) {
5524
6101
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5525
6102
  }
5526
6103
  function toIsoString(value, fallback) {
@@ -5650,7 +6227,7 @@ function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
5650
6227
  } : message;
5651
6228
  }
5652
6229
  function readLegacyContextCompactionMessageId(message) {
5653
- const checkpoint = isRecord$12(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
6230
+ const checkpoint = isRecord$13(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
5654
6231
  const checkpointId = typeof checkpoint?.id === "string" ? checkpoint.id : "";
5655
6232
  const coveredCount = checkpoint?.coveredSessionMessageCount;
5656
6233
  const legacyId = `${message?.sessionId}:service:context-compaction:${checkpointId}`;
@@ -5682,11 +6259,11 @@ function readReplayMessageId(event) {
5682
6259
  return readMessageFromSummaryEvent(event)?.id ?? null;
5683
6260
  }
5684
6261
  function readEventSessionId$2(event) {
5685
- const sessionId = ("payload" in event && isRecord$12(event.payload) ? event.payload : null)?.sessionId;
6262
+ const sessionId = ("payload" in event && isRecord$13(event.payload) ? event.payload : null)?.sessionId;
5686
6263
  return typeof sessionId === "string" ? sessionId : "";
5687
6264
  }
5688
6265
  function readReplayPayloadTimestamp(event) {
5689
- const payload = "payload" in event && isRecord$12(event.payload) ? event.payload : null;
6266
+ const payload = "payload" in event && isRecord$13(event.payload) ? event.payload : null;
5690
6267
  const timestamp = typeof payload?.timestamp === "string" ? payload.timestamp : "";
5691
6268
  return Number.isFinite(Date.parse(timestamp)) ? new Date(timestamp).toISOString() : null;
5692
6269
  }
@@ -6497,76 +7074,496 @@ function isPanelAppAgentCapability(value) {
6497
7074
  return PANEL_APP_AGENT_CAPABILITIES.includes(value);
6498
7075
  }
6499
7076
  //#endregion
6500
- //#region src/services/panel-app-asset-token.service.ts
6501
- const PANEL_APP_ASSET_TOKEN_TTL_MS = 7200 * 1e3;
6502
- var PanelAppAssetTokenService = class {
6503
- now;
6504
- secret;
6505
- ttlMs;
6506
- constructor(params = {}) {
6507
- this.now = params.now ?? Date.now;
6508
- this.secret = params.secret ?? randomBytes(32);
6509
- this.ttlMs = params.ttlMs ?? PANEL_APP_ASSET_TOKEN_TTL_MS;
6510
- }
6511
- issue = (params) => {
6512
- const claims = {
6513
- panelAppId: params.panelAppId,
6514
- sourceName: params.sourceName,
6515
- sourcePath: params.sourcePath,
6516
- expiresAt: this.now() + this.ttlMs,
6517
- nonce: randomBytes(12).toString("base64url")
6518
- };
6519
- const payload = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
6520
- return `${payload}.${this.sign(payload)}`;
7077
+ //#region src/utils/panel-app-manifest.utils.ts
7078
+ const PANEL_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7079
+ function parsePanelAppManifest(html) {
7080
+ return {
7081
+ ...readHtmlTitle(html),
7082
+ ...readStandardIcon(html),
7083
+ ...readPanelAppMeta(html),
7084
+ capabilities: readPanelAppCapabilities(html),
7085
+ client: false,
7086
+ serviceActions: readPanelAppServiceActions(html)
6521
7087
  };
6522
- verify = (token) => {
6523
- const [payload, signature, ...rest] = token.trim().split(".");
6524
- if (!payload || !signature || rest.length > 0 || !this.matchesSignature(payload, signature)) throw new PanelAppError("PANEL_APP_ASSET_TOKEN_INVALID", "invalid panel app asset token");
6525
- const claims = this.parseClaims(payload);
6526
- if (claims.expiresAt <= this.now()) throw new PanelAppError("PANEL_APP_ASSET_TOKEN_EXPIRED", "panel app asset token expired");
6527
- return claims;
7088
+ }
7089
+ function parsePanelAppFolderManifest(raw) {
7090
+ let parsed;
7091
+ try {
7092
+ parsed = JSON.parse(raw);
7093
+ } catch (error) {
7094
+ throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
7095
+ }
7096
+ if (!isRecord$12(parsed)) throw new Error("panel-app.json must contain an object.");
7097
+ const id = readOptionalString$8(parsed, "id");
7098
+ if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
7099
+ return {
7100
+ id,
7101
+ title: readRequiredString$7(parsed, "title"),
7102
+ description: readOptionalString$8(parsed, "description"),
7103
+ icon: readOptionalString$8(parsed, "icon"),
7104
+ entry: readRequiredString$7(parsed, "entry"),
7105
+ capabilities: readStringArray$1(parsed.capabilities, "capabilities"),
7106
+ client: readOptionalBoolean$2(parsed, "client"),
7107
+ serviceActions: readStringArray$1(parsed.actions, "actions")
6528
7108
  };
6529
- sign = (payload) => createHmac("sha256", this.secret).update(payload).digest("base64url");
6530
- matchesSignature = (payload, signature) => {
6531
- const expected = Buffer.from(this.sign(payload), "utf8");
6532
- const actual = Buffer.from(signature, "utf8");
6533
- return expected.length === actual.length && timingSafeEqual(expected, actual);
7109
+ }
7110
+ function readPanelAppMeta(html) {
7111
+ return {
7112
+ ...readPanelAppMetaField(html, "title"),
7113
+ ...readPanelAppMetaField(html, "description"),
7114
+ ...readPanelAppMetaField(html, "icon")
6534
7115
  };
6535
- parseClaims = (payload) => {
6536
- try {
6537
- const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
6538
- if (!isPanelAppAssetTokenClaims(claims)) throw new Error("invalid panel app asset token claims");
6539
- return claims;
6540
- } catch {
6541
- throw new PanelAppError("PANEL_APP_ASSET_TOKEN_INVALID", "invalid panel app asset token");
7116
+ }
7117
+ function readPanelAppMetaField(html, field) {
7118
+ const content = readMetaContent(html, `nextclaw-panel-${field}`, field === "icon" ? "attribute" : "text");
7119
+ const manifest = {};
7120
+ if (content) manifest[field] = content;
7121
+ return manifest;
7122
+ }
7123
+ function readHtmlTitle(html) {
7124
+ const title = normalizeTextValue(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]);
7125
+ return title ? { title } : {};
7126
+ }
7127
+ function readStandardIcon(html) {
7128
+ const icon = readLinkHref(html, (relTokens) => relTokens.includes("icon"));
7129
+ const appleTouchIcon = readLinkHref(html, (relTokens) => relTokens.some((token) => token === "apple-touch-icon" || token === "apple-touch-icon-precomposed"));
7130
+ const href = normalizeIconHref(icon ?? appleTouchIcon);
7131
+ return href ? { icon: href } : {};
7132
+ }
7133
+ function readMetaContent(html, name, valueKind) {
7134
+ const metaTags = html.matchAll(/<meta\s+([^>]*?)>/gi);
7135
+ for (const tag of metaTags) {
7136
+ const attributes = tag[1] ?? "";
7137
+ if (readHtmlAttribute(attributes, "name") === name) {
7138
+ const content = readHtmlAttribute(attributes, "content");
7139
+ return valueKind === "attribute" ? normalizeAttributeValue(content) : normalizeTextValue(content);
6542
7140
  }
6543
- };
6544
- };
6545
- function isPanelAppAssetTokenClaims(value) {
6546
- return typeof value === "object" && value !== null && "panelAppId" in value && "sourceName" in value && "sourcePath" in value && "expiresAt" in value && "nonce" in value && typeof value.panelAppId === "string" && typeof value.sourceName === "string" && typeof value.sourcePath === "string" && typeof value.expiresAt === "number" && typeof value.nonce === "string";
7141
+ }
6547
7142
  }
6548
- //#endregion
6549
- //#region src/stores/panel-app-state.store.ts
6550
- const PANEL_APP_STATE_FILE = ".panel-apps.state.json";
6551
- const PANEL_APP_STATE_VERSION = 1;
6552
- var PanelAppStateStore = class {
6553
- constructor(panelsPath) {
6554
- this.panelsPath = panelsPath;
7143
+ function readLinkHref(html, matchesRel) {
7144
+ const linkTags = html.matchAll(/<link\s+([^>]*?)>/gi);
7145
+ for (const tag of linkTags) {
7146
+ const attributes = tag[1] ?? "";
7147
+ if (matchesRel((readHtmlAttribute(attributes, "rel") ?? "").toLowerCase().split(/\s+/).filter(Boolean))) return normalizeAttributeValue(readHtmlAttribute(attributes, "href"));
6555
7148
  }
6556
- load = async () => {
6557
- try {
6558
- const parsed = JSON.parse(await readFile(this.getStatePath(), "utf8"));
6559
- return this.normalizeStateFile(parsed).apps;
6560
- } catch (error) {
6561
- if (this.isMissingFileError(error) || error instanceof SyntaxError) return {};
6562
- throw error;
6563
- }
6564
- };
6565
- updatePreferences = async (id, preferences) => {
6566
- const apps = await this.load();
6567
- const next = { ...apps[id] ?? {} };
6568
- if (typeof preferences.favorite === "boolean") next.favorite = preferences.favorite;
6569
- apps[id] = next;
7149
+ }
7150
+ function readHtmlAttribute(attributes, attribute) {
7151
+ const match = attributes.match(new RegExp(`(?:^|\\s)${attribute}\\s*=\\s*(["'])(.*?)\\1`, "i"));
7152
+ return match?.[2] ? decodeHtmlAttribute(match[2]) : void 0;
7153
+ }
7154
+ function readPanelAppServiceActions(html) {
7155
+ return parseTokenList(readMetaContent(html, "nextclaw-panel-actions", "attribute"));
7156
+ }
7157
+ function readPanelAppCapabilities(html) {
7158
+ return parseTokenList(readMetaContent(html, "nextclaw-panel-capabilities", "attribute"));
7159
+ }
7160
+ function parseTokenList(content) {
7161
+ if (!content) return [];
7162
+ return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
7163
+ }
7164
+ function readRequiredString$7(record, key) {
7165
+ const value = readOptionalString$8(record, key);
7166
+ if (!value) throw new Error(`panel app ${key} is required.`);
7167
+ return value;
7168
+ }
7169
+ function readOptionalString$8(record, key) {
7170
+ return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
7171
+ }
7172
+ function readOptionalBoolean$2(record, key) {
7173
+ const value = record[key];
7174
+ if (value === void 0) return false;
7175
+ if (typeof value !== "boolean") throw new Error(`panel app ${key} must be a boolean.`);
7176
+ return value;
7177
+ }
7178
+ function readStringArray$1(value, key) {
7179
+ if (value === void 0) return [];
7180
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
7181
+ return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
7182
+ }
7183
+ function isRecord$12(value) {
7184
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7185
+ }
7186
+ function normalizeTextValue(value) {
7187
+ return decodeHtmlAttribute(value ?? "").replace(/\s+/g, " ").trim() || void 0;
7188
+ }
7189
+ function normalizeAttributeValue(value) {
7190
+ return decodeHtmlAttribute(value ?? "").trim() || void 0;
7191
+ }
7192
+ function normalizeIconHref(value) {
7193
+ if (!value) return;
7194
+ if (value.startsWith("data:image/") || value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/")) return value;
7195
+ }
7196
+ function decodeHtmlAttribute(value) {
7197
+ return value.replace(/&quot;/g, "\"").replace(/&#34;/g, "\"").replace(/&apos;/g, "'").replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
7198
+ }
7199
+ const PANEL_APP_FOLDER_MANIFEST_FILE_NAME = "panel-app.json";
7200
+ function isPanelAppSourceEntry(entry) {
7201
+ return entry.isFile() && isPanelAppFileName(entry.name) || entry.isDirectory() && isPanelAppDirName(entry.name);
7202
+ }
7203
+ function isPanelAppFileName(fileName) {
7204
+ return hasSafeBaseName(fileName) && fileName.endsWith(".panel.html");
7205
+ }
7206
+ function isPanelAppDirName(dirName) {
7207
+ return hasSafeBaseName(dirName) && dirName.endsWith(".panel");
7208
+ }
7209
+ function toPanelAppTitle(sourceName) {
7210
+ return sourceName.replace(new RegExp(`${escapeRegExp(".panel.html")}$`), "").replace(new RegExp(`${escapeRegExp(".panel")}$`), "").replace(/[-_]+/g, " ").trim() || sourceName;
7211
+ }
7212
+ function encodePanelAppId(sourceName) {
7213
+ return Buffer.from(sourceName, "utf8").toString("base64url");
7214
+ }
7215
+ function decodePanelAppId(id) {
7216
+ const normalizedId = id.trim();
7217
+ if (!normalizedId) throw new PanelAppError("PANEL_APP_INVALID_ID", "panel app id is required");
7218
+ let sourceName = "";
7219
+ try {
7220
+ sourceName = Buffer.from(normalizedId, "base64url").toString("utf8");
7221
+ } catch {
7222
+ throw new PanelAppError("PANEL_APP_INVALID_ID", "invalid panel app id");
7223
+ }
7224
+ if (encodePanelAppId(sourceName) !== normalizedId || !isPanelAppFileName(sourceName) && !isPanelAppDirName(sourceName)) throw new PanelAppError("PANEL_APP_INVALID_ID", "invalid panel app id");
7225
+ return sourceName;
7226
+ }
7227
+ async function readPanelAppFolderManifest(dirPath, dirName) {
7228
+ const manifest = parsePanelAppFolderManifest(await readFile(join(dirPath, PANEL_APP_FOLDER_MANIFEST_FILE_NAME), "utf8"));
7229
+ const expectedId = dirName.slice(0, -6);
7230
+ if (manifest.id && manifest.id !== expectedId) throw new PanelAppError("PANEL_APP_MANIFEST_INVALID", "panel app manifest id must match the directory name");
7231
+ return {
7232
+ ...manifest,
7233
+ id: expectedId
7234
+ };
7235
+ }
7236
+ function resolvePanelAppRelativePath(rootPath, relativePath) {
7237
+ if (!relativePath.trim() || relativePath.includes("\0") || isAbsolute(relativePath)) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7238
+ const normalizedPath = normalize(relativePath);
7239
+ if (normalizedPath === "." || normalizedPath.startsWith("..")) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7240
+ const resolvedPath = resolve(rootPath, normalizedPath);
7241
+ const pathFromRoot = relative(resolve(rootPath), resolvedPath);
7242
+ if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7243
+ return resolvedPath;
7244
+ }
7245
+ function resolvePanelAppAssetContentType(path) {
7246
+ switch (extname(path).toLowerCase()) {
7247
+ case ".css": return "text/css; charset=utf-8";
7248
+ case ".js":
7249
+ case ".mjs": return "application/javascript; charset=utf-8";
7250
+ case ".json": return "application/json; charset=utf-8";
7251
+ case ".png": return "image/png";
7252
+ case ".svg": return "image/svg+xml; charset=utf-8";
7253
+ case ".webp": return "image/webp";
7254
+ case ".txt": return "text/plain; charset=utf-8";
7255
+ default: return "application/octet-stream";
7256
+ }
7257
+ }
7258
+ function resolvePanelAppIconUrl(id, icon) {
7259
+ if (!icon) return;
7260
+ if (icon.startsWith("data:image/") || icon.startsWith("http://") || icon.startsWith("https://") || icon.startsWith("/") || isLikelyTextIcon(icon)) return icon;
7261
+ return `/api/panel-apps/${encodeURIComponent(id)}/assets/${encodePanelAppAssetPath(icon)}`;
7262
+ }
7263
+ function injectPanelAppAssetBase(html, baseHref) {
7264
+ const base = `<base href="${baseHref}">`;
7265
+ return injectLocalScriptCrossOrigin((() => {
7266
+ if (/<base\b/i.test(html)) return html;
7267
+ if (/<head[^>]*>/i.test(html)) return html.replace(/<head[^>]*>/i, (head) => `${head}${base}`);
7268
+ return `${base}${html}`;
7269
+ })());
7270
+ }
7271
+ function injectLocalScriptCrossOrigin(html) {
7272
+ return html.replace(/<script\b(?=[^>]*\bsrc\s*=)(?![^>]*\bcrossorigin\b)[^>]*>/gi, (tag) => {
7273
+ const src = extractScriptSrc(tag);
7274
+ if (!src || !isLocalScriptSrc(src)) return tag;
7275
+ return `${tag.slice(0, -1)} crossorigin="anonymous">`;
7276
+ });
7277
+ }
7278
+ function extractScriptSrc(tag) {
7279
+ const match = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tag);
7280
+ return match?.[1] ?? match?.[2] ?? match?.[3];
7281
+ }
7282
+ function isLocalScriptSrc(src) {
7283
+ const value = src.trim();
7284
+ if (!value || value.startsWith("//")) return false;
7285
+ return !/^(?:https?|data|blob|javascript):/i.test(value);
7286
+ }
7287
+ function encodePanelAppAssetPath(path) {
7288
+ return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
7289
+ }
7290
+ function hasSafeBaseName(name) {
7291
+ return !name.includes("/") && !name.includes("\\") && !name.includes("\0");
7292
+ }
7293
+ function isLikelyTextIcon(icon) {
7294
+ return !icon.includes("/") && !icon.includes(".") && icon.length <= 4;
7295
+ }
7296
+ function escapeRegExp(value) {
7297
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7298
+ }
7299
+ //#endregion
7300
+ //#region src/utils/panel-app-content-source.utils.ts
7301
+ async function readPanelAppContentSource(params) {
7302
+ const { createAssetBaseHref, id, panelsPath, sourceService } = params;
7303
+ return await readResolvedPanelAppContentSource(await sourceService.resolveSource(panelsPath, id), createAssetBaseHref);
7304
+ }
7305
+ async function readPanelAppContentSourceByPath(params) {
7306
+ const { createAssetBaseHref, path, sourceService } = params;
7307
+ return await readResolvedPanelAppContentSource(await sourceService.resolveSourcePath(path), createAssetBaseHref);
7308
+ }
7309
+ async function readPanelAppContentSourceByIdOrPath(params) {
7310
+ const { sourcePath, ...standardSourceParams } = params;
7311
+ return sourcePath ? await readPanelAppContentSourceByPath({
7312
+ createAssetBaseHref: params.createAssetBaseHref,
7313
+ path: sourcePath,
7314
+ sourceService: params.sourceService
7315
+ }) : await readPanelAppContentSource(standardSourceParams);
7316
+ }
7317
+ async function readResolvedPanelAppContentSource(source, createAssetBaseHref) {
7318
+ const html = await readFile(source.entryPath, "utf8");
7319
+ const manifest = source.manifest ?? parsePanelAppManifest(html);
7320
+ const sourceId = encodePanelAppId(source.sourceName);
7321
+ return {
7322
+ appId: resolvePanelAppAppId(source, manifest),
7323
+ sourceId,
7324
+ source,
7325
+ manifest,
7326
+ html,
7327
+ htmlWithBase: source.kind === "folder" ? injectPanelAppAssetBase(html, createAssetBaseHref(source)) : html
7328
+ };
7329
+ }
7330
+ async function readPanelAppContentSourceByIdOrAppId(params) {
7331
+ const { appIdOrSourceId, createAssetBaseHref, panelsPath, sourceService } = params;
7332
+ try {
7333
+ return await readPanelAppContentSource({
7334
+ createAssetBaseHref,
7335
+ id: appIdOrSourceId,
7336
+ panelsPath,
7337
+ sourceService
7338
+ });
7339
+ } catch (error) {
7340
+ if (!isPanelAppError(error)) throw error;
7341
+ if (error.code !== "PANEL_APP_INVALID_ID" && error.code !== "PANEL_APP_NOT_FOUND") throw error;
7342
+ }
7343
+ const sources = await sourceService.listSources(panelsPath);
7344
+ for (const source of sources) {
7345
+ const html = await readFile(source.entryPath, "utf8");
7346
+ if (resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(html)) === appIdOrSourceId) return await readPanelAppContentSource({
7347
+ createAssetBaseHref,
7348
+ id: encodePanelAppId(source.sourceName),
7349
+ panelsPath,
7350
+ sourceService
7351
+ });
7352
+ }
7353
+ throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
7354
+ }
7355
+ function resolvePanelAppAppId(source, manifest) {
7356
+ return manifest.id ?? encodePanelAppId(source.sourceName);
7357
+ }
7358
+ //#endregion
7359
+ //#region src/managers/panel-app-package-state.manager.ts
7360
+ var PanelAppPackageStateManager = class {
7361
+ constructor(params) {
7362
+ this.params = params;
7363
+ }
7364
+ listSources = async () => {
7365
+ const panelsPath = this.params.getPanelsPath();
7366
+ const workspaceSources = await this.params.sourceService.listSources(panelsPath);
7367
+ const packageSources = (await this.listPackageComponentSources()).filter((component) => component.kind === "panel");
7368
+ const resolvedPackageSources = await Promise.all(packageSources.map(async (packageSource) => ({
7369
+ source: await this.params.sourceService.resolveSourcePath(packageSource.sourcePath),
7370
+ packageSource
7371
+ })));
7372
+ return [...workspaceSources.map((source) => ({ source })), ...resolvedPackageSources];
7373
+ };
7374
+ resolveSource = async (id) => {
7375
+ try {
7376
+ return await this.params.sourceService.resolveSource(this.params.getPanelsPath(), id);
7377
+ } catch (error) {
7378
+ if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND") throw error;
7379
+ }
7380
+ const match = (await this.listSources()).find(({ source, packageSource }) => packageSource && (encodePanelAppId(source.sourceName) === id || packageSource.id === id));
7381
+ if (!match) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
7382
+ return match.source;
7383
+ };
7384
+ findPackageSourceBySourceName = async (sourceName) => {
7385
+ return (await this.listPackageComponentSources()).find((component) => component.kind === "panel" && component.sourcePath.endsWith(`/${sourceName}`));
7386
+ };
7387
+ readContentSourceByIdOrAppId = async (id) => {
7388
+ const panelsPath = this.params.getPanelsPath();
7389
+ try {
7390
+ return await readPanelAppContentSourceByIdOrAppId({
7391
+ appIdOrSourceId: id,
7392
+ createAssetBaseHref: this.params.createAssetBaseHref,
7393
+ panelsPath,
7394
+ sourceService: this.params.sourceService
7395
+ });
7396
+ } catch (error) {
7397
+ if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND") throw error;
7398
+ }
7399
+ const packageSource = (await this.listPackageComponentSources()).find((component) => component.kind === "panel" && component.id === id);
7400
+ if (!packageSource) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
7401
+ return await readPanelAppContentSourceByIdOrPath({
7402
+ createAssetBaseHref: this.params.createAssetBaseHref,
7403
+ id,
7404
+ panelsPath,
7405
+ sourcePath: packageSource.sourcePath,
7406
+ sourceService: this.params.sourceService
7407
+ });
7408
+ };
7409
+ assertDeclaresClient = async (appId) => {
7410
+ const sources = await this.listSources();
7411
+ for (const { source } of sources) {
7412
+ const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
7413
+ if (resolvePanelAppAppId(source, manifest) === appId) {
7414
+ if (!manifest.client) throw new PanelAppError("PANEL_APP_CLIENT_NOT_DECLARED", "panel app did not declare client access");
7415
+ return;
7416
+ }
7417
+ }
7418
+ throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
7419
+ };
7420
+ assertCanActivate = async (components) => {
7421
+ const panelComponents = components.filter((component) => component.kind === "panel");
7422
+ if (panelComponents.length === 0) return;
7423
+ const workspaceSources = await this.params.sourceService.listSources(this.params.getPanelsPath());
7424
+ const workspaceIds = /* @__PURE__ */ new Set();
7425
+ for (const source of workspaceSources) {
7426
+ const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
7427
+ workspaceIds.add(resolvePanelAppAppId(source, manifest));
7428
+ }
7429
+ const activePackageSources = await this.listPackageComponentSources();
7430
+ for (const component of panelComponents) {
7431
+ const conflictsWithPackage = activePackageSources.some((active) => active.kind === "panel" && active.id === component.id && active.packageId !== component.packageId);
7432
+ if (workspaceIds.has(component.id) || conflictsWithPackage) throw new AppPackageError("APP_PACKAGE_CONFLICT", `Panel component id 冲突:${component.id}`);
7433
+ }
7434
+ };
7435
+ deactivate = (components) => {
7436
+ for (const component of components) if (component.kind === "panel") this.params.deleteBridgeSessions(component.id);
7437
+ };
7438
+ removeState = async (components) => {
7439
+ const panelsPath = this.params.getPanelsPath();
7440
+ for (const component of components) {
7441
+ if (component.kind !== "panel") continue;
7442
+ this.params.deleteBridgeSessions(component.id);
7443
+ await this.params.createStateStore(panelsPath).deleteEntry(encodePanelAppId(basename(component.sourcePath)));
7444
+ await this.params.createCapabilityGrantStore().deleteCaller({
7445
+ surface: "panel-app",
7446
+ appId: component.id
7447
+ });
7448
+ await this.params.createClientGrantStore().revoke(component.id);
7449
+ }
7450
+ };
7451
+ listPackageComponentSources = async () => await this.params.listPackageComponentSources?.() ?? [];
7452
+ };
7453
+ //#endregion
7454
+ //#region src/utils/panel-app-time.utils.ts
7455
+ function resolvePanelAppCreatedAt(fileStat) {
7456
+ return fileStat.birthtimeMs > 0 ? fileStat.birthtime.toISOString() : fileStat.mtime.toISOString();
7457
+ }
7458
+ function resolvePanelAppActivityMs(entry) {
7459
+ return Math.max(new Date(entry.lastOpenedAt ?? 0).getTime(), new Date(entry.createdAt).getTime(), new Date(entry.updatedAt).getTime());
7460
+ }
7461
+ //#endregion
7462
+ //#region src/presenters/panel-app-entry.presenter.ts
7463
+ var PanelAppEntryPresenter = class {
7464
+ constructor(params) {
7465
+ this.params = params;
7466
+ }
7467
+ build = async (source, state, packageSource) => {
7468
+ const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
7469
+ const id = encodePanelAppId(source.sourceName);
7470
+ const appId = resolvePanelAppAppId(source, manifest);
7471
+ const entry = {
7472
+ id,
7473
+ appId,
7474
+ fileName: source.sourceName,
7475
+ kind: source.kind,
7476
+ title: manifest.title ?? toPanelAppTitle(source.sourceName),
7477
+ contentPath: packageSource ? `${this.params.contentBasePath}/${encodeURIComponent(id)}/content?${new URLSearchParams({ path: source.sourcePath })}` : `${this.params.contentBasePath}/${encodeURIComponent(id)}/content`,
7478
+ createdAt: resolvePanelAppCreatedAt(source.sourceStat),
7479
+ updatedAt: source.sourceStat.mtime.toISOString(),
7480
+ sizeBytes: source.sourceStat.size,
7481
+ favorite: state.favorite ?? false,
7482
+ clientDeclared: manifest.client,
7483
+ clientGranted: await this.params.isClientGranted(appId, manifest.client),
7484
+ openCount: state.openCount ?? 0,
7485
+ sourceKind: packageSource ? "package" : "workspace",
7486
+ packageId: packageSource?.packageId,
7487
+ packageVersion: packageSource?.packageVersion
7488
+ };
7489
+ if (manifest.description) entry.description = manifest.description;
7490
+ if (manifest.icon) entry.icon = source.kind === "folder" ? packageSource ? `${this.params.createAssetBaseHref(source)}${manifest.icon.replace(/^\/+/, "")}` : resolvePanelAppIconUrl(id, manifest.icon) : manifest.icon;
7491
+ if (state.lastOpenedAt) entry.lastOpenedAt = state.lastOpenedAt;
7492
+ return entry;
7493
+ };
7494
+ compare = (left, right) => resolvePanelAppActivityMs(right) - resolvePanelAppActivityMs(left) || Number(right.favorite) - Number(left.favorite) || left.title.localeCompare(right.title);
7495
+ };
7496
+ //#endregion
7497
+ //#region src/services/panel-app-asset-token.service.ts
7498
+ const PANEL_APP_ASSET_TOKEN_TTL_MS = 7200 * 1e3;
7499
+ var PanelAppAssetTokenService = class {
7500
+ now;
7501
+ secret;
7502
+ ttlMs;
7503
+ constructor(params = {}) {
7504
+ this.now = params.now ?? Date.now;
7505
+ this.secret = params.secret ?? randomBytes(32);
7506
+ this.ttlMs = params.ttlMs ?? PANEL_APP_ASSET_TOKEN_TTL_MS;
7507
+ }
7508
+ issue = (params) => {
7509
+ const claims = {
7510
+ panelAppId: params.panelAppId,
7511
+ sourceName: params.sourceName,
7512
+ sourcePath: params.sourcePath,
7513
+ expiresAt: this.now() + this.ttlMs,
7514
+ nonce: randomBytes(12).toString("base64url")
7515
+ };
7516
+ const payload = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
7517
+ return `${payload}.${this.sign(payload)}`;
7518
+ };
7519
+ verify = (token) => {
7520
+ const [payload, signature, ...rest] = token.trim().split(".");
7521
+ if (!payload || !signature || rest.length > 0 || !this.matchesSignature(payload, signature)) throw new PanelAppError("PANEL_APP_ASSET_TOKEN_INVALID", "invalid panel app asset token");
7522
+ const claims = this.parseClaims(payload);
7523
+ if (claims.expiresAt <= this.now()) throw new PanelAppError("PANEL_APP_ASSET_TOKEN_EXPIRED", "panel app asset token expired");
7524
+ return claims;
7525
+ };
7526
+ sign = (payload) => createHmac("sha256", this.secret).update(payload).digest("base64url");
7527
+ matchesSignature = (payload, signature) => {
7528
+ const expected = Buffer.from(this.sign(payload), "utf8");
7529
+ const actual = Buffer.from(signature, "utf8");
7530
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
7531
+ };
7532
+ parseClaims = (payload) => {
7533
+ try {
7534
+ const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
7535
+ if (!isPanelAppAssetTokenClaims(claims)) throw new Error("invalid panel app asset token claims");
7536
+ return claims;
7537
+ } catch {
7538
+ throw new PanelAppError("PANEL_APP_ASSET_TOKEN_INVALID", "invalid panel app asset token");
7539
+ }
7540
+ };
7541
+ };
7542
+ function isPanelAppAssetTokenClaims(value) {
7543
+ return typeof value === "object" && value !== null && "panelAppId" in value && "sourceName" in value && "sourcePath" in value && "expiresAt" in value && "nonce" in value && typeof value.panelAppId === "string" && typeof value.sourceName === "string" && typeof value.sourcePath === "string" && typeof value.expiresAt === "number" && typeof value.nonce === "string";
7544
+ }
7545
+ //#endregion
7546
+ //#region src/stores/panel-app-state.store.ts
7547
+ const PANEL_APP_STATE_FILE = ".panel-apps.state.json";
7548
+ const PANEL_APP_STATE_VERSION = 1;
7549
+ var PanelAppStateStore = class {
7550
+ constructor(panelsPath) {
7551
+ this.panelsPath = panelsPath;
7552
+ }
7553
+ load = async () => {
7554
+ try {
7555
+ const parsed = JSON.parse(await readFile(this.getStatePath(), "utf8"));
7556
+ return this.normalizeStateFile(parsed).apps;
7557
+ } catch (error) {
7558
+ if (this.isMissingFileError(error) || error instanceof SyntaxError) return {};
7559
+ throw error;
7560
+ }
7561
+ };
7562
+ updatePreferences = async (id, preferences) => {
7563
+ const apps = await this.load();
7564
+ const next = { ...apps[id] ?? {} };
7565
+ if (typeof preferences.favorite === "boolean") next.favorite = preferences.favorite;
7566
+ apps[id] = next;
6570
7567
  await this.persist(apps);
6571
7568
  return next;
6572
7569
  };
@@ -7659,262 +8656,39 @@ function injectPanelAppClientScript(html, params) {
7659
8656
  return `${script}${html}`;
7660
8657
  }
7661
8658
  function getPanelAppClientInitScript(params) {
7662
- return `
7663
- (() => {
7664
- const marker = "${PANEL_APP_CLIENT_MARKER}";
7665
- if (typeof window.NextClawClient !== "function") {
7666
- console.error("[NextClaw] Panel App client SDK failed to load.");
7667
- return;
7668
- }
7669
- if (typeof window.createNextClawAppClient !== "function") {
7670
- console.error("[NextClaw] Panel App client projection failed to load.");
7671
- return;
7672
- }
7673
- const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
7674
- const hostClient = new window.NextClawClient({
7675
- baseUrl: window.location.origin,
7676
- headers: {
7677
- "x-nextclaw-panel-bridge-session": ${JSON.stringify(params.runtimeToken)}
7678
- }
7679
- });
7680
- const client = window.createNextClawAppClient(hostClient);
7681
- Object.defineProperty(window, "nextclaw", {
7682
- configurable: true,
7683
- value: {
7684
- ...existing,
7685
- client
7686
- }
7687
- });
7688
- Object.defineProperty(window.nextclaw, "__clientInitMarker", {
7689
- configurable: true,
7690
- enumerable: false,
7691
- value: marker
7692
- });
7693
- })();
7694
- `.trim();
7695
- }
7696
- //#endregion
7697
- //#region src/utils/panel-app-manifest.utils.ts
7698
- const PANEL_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7699
- function parsePanelAppManifest(html) {
7700
- return {
7701
- ...readHtmlTitle(html),
7702
- ...readStandardIcon(html),
7703
- ...readPanelAppMeta(html),
7704
- capabilities: readPanelAppCapabilities(html),
7705
- client: false,
7706
- serviceActions: readPanelAppServiceActions(html)
7707
- };
7708
- }
7709
- function parsePanelAppFolderManifest(raw) {
7710
- let parsed;
7711
- try {
7712
- parsed = JSON.parse(raw);
7713
- } catch (error) {
7714
- throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
7715
- }
7716
- if (!isRecord$8(parsed)) throw new Error("panel-app.json must contain an object.");
7717
- const id = readOptionalString$8(parsed, "id");
7718
- if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
7719
- return {
7720
- id,
7721
- title: readRequiredString$7(parsed, "title"),
7722
- description: readOptionalString$8(parsed, "description"),
7723
- icon: readOptionalString$8(parsed, "icon"),
7724
- entry: readRequiredString$7(parsed, "entry"),
7725
- capabilities: readStringArray$1(parsed.capabilities, "capabilities"),
7726
- client: readOptionalBoolean$2(parsed, "client"),
7727
- serviceActions: readStringArray$1(parsed.actions, "actions")
7728
- };
7729
- }
7730
- function readPanelAppMeta(html) {
7731
- return {
7732
- ...readPanelAppMetaField(html, "title"),
7733
- ...readPanelAppMetaField(html, "description"),
7734
- ...readPanelAppMetaField(html, "icon")
7735
- };
7736
- }
7737
- function readPanelAppMetaField(html, field) {
7738
- const content = readMetaContent(html, `nextclaw-panel-${field}`, field === "icon" ? "attribute" : "text");
7739
- const manifest = {};
7740
- if (content) manifest[field] = content;
7741
- return manifest;
7742
- }
7743
- function readHtmlTitle(html) {
7744
- const title = normalizeTextValue(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]);
7745
- return title ? { title } : {};
7746
- }
7747
- function readStandardIcon(html) {
7748
- const icon = readLinkHref(html, (relTokens) => relTokens.includes("icon"));
7749
- const appleTouchIcon = readLinkHref(html, (relTokens) => relTokens.some((token) => token === "apple-touch-icon" || token === "apple-touch-icon-precomposed"));
7750
- const href = normalizeIconHref(icon ?? appleTouchIcon);
7751
- return href ? { icon: href } : {};
7752
- }
7753
- function readMetaContent(html, name, valueKind) {
7754
- const metaTags = html.matchAll(/<meta\s+([^>]*?)>/gi);
7755
- for (const tag of metaTags) {
7756
- const attributes = tag[1] ?? "";
7757
- if (readHtmlAttribute(attributes, "name") === name) {
7758
- const content = readHtmlAttribute(attributes, "content");
7759
- return valueKind === "attribute" ? normalizeAttributeValue(content) : normalizeTextValue(content);
7760
- }
7761
- }
7762
- }
7763
- function readLinkHref(html, matchesRel) {
7764
- const linkTags = html.matchAll(/<link\s+([^>]*?)>/gi);
7765
- for (const tag of linkTags) {
7766
- const attributes = tag[1] ?? "";
7767
- if (matchesRel((readHtmlAttribute(attributes, "rel") ?? "").toLowerCase().split(/\s+/).filter(Boolean))) return normalizeAttributeValue(readHtmlAttribute(attributes, "href"));
7768
- }
7769
- }
7770
- function readHtmlAttribute(attributes, attribute) {
7771
- const match = attributes.match(new RegExp(`(?:^|\\s)${attribute}\\s*=\\s*(["'])(.*?)\\1`, "i"));
7772
- return match?.[2] ? decodeHtmlAttribute(match[2]) : void 0;
7773
- }
7774
- function readPanelAppServiceActions(html) {
7775
- return parseTokenList(readMetaContent(html, "nextclaw-panel-actions", "attribute"));
7776
- }
7777
- function readPanelAppCapabilities(html) {
7778
- return parseTokenList(readMetaContent(html, "nextclaw-panel-capabilities", "attribute"));
7779
- }
7780
- function parseTokenList(content) {
7781
- if (!content) return [];
7782
- return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
7783
- }
7784
- function readRequiredString$7(record, key) {
7785
- const value = readOptionalString$8(record, key);
7786
- if (!value) throw new Error(`panel app ${key} is required.`);
7787
- return value;
7788
- }
7789
- function readOptionalString$8(record, key) {
7790
- return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
7791
- }
7792
- function readOptionalBoolean$2(record, key) {
7793
- const value = record[key];
7794
- if (value === void 0) return false;
7795
- if (typeof value !== "boolean") throw new Error(`panel app ${key} must be a boolean.`);
7796
- return value;
7797
- }
7798
- function readStringArray$1(value, key) {
7799
- if (value === void 0) return [];
7800
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
7801
- return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
7802
- }
7803
- function isRecord$8(value) {
7804
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7805
- }
7806
- function normalizeTextValue(value) {
7807
- return decodeHtmlAttribute(value ?? "").replace(/\s+/g, " ").trim() || void 0;
7808
- }
7809
- function normalizeAttributeValue(value) {
7810
- return decodeHtmlAttribute(value ?? "").trim() || void 0;
7811
- }
7812
- function normalizeIconHref(value) {
7813
- if (!value) return;
7814
- if (value.startsWith("data:image/") || value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/")) return value;
7815
- }
7816
- function decodeHtmlAttribute(value) {
7817
- return value.replace(/&quot;/g, "\"").replace(/&#34;/g, "\"").replace(/&apos;/g, "'").replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
7818
- }
7819
- const PANEL_APP_FOLDER_MANIFEST_FILE_NAME = "panel-app.json";
7820
- function isPanelAppSourceEntry(entry) {
7821
- return entry.isFile() && isPanelAppFileName(entry.name) || entry.isDirectory() && isPanelAppDirName(entry.name);
7822
- }
7823
- function isPanelAppFileName(fileName) {
7824
- return hasSafeBaseName(fileName) && fileName.endsWith(".panel.html");
7825
- }
7826
- function isPanelAppDirName(dirName) {
7827
- return hasSafeBaseName(dirName) && dirName.endsWith(".panel");
7828
- }
7829
- function toPanelAppTitle(sourceName) {
7830
- return sourceName.replace(new RegExp(`${escapeRegExp(".panel.html")}$`), "").replace(new RegExp(`${escapeRegExp(".panel")}$`), "").replace(/[-_]+/g, " ").trim() || sourceName;
7831
- }
7832
- function encodePanelAppId(sourceName) {
7833
- return Buffer.from(sourceName, "utf8").toString("base64url");
7834
- }
7835
- function decodePanelAppId(id) {
7836
- const normalizedId = id.trim();
7837
- if (!normalizedId) throw new PanelAppError("PANEL_APP_INVALID_ID", "panel app id is required");
7838
- let sourceName = "";
7839
- try {
7840
- sourceName = Buffer.from(normalizedId, "base64url").toString("utf8");
7841
- } catch {
7842
- throw new PanelAppError("PANEL_APP_INVALID_ID", "invalid panel app id");
7843
- }
7844
- if (encodePanelAppId(sourceName) !== normalizedId || !isPanelAppFileName(sourceName) && !isPanelAppDirName(sourceName)) throw new PanelAppError("PANEL_APP_INVALID_ID", "invalid panel app id");
7845
- return sourceName;
7846
- }
7847
- async function readPanelAppFolderManifest(dirPath, dirName) {
7848
- const manifest = parsePanelAppFolderManifest(await readFile(join(dirPath, PANEL_APP_FOLDER_MANIFEST_FILE_NAME), "utf8"));
7849
- const expectedId = dirName.slice(0, -6);
7850
- if (manifest.id && manifest.id !== expectedId) throw new PanelAppError("PANEL_APP_MANIFEST_INVALID", "panel app manifest id must match the directory name");
7851
- return {
7852
- ...manifest,
7853
- id: expectedId
7854
- };
7855
- }
7856
- function resolvePanelAppRelativePath(rootPath, relativePath) {
7857
- if (!relativePath.trim() || relativePath.includes("\0") || isAbsolute(relativePath)) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7858
- const normalizedPath = normalize(relativePath);
7859
- if (normalizedPath === "." || normalizedPath.startsWith("..")) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7860
- const resolvedPath = resolve(rootPath, normalizedPath);
7861
- const pathFromRoot = relative(resolve(rootPath), resolvedPath);
7862
- if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7863
- return resolvedPath;
7864
- }
7865
- function resolvePanelAppAssetContentType(path) {
7866
- switch (extname(path).toLowerCase()) {
7867
- case ".css": return "text/css; charset=utf-8";
7868
- case ".js":
7869
- case ".mjs": return "application/javascript; charset=utf-8";
7870
- case ".json": return "application/json; charset=utf-8";
7871
- case ".png": return "image/png";
7872
- case ".svg": return "image/svg+xml; charset=utf-8";
7873
- case ".webp": return "image/webp";
7874
- case ".txt": return "text/plain; charset=utf-8";
7875
- default: return "application/octet-stream";
7876
- }
7877
- }
7878
- function resolvePanelAppIconUrl(id, icon) {
7879
- if (!icon) return;
7880
- if (icon.startsWith("data:image/") || icon.startsWith("http://") || icon.startsWith("https://") || icon.startsWith("/") || isLikelyTextIcon(icon)) return icon;
7881
- return `/api/panel-apps/${encodeURIComponent(id)}/assets/${encodePanelAppAssetPath(icon)}`;
7882
- }
7883
- function injectPanelAppAssetBase(html, baseHref) {
7884
- const base = `<base href="${baseHref}">`;
7885
- return injectLocalScriptCrossOrigin((() => {
7886
- if (/<base\b/i.test(html)) return html;
7887
- if (/<head[^>]*>/i.test(html)) return html.replace(/<head[^>]*>/i, (head) => `${head}${base}`);
7888
- return `${base}${html}`;
7889
- })());
7890
- }
7891
- function injectLocalScriptCrossOrigin(html) {
7892
- return html.replace(/<script\b(?=[^>]*\bsrc\s*=)(?![^>]*\bcrossorigin\b)[^>]*>/gi, (tag) => {
7893
- const src = extractScriptSrc(tag);
7894
- if (!src || !isLocalScriptSrc(src)) return tag;
7895
- return `${tag.slice(0, -1)} crossorigin="anonymous">`;
7896
- });
7897
- }
7898
- function extractScriptSrc(tag) {
7899
- const match = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tag);
7900
- return match?.[1] ?? match?.[2] ?? match?.[3];
7901
- }
7902
- function isLocalScriptSrc(src) {
7903
- const value = src.trim();
7904
- if (!value || value.startsWith("//")) return false;
7905
- return !/^(?:https?|data|blob|javascript):/i.test(value);
7906
- }
7907
- function encodePanelAppAssetPath(path) {
7908
- return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
7909
- }
7910
- function hasSafeBaseName(name) {
7911
- return !name.includes("/") && !name.includes("\\") && !name.includes("\0");
7912
- }
7913
- function isLikelyTextIcon(icon) {
7914
- return !icon.includes("/") && !icon.includes(".") && icon.length <= 4;
7915
- }
7916
- function escapeRegExp(value) {
7917
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8659
+ return `
8660
+ (() => {
8661
+ const marker = "${PANEL_APP_CLIENT_MARKER}";
8662
+ if (typeof window.NextClawClient !== "function") {
8663
+ console.error("[NextClaw] Panel App client SDK failed to load.");
8664
+ return;
8665
+ }
8666
+ if (typeof window.createNextClawAppClient !== "function") {
8667
+ console.error("[NextClaw] Panel App client projection failed to load.");
8668
+ return;
8669
+ }
8670
+ const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
8671
+ const hostClient = new window.NextClawClient({
8672
+ baseUrl: window.location.origin,
8673
+ headers: {
8674
+ "x-nextclaw-panel-bridge-session": ${JSON.stringify(params.runtimeToken)}
8675
+ }
8676
+ });
8677
+ const client = window.createNextClawAppClient(hostClient);
8678
+ Object.defineProperty(window, "nextclaw", {
8679
+ configurable: true,
8680
+ value: {
8681
+ ...existing,
8682
+ client
8683
+ }
8684
+ });
8685
+ Object.defineProperty(window.nextclaw, "__clientInitMarker", {
8686
+ configurable: true,
8687
+ enumerable: false,
8688
+ value: marker
8689
+ });
8690
+ })();
8691
+ `.trim();
7918
8692
  }
7919
8693
  //#endregion
7920
8694
  //#region src/services/panel-app-source.service.ts
@@ -8015,84 +8789,6 @@ function isMissingFileError$1(error) {
8015
8789
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
8016
8790
  }
8017
8791
  //#endregion
8018
- //#region src/utils/panel-app-time.utils.ts
8019
- function resolvePanelAppCreatedAt(fileStat) {
8020
- return fileStat.birthtimeMs > 0 ? fileStat.birthtime.toISOString() : fileStat.mtime.toISOString();
8021
- }
8022
- function resolvePanelAppActivityMs(entry) {
8023
- return Math.max(new Date(entry.lastOpenedAt ?? 0).getTime(), new Date(entry.createdAt).getTime(), new Date(entry.updatedAt).getTime());
8024
- }
8025
- //#endregion
8026
- //#region src/utils/panel-app-content-source.utils.ts
8027
- async function readPanelAppContentSource(params) {
8028
- const { createAssetBaseHref, id, panelsPath, sourceService } = params;
8029
- return await readResolvedPanelAppContentSource(await sourceService.resolveSource(panelsPath, id), createAssetBaseHref);
8030
- }
8031
- async function readPanelAppContentSourceByPath(params) {
8032
- const { createAssetBaseHref, path, sourceService } = params;
8033
- return await readResolvedPanelAppContentSource(await sourceService.resolveSourcePath(path), createAssetBaseHref);
8034
- }
8035
- async function readPanelAppContentSourceByIdOrPath(params) {
8036
- const { sourcePath, ...standardSourceParams } = params;
8037
- return sourcePath ? await readPanelAppContentSourceByPath({
8038
- createAssetBaseHref: params.createAssetBaseHref,
8039
- path: sourcePath,
8040
- sourceService: params.sourceService
8041
- }) : await readPanelAppContentSource(standardSourceParams);
8042
- }
8043
- async function readResolvedPanelAppContentSource(source, createAssetBaseHref) {
8044
- const html = await readFile(source.entryPath, "utf8");
8045
- const manifest = source.manifest ?? parsePanelAppManifest(html);
8046
- const sourceId = encodePanelAppId(source.sourceName);
8047
- return {
8048
- appId: resolvePanelAppAppId(source, manifest),
8049
- sourceId,
8050
- source,
8051
- manifest,
8052
- html,
8053
- htmlWithBase: source.kind === "folder" ? injectPanelAppAssetBase(html, createAssetBaseHref(source)) : html
8054
- };
8055
- }
8056
- async function readPanelAppContentSourceByIdOrAppId(params) {
8057
- const { appIdOrSourceId, createAssetBaseHref, panelsPath, sourceService } = params;
8058
- try {
8059
- return await readPanelAppContentSource({
8060
- createAssetBaseHref,
8061
- id: appIdOrSourceId,
8062
- panelsPath,
8063
- sourceService
8064
- });
8065
- } catch (error) {
8066
- if (!isPanelAppError(error)) throw error;
8067
- if (error.code !== "PANEL_APP_INVALID_ID" && error.code !== "PANEL_APP_NOT_FOUND") throw error;
8068
- }
8069
- const sources = await sourceService.listSources(panelsPath);
8070
- for (const source of sources) {
8071
- const html = await readFile(source.entryPath, "utf8");
8072
- if (resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(html)) === appIdOrSourceId) return await readPanelAppContentSource({
8073
- createAssetBaseHref,
8074
- id: encodePanelAppId(source.sourceName),
8075
- panelsPath,
8076
- sourceService
8077
- });
8078
- }
8079
- throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
8080
- }
8081
- function resolvePanelAppAppId(source, manifest) {
8082
- return manifest.id ?? encodePanelAppId(source.sourceName);
8083
- }
8084
- async function assertPanelAppDeclaresClient(params) {
8085
- const { appId, panelsPath, sourceService } = params;
8086
- const sources = await sourceService.listSources(panelsPath);
8087
- for (const source of sources) {
8088
- const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
8089
- if (resolvePanelAppAppId(source, manifest) !== appId) continue;
8090
- if (!manifest.client) throw new PanelAppError("PANEL_APP_CLIENT_NOT_DECLARED", "panel app did not declare client access");
8091
- return;
8092
- }
8093
- throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
8094
- }
8095
- //#endregion
8096
8792
  //#region src/managers/panel-app.manager.ts
8097
8793
  const PANEL_APP_CONTENT_BASE_PATH = "/api/panel-apps";
8098
8794
  const PANEL_APP_TOKENIZED_ASSET_BASE_PATH = "/api/panel-app-assets";
@@ -8106,6 +8802,8 @@ var PanelAppManager = class {
8106
8802
  agentBridgeService;
8107
8803
  assetTokenService = new PanelAppAssetTokenService();
8108
8804
  sourceService = new PanelAppSourceService();
8805
+ packageStateManager;
8806
+ entryPresenter;
8109
8807
  constructor(params) {
8110
8808
  this.params = params;
8111
8809
  this.agentRunClient = params.agentRunClient ?? (params.eventBus && params.ingress ? new AgentRunClient({
@@ -8116,16 +8814,31 @@ var PanelAppManager = class {
8116
8814
  agentRunClient: this.agentRunClient,
8117
8815
  createCapabilityGrantStore: this.createCapabilityGrantStore
8118
8816
  });
8817
+ this.packageStateManager = new PanelAppPackageStateManager({
8818
+ sourceService: this.sourceService,
8819
+ getPanelsPath: () => this.getPanelsPath(this.getWorkspacePath()),
8820
+ listPackageComponentSources: params.listPackageComponentSources,
8821
+ createAssetBaseHref: this.createAssetBaseHref,
8822
+ deleteBridgeSessions: this.deleteBridgeSessionsByPanelAppId,
8823
+ createStateStore: this.createStateStore,
8824
+ createCapabilityGrantStore: this.createCapabilityGrantStore,
8825
+ createClientGrantStore: this.createClientGrantStore
8826
+ });
8827
+ this.entryPresenter = new PanelAppEntryPresenter({
8828
+ contentBasePath: PANEL_APP_CONTENT_BASE_PATH,
8829
+ createAssetBaseHref: this.createAssetBaseHref,
8830
+ isClientGranted: this.isPanelAppClientGranted
8831
+ });
8119
8832
  }
8120
8833
  listPanelApps = async () => {
8121
8834
  const workspacePath = this.getWorkspacePath();
8122
8835
  const panelsPath = this.getPanelsPath(workspacePath);
8123
- const sources = await this.sourceService.listSources(panelsPath);
8836
+ const sources = await this.packageStateManager.listSources();
8124
8837
  const appState = await this.createStateStore(panelsPath).load();
8125
8838
  return {
8126
8839
  workspacePath,
8127
8840
  panelsPath,
8128
- entries: (await Promise.all(sources.map((source) => this.buildPanelAppEntry(source, appState[encodePanelAppId(source.sourceName)] ?? {})))).sort(this.comparePanelApps)
8841
+ entries: (await Promise.all(sources.map(({ source, packageSource }) => this.entryPresenter.build(source, appState[encodePanelAppId(source.sourceName)] ?? {}, packageSource)))).sort(this.entryPresenter.compare)
8129
8842
  };
8130
8843
  };
8131
8844
  getPanelAppContent = async (id, sourcePath) => {
@@ -8200,12 +8913,7 @@ var PanelAppManager = class {
8200
8913
  return session;
8201
8914
  };
8202
8915
  createPanelAppBridgeSession = async (params) => {
8203
- const resolved = await readPanelAppContentSourceByIdOrAppId({
8204
- appIdOrSourceId: params.id,
8205
- createAssetBaseHref: this.createAssetBaseHref,
8206
- panelsPath: this.getPanelsPath(this.getWorkspacePath()),
8207
- sourceService: this.sourceService
8208
- });
8916
+ const resolved = await this.packageStateManager.readContentSourceByIdOrAppId(params.id);
8209
8917
  return this.createPanelAppRuntimeTokenSession({
8210
8918
  appId: resolved.appId,
8211
8919
  clientDeclared: resolved.manifest.client,
@@ -8214,11 +8922,7 @@ var PanelAppManager = class {
8214
8922
  });
8215
8923
  };
8216
8924
  grantPanelAppClient = async (appId) => {
8217
- await assertPanelAppDeclaresClient({
8218
- appId,
8219
- panelsPath: this.getPanelsPath(this.getWorkspacePath()),
8220
- sourceService: this.sourceService
8221
- });
8925
+ await this.packageStateManager.assertDeclaresClient(appId);
8222
8926
  return await this.createClientGrantStore().grant({
8223
8927
  appId,
8224
8928
  grantedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8252,17 +8956,19 @@ var PanelAppManager = class {
8252
8956
  const fileName = await this.resolvePanelAppFileName(id);
8253
8957
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
8254
8958
  const state = await this.createStateStore(panelsPath).updatePreferences(encodePanelAppId(fileName), preferences);
8255
- return await this.buildPanelAppEntry(await this.sourceService.resolveSource(panelsPath, encodePanelAppId(fileName)), state);
8959
+ return await this.entryPresenter.build(await this.packageStateManager.resolveSource(encodePanelAppId(fileName)), state, await this.packageStateManager.findPackageSourceBySourceName(fileName));
8256
8960
  };
8257
8961
  recordPanelAppOpened = async (id) => {
8258
8962
  const fileName = await this.resolvePanelAppFileName(id);
8259
8963
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
8260
8964
  const state = await this.createStateStore(panelsPath).recordOpened(encodePanelAppId(fileName));
8261
- return await this.buildPanelAppEntry(await this.sourceService.resolveSource(panelsPath, encodePanelAppId(fileName)), state);
8965
+ return await this.entryPresenter.build(await this.packageStateManager.resolveSource(encodePanelAppId(fileName)), state, await this.packageStateManager.findPackageSourceBySourceName(fileName));
8262
8966
  };
8263
8967
  deletePanelApp = async (id) => {
8264
8968
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
8265
- const source = await this.sourceService.resolveSource(panelsPath, id);
8969
+ const source = await this.packageStateManager.resolveSource(id);
8970
+ const packageSource = await this.packageStateManager.findPackageSourceBySourceName(source.sourceName);
8971
+ if (packageSource) throw new PanelAppError("PANEL_APP_MANAGED_SOURCE", `package panel must be managed through Apps: ${packageSource.packageId}`);
8266
8972
  const panelAppId = encodePanelAppId(source.sourceName);
8267
8973
  const appId = resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8")));
8268
8974
  await rm(source.sourcePath, { recursive: source.kind === "folder" });
@@ -8292,36 +8998,12 @@ var PanelAppManager = class {
8292
8998
  createStateStore = (panelsPath) => new PanelAppStateStore(panelsPath);
8293
8999
  createCapabilityGrantStore = () => new PanelAppCapabilityGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CAPABILITY_GRANTS_FILE_NAME));
8294
9000
  createClientGrantStore = () => new PanelAppClientGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CLIENT_GRANTS_FILE_NAME));
8295
- buildPanelAppEntry = async (source, state) => {
8296
- const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
8297
- const id = encodePanelAppId(source.sourceName);
8298
- const appId = resolvePanelAppAppId(source, manifest);
8299
- const createdAt = resolvePanelAppCreatedAt(source.sourceStat);
8300
- const updatedAt = source.sourceStat.mtime.toISOString();
8301
- const entry = {
8302
- id,
8303
- appId,
8304
- fileName: source.sourceName,
8305
- kind: source.kind,
8306
- title: manifest.title ?? toPanelAppTitle(source.sourceName),
8307
- contentPath: `${PANEL_APP_CONTENT_BASE_PATH}/${encodeURIComponent(id)}/content`,
8308
- createdAt,
8309
- updatedAt,
8310
- sizeBytes: source.sourceStat.size,
8311
- favorite: state.favorite ?? false,
8312
- clientDeclared: manifest.client,
8313
- clientGranted: await this.isPanelAppClientGranted(appId, manifest.client),
8314
- openCount: state.openCount ?? 0
8315
- };
8316
- if (manifest.description) entry.description = manifest.description;
8317
- if (manifest.icon) entry.icon = source.kind === "folder" ? resolvePanelAppIconUrl(id, manifest.icon) : manifest.icon;
8318
- if (state.lastOpenedAt) entry.lastOpenedAt = state.lastOpenedAt;
8319
- return entry;
8320
- };
8321
9001
  resolvePanelAppFileName = async (id) => {
8322
- return (await this.sourceService.resolveSource(this.getPanelsPath(this.getWorkspacePath()), id)).sourceName;
9002
+ return (await this.packageStateManager.resolveSource(id)).sourceName;
8323
9003
  };
8324
- comparePanelApps = (left, right) => resolvePanelAppActivityMs(right) - resolvePanelAppActivityMs(left) || Number(right.favorite) - Number(left.favorite) || left.title.localeCompare(right.title);
9004
+ assertCanActivatePackageComponents = async (components) => await this.packageStateManager.assertCanActivate(components);
9005
+ deactivatePackageComponents = (components) => this.packageStateManager.deactivate(components);
9006
+ removePackageComponentState = async (components) => await this.packageStateManager.removeState(components);
8325
9007
  deleteExpiredBridgeSessions = () => {
8326
9008
  const now = Date.now();
8327
9009
  for (const [token, session] of this.bridgeSessions) if (new Date(session.expiresAt).getTime() <= now) this.bridgeSessions.delete(token);
@@ -8777,7 +9459,7 @@ var McpServiceAppRuntimeService = class {
8777
9459
  command: manifest.command,
8778
9460
  args: manifest.args,
8779
9461
  cwd: app.dirPath,
8780
- env: createRuntimeChildEnv(process.env),
9462
+ env: createRuntimeChildEnv(process.env, this.createAppRuntimeEnv(app)),
8781
9463
  stderr: "pipe"
8782
9464
  },
8783
9465
  scope: {
@@ -8790,6 +9472,15 @@ var McpServiceAppRuntimeService = class {
8790
9472
  }
8791
9473
  }
8792
9474
  });
9475
+ createAppRuntimeEnv = (app) => {
9476
+ if (app.sourceKind !== "package" || !app.packageId || !app.packageVersion || !app.packageDirectory || !app.dataDirectory) return {};
9477
+ return {
9478
+ NEXTCLAW_APP_ID: app.packageId,
9479
+ NEXTCLAW_APP_VERSION: app.packageVersion,
9480
+ NEXTCLAW_APP_DATA_DIR: app.dataDirectory,
9481
+ NEXTCLAW_APP_PACKAGE_DIR: app.packageDirectory
9482
+ };
9483
+ };
8793
9484
  toServiceAction = (manifest, tool) => {
8794
9485
  const actionId = buildServiceActionId(manifest.id, tool.toolName);
8795
9486
  const manifestAction = manifest.actions[tool.toolName];
@@ -8884,12 +9575,12 @@ var ServiceActionGrantStore = class {
8884
9575
  };
8885
9576
  };
8886
9577
  function normalizeStoreData(value) {
8887
- if (!isRecord$7(value) || value.version !== 1 || !isRecord$7(value.grants)) return structuredClone(EMPTY_GRANTS);
9578
+ if (!isRecord$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS);
8888
9579
  const grants = {};
8889
9580
  for (const [callerKey, callerValue] of Object.entries(value.grants)) {
8890
- if (!isRecord$7(callerValue) || !isRecord$7(callerValue.actions)) continue;
9581
+ if (!isRecord$8(callerValue) || !isRecord$8(callerValue.actions)) continue;
8891
9582
  const actions = {};
8892
- for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$7(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
9583
+ for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$8(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
8893
9584
  grantedAt: actionValue.grantedAt,
8894
9585
  risk: actionValue.risk
8895
9586
  };
@@ -8903,11 +9594,11 @@ function normalizeStoreData(value) {
8903
9594
  function isServiceActionRisk(value) {
8904
9595
  return value === "read" || value === "write" || value === "external" || value === "dangerous";
8905
9596
  }
8906
- function isRecord$7(value) {
9597
+ function isRecord$8(value) {
8907
9598
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8908
9599
  }
8909
9600
  function isMissingFileError(error) {
8910
- return isRecord$7(error) && error.code === "ENOENT";
9601
+ return isRecord$8(error) && error.code === "ENOENT";
8911
9602
  }
8912
9603
  //#endregion
8913
9604
  //#region src/utils/service-app-manifest.utils.ts
@@ -8932,7 +9623,7 @@ function parseServiceAppManifest(raw) {
8932
9623
  } catch (error) {
8933
9624
  throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
8934
9625
  }
8935
- if (!isRecord$6(parsed)) throw new Error("service-app.json must contain an object.");
9626
+ if (!isRecord$7(parsed)) throw new Error("service-app.json must contain an object.");
8936
9627
  const id = readRequiredString$6(parsed, "id");
8937
9628
  if (!SERVICE_APP_ID_PATTERN.test(id)) throw new Error("service app id must be kebab-case.");
8938
9629
  const protocol = readOptionalString$7(parsed, "protocol") ?? "mcp";
@@ -8950,16 +9641,16 @@ function parseServiceAppManifest(raw) {
8950
9641
  }
8951
9642
  function readManifestActions(value) {
8952
9643
  if (value === void 0) throw new Error("service app actions are required.");
8953
- if (!isRecord$6(value)) throw new Error("service app actions must be an object.");
9644
+ if (!isRecord$7(value)) throw new Error("service app actions must be an object.");
8954
9645
  if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
8955
9646
  const actions = {};
8956
9647
  for (const [name, action] of Object.entries(value)) {
8957
9648
  if (!name.trim()) throw new Error("service app action name cannot be empty.");
8958
- if (!isRecord$6(action)) throw new Error(`service app action ${name} must be an object.`);
9649
+ if (!isRecord$7(action)) throw new Error(`service app action ${name} must be an object.`);
8959
9650
  const risk = readOptionalString$7(action, "risk");
8960
9651
  if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
8961
9652
  const inputSchema = action.inputSchema;
8962
- if (inputSchema !== void 0 && !isRecord$6(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
9653
+ if (inputSchema !== void 0 && !isRecord$7(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
8963
9654
  actions[name] = {
8964
9655
  risk,
8965
9656
  title: readOptionalString$7(action, "title"),
@@ -8987,7 +9678,7 @@ function readStringArray(value, key) {
8987
9678
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
8988
9679
  return value;
8989
9680
  }
8990
- function isRecord$6(value) {
9681
+ function isRecord$7(value) {
8991
9682
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8992
9683
  }
8993
9684
  //#endregion
@@ -9045,10 +9736,13 @@ var ServiceAppManager = class {
9045
9736
  const workspacePath = this.getWorkspacePath();
9046
9737
  const serviceAppsPath = this.getServiceAppsPath(workspacePath);
9047
9738
  const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
9739
+ const workspaceEntries = await Promise.all(dirNames.map((dirName) => this.buildServiceAppRecord(serviceAppsPath, dirName)));
9740
+ const packageSources = (await this.listPackageComponentSources()).filter((component) => component.kind === "service");
9741
+ const packageEntries = await Promise.all(packageSources.map((source) => this.buildServiceAppRecordFromPackage(source)));
9048
9742
  return {
9049
9743
  workspacePath,
9050
9744
  serviceAppsPath,
9051
- entries: (await Promise.all(dirNames.map((dirName) => this.buildServiceAppRecord(serviceAppsPath, dirName)))).filter((entry) => Boolean(entry)).sort((left, right) => left.title.localeCompare(right.title))
9745
+ entries: [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry)).sort((left, right) => left.title.localeCompare(right.title))
9052
9746
  };
9053
9747
  };
9054
9748
  getServiceApp = async (appId) => {
@@ -9126,6 +9820,7 @@ var ServiceAppManager = class {
9126
9820
  };
9127
9821
  deleteServiceApp = async (appId) => {
9128
9822
  const { record } = await this.requireServiceApp(appId);
9823
+ if (record.sourceKind === "package") throw new ServiceAppError("SERVICE_APP_MANAGED_SOURCE", `package service must be managed through Apps: ${record.packageId}`);
9129
9824
  await this.runtimeService.restart(record.id);
9130
9825
  await rm(record.dirPath, { recursive: true });
9131
9826
  await this.createGrantStore().revokeActionsByPrefix(`${record.id}.`);
@@ -9137,6 +9832,31 @@ var ServiceAppManager = class {
9137
9832
  dispose = async () => {
9138
9833
  await this.runtimeService.dispose();
9139
9834
  };
9835
+ assertCanActivatePackageComponents = async (components) => {
9836
+ const serviceComponents = components.filter((component) => component.kind === "service");
9837
+ if (serviceComponents.length === 0) return;
9838
+ const workspacePath = this.getServiceAppsPath(this.getWorkspacePath());
9839
+ const workspaceIds = /* @__PURE__ */ new Set();
9840
+ for (const dirName of await this.listServiceAppDirNames(workspacePath)) try {
9841
+ workspaceIds.add((await readServiceAppManifest(join(workspacePath, dirName))).id);
9842
+ } catch {
9843
+ continue;
9844
+ }
9845
+ const activePackageSources = await this.listPackageComponentSources();
9846
+ for (const component of serviceComponents) {
9847
+ const conflictsWithPackage = activePackageSources.some((active) => active.kind === "service" && active.id === component.id && active.packageId !== component.packageId);
9848
+ if (workspaceIds.has(component.id) || conflictsWithPackage) throw new AppPackageError("APP_PACKAGE_CONFLICT", `Service component id 冲突:${component.id}`);
9849
+ }
9850
+ };
9851
+ deactivatePackageComponents = async (components) => {
9852
+ const serviceIds = components.filter((component) => component.kind === "service").map((component) => component.id);
9853
+ await Promise.all(serviceIds.map(async (serviceId) => await this.runtimeService.restart(serviceId)));
9854
+ };
9855
+ removePackageComponentGrants = async (components) => {
9856
+ const serviceIds = components.filter((component) => component.kind === "service").map((component) => component.id);
9857
+ const grantStore = this.createGrantStore();
9858
+ for (const serviceId of serviceIds) await grantStore.revokeActionsByPrefix(`${serviceId}.`);
9859
+ };
9140
9860
  withGrantState = async (action, params) => {
9141
9861
  if (!params.caller) return action;
9142
9862
  const granted = await this.createGrantStore().isGranted(params.caller, action.id);
@@ -9161,14 +9881,23 @@ var ServiceAppManager = class {
9161
9881
  return await this.requireServiceApp(appId);
9162
9882
  };
9163
9883
  requireServiceApp = async (appId) => {
9164
- const dirPath = join(this.getServiceAppsPath(this.getWorkspacePath()), appId);
9884
+ let dirPath = join(this.getServiceAppsPath(this.getWorkspacePath()), appId);
9885
+ let packageSource;
9886
+ try {
9887
+ if (!(await stat(dirPath)).isDirectory()) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
9888
+ } catch (error) {
9889
+ if (!this.isMissingFileError(error)) throw error;
9890
+ packageSource = (await this.listPackageComponentSources()).find((component) => component.kind === "service" && component.id === appId);
9891
+ if (!packageSource) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
9892
+ dirPath = packageSource.sourcePath;
9893
+ }
9165
9894
  try {
9166
9895
  if (!(await stat(dirPath)).isDirectory()) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
9167
9896
  const manifest = await readServiceAppManifest(dirPath);
9168
9897
  if (manifest.id !== appId) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app manifest id must match directory name");
9169
9898
  return {
9170
9899
  manifest,
9171
- record: this.toServiceAppRecord(dirPath, manifest)
9900
+ record: this.toServiceAppRecord(dirPath, manifest, packageSource)
9172
9901
  };
9173
9902
  } catch (error) {
9174
9903
  if (isServiceAppError(error)) throw error;
@@ -9180,7 +9909,7 @@ var ServiceAppManager = class {
9180
9909
  const workspacePath = this.getWorkspacePath();
9181
9910
  const serviceAppsPath = this.getServiceAppsPath(workspacePath);
9182
9911
  const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
9183
- return (await Promise.all(dirNames.map(async (dirName) => {
9912
+ const workspaceEntries = await Promise.all(dirNames.map(async (dirName) => {
9184
9913
  const dirPath = join(serviceAppsPath, dirName);
9185
9914
  try {
9186
9915
  const manifest = await readServiceAppManifest(dirPath);
@@ -9191,7 +9920,19 @@ var ServiceAppManager = class {
9191
9920
  } catch {
9192
9921
  return null;
9193
9922
  }
9194
- }))).filter((entry) => Boolean(entry));
9923
+ }));
9924
+ const packageEntries = await Promise.all((await this.listPackageComponentSources()).filter((component) => component.kind === "service").map(async (component) => {
9925
+ try {
9926
+ const manifest = await readServiceAppManifest(component.sourcePath);
9927
+ return {
9928
+ manifest,
9929
+ record: this.toServiceAppRecord(component.sourcePath, manifest, component)
9930
+ };
9931
+ } catch {
9932
+ return null;
9933
+ }
9934
+ }));
9935
+ return [...workspaceEntries, ...packageEntries].filter((entry) => Boolean(entry));
9195
9936
  };
9196
9937
  buildServiceAppRecord = async (serviceAppsPath, dirName) => {
9197
9938
  const dirPath = join(serviceAppsPath, dirName);
@@ -9213,7 +9954,7 @@ var ServiceAppManager = class {
9213
9954
  };
9214
9955
  }
9215
9956
  };
9216
- toServiceAppRecord = (dirPath, manifest) => {
9957
+ toServiceAppRecord = (dirPath, manifest, packageSource) => {
9217
9958
  const runtimeStatus = this.runtimeService.getStatus(manifest.id);
9218
9959
  return {
9219
9960
  id: manifest.id,
@@ -9230,7 +9971,12 @@ var ServiceAppManager = class {
9230
9971
  lastError: runtimeStatus.lastError,
9231
9972
  lastStartedAt: runtimeStatus.lastStartedAt,
9232
9973
  lastReadyAt: runtimeStatus.lastReadyAt,
9233
- lastFailedAt: runtimeStatus.lastFailedAt
9974
+ lastFailedAt: runtimeStatus.lastFailedAt,
9975
+ sourceKind: packageSource ? "package" : "workspace",
9976
+ packageId: packageSource?.packageId,
9977
+ packageVersion: packageSource?.packageVersion,
9978
+ packageDirectory: packageSource ? join(packageSource.sourcePath, "..", "..") : void 0,
9979
+ dataDirectory: packageSource?.dataDirectory
9234
9980
  };
9235
9981
  };
9236
9982
  assertCaller = (caller) => {
@@ -9243,6 +9989,31 @@ var ServiceAppManager = class {
9243
9989
  getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
9244
9990
  getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
9245
9991
  createGrantStore = () => new ServiceActionGrantStore(join(this.getServiceAppsPath(this.getWorkspacePath()), SERVICE_ACTION_GRANTS_FILE_NAME));
9992
+ listPackageComponentSources = async () => await this.params.listPackageComponentSources?.() ?? [];
9993
+ buildServiceAppRecordFromPackage = async (source) => {
9994
+ try {
9995
+ const manifest = await readServiceAppManifest(source.sourcePath);
9996
+ if (manifest.id !== source.id) throw new Error(`service component id mismatch: ${source.id}`);
9997
+ return this.toServiceAppRecord(source.sourcePath, manifest, source);
9998
+ } catch (error) {
9999
+ return {
10000
+ id: source.id,
10001
+ title: toTitle(source.id),
10002
+ dirPath: source.sourcePath,
10003
+ manifestPath: source.manifestPath,
10004
+ cwd: source.sourcePath,
10005
+ enabled: false,
10006
+ protocol: "mcp",
10007
+ status: "failed",
10008
+ lastError: error instanceof Error ? error.message : String(error),
10009
+ sourceKind: "package",
10010
+ packageId: source.packageId,
10011
+ packageVersion: source.packageVersion,
10012
+ packageDirectory: join(source.sourcePath, "..", ".."),
10013
+ dataDirectory: source.dataDirectory
10014
+ };
10015
+ }
10016
+ };
9246
10017
  listServiceAppDirNames = async (serviceAppsPath) => {
9247
10018
  try {
9248
10019
  return (await readdir(serviceAppsPath, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
@@ -9456,7 +10227,7 @@ function parseFrontmatterBlock(raw) {
9456
10227
  function parseYamlFrontmatter(raw) {
9457
10228
  try {
9458
10229
  const parsed = parse(raw);
9459
- return isRecord$5(parsed) ? parsed : {};
10230
+ return isRecord$6(parsed) ? parsed : {};
9460
10231
  } catch (error) {
9461
10232
  const message = error instanceof Error ? error.message : String(error);
9462
10233
  throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
@@ -9468,7 +10239,7 @@ function readString$5(record, ...names) {
9468
10239
  }
9469
10240
  function readLocalizedTextMap(record, ...names) {
9470
10241
  const value = readValue(record, names);
9471
- if (!isRecord$5(value)) return;
10242
+ if (!isRecord$6(value)) return;
9472
10243
  const localized = Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0).map(([locale, text]) => [normalizeLocaleTag(locale), text.trim()]));
9473
10244
  return Object.keys(localized).length > 0 ? localized : void 0;
9474
10245
  }
@@ -9492,7 +10263,7 @@ function normalizeFrontmatterKey(raw) {
9492
10263
  function normalizeLocaleTag(raw) {
9493
10264
  return raw.trim().toLowerCase();
9494
10265
  }
9495
- function isRecord$5(value) {
10266
+ function isRecord$6(value) {
9496
10267
  return typeof value === "object" && value !== null && !Array.isArray(value);
9497
10268
  }
9498
10269
  //#endregion
@@ -9639,23 +10410,61 @@ function resolveAgentHandoffDepth(metadata) {
9639
10410
  }
9640
10411
  //#endregion
9641
10412
  //#region src/utils/ncp-agent-unfinished-run.utils.ts
9642
- function readUnfinishedNcpAgentRun(sessionId, events) {
9643
- let activeRun = null;
9644
- for (const event of events) {
9645
- if (event.type === NcpEventType.RunStarted) {
9646
- activeRun = {
9647
- sessionId,
9648
- ...event.payload.messageId ? { messageId: event.payload.messageId } : {},
9649
- ...event.payload.runId ? { runId: event.payload.runId } : {},
9650
- ...event.payload.startedAt ? { startedAt: event.payload.startedAt } : {}
9651
- };
9652
- continue;
9653
- }
9654
- if (activeRun && (event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError || event.type === NcpEventType.MessageAbort) && (!event.payload.runId || !activeRun.runId || event.payload.runId === activeRun.runId)) activeRun = null;
9655
- }
10413
+ function applyNcpAgentRunLifecycleEvent(sessionId, activeRun, event) {
10414
+ if (event.type === NcpEventType.RunStarted) return {
10415
+ sessionId,
10416
+ ...event.payload.messageId ? { messageId: event.payload.messageId } : {},
10417
+ ...event.payload.runId ? { runId: event.payload.runId } : {},
10418
+ ...event.payload.startedAt ? { startedAt: event.payload.startedAt } : {}
10419
+ };
10420
+ if (activeRun && (event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError || event.type === NcpEventType.MessageAbort) && (!event.payload.runId || !activeRun.runId || event.payload.runId === activeRun.runId)) return null;
9656
10421
  return activeRun;
9657
10422
  }
9658
10423
  //#endregion
10424
+ //#region src/stores/ncp-agent-unfinished-run.store.ts
10425
+ var NcpAgentUnfinishedRunStore = class {
10426
+ constructor(journalDir, listSessionIds) {
10427
+ this.journalDir = journalDir;
10428
+ this.listSessionIds = listSessionIds;
10429
+ }
10430
+ list = async () => {
10431
+ const runs = [];
10432
+ for (const sessionId of await this.listSessionIds()) {
10433
+ const run = await this.read(sessionId);
10434
+ if (run) runs.push(run);
10435
+ }
10436
+ return runs;
10437
+ };
10438
+ read = async (sessionId) => {
10439
+ const input = createReadStream(this.sessionPath(sessionId), { encoding: "utf-8" });
10440
+ const lines = createInterface({
10441
+ input,
10442
+ crlfDelay: Infinity
10443
+ });
10444
+ let activeRun = null;
10445
+ try {
10446
+ for await (const line of lines) {
10447
+ if (!line.trim()) continue;
10448
+ let parsed;
10449
+ try {
10450
+ parsed = JSON.parse(line);
10451
+ } catch {
10452
+ continue;
10453
+ }
10454
+ if (!isRecord$13(parsed) || parsed._type !== "event" || !isRecord$13(parsed.event)) continue;
10455
+ activeRun = applyNcpAgentRunLifecycleEvent(sessionId, activeRun, parsed.event);
10456
+ }
10457
+ return activeRun;
10458
+ } catch {
10459
+ return null;
10460
+ } finally {
10461
+ lines.close();
10462
+ input.destroy();
10463
+ }
10464
+ };
10465
+ sessionPath = (sessionId) => join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.jsonl`);
10466
+ };
10467
+ //#endregion
9659
10468
  //#region src/utils/ncp-agent-session-journal-entry.utils.ts
9660
10469
  function isNcpAgentSessionMessageProjectionBoundaryEvent(event) {
9661
10470
  return event.type === NcpEventType.MessageSent || event.type === NcpEventType.MessageCompleted || event.type === NcpEventType.MessageAbort || event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError || event.type === "session.snapshot.message";
@@ -9663,11 +10472,11 @@ function isNcpAgentSessionMessageProjectionBoundaryEvent(event) {
9663
10472
  function serializeNcpAgentSessionJournalEntry(entry) {
9664
10473
  const serialized = JSON.stringify(entry);
9665
10474
  if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
9666
- if (!isRecord$12(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
10475
+ if (!isRecord$13(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
9667
10476
  return serialized;
9668
10477
  }
9669
10478
  function attachNcpAgentSessionJournalTimestamp(event, timestamp) {
9670
- if (!("payload" in event) || !isRecord$12(event.payload)) return event;
10479
+ if (!("payload" in event) || !isRecord$13(event.payload)) return event;
9671
10480
  return {
9672
10481
  ...event,
9673
10482
  payload: {
@@ -9692,7 +10501,7 @@ var NcpAgentSessionJournalParser = class {
9692
10501
  this.applyMetadata(parsed);
9693
10502
  return;
9694
10503
  }
9695
- if (parsed._type === "event" && isRecord$12(parsed.event)) this.applyEvent(parsed, lineEndOffset);
10504
+ if (parsed._type === "event" && isRecord$13(parsed.event)) this.applyEvent(parsed, lineEndOffset);
9696
10505
  };
9697
10506
  finish = () => ({
9698
10507
  metadata: this.metadata,
@@ -9706,14 +10515,14 @@ var NcpAgentSessionJournalParser = class {
9706
10515
  parseLine = (line, index) => {
9707
10516
  try {
9708
10517
  const parsed = JSON.parse(line);
9709
- return isRecord$12(parsed) ? parsed : null;
10518
+ return isRecord$13(parsed) ? parsed : null;
9710
10519
  } catch (error) {
9711
10520
  console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
9712
10521
  return null;
9713
10522
  }
9714
10523
  };
9715
10524
  applyMetadata = (entry) => {
9716
- this.metadata = isRecord$12(entry.metadata) ? structuredClone(entry.metadata) : {};
10525
+ this.metadata = isRecord$13(entry.metadata) ? structuredClone(entry.metadata) : {};
9717
10526
  this.agentId = normalizeNcpAgentId(typeof entry.agent_id === "string" ? entry.agent_id : void 0);
9718
10527
  this.createdAt = toIsoString(entry.created_at, this.createdAt);
9719
10528
  this.updatedAt = toIsoString(entry.updated_at, this.updatedAt);
@@ -9740,25 +10549,6 @@ function parseNcpAgentSessionJournal(raw) {
9740
10549
  return parser.finish();
9741
10550
  }
9742
10551
  //#endregion
9743
- //#region src/stores/ncp-agent-unfinished-run.store.ts
9744
- var NcpAgentUnfinishedRunStore = class {
9745
- constructor(journalDir, listSessionIds) {
9746
- this.journalDir = journalDir;
9747
- this.listSessionIds = listSessionIds;
9748
- }
9749
- list = async () => {
9750
- return (await Promise.all((await this.listSessionIds()).map(this.read))).filter((run) => run !== null);
9751
- };
9752
- read = async (sessionId) => {
9753
- try {
9754
- return readUnfinishedNcpAgentRun(sessionId, parseNcpAgentSessionJournal(await readFile(this.sessionPath(sessionId), "utf-8")).events);
9755
- } catch {
9756
- return null;
9757
- }
9758
- };
9759
- sessionPath = (sessionId) => join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.jsonl`);
9760
- };
9761
- //#endregion
9762
10552
  //#region src/stores/ncp-agent-session-metadata.store.ts
9763
10553
  var NcpAgentSessionMetadataStore = class {
9764
10554
  constructor(journalDir) {
@@ -9779,7 +10569,7 @@ var NcpAgentSessionMetadataStore = class {
9779
10569
  read = async (sessionId, activitySnapshot) => {
9780
10570
  try {
9781
10571
  const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
9782
- if (!isRecord$12(parsed) || parsed._type !== "metadata" || !isRecord$12(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
10572
+ if (!isRecord$13(parsed) || parsed._type !== "metadata" || !isRecord$13(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
9783
10573
  const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
9784
10574
  const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
9785
10575
  return {
@@ -9849,7 +10639,7 @@ function deduplicateNcpAgentSessionTailMessages(messages) {
9849
10639
  function isNcpAgentSessionMessageProjectionMeta(value, sessionId) {
9850
10640
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
9851
10641
  const meta = value;
9852
- return meta.version === 6 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$4(meta.contextWindow));
10642
+ return meta.version === 6 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$5(meta.contextWindow));
9853
10643
  }
9854
10644
  function readActiveAssistantMessageId(messages) {
9855
10645
  for (let index = messages.length - 1; index >= 0; index -= 1) {
@@ -9872,12 +10662,12 @@ function mergePendingCompactionMessageIds(current, messages) {
9872
10662
  }
9873
10663
  return pending;
9874
10664
  }
9875
- function isRecord$4(value) {
10665
+ function isRecord$5(value) {
9876
10666
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9877
10667
  }
9878
10668
  function readCompactionStatus(message) {
9879
10669
  const checkpoint = message.metadata?.checkpoint;
9880
- if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$4(checkpoint)) return null;
10670
+ if (message.metadata?.nextclaw_timeline_kind !== "context_compaction" || !isRecord$5(checkpoint)) return null;
9881
10671
  return typeof checkpoint.status === "string" ? checkpoint.status : null;
9882
10672
  }
9883
10673
  //#endregion
@@ -11894,7 +12684,7 @@ var CurrentSessionContextProvider = class {
11894
12684
  const MAX_EXCERPT_COUNT = 16;
11895
12685
  const MAX_EXCERPT_CHARACTERS$1 = 8e3;
11896
12686
  const MAX_TOTAL_CONTEXT_CHARACTERS$1 = 96e3;
11897
- function isRecord$3(value) {
12687
+ function isRecord$4(value) {
11898
12688
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11899
12689
  }
11900
12690
  function readString$3(value) {
@@ -11905,11 +12695,11 @@ function escapeAttribute$1(value) {
11905
12695
  }
11906
12696
  function readConversationExcerpts(metadata) {
11907
12697
  const raw = metadata?.[CHAT_INLINE_TOKENS_METADATA_KEY];
11908
- const entries = isRecord$3(raw) && raw.schemaVersion === CHAT_INLINE_TOKENS_SCHEMA_VERSION && Array.isArray(raw.items) ? raw.items : [];
12698
+ const entries = isRecord$4(raw) && raw.schemaVersion === CHAT_INLINE_TOKENS_SCHEMA_VERSION && Array.isArray(raw.items) ? raw.items : [];
11909
12699
  const excerpts = [];
11910
12700
  const seen = /* @__PURE__ */ new Set();
11911
12701
  for (const entry of entries) {
11912
- if (!isRecord$3(entry) || entry.kind !== CHAT_CONVERSATION_EXCERPT_TOKEN_KIND) continue;
12702
+ if (!isRecord$4(entry) || entry.kind !== CHAT_CONVERSATION_EXCERPT_TOKEN_KIND) continue;
11913
12703
  const key = readString$3(entry.key);
11914
12704
  const messageId = readString$3(entry.messageId);
11915
12705
  const label = readString$3(entry.label);
@@ -11954,27 +12744,63 @@ var ConversationExcerptContextProvider = class {
11954
12744
  };
11955
12745
  };
11956
12746
  //#endregion
11957
- //#region src/contributions/context-provider/providers/inbox-delivery-context.provider.ts
11958
- var InboxDeliveryContextProvider = class {
11959
- constructor(deliveryManager, sessionManager) {
11960
- this.deliveryManager = deliveryManager;
11961
- this.sessionManager = sessionManager;
12747
+ //#region src/contributions/context-provider/providers/system-object-reference-context.provider.ts
12748
+ function isRecord$3(value) {
12749
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
12750
+ }
12751
+ function readSystemObjectReferences(metadata) {
12752
+ const raw = metadata?.[CHAT_INLINE_TOKENS_METADATA_KEY];
12753
+ if (!isRecord$3(raw) || raw.schemaVersion !== CHAT_INLINE_TOKENS_SCHEMA_VERSION || !Array.isArray(raw.items)) return [];
12754
+ const references = [];
12755
+ const seen = /* @__PURE__ */ new Set();
12756
+ for (const item of raw.items) {
12757
+ if (!isRecord$3(item) || item.kind !== CHAT_SYSTEM_OBJECT_TOKEN_KIND) continue;
12758
+ const reference = readSystemObjectResolvedReference(item.reference);
12759
+ if (!reference || seen.has(`${reference.uri}@${reference.version}`)) continue;
12760
+ seen.add(`${reference.uri}@${reference.version}`);
12761
+ references.push(reference);
12762
+ }
12763
+ return references;
12764
+ }
12765
+ var SystemObjectReferenceContextProvider = class {
12766
+ constructor(assetStore) {
12767
+ this.assetStore = assetStore;
11962
12768
  }
11963
12769
  provide = async (request) => {
11964
- if (!request.sessionId) return [];
11965
- const rawDeliveryId = (await this.sessionManager.getSessionRecord(request.sessionId))?.metadata?.[INBOX_DELIVERY_SESSION_METADATA_KEY];
11966
- if (typeof rawDeliveryId !== "string" || !rawDeliveryId.trim()) return [];
11967
- const delivery = await this.deliveryManager.getDelivery(rawDeliveryId);
11968
- if (!delivery) return [];
11969
- const lines = [
11970
- "## Inbox Delivery Context",
11971
- "This chat was opened from a durable inbox delivery. Treat the following content as the shared subject for this conversation.",
11972
- `Delivery ID: ${delivery.id}`,
11973
- `Title: ${delivery.title}`
11974
- ];
11975
- if (delivery.summary) lines.push(`Summary: ${delivery.summary}`);
11976
- lines.push("", "### Delivered content", delivery.content);
11977
- return [lines.join("\n")];
12770
+ const references = readSystemObjectReferences(request.message.metadata ?? request.metadata);
12771
+ if (references.length === 0) return [];
12772
+ return [[
12773
+ "## Explicit System Object References",
12774
+ "The user visibly referenced these immutable NextClaw-managed object snapshots in the current message.",
12775
+ "",
12776
+ (await Promise.all(references.map(async (reference) => {
12777
+ const asset = await this.assetStore.statRecord(reference.assetUri);
12778
+ if (!asset || asset.sha256 !== reference.version || asset.sizeBytes !== reference.sizeBytes || asset.fileName !== reference.fileName || asset.mimeType !== reference.mimeType || !isTextLikeAsset({
12779
+ mimeType: asset.mimeType,
12780
+ fileName: asset.fileName
12781
+ })) return [
12782
+ `### ${reference.label}`,
12783
+ `Object: ${reference.uri}`,
12784
+ `Version: ${reference.version}`,
12785
+ "Snapshot unavailable. Do not fall back to the live object."
12786
+ ].join("\n");
12787
+ const bytes = await this.assetStore.readAssetBytes(reference.assetUri);
12788
+ if (!bytes) return [
12789
+ `### ${reference.label}`,
12790
+ `Object: ${reference.uri}`,
12791
+ `Version: ${reference.version}`,
12792
+ "Snapshot unavailable. Do not fall back to the live object."
12793
+ ].join("\n");
12794
+ return [
12795
+ `### ${reference.label}`,
12796
+ `Object: ${reference.uri}`,
12797
+ `Type: ${reference.objectType}`,
12798
+ `Version: ${reference.version}`,
12799
+ "",
12800
+ bytes.toString("utf8")
12801
+ ].join("\n");
12802
+ }))).join("\n\n")
12803
+ ].join("\n")];
11978
12804
  };
11979
12805
  };
11980
12806
  //#endregion
@@ -12051,23 +12877,24 @@ const createCliQuickReferenceContextProvider = () => {
12051
12877
  return staticBlock([
12052
12878
  `## ${APP_NAME} CLI Quick Reference`,
12053
12879
  `${APP_NAME} is controlled via subcommands. Do not invent commands.`,
12054
- "To manage the Gateway daemon service (start/stop/restart):",
12055
- `- ${appLower} gateway status`,
12056
- `- ${appLower} gateway start`,
12057
- `- ${appLower} gateway stop`,
12058
- `- ${appLower} gateway restart`,
12059
- `If unsure, ask the user to run \`${appLower} help\` (or \`${appLower} gateway --help\`) and paste the output.`
12880
+ "To manage the background service:",
12881
+ `- ${appLower} status`,
12882
+ `- ${appLower} start`,
12883
+ `- ${appLower} restart`,
12884
+ `- ${appLower} stop`,
12885
+ `The \`${appLower} gateway\` command starts a foreground gateway; it has no lifecycle subcommands.`,
12886
+ `If unsure, ask the user to run \`${appLower} help\` and paste the output.`
12060
12887
  ]);
12061
12888
  };
12062
12889
  const createSelfUpdateContextProvider = () => staticBlock([
12063
12890
  `## ${APP_NAME} Self-Update`,
12064
12891
  "Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.",
12065
12892
  "Do not run config.apply or update.run unless the user explicitly requests an update or config change; if it's not explicit, ask first.",
12066
- "Actions: config.get, config.schema, config.apply (validate + write full config, then restart), config.patch (merge + restart), update.run (update deps or git, then restart).",
12893
+ "Actions: config.get, config.schema, config.apply (validate + write full config), config.patch (merge config), update.run (update the runtime and relaunch the service).",
12067
12894
  "When patching config, copy enum values exactly from config.schema; never invent new variants.",
12068
12895
  "session.dmScope legal values are exactly: main | per-peer | per-channel-peer | per-account-channel-peer.",
12069
12896
  "If an enum/path is uncertain, stop and call config.schema first; do not guess.",
12070
- `After restart, ${APP_NAME} pings the last active session automatically.`
12897
+ `If a config change requires restart, tell the user to run \`${APP_NAME.toLowerCase()} restart\` in an external terminal. Do not run it from the active agent session.`
12071
12898
  ]);
12072
12899
  const createReplyTagsContextProvider = () => staticBlock([
12073
12900
  "## Reply Tags",
@@ -12830,7 +13657,7 @@ var ContextProviderContribution = class {
12830
13657
  new SkillsContextProvider(context),
12831
13658
  createSessionOrchestrationContextProvider(),
12832
13659
  new ExecutionPolicyContextProvider(context),
12833
- new InboxDeliveryContextProvider(this.kernel.inboxDeliveryManager, this.kernel.sessionManager),
13660
+ new SystemObjectReferenceContextProvider(this.kernel.assetStore),
12834
13661
  new CurrentSessionContextProvider(context),
12835
13662
  new ReplyFormatContextProvider()
12836
13663
  ]) this.cleanups.push(this.kernel.contextProviderManager.register(provider));
@@ -14186,6 +15013,10 @@ var ToolProviderContribution = class {
14186
15013
  };
14187
15014
  //#endregion
14188
15015
  //#region src/app/nextclaw-kernel.ts
15016
+ function resolveKernelAppHomeDirectory(options) {
15017
+ const homeDir = options.homeDir?.trim();
15018
+ return resolve(homeDir ? expandHome(homeDir) : getDataDir(), "apps");
15019
+ }
14189
15020
  function resolveKernelSessionsDir(options) {
14190
15021
  const homeDir = options.homeDir?.trim();
14191
15022
  if (homeDir) return ensureDir(resolve(expandHome(homeDir), "sessions"));
@@ -14234,6 +15065,7 @@ var NextclawKernel = class {
14234
15065
  control;
14235
15066
  skills;
14236
15067
  automation;
15068
+ appPackageManager;
14237
15069
  channels;
14238
15070
  sessionRequests;
14239
15071
  sessionSearch;
@@ -14241,6 +15073,7 @@ var NextclawKernel = class {
14241
15073
  mcpManager;
14242
15074
  sessionManager;
14243
15075
  inboxDeliveryManager;
15076
+ systemObjectReferenceManager;
14244
15077
  panelAppManager;
14245
15078
  preferenceManager;
14246
15079
  projectManager;
@@ -14295,16 +15128,26 @@ var NextclawKernel = class {
14295
15128
  });
14296
15129
  this.inboxDeliveryManager = new InboxDeliveryManager({
14297
15130
  eventBus: this.eventBus,
14298
- sessionManager: this.sessionManager,
14299
15131
  storePath: resolveKernelInboxDeliveryStorePath(options)
14300
15132
  });
15133
+ this.systemObjectReferenceManager = new SystemObjectReferenceManager(this.assetStore, [createInboxDeliverySystemObjectProvider(this.inboxDeliveryManager), createCronJobSystemObjectProvider(this.automation)]);
15134
+ this.appPackageManager = new AppPackageManager({
15135
+ appHomeDirectory: resolveKernelAppHomeDirectory(options),
15136
+ builtInAppsDirectory: options.builtInAppsDirectory,
15137
+ productVersion: options.productVersion
15138
+ });
14301
15139
  this.panelAppManager = new PanelAppManager({
14302
15140
  configManager: this.configManager,
14303
15141
  eventBus: this.eventBus,
14304
- ingress: this.ingress
15142
+ ingress: this.ingress,
15143
+ listPackageComponentSources: this.appPackageManager.listActiveComponentSources
14305
15144
  });
14306
15145
  this.preferenceManager = new PreferenceManager({ storePath: resolveKernelPreferenceStorePath(options) });
14307
- this.serviceAppManager = new ServiceAppManager({ configManager: this.configManager });
15146
+ this.serviceAppManager = new ServiceAppManager({
15147
+ configManager: this.configManager,
15148
+ listPackageComponentSources: this.appPackageManager.listActiveComponentSources
15149
+ });
15150
+ this.installAppPackageRuntimeHooks();
14308
15151
  this.extensions = new ExtensionManager({
14309
15152
  configManager: this.configManager,
14310
15153
  eventBus: this.eventBus,
@@ -14344,6 +15187,22 @@ var NextclawKernel = class {
14344
15187
  new ContextWindowContribution(this)
14345
15188
  ];
14346
15189
  }
15190
+ installAppPackageRuntimeHooks = () => {
15191
+ this.appPackageManager.installRuntimeHooks({
15192
+ assertCanActivate: async (sources) => {
15193
+ await this.panelAppManager.assertCanActivatePackageComponents(sources);
15194
+ await this.serviceAppManager.assertCanActivatePackageComponents(sources);
15195
+ },
15196
+ beforeDeactivate: async (sources) => {
15197
+ this.panelAppManager.deactivatePackageComponents(sources);
15198
+ await this.serviceAppManager.deactivatePackageComponents(sources);
15199
+ },
15200
+ beforeUninstall: async (sources) => {
15201
+ await this.panelAppManager.removePackageComponentState(sources);
15202
+ await this.serviceAppManager.removePackageComponentGrants(sources);
15203
+ }
15204
+ });
15205
+ };
14347
15206
  listSessionTypes = (params) => this.agentRuntimeManager.listSessionTypes(params);
14348
15207
  isSessionRunning = (sessionId) => this.sessionRunManager.isSessionRunning(sessionId);
14349
15208
  provideGatewayController = (gatewayController) => {
@@ -14944,6 +15803,6 @@ function resolveLegacyEventType(message) {
14944
15803
  return `message.${role || "other"}`;
14945
15804
  }
14946
15805
  //#endregion
14947
- export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, 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, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildServiceActionId, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextCompactionMessageId, createContextWindowSignature, createLlmUsageRecord, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getAutomaticUpdateCheckDelay, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, injectUiContentParamsBootstrap, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, 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 };
15806
+ 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 };
14948
15807
 
14949
15808
  //# sourceMappingURL=index.js.map