@nextclaw/kernel 0.6.24 → 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,14 +3,15 @@ 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
10
  import { appendFileSync, chmodSync, constants, createReadStream, 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";
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";
@@ -1232,7 +1233,7 @@ const SESSION_ACTIVITY_PREVIEW_STATUS_KINDS = new Set([
1232
1233
  "run-failed",
1233
1234
  "run-interrupted"
1234
1235
  ]);
1235
- function isRecord$15(value) {
1236
+ function isRecord$16(value) {
1236
1237
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1237
1238
  }
1238
1239
  function readOptionalString$12(value) {
@@ -1241,7 +1242,7 @@ function readOptionalString$12(value) {
1241
1242
  return trimmed.length > 0 ? trimmed : void 0;
1242
1243
  }
1243
1244
  function readSessionActivityPreviewMetadata(value) {
1244
- if (!isRecord$15(value)) return null;
1245
+ if (!isRecord$16(value)) return null;
1245
1246
  const state = value.state;
1246
1247
  const timestamp = readOptionalString$12(value.timestamp);
1247
1248
  if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
@@ -1335,7 +1336,7 @@ var SessionActivityPreviewEventService = class {
1335
1336
  //#endregion
1336
1337
  //#region src/managers/agent-run-session-command.manager.ts
1337
1338
  const CONTINUATION_PROMPT = "Continue from where you stopped. Preserve completed work and avoid repeating it.";
1338
- function isRecord$14(value) {
1339
+ function isRecord$15(value) {
1339
1340
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1340
1341
  }
1341
1342
  function hasSendableMessageContent(message) {
@@ -1343,7 +1344,7 @@ function hasSendableMessageContent(message) {
1343
1344
  }
1344
1345
  function readSessionActivityState(metadata) {
1345
1346
  const preview = metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY];
1346
- return isRecord$14(preview) && typeof preview.state === "string" ? preview.state : null;
1347
+ return isRecord$15(preview) && typeof preview.state === "string" ? preview.state : null;
1347
1348
  }
1348
1349
  var AgentRunSessionCommandManager = class {
1349
1350
  pendingCommands = /* @__PURE__ */ new Map();
@@ -2504,6 +2505,318 @@ var AutomationManager = class extends CronService {
2504
2505
  }
2505
2506
  };
2506
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
2507
2820
  //#region src/managers/channel.manager.ts
2508
2821
  var ChannelManager = class {
2509
2822
  channels = {};
@@ -4551,11 +4864,11 @@ var ExtensionManager = class {
4551
4864
  //#endregion
4552
4865
  //#region src/utils/model-message-vision.utils.ts
4553
4866
  const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
4554
- function isRecord$13(value) {
4867
+ function isRecord$14(value) {
4555
4868
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4556
4869
  }
4557
4870
  function isImageContentPart(value) {
4558
- if (!isRecord$13(value)) return false;
4871
+ if (!isRecord$14(value)) return false;
4559
4872
  const type = value.type;
4560
4873
  return type === "image_url" || type === "input_image";
4561
4874
  }
@@ -4571,7 +4884,7 @@ function normalizeContentWithoutVision(content) {
4571
4884
  };
4572
4885
  });
4573
4886
  if (!sawImage) return content;
4574
- 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);
4575
4888
  if (textParts.length === parts.length) return textParts.join("\n\n");
4576
4889
  return parts;
4577
4890
  }
@@ -5206,7 +5519,8 @@ var LlmUsageManager = class {
5206
5519
  };
5207
5520
  //#endregion
5208
5521
  //#region src/stores/inbox-delivery.store.ts
5209
- const INBOX_DELIVERY_STORE_VERSION = 1;
5522
+ const INBOX_DELIVERY_STORE_VERSION = 2;
5523
+ const LEGACY_INBOX_DELIVERY_STORE_VERSION = 1;
5210
5524
  var InboxDeliveryStoreError = class extends Error {
5211
5525
  constructor(message) {
5212
5526
  super(message);
@@ -5243,15 +5557,19 @@ var InboxDeliveryStore = class {
5243
5557
  };
5244
5558
  parseStoreFile = (source) => {
5245
5559
  const value = JSON.parse(source);
5246
- 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");
5247
5561
  return {
5248
5562
  version: INBOX_DELIVERY_STORE_VERSION,
5249
- deliveries: value.deliveries.map((delivery) => structuredClone(delivery))
5563
+ deliveries: value.deliveries.map((delivery) => this.toCurrentDelivery(delivery))
5250
5564
  };
5251
5565
  };
5252
- isDelivery = (value) => {
5566
+ isDelivery = (value, legacy) => {
5253
5567
  if (!this.isRecord(value) || !this.isSource(value.source)) return false;
5254
- 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);
5255
5573
  };
5256
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);
5257
5575
  isOptionalString = (value) => value === null || typeof value === "string";
@@ -5309,8 +5627,7 @@ var InboxDeliveryManager = class {
5309
5627
  updatedAt: now,
5310
5628
  presentedAt: null,
5311
5629
  readAt: null,
5312
- archivedAt: null,
5313
- conversationSessionId: null
5630
+ archivedAt: null
5314
5631
  };
5315
5632
  const deliveries = await this.store.list();
5316
5633
  await this.store.save([delivery, ...deliveries]);
@@ -5337,41 +5654,6 @@ var InboxDeliveryManager = class {
5337
5654
  this.publishChange(deliveryId, "delete");
5338
5655
  return true;
5339
5656
  });
5340
- continueInChat = async (deliveryId) => await this.mutate(async () => {
5341
- const deliveries = await this.store.list();
5342
- const index = deliveries.findIndex(({ id }) => id === deliveryId);
5343
- if (index < 0) throw this.notFound(deliveryId);
5344
- const current = deliveries[index];
5345
- const existingSession = current.conversationSessionId ? await this.options.sessionManager.getSessionRecord(current.conversationSessionId) : null;
5346
- let sessionId = current.conversationSessionId;
5347
- let created = false;
5348
- if (!sessionId || !existingSession) {
5349
- sessionId = (await this.options.sessionManager.createSession({
5350
- sourceSessionMetadata: {},
5351
- metadataOverrides: { [INBOX_DELIVERY_SESSION_METADATA_KEY]: current.id },
5352
- task: `Continue discussing inbox delivery: ${current.title}`,
5353
- title: current.title,
5354
- agentId: current.source.agentId ?? void 0
5355
- })).sessionId;
5356
- created = true;
5357
- }
5358
- const now = (/* @__PURE__ */ new Date()).toISOString();
5359
- const delivery = {
5360
- ...current,
5361
- updatedAt: now,
5362
- presentedAt: current.presentedAt ?? now,
5363
- readAt: current.readAt ?? now,
5364
- conversationSessionId: sessionId
5365
- };
5366
- deliveries[index] = delivery;
5367
- await this.store.save(deliveries);
5368
- this.publishChange(deliveryId, "upsert");
5369
- return {
5370
- delivery: structuredClone(delivery),
5371
- sessionId,
5372
- created
5373
- };
5374
- });
5375
5657
  applyStateAction = (delivery, action, now) => {
5376
5658
  switch (action) {
5377
5659
  case "present": return {
@@ -5427,6 +5709,300 @@ var InboxDeliveryManager = class {
5427
5709
  };
5428
5710
  };
5429
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
5430
6006
  //#region src/managers/mcp.manager.ts
5431
6007
  var McpManager = class {
5432
6008
  currentMcpConfig;
@@ -5521,7 +6097,7 @@ function safeNcpSessionFilename(value) {
5521
6097
  function normalizeNcpAgentId(agentId) {
5522
6098
  return agentId?.trim().toLowerCase() || void 0;
5523
6099
  }
5524
- function isRecord$12(value) {
6100
+ function isRecord$13(value) {
5525
6101
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5526
6102
  }
5527
6103
  function toIsoString(value, fallback) {
@@ -5651,7 +6227,7 @@ function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
5651
6227
  } : message;
5652
6228
  }
5653
6229
  function readLegacyContextCompactionMessageId(message) {
5654
- const checkpoint = isRecord$12(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
6230
+ const checkpoint = isRecord$13(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
5655
6231
  const checkpointId = typeof checkpoint?.id === "string" ? checkpoint.id : "";
5656
6232
  const coveredCount = checkpoint?.coveredSessionMessageCount;
5657
6233
  const legacyId = `${message?.sessionId}:service:context-compaction:${checkpointId}`;
@@ -5683,11 +6259,11 @@ function readReplayMessageId(event) {
5683
6259
  return readMessageFromSummaryEvent(event)?.id ?? null;
5684
6260
  }
5685
6261
  function readEventSessionId$2(event) {
5686
- 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;
5687
6263
  return typeof sessionId === "string" ? sessionId : "";
5688
6264
  }
5689
6265
  function readReplayPayloadTimestamp(event) {
5690
- 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;
5691
6267
  const timestamp = typeof payload?.timestamp === "string" ? payload.timestamp : "";
5692
6268
  return Number.isFinite(Date.parse(timestamp)) ? new Date(timestamp).toISOString() : null;
5693
6269
  }
@@ -6498,1424 +7074,1621 @@ function isPanelAppAgentCapability(value) {
6498
7074
  return PANEL_APP_AGENT_CAPABILITIES.includes(value);
6499
7075
  }
6500
7076
  //#endregion
6501
- //#region src/services/panel-app-asset-token.service.ts
6502
- const PANEL_APP_ASSET_TOKEN_TTL_MS = 7200 * 1e3;
6503
- var PanelAppAssetTokenService = class {
6504
- now;
6505
- secret;
6506
- ttlMs;
6507
- constructor(params = {}) {
6508
- this.now = params.now ?? Date.now;
6509
- this.secret = params.secret ?? randomBytes(32);
6510
- this.ttlMs = params.ttlMs ?? PANEL_APP_ASSET_TOKEN_TTL_MS;
6511
- }
6512
- issue = (params) => {
6513
- const claims = {
6514
- panelAppId: params.panelAppId,
6515
- sourceName: params.sourceName,
6516
- sourcePath: params.sourcePath,
6517
- expiresAt: this.now() + this.ttlMs,
6518
- nonce: randomBytes(12).toString("base64url")
6519
- };
6520
- const payload = Buffer.from(JSON.stringify(claims), "utf8").toString("base64url");
6521
- 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)
6522
7087
  };
6523
- verify = (token) => {
6524
- const [payload, signature, ...rest] = token.trim().split(".");
6525
- if (!payload || !signature || rest.length > 0 || !this.matchesSignature(payload, signature)) throw new PanelAppError("PANEL_APP_ASSET_TOKEN_INVALID", "invalid panel app asset token");
6526
- const claims = this.parseClaims(payload);
6527
- if (claims.expiresAt <= this.now()) throw new PanelAppError("PANEL_APP_ASSET_TOKEN_EXPIRED", "panel app asset token expired");
6528
- 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")
6529
7108
  };
6530
- sign = (payload) => createHmac("sha256", this.secret).update(payload).digest("base64url");
6531
- matchesSignature = (payload, signature) => {
6532
- const expected = Buffer.from(this.sign(payload), "utf8");
6533
- const actual = Buffer.from(signature, "utf8");
6534
- 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")
6535
7115
  };
6536
- parseClaims = (payload) => {
6537
- try {
6538
- const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
6539
- if (!isPanelAppAssetTokenClaims(claims)) throw new Error("invalid panel app asset token claims");
6540
- return claims;
6541
- } catch {
6542
- 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);
6543
7140
  }
6544
- };
6545
- };
6546
- function isPanelAppAssetTokenClaims(value) {
6547
- 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
+ }
6548
7142
  }
6549
- //#endregion
6550
- //#region src/stores/panel-app-state.store.ts
6551
- const PANEL_APP_STATE_FILE = ".panel-apps.state.json";
6552
- const PANEL_APP_STATE_VERSION = 1;
6553
- var PanelAppStateStore = class {
6554
- constructor(panelsPath) {
6555
- 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"));
6556
7148
  }
6557
- load = async () => {
6558
- try {
6559
- const parsed = JSON.parse(await readFile(this.getStatePath(), "utf8"));
6560
- return this.normalizeStateFile(parsed).apps;
6561
- } catch (error) {
6562
- if (this.isMissingFileError(error) || error instanceof SyntaxError) return {};
6563
- throw error;
6564
- }
6565
- };
6566
- updatePreferences = async (id, preferences) => {
6567
- const apps = await this.load();
6568
- const next = { ...apps[id] ?? {} };
6569
- if (typeof preferences.favorite === "boolean") next.favorite = preferences.favorite;
6570
- apps[id] = next;
6571
- await this.persist(apps);
6572
- return next;
6573
- };
6574
- recordOpened = async (id, openedAt = /* @__PURE__ */ new Date()) => {
6575
- const apps = await this.load();
6576
- const current = apps[id] ?? {};
6577
- const next = {
6578
- ...current,
6579
- lastOpenedAt: openedAt.toISOString(),
6580
- openCount: Math.max(0, current.openCount ?? 0) + 1
6581
- };
6582
- apps[id] = next;
6583
- await this.persist(apps);
6584
- return next;
6585
- };
6586
- deleteEntry = async (id) => {
6587
- const apps = await this.load();
6588
- if (!(id in apps)) return;
6589
- delete apps[id];
6590
- await this.persist(apps);
6591
- };
6592
- persist = async (apps) => {
6593
- const statePath = this.getStatePath();
6594
- const tempPath = `${statePath}.${randomUUID()}.tmp`;
6595
- const stateFile = {
6596
- version: PANEL_APP_STATE_VERSION,
6597
- apps
6598
- };
6599
- await mkdir(dirname(statePath), { recursive: true });
6600
- try {
6601
- await writeFile(tempPath, `${JSON.stringify(stateFile, null, 2)}\n`, "utf8");
6602
- await rename(tempPath, statePath);
6603
- } catch (error) {
6604
- await rm(tempPath, { force: true }).catch(() => void 0);
6605
- throw error;
6606
- }
6607
- };
6608
- getStatePath = () => join(this.panelsPath, PANEL_APP_STATE_FILE);
6609
- normalizeStateFile = (value) => {
6610
- if (!this.isRecord(value) || !this.isRecord(value.apps)) return {
6611
- version: PANEL_APP_STATE_VERSION,
6612
- apps: {}
6613
- };
6614
- return {
6615
- version: PANEL_APP_STATE_VERSION,
6616
- apps: Object.fromEntries(Object.entries(value.apps).flatMap(([id, entry]) => {
6617
- if (!this.isRecord(entry)) return [];
6618
- return [[id, this.normalizeStateEntry(entry)]];
6619
- }))
6620
- };
6621
- };
6622
- normalizeStateEntry = (entry) => {
6623
- const normalized = {};
6624
- if (typeof entry.favorite === "boolean") normalized.favorite = entry.favorite;
6625
- if (typeof entry.lastOpenedAt === "string") normalized.lastOpenedAt = entry.lastOpenedAt;
6626
- if (typeof entry.openCount === "number") normalized.openCount = entry.openCount;
6627
- return normalized;
6628
- };
6629
- isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6630
- isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
6631
- };
6632
- //#endregion
6633
- //#region src/stores/panel-app-capability-grant.store.ts
6634
- const EMPTY_GRANTS$2 = {
6635
- version: 1,
6636
- grants: {}
6637
- };
6638
- var PanelAppCapabilityGrantStore = class {
6639
- constructor(filePath) {
6640
- this.filePath = filePath;
6641
- }
6642
- isGranted = async (caller, capability) => {
6643
- const data = await this.load();
6644
- return Boolean(data.grants[getCallerKey(caller)]?.capabilities[capability]);
6645
- };
6646
- grant = async (params) => {
6647
- const { caller, capability, grantedAt } = params;
6648
- const data = await this.load();
6649
- const callerKey = getCallerKey(caller);
6650
- const callerGrants = data.grants[callerKey] ?? { capabilities: {} };
6651
- callerGrants.capabilities[capability] = { grantedAt };
6652
- data.grants[callerKey] = callerGrants;
6653
- await this.save(data);
6654
- return {
6655
- caller,
6656
- capability,
6657
- grantedAt
6658
- };
6659
- };
6660
- deleteCaller = async (caller) => {
6661
- const data = await this.load();
6662
- const callerKey = getCallerKey(caller);
6663
- if (!(callerKey in data.grants)) return;
6664
- delete data.grants[callerKey];
6665
- await this.save(data);
6666
- };
6667
- load = async () => {
6668
- try {
6669
- return normalizeStoreData$2(JSON.parse(await readFile(this.filePath, "utf8")));
6670
- } catch (error) {
6671
- if (isMissingFileError$3(error)) return structuredClone(EMPTY_GRANTS$2);
6672
- throw error;
6673
- }
6674
- };
6675
- save = async (data) => {
6676
- await mkdir(dirname(this.filePath), { recursive: true });
6677
- await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
6678
- };
6679
- };
6680
- function normalizeStoreData$2(value) {
6681
- if (!isRecord$11(value) || value.version !== 1 || !isRecord$11(value.grants)) return structuredClone(EMPTY_GRANTS$2);
6682
- const grants = {};
6683
- for (const [callerKey, callerValue] of Object.entries(value.grants)) {
6684
- if (!isRecord$11(callerValue) || !isRecord$11(callerValue.capabilities)) continue;
6685
- const capabilities = {};
6686
- for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$11(grantValue) && typeof grantValue.grantedAt === "string") capabilities[capability] = { grantedAt: grantValue.grantedAt };
6687
- grants[callerKey] = { capabilities };
6688
- }
6689
- return {
6690
- version: 1,
6691
- grants
6692
- };
6693
7149
  }
6694
- function getCallerKey(caller) {
6695
- return `${caller.surface}:${caller.appId}`;
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;
6696
7153
  }
6697
- function isRecord$11(value) {
6698
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7154
+ function readPanelAppServiceActions(html) {
7155
+ return parseTokenList(readMetaContent(html, "nextclaw-panel-actions", "attribute"));
6699
7156
  }
6700
- function isMissingFileError$3(error) {
6701
- return Boolean(error) && typeof error === "object" && error.code === "ENOENT";
7157
+ function readPanelAppCapabilities(html) {
7158
+ return parseTokenList(readMetaContent(html, "nextclaw-panel-capabilities", "attribute"));
6702
7159
  }
6703
- //#endregion
6704
- //#region src/stores/panel-app-client-grant.store.ts
6705
- const EMPTY_GRANTS$1 = {
6706
- grants: {},
6707
- version: 1
6708
- };
6709
- var PanelAppClientGrantStore = class {
6710
- constructor(filePath) {
6711
- this.filePath = filePath;
6712
- }
6713
- isGranted = async (appId) => {
6714
- const data = await this.load();
6715
- return Boolean(data.grants[appId]);
6716
- };
6717
- grant = async (params) => {
6718
- const data = await this.load();
6719
- data.grants[params.appId] = { grantedAt: params.grantedAt };
6720
- await this.save(data);
6721
- return params;
6722
- };
6723
- revoke = async (appId) => {
6724
- const data = await this.load();
6725
- if (!data.grants[appId]) return;
6726
- delete data.grants[appId];
6727
- await this.save(data);
6728
- };
6729
- load = async () => {
6730
- try {
6731
- return normalizeStoreData$1(JSON.parse(await readFile(this.filePath, "utf8")));
6732
- } catch (error) {
6733
- if (isMissingFileError$2(error)) return structuredClone(EMPTY_GRANTS$1);
6734
- throw error;
6735
- }
6736
- };
6737
- save = async (data) => {
6738
- await mkdir(dirname(this.filePath), { recursive: true });
6739
- await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
6740
- };
6741
- };
6742
- function normalizeStoreData$1(value) {
6743
- if (!isRecord$10(value) || value.version !== 1 || !isRecord$10(value.grants)) return structuredClone(EMPTY_GRANTS$1);
6744
- const grants = {};
6745
- for (const [appId, grant] of Object.entries(value.grants)) if (isRecord$10(grant) && typeof grant.grantedAt === "string") grants[appId] = { grantedAt: grant.grantedAt };
6746
- return {
6747
- grants,
6748
- version: 1
6749
- };
7160
+ function parseTokenList(content) {
7161
+ if (!content) return [];
7162
+ return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
6750
7163
  }
6751
- function isRecord$10(value) {
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) {
6752
7184
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6753
7185
  }
6754
- function isMissingFileError$2(error) {
6755
- return isRecord$10(error) && error.code === "ENOENT";
7186
+ function normalizeTextValue(value) {
7187
+ return decodeHtmlAttribute(value ?? "").replace(/\s+/g, " ").trim() || void 0;
6756
7188
  }
6757
- //#endregion
6758
- //#region src/services/agent-run-client.service.ts
6759
- function readEventCorrelationId(event) {
6760
- if (!("payload" in event)) return;
6761
- const correlationId = "correlationId" in event.payload ? event.payload.correlationId : void 0;
6762
- return typeof correlationId === "string" && correlationId.length > 0 ? correlationId : void 0;
7189
+ function normalizeAttributeValue(value) {
7190
+ return decodeHtmlAttribute(value ?? "").trim() || void 0;
6763
7191
  }
6764
- function readEventSessionId(event) {
6765
- if (!("payload" in event)) return;
6766
- const sessionId = "sessionId" in event.payload ? event.payload.sessionId : void 0;
6767
- return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
7192
+ function normalizeIconHref(value) {
7193
+ if (!value) return;
7194
+ if (value.startsWith("data:image/") || value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/")) return value;
6768
7195
  }
6769
- function readEventRunId(event) {
6770
- if (!("payload" in event)) return;
6771
- const runId = "runId" in event.payload ? event.payload.runId : void 0;
6772
- return typeof runId === "string" && runId.length > 0 ? runId : void 0;
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, "&");
6773
7198
  }
6774
- function isTerminalEvent(event) {
6775
- return event.type === NcpEventType.MessageFailed || event.type === NcpEventType.RunError || event.type === NcpEventType.RunFinished;
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);
6776
7202
  }
6777
- function isRunEventMatch(params) {
6778
- const { correlationId, event, runId, sessionId } = params;
6779
- const eventCorrelationId = readEventCorrelationId(event);
6780
- if (eventCorrelationId) return eventCorrelationId === correlationId;
6781
- if (!sessionId || !runId) return false;
6782
- return readEventSessionId(event) === sessionId && readEventRunId(event) === runId;
7203
+ function isPanelAppFileName(fileName) {
7204
+ return hasSafeBaseName(fileName) && fileName.endsWith(".panel.html");
6783
7205
  }
6784
- var AgentRunEventQueue = class {
6785
- values = [];
6786
- waiters = [];
6787
- closed = false;
6788
- push = (value) => {
6789
- if (this.closed) return;
6790
- const waiter = this.waiters.shift();
6791
- if (waiter) {
6792
- waiter({
6793
- done: false,
6794
- value
6795
- });
6796
- return;
6797
- }
6798
- this.values.push(value);
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
6799
7234
  };
6800
- close = () => {
6801
- if (this.closed) return;
6802
- this.closed = true;
6803
- while (this.waiters.length > 0) this.waiters.shift()?.({
6804
- done: true,
6805
- value: void 0
6806
- });
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
6807
7328
  };
6808
- next = async () => {
6809
- if (this.values.length > 0) return {
6810
- done: false,
6811
- value: this.values.shift()
6812
- };
6813
- if (this.closed) return {
6814
- done: true,
6815
- value: void 0
6816
- };
6817
- return await new Promise((resolve) => {
6818
- this.waiters.push(resolve);
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
6819
7338
  });
6820
- };
6821
- [Symbol.asyncIterator] = () => ({ next: this.next });
6822
- };
6823
- var AgentRunObserver = class {
6824
- queue = new AgentRunEventQueue();
6825
- unsubscribe;
6826
- abortCleanup;
6827
- completedMessage;
6828
- disposed = false;
6829
- runId;
6830
- sessionId;
6831
- constructor(options) {
6832
- this.options = options;
6833
- this.unsubscribe = options.eventBus.on(eventKeys.ncpEvent, this.handleEvent);
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;
6834
7342
  }
6835
- attachHandle = (handle) => {
6836
- this.sessionId = handle.sessionId;
6837
- this.runId = handle.runId ?? void 0;
6838
- const { abortSignal } = this.options;
6839
- if (!abortSignal) return;
6840
- const abort = () => {
6841
- this.options.ingress.handle({
6842
- type: ingressKeys.agentRun.abort,
6843
- payload: {
6844
- sessionId: handle.sessionId,
6845
- correlationId: this.options.correlationId
6846
- }
6847
- }, { source: "agent-run-client" });
6848
- };
6849
- if (abortSignal.aborted) {
6850
- abort();
6851
- return;
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;
6852
7379
  }
6853
- abortSignal.addEventListener("abort", abort, { once: true });
6854
- this.abortCleanup = () => abortSignal.removeEventListener("abort", abort);
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;
6855
7383
  };
6856
- stream = async function* () {
6857
- for await (const event of this.queue) yield event;
7384
+ findPackageSourceBySourceName = async (sourceName) => {
7385
+ return (await this.listPackageComponentSources()).find((component) => component.kind === "panel" && component.sourcePath.endsWith(`/${sourceName}`));
6858
7386
  };
6859
- waitForReply = async (options = {}) => {
6860
- for await (const event of this.queue) {
6861
- if (event.type === NcpEventType.MessageTextDelta) {
6862
- options.onAssistantDelta?.(event.payload.delta);
6863
- continue;
6864
- }
6865
- if (event.type === NcpEventType.MessageCompleted) {
6866
- this.completedMessage = event.payload.message;
6867
- continue;
6868
- }
6869
- if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
6870
- if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? options.runErrorMessage ?? "NCP run failed.");
6871
- if (event.type === NcpEventType.RunFinished) {
6872
- if (!this.completedMessage) throw new Error(options.missingCompletedMessageError ?? "NCP run completed without a final assistant message.");
6873
- return this.completedMessage;
6874
- }
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;
6875
7398
  }
6876
- throw new Error(options.missingCompletedMessageError ?? "NCP run completed without a final assistant message.");
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
+ });
6877
7408
  };
6878
- dispose = () => {
6879
- if (this.disposed) return;
6880
- this.disposed = true;
6881
- this.abortCleanup?.();
6882
- this.unsubscribe();
6883
- this.queue.close();
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");
6884
7419
  };
6885
- handleEvent = (event) => {
6886
- if (this.disposed || !isRunEventMatch({
6887
- correlationId: this.options.correlationId,
6888
- event,
6889
- runId: this.runId,
6890
- sessionId: this.sessionId
6891
- })) return;
6892
- this.options.onEvent?.(event);
6893
- this.queue.push(event);
6894
- if (isTerminalEvent(event)) this.queue.close();
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
+ }
6895
7450
  };
7451
+ listPackageComponentSources = async () => await this.params.listPackageComponentSources?.() ?? [];
6896
7452
  };
6897
- var AgentRunClient = class {
6898
- constructor(options) {
6899
- this.options = options;
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;
6900
7466
  }
6901
- send = async (input) => {
6902
- return await this.sendWithCorrelation(input, randomUUID());
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;
6903
7493
  };
6904
- sendAndWaitForReply = async (input, options = {}) => {
6905
- const correlationId = randomUUID();
6906
- const observer = this.prepareObserver(correlationId, options);
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) => {
6907
7533
  try {
6908
- const handle = await this.sendWithCorrelation(input, correlationId);
6909
- observer.attachHandle(handle);
6910
- const completedMessage = await observer.waitForReply(options);
6911
- return {
6912
- handle,
6913
- completedMessage,
6914
- text: extractTextFromNcpMessage(completedMessage)
6915
- };
6916
- } finally {
6917
- observer.dispose();
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");
6918
7539
  }
6919
7540
  };
6920
- sendAndStreamEvents = async function* (input, options = {}) {
6921
- const correlationId = randomUUID();
6922
- const observer = this.prepareObserver(correlationId, options);
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 () => {
6923
7554
  try {
6924
- const handle = await this.sendWithCorrelation(input, correlationId);
6925
- observer.attachHandle(handle);
6926
- for await (const event of observer.stream()) yield event;
6927
- } finally {
6928
- observer.dispose();
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;
6929
7560
  }
6930
7561
  };
6931
- prepareObserver = (correlationId, options) => {
6932
- return new AgentRunObserver({
6933
- abortSignal: options.abortSignal,
6934
- correlationId,
6935
- eventBus: this.options.eventBus,
6936
- ingress: this.options.ingress,
6937
- onEvent: options.onEvent
6938
- });
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;
7567
+ await this.persist(apps);
7568
+ return next;
6939
7569
  };
6940
- sendWithCorrelation = async (input, correlationId) => {
6941
- return await this.options.ingress.handle({
6942
- type: ingressKeys.agentRun.send,
6943
- payload: {
6944
- ...input,
6945
- correlationId
6946
- }
6947
- }, { source: "agent-run-client" });
7570
+ recordOpened = async (id, openedAt = /* @__PURE__ */ new Date()) => {
7571
+ const apps = await this.load();
7572
+ const current = apps[id] ?? {};
7573
+ const next = {
7574
+ ...current,
7575
+ lastOpenedAt: openedAt.toISOString(),
7576
+ openCount: Math.max(0, current.openCount ?? 0) + 1
7577
+ };
7578
+ apps[id] = next;
7579
+ await this.persist(apps);
7580
+ return next;
7581
+ };
7582
+ deleteEntry = async (id) => {
7583
+ const apps = await this.load();
7584
+ if (!(id in apps)) return;
7585
+ delete apps[id];
7586
+ await this.persist(apps);
7587
+ };
7588
+ persist = async (apps) => {
7589
+ const statePath = this.getStatePath();
7590
+ const tempPath = `${statePath}.${randomUUID()}.tmp`;
7591
+ const stateFile = {
7592
+ version: PANEL_APP_STATE_VERSION,
7593
+ apps
7594
+ };
7595
+ await mkdir(dirname(statePath), { recursive: true });
7596
+ try {
7597
+ await writeFile(tempPath, `${JSON.stringify(stateFile, null, 2)}\n`, "utf8");
7598
+ await rename(tempPath, statePath);
7599
+ } catch (error) {
7600
+ await rm(tempPath, { force: true }).catch(() => void 0);
7601
+ throw error;
7602
+ }
7603
+ };
7604
+ getStatePath = () => join(this.panelsPath, PANEL_APP_STATE_FILE);
7605
+ normalizeStateFile = (value) => {
7606
+ if (!this.isRecord(value) || !this.isRecord(value.apps)) return {
7607
+ version: PANEL_APP_STATE_VERSION,
7608
+ apps: {}
7609
+ };
7610
+ return {
7611
+ version: PANEL_APP_STATE_VERSION,
7612
+ apps: Object.fromEntries(Object.entries(value.apps).flatMap(([id, entry]) => {
7613
+ if (!this.isRecord(entry)) return [];
7614
+ return [[id, this.normalizeStateEntry(entry)]];
7615
+ }))
7616
+ };
6948
7617
  };
7618
+ normalizeStateEntry = (entry) => {
7619
+ const normalized = {};
7620
+ if (typeof entry.favorite === "boolean") normalized.favorite = entry.favorite;
7621
+ if (typeof entry.lastOpenedAt === "string") normalized.lastOpenedAt = entry.lastOpenedAt;
7622
+ if (typeof entry.openCount === "number") normalized.openCount = entry.openCount;
7623
+ return normalized;
7624
+ };
7625
+ isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7626
+ isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
6949
7627
  };
6950
7628
  //#endregion
6951
- //#region src/tools/structured-result.tools.ts
6952
- const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
6953
- var StructuredResultSubmitTool = class {
6954
- name = STRUCTURED_RESULT_TOOL_NAME;
6955
- description = "Submit the structured object result for this request.";
6956
- constructor(contract) {
6957
- this.contract = contract;
6958
- }
6959
- get parameters() {
6960
- return this.contract.schema;
6961
- }
6962
- validateArgs = (args) => validateToolArgs(args, this.contract.schema);
6963
- execute = async (args) => args;
7629
+ //#region src/stores/panel-app-capability-grant.store.ts
7630
+ const EMPTY_GRANTS$2 = {
7631
+ version: 1,
7632
+ grants: {}
6964
7633
  };
6965
- //#endregion
6966
- //#region src/utils/panel-app-agent.utils.ts
6967
- const PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS = 6e4;
6968
- const PANEL_APP_AGENT_MAX_TIMEOUT_MS = 12e4;
6969
- const PANEL_APP_AGENT_MAX_PROMPT_CHARS = 2e4;
6970
- const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
6971
- function normalizePanelAppGenerateObjectInput(input) {
6972
- const peerId = input.peerId.trim();
6973
- const prompt = input.prompt.trim();
6974
- if (!peerId || !prompt || !isRecord$9(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
6975
- if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
6976
- if (stringifyJsonValue(input.context ?? null).length > PANEL_APP_AGENT_MAX_CONTEXT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject context is too large");
6977
- return {
6978
- context: input.context,
6979
- peerId,
6980
- prompt,
6981
- schema: input.schema,
6982
- timeoutMs: normalizeTimeoutMs(input.timeoutMs),
6983
- title: input.title?.trim() || void 0
7634
+ var PanelAppCapabilityGrantStore = class {
7635
+ constructor(filePath) {
7636
+ this.filePath = filePath;
7637
+ }
7638
+ isGranted = async (caller, capability) => {
7639
+ const data = await this.load();
7640
+ return Boolean(data.grants[getCallerKey(caller)]?.capabilities[capability]);
6984
7641
  };
6985
- }
6986
- function createPanelAppGenerateObjectMessage(params) {
6987
- const { bridgeSession, request, requestId } = params;
6988
- return {
6989
- id: `panel-app-agent-message-${randomUUID()}`,
6990
- metadata: {
6991
- ...createPanelAppAgentMetadata(bridgeSession),
6992
- panel_app_peer_id: request.peerId,
6993
- structured_result: {
6994
- request_id: requestId,
6995
- schema: structuredClone(request.schema),
6996
- tool_name: STRUCTURED_RESULT_TOOL_NAME
6997
- }
6998
- },
6999
- parts: [{
7000
- type: "text",
7001
- text: buildGenerateObjectPrompt(bridgeSession, request)
7002
- }],
7003
- role: "user",
7004
- status: "final",
7005
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
7642
+ grant = async (params) => {
7643
+ const { caller, capability, grantedAt } = params;
7644
+ const data = await this.load();
7645
+ const callerKey = getCallerKey(caller);
7646
+ const callerGrants = data.grants[callerKey] ?? { capabilities: {} };
7647
+ callerGrants.capabilities[capability] = { grantedAt };
7648
+ data.grants[callerKey] = callerGrants;
7649
+ await this.save(data);
7650
+ return {
7651
+ caller,
7652
+ capability,
7653
+ grantedAt
7654
+ };
7006
7655
  };
7007
- }
7008
- async function waitForPanelAppStructuredResult(agentRunClient, params) {
7009
- const iterator = agentRunClient.sendAndStreamEvents(params.payload)[Symbol.asyncIterator]();
7010
- let timeoutId;
7011
- const timeout = new Promise((_, reject) => {
7012
- timeoutId = setTimeout(() => reject(new PanelAppError("AGENT_OBJECT_RESULT_TIMEOUT", "agent object result timed out")), params.timeoutMs);
7013
- });
7014
- let resultToolCallId = null;
7015
- try {
7016
- while (true) {
7017
- const next = await Promise.race([iterator.next(), timeout]);
7018
- if (next.done) break;
7019
- const result = readStructuredResultEvent(next.value, resultToolCallId);
7020
- if (result.matchedToolCallId) resultToolCallId = result.matchedToolCallId;
7021
- if (result.submitted) return result.content;
7022
- throwIfTerminalError(next.value);
7023
- }
7024
- } finally {
7025
- if (timeoutId) clearTimeout(timeoutId);
7026
- await iterator.return?.(void 0);
7027
- }
7028
- throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
7029
- }
7030
- function withPanelAppAgentMetadata(payload, bridgeSession) {
7031
- const panelAppMetadata = createPanelAppAgentMetadata(bridgeSession);
7032
- const peerId = readOptionalPeerId(payload.peerId);
7033
- const sessionId = readOptionalSessionId(payload.sessionId);
7034
- if (peerId && sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request cannot include both sessionId and peerId");
7035
- const metadata = {
7036
- ...payload.metadata ?? {},
7037
- ...panelAppMetadata
7656
+ deleteCaller = async (caller) => {
7657
+ const data = await this.load();
7658
+ const callerKey = getCallerKey(caller);
7659
+ if (!(callerKey in data.grants)) return;
7660
+ delete data.grants[callerKey];
7661
+ await this.save(data);
7038
7662
  };
7039
- if (peerId) metadata.panel_app_peer_id = peerId;
7040
- if (Array.isArray(payload.content)) return {
7041
- content: structuredClone(payload.content),
7042
- metadata,
7043
- peerId,
7044
- sessionId
7663
+ load = async () => {
7664
+ try {
7665
+ return normalizeStoreData$2(JSON.parse(await readFile(this.filePath, "utf8")));
7666
+ } catch (error) {
7667
+ if (isMissingFileError$3(error)) return structuredClone(EMPTY_GRANTS$2);
7668
+ throw error;
7669
+ }
7045
7670
  };
7046
- if (!payload.message) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request is required");
7047
- return {
7048
- message: {
7049
- ...structuredClone(payload.message),
7050
- metadata: {
7051
- ...payload.message.metadata ?? {},
7052
- ...panelAppMetadata
7053
- }
7054
- },
7055
- metadata,
7056
- peerId,
7057
- sessionId
7671
+ save = async (data) => {
7672
+ await mkdir(dirname(this.filePath), { recursive: true });
7673
+ await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
7058
7674
  };
7059
- }
7060
- function createPanelAppAgentMetadata(bridgeSession) {
7675
+ };
7676
+ function normalizeStoreData$2(value) {
7677
+ if (!isRecord$11(value) || value.version !== 1 || !isRecord$11(value.grants)) return structuredClone(EMPTY_GRANTS$2);
7678
+ const grants = {};
7679
+ for (const [callerKey, callerValue] of Object.entries(value.grants)) {
7680
+ if (!isRecord$11(callerValue) || !isRecord$11(callerValue.capabilities)) continue;
7681
+ const capabilities = {};
7682
+ for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$11(grantValue) && typeof grantValue.grantedAt === "string") capabilities[capability] = { grantedAt: grantValue.grantedAt };
7683
+ grants[callerKey] = { capabilities };
7684
+ }
7061
7685
  return {
7062
- agent_peer_scope: `panel-app:${bridgeSession.appId}`,
7063
- panel_app_bridge_request_id: randomUUID(),
7064
- panel_app_id: bridgeSession.appId,
7065
- source_kind: "panel_app"
7686
+ version: 1,
7687
+ grants
7066
7688
  };
7067
7689
  }
7068
- function normalizeTimeoutMs(timeoutMs) {
7069
- if (!Number.isFinite(timeoutMs) || typeof timeoutMs !== "number") return PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS;
7070
- return Math.min(PANEL_APP_AGENT_MAX_TIMEOUT_MS, Math.max(1e3, Math.trunc(timeoutMs)));
7071
- }
7072
- function readOptionalPeerId(value) {
7073
- if (typeof value !== "string") return;
7074
- const peerId = value.trim();
7075
- if (!peerId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent peerId cannot be empty");
7076
- return peerId;
7690
+ function getCallerKey(caller) {
7691
+ return `${caller.surface}:${caller.appId}`;
7077
7692
  }
7078
- function readOptionalSessionId(value) {
7079
- if (typeof value !== "string") return;
7080
- const sessionId = value.trim();
7081
- if (!sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent sessionId cannot be empty");
7082
- return sessionId;
7693
+ function isRecord$11(value) {
7694
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7083
7695
  }
7084
- function buildGenerateObjectPrompt(bridgeSession, request) {
7085
- return [
7086
- "Panel App generateObject request",
7087
- "",
7088
- `Panel App ID: ${bridgeSession.appId}`,
7089
- `Peer ID: ${request.peerId}`,
7090
- "",
7091
- "Context JSON:",
7092
- stringifyJsonValue(request.context ?? null),
7093
- "",
7094
- "Task:",
7095
- request.prompt,
7096
- "",
7097
- "Result contract:",
7098
- `Call the ${STRUCTURED_RESULT_TOOL_NAME} tool exactly once with an object matching the provided schema.`,
7099
- "Do not use natural language as the result."
7100
- ].join("\n");
7696
+ function isMissingFileError$3(error) {
7697
+ return Boolean(error) && typeof error === "object" && error.code === "ENOENT";
7101
7698
  }
7102
- function readStructuredResultEvent(event, resultToolCallId) {
7103
- if (event.type === NcpEventType.MessageToolCallStart && event.payload.toolName === "nextclaw_submit_result") return {
7104
- matchedToolCallId: event.payload.toolCallId,
7105
- submitted: false
7699
+ //#endregion
7700
+ //#region src/stores/panel-app-client-grant.store.ts
7701
+ const EMPTY_GRANTS$1 = {
7702
+ grants: {},
7703
+ version: 1
7704
+ };
7705
+ var PanelAppClientGrantStore = class {
7706
+ constructor(filePath) {
7707
+ this.filePath = filePath;
7708
+ }
7709
+ isGranted = async (appId) => {
7710
+ const data = await this.load();
7711
+ return Boolean(data.grants[appId]);
7106
7712
  };
7107
- if (event.type !== NcpEventType.MessageToolCallResult || event.payload.toolCallId !== resultToolCallId) return { submitted: false };
7108
- assertToolResultContent(event.payload.content);
7713
+ grant = async (params) => {
7714
+ const data = await this.load();
7715
+ data.grants[params.appId] = { grantedAt: params.grantedAt };
7716
+ await this.save(data);
7717
+ return params;
7718
+ };
7719
+ revoke = async (appId) => {
7720
+ const data = await this.load();
7721
+ if (!data.grants[appId]) return;
7722
+ delete data.grants[appId];
7723
+ await this.save(data);
7724
+ };
7725
+ load = async () => {
7726
+ try {
7727
+ return normalizeStoreData$1(JSON.parse(await readFile(this.filePath, "utf8")));
7728
+ } catch (error) {
7729
+ if (isMissingFileError$2(error)) return structuredClone(EMPTY_GRANTS$1);
7730
+ throw error;
7731
+ }
7732
+ };
7733
+ save = async (data) => {
7734
+ await mkdir(dirname(this.filePath), { recursive: true });
7735
+ await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
7736
+ };
7737
+ };
7738
+ function normalizeStoreData$1(value) {
7739
+ if (!isRecord$10(value) || value.version !== 1 || !isRecord$10(value.grants)) return structuredClone(EMPTY_GRANTS$1);
7740
+ const grants = {};
7741
+ for (const [appId, grant] of Object.entries(value.grants)) if (isRecord$10(grant) && typeof grant.grantedAt === "string") grants[appId] = { grantedAt: grant.grantedAt };
7109
7742
  return {
7110
- content: event.payload.content,
7111
- submitted: true
7743
+ grants,
7744
+ version: 1
7112
7745
  };
7113
7746
  }
7114
- function assertToolResultContent(content) {
7115
- if (!isRecord$9(content) || content.ok !== false || !isRecord$9(content.error)) return;
7116
- if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
7117
- throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
7747
+ function isRecord$10(value) {
7748
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7118
7749
  }
7119
- function throwIfTerminalError(event) {
7120
- if (event.type === NcpEventType.MessageFailed) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error.message);
7121
- if (event.type === NcpEventType.RunError) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error ?? "agent object request failed");
7122
- if (event.type === NcpEventType.RunFinished) throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
7750
+ function isMissingFileError$2(error) {
7751
+ return isRecord$10(error) && error.code === "ENOENT";
7123
7752
  }
7124
- function stringifyJsonValue(value) {
7125
- try {
7126
- return JSON.stringify(value, null, 2);
7127
- } catch {
7128
- throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "value is not JSON serializable");
7129
- }
7753
+ //#endregion
7754
+ //#region src/services/agent-run-client.service.ts
7755
+ function readEventCorrelationId(event) {
7756
+ if (!("payload" in event)) return;
7757
+ const correlationId = "correlationId" in event.payload ? event.payload.correlationId : void 0;
7758
+ return typeof correlationId === "string" && correlationId.length > 0 ? correlationId : void 0;
7130
7759
  }
7131
- function isRecord$9(value) {
7132
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7760
+ function readEventSessionId(event) {
7761
+ if (!("payload" in event)) return;
7762
+ const sessionId = "sessionId" in event.payload ? event.payload.sessionId : void 0;
7763
+ return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
7133
7764
  }
7134
- //#endregion
7135
- //#region src/services/panel-app-agent-bridge.service.ts
7136
- var PanelAppAgentBridgeService = class {
7137
- constructor(params) {
7138
- this.params = params;
7139
- }
7140
- sendAgentMessage = async (bridgeSession, payload) => {
7141
- await this.assertAgentCapabilityGranted(bridgeSession, "agent:send");
7142
- return await this.requireAgentRunClient().send(withPanelAppAgentMetadata(payload, bridgeSession));
7765
+ function readEventRunId(event) {
7766
+ if (!("payload" in event)) return;
7767
+ const runId = "runId" in event.payload ? event.payload.runId : void 0;
7768
+ return typeof runId === "string" && runId.length > 0 ? runId : void 0;
7769
+ }
7770
+ function isTerminalEvent(event) {
7771
+ return event.type === NcpEventType.MessageFailed || event.type === NcpEventType.RunError || event.type === NcpEventType.RunFinished;
7772
+ }
7773
+ function isRunEventMatch(params) {
7774
+ const { correlationId, event, runId, sessionId } = params;
7775
+ const eventCorrelationId = readEventCorrelationId(event);
7776
+ if (eventCorrelationId) return eventCorrelationId === correlationId;
7777
+ if (!sessionId || !runId) return false;
7778
+ return readEventSessionId(event) === sessionId && readEventRunId(event) === runId;
7779
+ }
7780
+ var AgentRunEventQueue = class {
7781
+ values = [];
7782
+ waiters = [];
7783
+ closed = false;
7784
+ push = (value) => {
7785
+ if (this.closed) return;
7786
+ const waiter = this.waiters.shift();
7787
+ if (waiter) {
7788
+ waiter({
7789
+ done: false,
7790
+ value
7791
+ });
7792
+ return;
7793
+ }
7794
+ this.values.push(value);
7143
7795
  };
7144
- generateAgentObject = async (bridgeSession, input) => {
7145
- await this.assertAgentCapabilityGranted(bridgeSession, "agent:generateObject");
7146
- const request = normalizePanelAppGenerateObjectInput(input);
7147
- const message = createPanelAppGenerateObjectMessage({
7148
- bridgeSession,
7149
- request,
7150
- requestId: randomUUID()
7796
+ close = () => {
7797
+ if (this.closed) return;
7798
+ this.closed = true;
7799
+ while (this.waiters.length > 0) this.waiters.shift()?.({
7800
+ done: true,
7801
+ value: void 0
7151
7802
  });
7152
- return { result: await waitForPanelAppStructuredResult(this.requireAgentRunClient(), {
7153
- payload: {
7154
- message,
7155
- metadata: {
7156
- ...createPanelAppAgentMetadata(bridgeSession),
7157
- panel_app_peer_id: request.peerId
7158
- },
7159
- peerId: request.peerId
7160
- },
7161
- timeoutMs: request.timeoutMs
7162
- }) };
7163
7803
  };
7164
- grantAgentCapability = async (bridgeSession, capability) => {
7165
- if (!isPanelAppAgentCapability(capability)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "unknown panel app agent capability");
7166
- this.assertDeclaredCapability(bridgeSession, capability);
7167
- return await this.params.createCapabilityGrantStore().grant({
7168
- caller: bridgeSession.caller,
7169
- capability,
7170
- grantedAt: (/* @__PURE__ */ new Date()).toISOString()
7804
+ next = async () => {
7805
+ if (this.values.length > 0) return {
7806
+ done: false,
7807
+ value: this.values.shift()
7808
+ };
7809
+ if (this.closed) return {
7810
+ done: true,
7811
+ value: void 0
7812
+ };
7813
+ return await new Promise((resolve) => {
7814
+ this.waiters.push(resolve);
7171
7815
  });
7172
7816
  };
7173
- assertAgentCapabilityGranted = async (bridgeSession, capability) => {
7174
- this.assertDeclaredCapability(bridgeSession, capability);
7175
- if (!await this.params.createCapabilityGrantStore().isGranted(bridgeSession.caller, capability)) throw new PanelAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to use ${capability}.`);
7817
+ [Symbol.asyncIterator] = () => ({ next: this.next });
7818
+ };
7819
+ var AgentRunObserver = class {
7820
+ queue = new AgentRunEventQueue();
7821
+ unsubscribe;
7822
+ abortCleanup;
7823
+ completedMessage;
7824
+ disposed = false;
7825
+ runId;
7826
+ sessionId;
7827
+ constructor(options) {
7828
+ this.options = options;
7829
+ this.unsubscribe = options.eventBus.on(eventKeys.ncpEvent, this.handleEvent);
7830
+ }
7831
+ attachHandle = (handle) => {
7832
+ this.sessionId = handle.sessionId;
7833
+ this.runId = handle.runId ?? void 0;
7834
+ const { abortSignal } = this.options;
7835
+ if (!abortSignal) return;
7836
+ const abort = () => {
7837
+ this.options.ingress.handle({
7838
+ type: ingressKeys.agentRun.abort,
7839
+ payload: {
7840
+ sessionId: handle.sessionId,
7841
+ correlationId: this.options.correlationId
7842
+ }
7843
+ }, { source: "agent-run-client" });
7844
+ };
7845
+ if (abortSignal.aborted) {
7846
+ abort();
7847
+ return;
7848
+ }
7849
+ abortSignal.addEventListener("abort", abort, { once: true });
7850
+ this.abortCleanup = () => abortSignal.removeEventListener("abort", abort);
7176
7851
  };
7177
- assertDeclaredCapability = (bridgeSession, capability) => {
7178
- if (!bridgeSession.declaredCapabilities.includes(capability)) throw new PanelAppError("PANEL_APP_CAPABILITY_NOT_DECLARED", this.describeMissingAgentCapability(bridgeSession.declaredCapabilities, capability));
7852
+ stream = async function* () {
7853
+ for await (const event of this.queue) yield event;
7179
7854
  };
7180
- describeMissingAgentCapability = (declaredCapabilities, capability) => {
7181
- const declared = declaredCapabilities.length > 0 ? declaredCapabilities.join(", ") : "none";
7182
- const valid = PANEL_APP_AGENT_CAPABILITIES.join(", ");
7183
- const hint = declaredCapabilities.includes(capability.replace(":", ".")) ? ` Use ${capability}, not ${capability.replace(":", ".")}.` : "";
7184
- return [
7185
- `panel app did not declare ${capability}.`,
7186
- `Declared: ${declared}.`,
7187
- `Valid capabilities: ${valid}.`,
7188
- `Declare it with nextclaw-panel-capabilities or panel-app.json capabilities.`,
7189
- hint.trim()
7190
- ].filter(Boolean).join(" ");
7855
+ waitForReply = async (options = {}) => {
7856
+ for await (const event of this.queue) {
7857
+ if (event.type === NcpEventType.MessageTextDelta) {
7858
+ options.onAssistantDelta?.(event.payload.delta);
7859
+ continue;
7860
+ }
7861
+ if (event.type === NcpEventType.MessageCompleted) {
7862
+ this.completedMessage = event.payload.message;
7863
+ continue;
7864
+ }
7865
+ if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
7866
+ if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? options.runErrorMessage ?? "NCP run failed.");
7867
+ if (event.type === NcpEventType.RunFinished) {
7868
+ if (!this.completedMessage) throw new Error(options.missingCompletedMessageError ?? "NCP run completed without a final assistant message.");
7869
+ return this.completedMessage;
7870
+ }
7871
+ }
7872
+ throw new Error(options.missingCompletedMessageError ?? "NCP run completed without a final assistant message.");
7191
7873
  };
7192
- requireAgentRunClient = () => {
7193
- if (!this.params.agentRunClient) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "panel app agent client is not configured");
7194
- return this.params.agentRunClient;
7874
+ dispose = () => {
7875
+ if (this.disposed) return;
7876
+ this.disposed = true;
7877
+ this.abortCleanup?.();
7878
+ this.unsubscribe();
7879
+ this.queue.close();
7880
+ };
7881
+ handleEvent = (event) => {
7882
+ if (this.disposed || !isRunEventMatch({
7883
+ correlationId: this.options.correlationId,
7884
+ event,
7885
+ runId: this.runId,
7886
+ sessionId: this.sessionId
7887
+ })) return;
7888
+ this.options.onEvent?.(event);
7889
+ this.queue.push(event);
7890
+ if (isTerminalEvent(event)) this.queue.close();
7891
+ };
7892
+ };
7893
+ var AgentRunClient = class {
7894
+ constructor(options) {
7895
+ this.options = options;
7896
+ }
7897
+ send = async (input) => {
7898
+ return await this.sendWithCorrelation(input, randomUUID());
7899
+ };
7900
+ sendAndWaitForReply = async (input, options = {}) => {
7901
+ const correlationId = randomUUID();
7902
+ const observer = this.prepareObserver(correlationId, options);
7903
+ try {
7904
+ const handle = await this.sendWithCorrelation(input, correlationId);
7905
+ observer.attachHandle(handle);
7906
+ const completedMessage = await observer.waitForReply(options);
7907
+ return {
7908
+ handle,
7909
+ completedMessage,
7910
+ text: extractTextFromNcpMessage(completedMessage)
7911
+ };
7912
+ } finally {
7913
+ observer.dispose();
7914
+ }
7915
+ };
7916
+ sendAndStreamEvents = async function* (input, options = {}) {
7917
+ const correlationId = randomUUID();
7918
+ const observer = this.prepareObserver(correlationId, options);
7919
+ try {
7920
+ const handle = await this.sendWithCorrelation(input, correlationId);
7921
+ observer.attachHandle(handle);
7922
+ for await (const event of observer.stream()) yield event;
7923
+ } finally {
7924
+ observer.dispose();
7925
+ }
7926
+ };
7927
+ prepareObserver = (correlationId, options) => {
7928
+ return new AgentRunObserver({
7929
+ abortSignal: options.abortSignal,
7930
+ correlationId,
7931
+ eventBus: this.options.eventBus,
7932
+ ingress: this.options.ingress,
7933
+ onEvent: options.onEvent
7934
+ });
7935
+ };
7936
+ sendWithCorrelation = async (input, correlationId) => {
7937
+ return await this.options.ingress.handle({
7938
+ type: ingressKeys.agentRun.send,
7939
+ payload: {
7940
+ ...input,
7941
+ correlationId
7942
+ }
7943
+ }, { source: "agent-run-client" });
7195
7944
  };
7196
7945
  };
7197
7946
  //#endregion
7198
- //#region src/utils/ui-content-params-injection.utils.ts
7199
- const UI_CONTENT_PARAMS_BOOTSTRAP_MARKER = "nextclaw:content-params:bootstrap";
7200
- function getUiContentParamsBootstrapScript() {
7201
- return `
7202
- /* ${UI_CONTENT_PARAMS_BOOTSTRAP_MARKER} */
7203
- (() => {
7204
- const contract = ${JSON.stringify(UI_CONTENT_PARAMS_HOST_CONTRACT)};
7205
- const rawWindowName = typeof window.name === "string" ? window.name : "";
7206
- if (!rawWindowName.startsWith(contract.windowNamePrefix)) {
7207
- return;
7208
- }
7209
- window.name = "";
7210
- let params;
7211
- try {
7212
- params = JSON.parse(rawWindowName.slice(contract.windowNamePrefix.length));
7213
- } catch {
7214
- return;
7215
- }
7216
- if (!params || typeof params !== "object" || Array.isArray(params)) {
7217
- return;
7218
- }
7219
- const freezeJson = (value) => {
7220
- if (!value || typeof value !== "object" || Object.isFrozen(value)) {
7221
- return value;
7222
- }
7223
- Object.values(value).forEach(freezeJson);
7224
- return Object.freeze(value);
7225
- };
7226
- const existing = window.nextclaw && typeof window.nextclaw === "object"
7227
- ? window.nextclaw
7228
- : {};
7229
- Object.defineProperty(window, "nextclaw", {
7230
- configurable: true,
7231
- value: {
7232
- ...existing,
7233
- params: freezeJson(params)
7234
- }
7235
- });
7236
- })();
7237
- `.trim();
7238
- }
7239
- function injectUiContentParamsBootstrap(html) {
7240
- if (html.includes(UI_CONTENT_PARAMS_BOOTSTRAP_MARKER)) return html;
7241
- const script = `<script>${getUiContentParamsBootstrapScript()}<\/script>`;
7242
- const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
7243
- if (headMatch?.index !== void 0) {
7244
- const insertAt = headMatch.index + headMatch[0].length;
7245
- return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
7947
+ //#region src/tools/structured-result.tools.ts
7948
+ const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
7949
+ var StructuredResultSubmitTool = class {
7950
+ name = STRUCTURED_RESULT_TOOL_NAME;
7951
+ description = "Submit the structured object result for this request.";
7952
+ constructor(contract) {
7953
+ this.contract = contract;
7246
7954
  }
7247
- return `${script}${html}`;
7248
- }
7955
+ get parameters() {
7956
+ return this.contract.schema;
7957
+ }
7958
+ validateArgs = (args) => validateToolArgs(args, this.contract.schema);
7959
+ execute = async (args) => args;
7960
+ };
7249
7961
  //#endregion
7250
- //#region src/utils/panel-app-bridge.utils.ts
7251
- const PANEL_APP_BRIDGE_MARKER = "nextclaw:panel-app-service-actions:request";
7252
- function getPanelAppInlineContentHeightReporterScript() {
7253
- return `
7254
- function installInlineContentHeightReporter() {
7255
- const inlineHostContract = ${JSON.stringify(PANEL_APP_INLINE_HOST_CONTRACT)};
7256
- const readInlineContentHeight = ${readInlineContentHeight.toString()};
7257
- if (!window.location || !window.document) {
7258
- return;
7259
- }
7260
- const searchParams = new URLSearchParams(window.location.search);
7261
- if (
7262
- searchParams.get(inlineHostContract.displayModeSearchParam) !== inlineHostContract.displayMode ||
7263
- searchParams.get(inlineHostContract.placementSearchParam) !== inlineHostContract.placement
7264
- ) {
7265
- return;
7266
- }
7267
- const start = () => {
7268
- const { body, documentElement } = window.document;
7269
- if (!documentElement) {
7270
- return;
7271
- }
7272
- let lastHeight = 0;
7273
- const reportHeight = () => {
7274
- const height = readInlineContentHeight(body, documentElement);
7275
- if (height > 0 && height !== lastHeight) {
7276
- lastHeight = height;
7277
- window.parent.postMessage({ type: inlineHostContract.contentHeightMessageType, height }, "*");
7278
- }
7279
- };
7280
- if (typeof window.ResizeObserver === "function") {
7281
- const observer = new window.ResizeObserver(reportHeight);
7282
- observer.observe(documentElement);
7283
- if (body) {
7284
- observer.observe(body);
7285
- }
7286
- }
7287
- window.addEventListener("load", reportHeight);
7288
- reportHeight();
7289
- };
7290
- if (window.document.readyState === "loading") {
7291
- window.document.addEventListener("DOMContentLoaded", start, { once: true });
7292
- return;
7293
- }
7294
- start();
7295
- }`.trim();
7296
- }
7297
- function getPanelAppScrollSurfaceHelpersScript() {
7298
- return `
7299
- function readScrollPosition(element) {
7300
- const x = element ? element.scrollLeft : window.scrollX;
7301
- const y = element ? element.scrollTop : window.scrollY;
7302
- if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) {
7303
- return null;
7304
- }
7305
- return { x, y };
7306
- }
7307
-
7308
- function getScrollSurface(target) {
7309
- const root = window.document.scrollingElement;
7310
- if (
7311
- !target ||
7312
- target === window.document ||
7313
- target === root ||
7314
- target === window.document.documentElement ||
7315
- target === window.document.body
7316
- ) {
7317
- return { kind: "document" };
7318
- }
7319
- if (!target.parentElement || !target.children || typeof target.scrollTop !== "number") {
7320
- return null;
7321
- }
7322
- const path = [];
7323
- let element = target;
7324
- while (element && element !== window.document.body) {
7325
- const parent = element.parentElement;
7326
- if (!parent || !parent.children) {
7327
- return null;
7328
- }
7329
- const index = Array.prototype.indexOf.call(parent.children, element);
7330
- const tagName = typeof element.tagName === "string" ? element.tagName.toLowerCase() : "";
7331
- if (index < 0 || !tagName) {
7332
- return null;
7333
- }
7334
- path.unshift({ index, tagName });
7335
- element = parent;
7336
- }
7337
- return element === window.document.body && path.length > 0 ? { kind: "element", path } : null;
7338
- }
7339
-
7340
- function resolveScrollSurface(target) {
7341
- if (target.kind === "document") {
7342
- return null;
7343
- }
7344
- let element = window.document.body;
7345
- for (const segment of target.path) {
7346
- const child = element?.children?.[segment.index];
7347
- if (!child || child.tagName?.toLowerCase() !== segment.tagName) {
7348
- return undefined;
7349
- }
7350
- element = child;
7351
- }
7352
- return element;
7353
- }
7354
-
7355
- function isScrollTarget(value) {
7356
- if (!value || typeof value !== "object") {
7357
- return false;
7358
- }
7359
- if (value.kind === "document") {
7360
- return true;
7361
- }
7362
- return value.kind === "element" &&
7363
- Array.isArray(value.path) &&
7364
- value.path.length > 0 &&
7365
- value.path.length <= 30 &&
7366
- value.path.every((segment) =>
7367
- segment &&
7368
- Number.isInteger(segment.index) &&
7369
- segment.index >= 0 &&
7370
- segment.index <= 1000 &&
7371
- typeof segment.tagName === "string" &&
7372
- segment.tagName.length > 0 &&
7373
- segment.tagName.length <= 32
7374
- );
7375
- }`.trim();
7376
- }
7377
- function getPanelAppScrollRestorationScript() {
7378
- return `
7379
- function installScrollRestoration() {
7380
- const scrollContract = ${JSON.stringify(PANEL_APP_SCROLL_RESTORATION_CONTRACT)};
7381
- const inlineHostContract = ${JSON.stringify(PANEL_APP_INLINE_HOST_CONTRACT)};
7382
- const searchParams = new URLSearchParams(window.location.search);
7383
- if (
7384
- searchParams.get(inlineHostContract.displayModeSearchParam) === inlineHostContract.displayMode &&
7385
- searchParams.get(inlineHostContract.placementSearchParam) === inlineHostContract.placement
7386
- ) {
7387
- return;
7388
- }
7389
- let isScrollReportScheduled = false;
7390
- let latestScrollSurface = null;
7391
-
7392
- ${getPanelAppScrollSurfaceHelpersScript()}
7393
-
7394
- function reportScroll() {
7395
- isScrollReportScheduled = false;
7396
- const surface = latestScrollSurface;
7397
- latestScrollSurface = null;
7398
- if (!surface) {
7399
- return;
7400
- }
7401
- const position = readScrollPosition(resolveScrollSurface(surface));
7402
- if (!position) {
7403
- return;
7404
- }
7405
- window.parent.postMessage({
7406
- type: scrollContract.scrollMessageType,
7407
- version: scrollContract.version,
7408
- target: surface,
7409
- ...position,
7410
- }, "*");
7411
- }
7412
-
7413
- function scheduleScrollReport(event) {
7414
- const surface = getScrollSurface(event?.target);
7415
- if (!surface) {
7416
- return;
7417
- }
7418
- latestScrollSurface = surface;
7419
- if (isScrollReportScheduled) {
7420
- return;
7421
- }
7422
- isScrollReportScheduled = true;
7423
- if (typeof window.requestAnimationFrame === "function") {
7424
- window.requestAnimationFrame(reportScroll);
7425
- return;
7426
- }
7427
- reportScroll();
7428
- }
7429
-
7430
- function applyScrollPosition(target, x, y) {
7431
- const element = resolveScrollSurface(target);
7432
- if (element === undefined) {
7433
- return false;
7434
- }
7435
- if (element && typeof element.scrollTo === "function") {
7436
- element.scrollTo(x, y);
7437
- } else if (element) {
7438
- element.scrollLeft = x;
7439
- element.scrollTop = y;
7440
- } else {
7441
- window.scrollTo(x, y);
7442
- }
7443
- const position = readScrollPosition(element);
7444
- return position && Math.abs(position.x - x) <= 1 && Math.abs(position.y - y) <= 1;
7445
- }
7446
-
7447
- function restoreScroll(target, x, y) {
7448
- if (applyScrollPosition(target, x, y)) {
7449
- return;
7450
- }
7451
- let resizeObserver;
7452
- let mutationObserver;
7453
- let timeoutId;
7454
- const stop = () => {
7455
- resizeObserver?.disconnect();
7456
- mutationObserver?.disconnect();
7457
- if (timeoutId !== undefined && typeof window.clearTimeout === "function") {
7458
- window.clearTimeout(timeoutId);
7459
- }
7460
- };
7461
- const retry = () => {
7462
- if (applyScrollPosition(target, x, y)) {
7463
- stop();
7464
- }
7465
- };
7466
- if (typeof window.ResizeObserver === "function") {
7467
- resizeObserver = new window.ResizeObserver(retry);
7468
- resizeObserver.observe(window.document.documentElement);
7469
- if (window.document.body) {
7470
- resizeObserver.observe(window.document.body);
7471
- }
7472
- }
7473
- if (typeof window.MutationObserver === "function" && window.document.body) {
7474
- mutationObserver = new window.MutationObserver(retry);
7475
- mutationObserver.observe(window.document.body, { childList: true, subtree: true });
7476
- }
7477
- if (typeof window.setTimeout === "function") {
7478
- timeoutId = window.setTimeout(stop, 10000);
7479
- }
7480
- }
7481
-
7482
- if (typeof window.document.addEventListener === "function") {
7483
- window.document.addEventListener("scroll", scheduleScrollReport, true);
7484
- }
7485
- window.addEventListener("message", (event) => {
7486
- const data = event.data;
7487
- if (
7488
- event.source !== window.parent ||
7489
- !data ||
7490
- data.type !== scrollContract.restoreScrollMessageType ||
7491
- data.version !== scrollContract.version ||
7492
- !isScrollTarget(data.target) ||
7493
- !Number.isFinite(data.x) ||
7494
- !Number.isFinite(data.y) ||
7495
- data.x < 0 ||
7496
- data.y < 0
7497
- ) {
7498
- return;
7499
- }
7500
- restoreScroll(data.target, data.x, data.y);
7501
- });
7502
- }`.trim();
7503
- }
7504
- function injectPanelAppBridgeScript(html, params) {
7505
- if (html.includes(PANEL_APP_BRIDGE_MARKER)) return html;
7506
- const script = `<script>${getPanelAppBridgeScript(params)}<\/script>`;
7507
- const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
7508
- if (headMatch?.index !== void 0) {
7509
- const insertAt = headMatch.index + headMatch[0].length;
7510
- return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
7511
- }
7512
- return `${script}${html}`;
7513
- }
7514
- function getPanelAppBridgeScript(params = {
7515
- appId: "",
7516
- runtimeToken: ""
7517
- }) {
7518
- const appId = JSON.stringify(params.appId);
7519
- const runtimeToken = JSON.stringify(params.runtimeToken);
7520
- return `
7521
- ${getUiContentParamsBootstrapScript()}
7522
- (() => {
7523
- const requestType = "nextclaw:panel-app-service-actions:request";
7524
- const responseType = "nextclaw:panel-app-service-actions:response";
7525
- const appId = ${appId};
7526
- const runtimeToken = ${runtimeToken};
7527
- const pending = new Map();
7528
- let counter = 0;
7529
-
7530
- function createRequestId() {
7531
- counter += 1;
7532
- return "panel-bridge-" + Date.now().toString(36) + "-" + counter.toString(36);
7533
- }
7534
-
7535
- function request(method, payload) {
7536
- const requestId = createRequestId();
7537
- return new Promise((resolve, reject) => {
7538
- pending.set(requestId, { method, resolve, reject });
7539
- window.parent.postMessage({ type: requestType, requestId, appId, runtimeToken, method, payload }, "*");
7540
- });
7541
- }
7542
-
7543
- ${getPanelAppInlineContentHeightReporterScript()}
7544
- ${getPanelAppScrollRestorationScript()}
7545
-
7546
- function resolveApiFetchUrl(input) {
7547
- const raw = typeof input === "string" || input instanceof URL ? input.toString() : input?.url;
7548
- if (typeof raw !== "string") {
7549
- return null;
7550
- }
7551
- try {
7552
- const url = new URL(raw, window.location.href);
7553
- return url.origin === window.location.origin && url.pathname.startsWith("/api/") ? url : null;
7554
- } catch {
7555
- return null;
7556
- }
7557
- }
7558
-
7559
- function createFetchInitWithRuntimeToken(input, init) {
7560
- if (!resolveApiFetchUrl(input)) {
7561
- return init;
7562
- }
7563
- const headers = new Headers(init?.headers || (typeof input === "object" && input ? input.headers : undefined));
7564
- if (!headers.has("x-nextclaw-panel-bridge-session")) {
7565
- headers.set("x-nextclaw-panel-bridge-session", runtimeToken);
7566
- }
7567
- return { ...init, headers };
7568
- }
7569
-
7570
- const nativeFetch = window.fetch?.bind(window);
7571
- if (nativeFetch) {
7572
- window.fetch = (input, init) => nativeFetch(input, createFetchInitWithRuntimeToken(input, init));
7573
- }
7574
-
7575
- function unwrapServiceActionResult(result) {
7576
- if (!result || typeof result !== "object") {
7577
- return result;
7578
- }
7579
- if (Object.prototype.hasOwnProperty.call(result, "structuredContent") && result.structuredContent !== undefined) {
7580
- return result.structuredContent;
7581
- }
7582
- const content = Array.isArray(result.content) ? result.content : undefined;
7583
- if (content && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string") {
7584
- try {
7585
- return JSON.parse(content[0].text);
7586
- } catch {
7587
- return content[0].text;
7588
- }
7589
- }
7590
- return result;
7591
- }
7592
-
7593
- function resolveBridgeData(entry, data) {
7594
- if (entry.method === "list") {
7595
- return Array.isArray(data.data?.actions) ? data.data.actions : [];
7596
- }
7597
- if (entry.method === "invoke") {
7598
- return unwrapServiceActionResult(data.data?.result);
7599
- }
7600
- if (entry.method === "agent.generateObject") {
7601
- return data.data?.result;
7602
- }
7603
- return data.data;
7604
- }
7605
-
7606
- window.addEventListener("message", (event) => {
7607
- const data = event.data;
7608
- if (!data || data.type !== responseType || typeof data.requestId !== "string") {
7609
- return;
7610
- }
7611
- const entry = pending.get(data.requestId);
7612
- if (!entry) {
7613
- return;
7614
- }
7615
- pending.delete(data.requestId);
7616
- if (data.ok) {
7617
- entry.resolve(resolveBridgeData(entry, data));
7618
- return;
7619
- }
7620
- const error = new Error(data.error?.message || "NextClaw panel bridge request failed.");
7621
- error.code = data.error?.code;
7622
- error.details = data.error?.details;
7623
- entry.reject(error);
7624
- });
7625
-
7626
- const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
7627
- Object.defineProperty(window, "nextclaw", {
7628
- configurable: true,
7629
- value: {
7630
- ...existing,
7631
- serviceActions: {
7632
- list: () => request("list", {}),
7633
- invoke: (actionId, input) => request("invoke", { actionId, input }),
7634
- requestGrant: (actionId) => request("requestGrant", { actionId }),
7635
- revokeGrant: (actionId) => request("revokeGrant", { actionId })
7636
- },
7637
- agent: {
7638
- send: (input) => request("agent.send", { request: input }),
7639
- generateObject: (input) => request("agent.generateObject", { input })
7640
- }
7641
- }
7642
- });
7643
- installInlineContentHeightReporter();
7644
- installScrollRestoration();
7645
- })();
7646
- `.trim();
7647
- }
7648
- //#endregion
7649
- //#region src/utils/panel-app-client-injection.utils.ts
7650
- const PANEL_APP_CLIENT_MARKER = "nextclaw:panel-app-client:init";
7651
- const PANEL_APP_CLIENT_SDK_PATH = "/api/panel-app-client-sdk.js";
7652
- function injectPanelAppClientScript(html, params) {
7653
- if (html.includes(PANEL_APP_CLIENT_MARKER)) return html;
7654
- const script = [`<script src="${PANEL_APP_CLIENT_SDK_PATH}" crossorigin="anonymous"><\/script>`, `<script>${getPanelAppClientInitScript(params)}<\/script>`].join("");
7655
- const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
7656
- if (headMatch?.index !== void 0) {
7657
- const insertAt = headMatch.index + headMatch[0].length;
7658
- return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
7659
- }
7660
- return `${script}${html}`;
7661
- }
7662
- function getPanelAppClientInitScript(params) {
7663
- return `
7664
- (() => {
7665
- const marker = "${PANEL_APP_CLIENT_MARKER}";
7666
- if (typeof window.NextClawClient !== "function") {
7667
- console.error("[NextClaw] Panel App client SDK failed to load.");
7668
- return;
7669
- }
7670
- if (typeof window.createNextClawAppClient !== "function") {
7671
- console.error("[NextClaw] Panel App client projection failed to load.");
7672
- return;
7673
- }
7674
- const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
7675
- const hostClient = new window.NextClawClient({
7676
- baseUrl: window.location.origin,
7677
- headers: {
7678
- "x-nextclaw-panel-bridge-session": ${JSON.stringify(params.runtimeToken)}
7679
- }
7680
- });
7681
- const client = window.createNextClawAppClient(hostClient);
7682
- Object.defineProperty(window, "nextclaw", {
7683
- configurable: true,
7684
- value: {
7685
- ...existing,
7686
- client
7687
- }
7688
- });
7689
- Object.defineProperty(window.nextclaw, "__clientInitMarker", {
7690
- configurable: true,
7691
- enumerable: false,
7692
- value: marker
7693
- });
7694
- })();
7695
- `.trim();
7696
- }
7697
- //#endregion
7698
- //#region src/utils/panel-app-manifest.utils.ts
7699
- const PANEL_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7700
- function parsePanelAppManifest(html) {
7701
- return {
7702
- ...readHtmlTitle(html),
7703
- ...readStandardIcon(html),
7704
- ...readPanelAppMeta(html),
7705
- capabilities: readPanelAppCapabilities(html),
7706
- client: false,
7707
- serviceActions: readPanelAppServiceActions(html)
7708
- };
7709
- }
7710
- function parsePanelAppFolderManifest(raw) {
7711
- let parsed;
7712
- try {
7713
- parsed = JSON.parse(raw);
7714
- } catch (error) {
7715
- throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
7716
- }
7717
- if (!isRecord$8(parsed)) throw new Error("panel-app.json must contain an object.");
7718
- const id = readOptionalString$8(parsed, "id");
7719
- if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
7962
+ //#region src/utils/panel-app-agent.utils.ts
7963
+ const PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS = 6e4;
7964
+ const PANEL_APP_AGENT_MAX_TIMEOUT_MS = 12e4;
7965
+ const PANEL_APP_AGENT_MAX_PROMPT_CHARS = 2e4;
7966
+ const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
7967
+ function normalizePanelAppGenerateObjectInput(input) {
7968
+ const peerId = input.peerId.trim();
7969
+ const prompt = input.prompt.trim();
7970
+ if (!peerId || !prompt || !isRecord$9(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
7971
+ if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
7972
+ if (stringifyJsonValue(input.context ?? null).length > PANEL_APP_AGENT_MAX_CONTEXT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject context is too large");
7720
7973
  return {
7721
- id,
7722
- title: readRequiredString$7(parsed, "title"),
7723
- description: readOptionalString$8(parsed, "description"),
7724
- icon: readOptionalString$8(parsed, "icon"),
7725
- entry: readRequiredString$7(parsed, "entry"),
7726
- capabilities: readStringArray$1(parsed.capabilities, "capabilities"),
7727
- client: readOptionalBoolean$2(parsed, "client"),
7728
- serviceActions: readStringArray$1(parsed.actions, "actions")
7974
+ context: input.context,
7975
+ peerId,
7976
+ prompt,
7977
+ schema: input.schema,
7978
+ timeoutMs: normalizeTimeoutMs(input.timeoutMs),
7979
+ title: input.title?.trim() || void 0
7729
7980
  };
7730
7981
  }
7731
- function readPanelAppMeta(html) {
7982
+ function createPanelAppGenerateObjectMessage(params) {
7983
+ const { bridgeSession, request, requestId } = params;
7732
7984
  return {
7733
- ...readPanelAppMetaField(html, "title"),
7734
- ...readPanelAppMetaField(html, "description"),
7735
- ...readPanelAppMetaField(html, "icon")
7985
+ id: `panel-app-agent-message-${randomUUID()}`,
7986
+ metadata: {
7987
+ ...createPanelAppAgentMetadata(bridgeSession),
7988
+ panel_app_peer_id: request.peerId,
7989
+ structured_result: {
7990
+ request_id: requestId,
7991
+ schema: structuredClone(request.schema),
7992
+ tool_name: STRUCTURED_RESULT_TOOL_NAME
7993
+ }
7994
+ },
7995
+ parts: [{
7996
+ type: "text",
7997
+ text: buildGenerateObjectPrompt(bridgeSession, request)
7998
+ }],
7999
+ role: "user",
8000
+ status: "final",
8001
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7736
8002
  };
7737
8003
  }
7738
- function readPanelAppMetaField(html, field) {
7739
- const content = readMetaContent(html, `nextclaw-panel-${field}`, field === "icon" ? "attribute" : "text");
7740
- const manifest = {};
7741
- if (content) manifest[field] = content;
7742
- return manifest;
7743
- }
7744
- function readHtmlTitle(html) {
7745
- const title = normalizeTextValue(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]);
7746
- return title ? { title } : {};
7747
- }
7748
- function readStandardIcon(html) {
7749
- const icon = readLinkHref(html, (relTokens) => relTokens.includes("icon"));
7750
- const appleTouchIcon = readLinkHref(html, (relTokens) => relTokens.some((token) => token === "apple-touch-icon" || token === "apple-touch-icon-precomposed"));
7751
- const href = normalizeIconHref(icon ?? appleTouchIcon);
7752
- return href ? { icon: href } : {};
7753
- }
7754
- function readMetaContent(html, name, valueKind) {
7755
- const metaTags = html.matchAll(/<meta\s+([^>]*?)>/gi);
7756
- for (const tag of metaTags) {
7757
- const attributes = tag[1] ?? "";
7758
- if (readHtmlAttribute(attributes, "name") === name) {
7759
- const content = readHtmlAttribute(attributes, "content");
7760
- return valueKind === "attribute" ? normalizeAttributeValue(content) : normalizeTextValue(content);
8004
+ async function waitForPanelAppStructuredResult(agentRunClient, params) {
8005
+ const iterator = agentRunClient.sendAndStreamEvents(params.payload)[Symbol.asyncIterator]();
8006
+ let timeoutId;
8007
+ const timeout = new Promise((_, reject) => {
8008
+ timeoutId = setTimeout(() => reject(new PanelAppError("AGENT_OBJECT_RESULT_TIMEOUT", "agent object result timed out")), params.timeoutMs);
8009
+ });
8010
+ let resultToolCallId = null;
8011
+ try {
8012
+ while (true) {
8013
+ const next = await Promise.race([iterator.next(), timeout]);
8014
+ if (next.done) break;
8015
+ const result = readStructuredResultEvent(next.value, resultToolCallId);
8016
+ if (result.matchedToolCallId) resultToolCallId = result.matchedToolCallId;
8017
+ if (result.submitted) return result.content;
8018
+ throwIfTerminalError(next.value);
7761
8019
  }
8020
+ } finally {
8021
+ if (timeoutId) clearTimeout(timeoutId);
8022
+ await iterator.return?.(void 0);
7762
8023
  }
8024
+ throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
7763
8025
  }
7764
- function readLinkHref(html, matchesRel) {
7765
- const linkTags = html.matchAll(/<link\s+([^>]*?)>/gi);
7766
- for (const tag of linkTags) {
7767
- const attributes = tag[1] ?? "";
7768
- if (matchesRel((readHtmlAttribute(attributes, "rel") ?? "").toLowerCase().split(/\s+/).filter(Boolean))) return normalizeAttributeValue(readHtmlAttribute(attributes, "href"));
7769
- }
7770
- }
7771
- function readHtmlAttribute(attributes, attribute) {
7772
- const match = attributes.match(new RegExp(`(?:^|\\s)${attribute}\\s*=\\s*(["'])(.*?)\\1`, "i"));
7773
- return match?.[2] ? decodeHtmlAttribute(match[2]) : void 0;
7774
- }
7775
- function readPanelAppServiceActions(html) {
7776
- return parseTokenList(readMetaContent(html, "nextclaw-panel-actions", "attribute"));
7777
- }
7778
- function readPanelAppCapabilities(html) {
7779
- return parseTokenList(readMetaContent(html, "nextclaw-panel-capabilities", "attribute"));
7780
- }
7781
- function parseTokenList(content) {
7782
- if (!content) return [];
7783
- return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
7784
- }
7785
- function readRequiredString$7(record, key) {
7786
- const value = readOptionalString$8(record, key);
7787
- if (!value) throw new Error(`panel app ${key} is required.`);
7788
- return value;
7789
- }
7790
- function readOptionalString$8(record, key) {
7791
- return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
7792
- }
7793
- function readOptionalBoolean$2(record, key) {
7794
- const value = record[key];
7795
- if (value === void 0) return false;
7796
- if (typeof value !== "boolean") throw new Error(`panel app ${key} must be a boolean.`);
7797
- return value;
7798
- }
7799
- function readStringArray$1(value, key) {
7800
- if (value === void 0) return [];
7801
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
7802
- return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
7803
- }
7804
- function isRecord$8(value) {
7805
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7806
- }
7807
- function normalizeTextValue(value) {
7808
- return decodeHtmlAttribute(value ?? "").replace(/\s+/g, " ").trim() || void 0;
7809
- }
7810
- function normalizeAttributeValue(value) {
7811
- return decodeHtmlAttribute(value ?? "").trim() || void 0;
7812
- }
7813
- function normalizeIconHref(value) {
7814
- if (!value) return;
7815
- if (value.startsWith("data:image/") || value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/")) return value;
7816
- }
7817
- function decodeHtmlAttribute(value) {
7818
- return value.replace(/&quot;/g, "\"").replace(/&#34;/g, "\"").replace(/&apos;/g, "'").replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
7819
- }
7820
- const PANEL_APP_FOLDER_MANIFEST_FILE_NAME = "panel-app.json";
7821
- function isPanelAppSourceEntry(entry) {
7822
- return entry.isFile() && isPanelAppFileName(entry.name) || entry.isDirectory() && isPanelAppDirName(entry.name);
8026
+ function withPanelAppAgentMetadata(payload, bridgeSession) {
8027
+ const panelAppMetadata = createPanelAppAgentMetadata(bridgeSession);
8028
+ const peerId = readOptionalPeerId(payload.peerId);
8029
+ const sessionId = readOptionalSessionId(payload.sessionId);
8030
+ if (peerId && sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request cannot include both sessionId and peerId");
8031
+ const metadata = {
8032
+ ...payload.metadata ?? {},
8033
+ ...panelAppMetadata
8034
+ };
8035
+ if (peerId) metadata.panel_app_peer_id = peerId;
8036
+ if (Array.isArray(payload.content)) return {
8037
+ content: structuredClone(payload.content),
8038
+ metadata,
8039
+ peerId,
8040
+ sessionId
8041
+ };
8042
+ if (!payload.message) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request is required");
8043
+ return {
8044
+ message: {
8045
+ ...structuredClone(payload.message),
8046
+ metadata: {
8047
+ ...payload.message.metadata ?? {},
8048
+ ...panelAppMetadata
8049
+ }
8050
+ },
8051
+ metadata,
8052
+ peerId,
8053
+ sessionId
8054
+ };
7823
8055
  }
7824
- function isPanelAppFileName(fileName) {
7825
- return hasSafeBaseName(fileName) && fileName.endsWith(".panel.html");
8056
+ function createPanelAppAgentMetadata(bridgeSession) {
8057
+ return {
8058
+ agent_peer_scope: `panel-app:${bridgeSession.appId}`,
8059
+ panel_app_bridge_request_id: randomUUID(),
8060
+ panel_app_id: bridgeSession.appId,
8061
+ source_kind: "panel_app"
8062
+ };
7826
8063
  }
7827
- function isPanelAppDirName(dirName) {
7828
- return hasSafeBaseName(dirName) && dirName.endsWith(".panel");
8064
+ function normalizeTimeoutMs(timeoutMs) {
8065
+ if (!Number.isFinite(timeoutMs) || typeof timeoutMs !== "number") return PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS;
8066
+ return Math.min(PANEL_APP_AGENT_MAX_TIMEOUT_MS, Math.max(1e3, Math.trunc(timeoutMs)));
7829
8067
  }
7830
- function toPanelAppTitle(sourceName) {
7831
- return sourceName.replace(new RegExp(`${escapeRegExp(".panel.html")}$`), "").replace(new RegExp(`${escapeRegExp(".panel")}$`), "").replace(/[-_]+/g, " ").trim() || sourceName;
8068
+ function readOptionalPeerId(value) {
8069
+ if (typeof value !== "string") return;
8070
+ const peerId = value.trim();
8071
+ if (!peerId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent peerId cannot be empty");
8072
+ return peerId;
7832
8073
  }
7833
- function encodePanelAppId(sourceName) {
7834
- return Buffer.from(sourceName, "utf8").toString("base64url");
8074
+ function readOptionalSessionId(value) {
8075
+ if (typeof value !== "string") return;
8076
+ const sessionId = value.trim();
8077
+ if (!sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent sessionId cannot be empty");
8078
+ return sessionId;
7835
8079
  }
7836
- function decodePanelAppId(id) {
7837
- const normalizedId = id.trim();
7838
- if (!normalizedId) throw new PanelAppError("PANEL_APP_INVALID_ID", "panel app id is required");
7839
- let sourceName = "";
7840
- try {
7841
- sourceName = Buffer.from(normalizedId, "base64url").toString("utf8");
7842
- } catch {
7843
- throw new PanelAppError("PANEL_APP_INVALID_ID", "invalid panel app id");
7844
- }
7845
- if (encodePanelAppId(sourceName) !== normalizedId || !isPanelAppFileName(sourceName) && !isPanelAppDirName(sourceName)) throw new PanelAppError("PANEL_APP_INVALID_ID", "invalid panel app id");
7846
- return sourceName;
8080
+ function buildGenerateObjectPrompt(bridgeSession, request) {
8081
+ return [
8082
+ "Panel App generateObject request",
8083
+ "",
8084
+ `Panel App ID: ${bridgeSession.appId}`,
8085
+ `Peer ID: ${request.peerId}`,
8086
+ "",
8087
+ "Context JSON:",
8088
+ stringifyJsonValue(request.context ?? null),
8089
+ "",
8090
+ "Task:",
8091
+ request.prompt,
8092
+ "",
8093
+ "Result contract:",
8094
+ `Call the ${STRUCTURED_RESULT_TOOL_NAME} tool exactly once with an object matching the provided schema.`,
8095
+ "Do not use natural language as the result."
8096
+ ].join("\n");
7847
8097
  }
7848
- async function readPanelAppFolderManifest(dirPath, dirName) {
7849
- const manifest = parsePanelAppFolderManifest(await readFile(join(dirPath, PANEL_APP_FOLDER_MANIFEST_FILE_NAME), "utf8"));
7850
- const expectedId = dirName.slice(0, -6);
7851
- if (manifest.id && manifest.id !== expectedId) throw new PanelAppError("PANEL_APP_MANIFEST_INVALID", "panel app manifest id must match the directory name");
8098
+ function readStructuredResultEvent(event, resultToolCallId) {
8099
+ if (event.type === NcpEventType.MessageToolCallStart && event.payload.toolName === "nextclaw_submit_result") return {
8100
+ matchedToolCallId: event.payload.toolCallId,
8101
+ submitted: false
8102
+ };
8103
+ if (event.type !== NcpEventType.MessageToolCallResult || event.payload.toolCallId !== resultToolCallId) return { submitted: false };
8104
+ assertToolResultContent(event.payload.content);
7852
8105
  return {
7853
- ...manifest,
7854
- id: expectedId
8106
+ content: event.payload.content,
8107
+ submitted: true
7855
8108
  };
7856
8109
  }
7857
- function resolvePanelAppRelativePath(rootPath, relativePath) {
7858
- if (!relativePath.trim() || relativePath.includes("\0") || isAbsolute(relativePath)) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7859
- const normalizedPath = normalize(relativePath);
7860
- if (normalizedPath === "." || normalizedPath.startsWith("..")) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7861
- const resolvedPath = resolve(rootPath, normalizedPath);
7862
- const pathFromRoot = relative(resolve(rootPath), resolvedPath);
7863
- if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot)) throw new PanelAppError("PANEL_APP_INVALID_ASSET_PATH", "invalid panel app asset path");
7864
- return resolvedPath;
8110
+ function assertToolResultContent(content) {
8111
+ if (!isRecord$9(content) || content.ok !== false || !isRecord$9(content.error)) return;
8112
+ if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
8113
+ throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
7865
8114
  }
7866
- function resolvePanelAppAssetContentType(path) {
7867
- switch (extname(path).toLowerCase()) {
7868
- case ".css": return "text/css; charset=utf-8";
7869
- case ".js":
7870
- case ".mjs": return "application/javascript; charset=utf-8";
7871
- case ".json": return "application/json; charset=utf-8";
7872
- case ".png": return "image/png";
7873
- case ".svg": return "image/svg+xml; charset=utf-8";
7874
- case ".webp": return "image/webp";
7875
- case ".txt": return "text/plain; charset=utf-8";
7876
- default: return "application/octet-stream";
8115
+ function throwIfTerminalError(event) {
8116
+ if (event.type === NcpEventType.MessageFailed) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error.message);
8117
+ if (event.type === NcpEventType.RunError) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error ?? "agent object request failed");
8118
+ if (event.type === NcpEventType.RunFinished) throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
8119
+ }
8120
+ function stringifyJsonValue(value) {
8121
+ try {
8122
+ return JSON.stringify(value, null, 2);
8123
+ } catch {
8124
+ throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "value is not JSON serializable");
7877
8125
  }
7878
8126
  }
7879
- function resolvePanelAppIconUrl(id, icon) {
7880
- if (!icon) return;
7881
- if (icon.startsWith("data:image/") || icon.startsWith("http://") || icon.startsWith("https://") || icon.startsWith("/") || isLikelyTextIcon(icon)) return icon;
7882
- return `/api/panel-apps/${encodeURIComponent(id)}/assets/${encodePanelAppAssetPath(icon)}`;
8127
+ function isRecord$9(value) {
8128
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7883
8129
  }
7884
- function injectPanelAppAssetBase(html, baseHref) {
7885
- const base = `<base href="${baseHref}">`;
7886
- return injectLocalScriptCrossOrigin((() => {
7887
- if (/<base\b/i.test(html)) return html;
7888
- if (/<head[^>]*>/i.test(html)) return html.replace(/<head[^>]*>/i, (head) => `${head}${base}`);
7889
- return `${base}${html}`;
7890
- })());
8130
+ //#endregion
8131
+ //#region src/services/panel-app-agent-bridge.service.ts
8132
+ var PanelAppAgentBridgeService = class {
8133
+ constructor(params) {
8134
+ this.params = params;
8135
+ }
8136
+ sendAgentMessage = async (bridgeSession, payload) => {
8137
+ await this.assertAgentCapabilityGranted(bridgeSession, "agent:send");
8138
+ return await this.requireAgentRunClient().send(withPanelAppAgentMetadata(payload, bridgeSession));
8139
+ };
8140
+ generateAgentObject = async (bridgeSession, input) => {
8141
+ await this.assertAgentCapabilityGranted(bridgeSession, "agent:generateObject");
8142
+ const request = normalizePanelAppGenerateObjectInput(input);
8143
+ const message = createPanelAppGenerateObjectMessage({
8144
+ bridgeSession,
8145
+ request,
8146
+ requestId: randomUUID()
8147
+ });
8148
+ return { result: await waitForPanelAppStructuredResult(this.requireAgentRunClient(), {
8149
+ payload: {
8150
+ message,
8151
+ metadata: {
8152
+ ...createPanelAppAgentMetadata(bridgeSession),
8153
+ panel_app_peer_id: request.peerId
8154
+ },
8155
+ peerId: request.peerId
8156
+ },
8157
+ timeoutMs: request.timeoutMs
8158
+ }) };
8159
+ };
8160
+ grantAgentCapability = async (bridgeSession, capability) => {
8161
+ if (!isPanelAppAgentCapability(capability)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "unknown panel app agent capability");
8162
+ this.assertDeclaredCapability(bridgeSession, capability);
8163
+ return await this.params.createCapabilityGrantStore().grant({
8164
+ caller: bridgeSession.caller,
8165
+ capability,
8166
+ grantedAt: (/* @__PURE__ */ new Date()).toISOString()
8167
+ });
8168
+ };
8169
+ assertAgentCapabilityGranted = async (bridgeSession, capability) => {
8170
+ this.assertDeclaredCapability(bridgeSession, capability);
8171
+ if (!await this.params.createCapabilityGrantStore().isGranted(bridgeSession.caller, capability)) throw new PanelAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to use ${capability}.`);
8172
+ };
8173
+ assertDeclaredCapability = (bridgeSession, capability) => {
8174
+ if (!bridgeSession.declaredCapabilities.includes(capability)) throw new PanelAppError("PANEL_APP_CAPABILITY_NOT_DECLARED", this.describeMissingAgentCapability(bridgeSession.declaredCapabilities, capability));
8175
+ };
8176
+ describeMissingAgentCapability = (declaredCapabilities, capability) => {
8177
+ const declared = declaredCapabilities.length > 0 ? declaredCapabilities.join(", ") : "none";
8178
+ const valid = PANEL_APP_AGENT_CAPABILITIES.join(", ");
8179
+ const hint = declaredCapabilities.includes(capability.replace(":", ".")) ? ` Use ${capability}, not ${capability.replace(":", ".")}.` : "";
8180
+ return [
8181
+ `panel app did not declare ${capability}.`,
8182
+ `Declared: ${declared}.`,
8183
+ `Valid capabilities: ${valid}.`,
8184
+ `Declare it with nextclaw-panel-capabilities or panel-app.json capabilities.`,
8185
+ hint.trim()
8186
+ ].filter(Boolean).join(" ");
8187
+ };
8188
+ requireAgentRunClient = () => {
8189
+ if (!this.params.agentRunClient) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "panel app agent client is not configured");
8190
+ return this.params.agentRunClient;
8191
+ };
8192
+ };
8193
+ //#endregion
8194
+ //#region src/utils/ui-content-params-injection.utils.ts
8195
+ const UI_CONTENT_PARAMS_BOOTSTRAP_MARKER = "nextclaw:content-params:bootstrap";
8196
+ function getUiContentParamsBootstrapScript() {
8197
+ return `
8198
+ /* ${UI_CONTENT_PARAMS_BOOTSTRAP_MARKER} */
8199
+ (() => {
8200
+ const contract = ${JSON.stringify(UI_CONTENT_PARAMS_HOST_CONTRACT)};
8201
+ const rawWindowName = typeof window.name === "string" ? window.name : "";
8202
+ if (!rawWindowName.startsWith(contract.windowNamePrefix)) {
8203
+ return;
8204
+ }
8205
+ window.name = "";
8206
+ let params;
8207
+ try {
8208
+ params = JSON.parse(rawWindowName.slice(contract.windowNamePrefix.length));
8209
+ } catch {
8210
+ return;
8211
+ }
8212
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
8213
+ return;
8214
+ }
8215
+ const freezeJson = (value) => {
8216
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) {
8217
+ return value;
8218
+ }
8219
+ Object.values(value).forEach(freezeJson);
8220
+ return Object.freeze(value);
8221
+ };
8222
+ const existing = window.nextclaw && typeof window.nextclaw === "object"
8223
+ ? window.nextclaw
8224
+ : {};
8225
+ Object.defineProperty(window, "nextclaw", {
8226
+ configurable: true,
8227
+ value: {
8228
+ ...existing,
8229
+ params: freezeJson(params)
8230
+ }
8231
+ });
8232
+ })();
8233
+ `.trim();
7891
8234
  }
7892
- function injectLocalScriptCrossOrigin(html) {
7893
- return html.replace(/<script\b(?=[^>]*\bsrc\s*=)(?![^>]*\bcrossorigin\b)[^>]*>/gi, (tag) => {
7894
- const src = extractScriptSrc(tag);
7895
- if (!src || !isLocalScriptSrc(src)) return tag;
7896
- return `${tag.slice(0, -1)} crossorigin="anonymous">`;
7897
- });
8235
+ function injectUiContentParamsBootstrap(html) {
8236
+ if (html.includes(UI_CONTENT_PARAMS_BOOTSTRAP_MARKER)) return html;
8237
+ const script = `<script>${getUiContentParamsBootstrapScript()}<\/script>`;
8238
+ const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
8239
+ if (headMatch?.index !== void 0) {
8240
+ const insertAt = headMatch.index + headMatch[0].length;
8241
+ return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
8242
+ }
8243
+ return `${script}${html}`;
7898
8244
  }
7899
- function extractScriptSrc(tag) {
7900
- const match = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tag);
7901
- return match?.[1] ?? match?.[2] ?? match?.[3];
8245
+ //#endregion
8246
+ //#region src/utils/panel-app-bridge.utils.ts
8247
+ const PANEL_APP_BRIDGE_MARKER = "nextclaw:panel-app-service-actions:request";
8248
+ function getPanelAppInlineContentHeightReporterScript() {
8249
+ return `
8250
+ function installInlineContentHeightReporter() {
8251
+ const inlineHostContract = ${JSON.stringify(PANEL_APP_INLINE_HOST_CONTRACT)};
8252
+ const readInlineContentHeight = ${readInlineContentHeight.toString()};
8253
+ if (!window.location || !window.document) {
8254
+ return;
8255
+ }
8256
+ const searchParams = new URLSearchParams(window.location.search);
8257
+ if (
8258
+ searchParams.get(inlineHostContract.displayModeSearchParam) !== inlineHostContract.displayMode ||
8259
+ searchParams.get(inlineHostContract.placementSearchParam) !== inlineHostContract.placement
8260
+ ) {
8261
+ return;
8262
+ }
8263
+ const start = () => {
8264
+ const { body, documentElement } = window.document;
8265
+ if (!documentElement) {
8266
+ return;
8267
+ }
8268
+ let lastHeight = 0;
8269
+ const reportHeight = () => {
8270
+ const height = readInlineContentHeight(body, documentElement);
8271
+ if (height > 0 && height !== lastHeight) {
8272
+ lastHeight = height;
8273
+ window.parent.postMessage({ type: inlineHostContract.contentHeightMessageType, height }, "*");
8274
+ }
8275
+ };
8276
+ if (typeof window.ResizeObserver === "function") {
8277
+ const observer = new window.ResizeObserver(reportHeight);
8278
+ observer.observe(documentElement);
8279
+ if (body) {
8280
+ observer.observe(body);
8281
+ }
8282
+ }
8283
+ window.addEventListener("load", reportHeight);
8284
+ reportHeight();
8285
+ };
8286
+ if (window.document.readyState === "loading") {
8287
+ window.document.addEventListener("DOMContentLoaded", start, { once: true });
8288
+ return;
8289
+ }
8290
+ start();
8291
+ }`.trim();
8292
+ }
8293
+ function getPanelAppScrollSurfaceHelpersScript() {
8294
+ return `
8295
+ function readScrollPosition(element) {
8296
+ const x = element ? element.scrollLeft : window.scrollX;
8297
+ const y = element ? element.scrollTop : window.scrollY;
8298
+ if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) {
8299
+ return null;
8300
+ }
8301
+ return { x, y };
8302
+ }
8303
+
8304
+ function getScrollSurface(target) {
8305
+ const root = window.document.scrollingElement;
8306
+ if (
8307
+ !target ||
8308
+ target === window.document ||
8309
+ target === root ||
8310
+ target === window.document.documentElement ||
8311
+ target === window.document.body
8312
+ ) {
8313
+ return { kind: "document" };
8314
+ }
8315
+ if (!target.parentElement || !target.children || typeof target.scrollTop !== "number") {
8316
+ return null;
8317
+ }
8318
+ const path = [];
8319
+ let element = target;
8320
+ while (element && element !== window.document.body) {
8321
+ const parent = element.parentElement;
8322
+ if (!parent || !parent.children) {
8323
+ return null;
8324
+ }
8325
+ const index = Array.prototype.indexOf.call(parent.children, element);
8326
+ const tagName = typeof element.tagName === "string" ? element.tagName.toLowerCase() : "";
8327
+ if (index < 0 || !tagName) {
8328
+ return null;
8329
+ }
8330
+ path.unshift({ index, tagName });
8331
+ element = parent;
8332
+ }
8333
+ return element === window.document.body && path.length > 0 ? { kind: "element", path } : null;
8334
+ }
8335
+
8336
+ function resolveScrollSurface(target) {
8337
+ if (target.kind === "document") {
8338
+ return null;
8339
+ }
8340
+ let element = window.document.body;
8341
+ for (const segment of target.path) {
8342
+ const child = element?.children?.[segment.index];
8343
+ if (!child || child.tagName?.toLowerCase() !== segment.tagName) {
8344
+ return undefined;
8345
+ }
8346
+ element = child;
8347
+ }
8348
+ return element;
8349
+ }
8350
+
8351
+ function isScrollTarget(value) {
8352
+ if (!value || typeof value !== "object") {
8353
+ return false;
8354
+ }
8355
+ if (value.kind === "document") {
8356
+ return true;
8357
+ }
8358
+ return value.kind === "element" &&
8359
+ Array.isArray(value.path) &&
8360
+ value.path.length > 0 &&
8361
+ value.path.length <= 30 &&
8362
+ value.path.every((segment) =>
8363
+ segment &&
8364
+ Number.isInteger(segment.index) &&
8365
+ segment.index >= 0 &&
8366
+ segment.index <= 1000 &&
8367
+ typeof segment.tagName === "string" &&
8368
+ segment.tagName.length > 0 &&
8369
+ segment.tagName.length <= 32
8370
+ );
8371
+ }`.trim();
7902
8372
  }
7903
- function isLocalScriptSrc(src) {
7904
- const value = src.trim();
7905
- if (!value || value.startsWith("//")) return false;
7906
- return !/^(?:https?|data|blob|javascript):/i.test(value);
8373
+ function getPanelAppScrollRestorationScript() {
8374
+ return `
8375
+ function installScrollRestoration() {
8376
+ const scrollContract = ${JSON.stringify(PANEL_APP_SCROLL_RESTORATION_CONTRACT)};
8377
+ const inlineHostContract = ${JSON.stringify(PANEL_APP_INLINE_HOST_CONTRACT)};
8378
+ const searchParams = new URLSearchParams(window.location.search);
8379
+ if (
8380
+ searchParams.get(inlineHostContract.displayModeSearchParam) === inlineHostContract.displayMode &&
8381
+ searchParams.get(inlineHostContract.placementSearchParam) === inlineHostContract.placement
8382
+ ) {
8383
+ return;
8384
+ }
8385
+ let isScrollReportScheduled = false;
8386
+ let latestScrollSurface = null;
8387
+
8388
+ ${getPanelAppScrollSurfaceHelpersScript()}
8389
+
8390
+ function reportScroll() {
8391
+ isScrollReportScheduled = false;
8392
+ const surface = latestScrollSurface;
8393
+ latestScrollSurface = null;
8394
+ if (!surface) {
8395
+ return;
8396
+ }
8397
+ const position = readScrollPosition(resolveScrollSurface(surface));
8398
+ if (!position) {
8399
+ return;
8400
+ }
8401
+ window.parent.postMessage({
8402
+ type: scrollContract.scrollMessageType,
8403
+ version: scrollContract.version,
8404
+ target: surface,
8405
+ ...position,
8406
+ }, "*");
8407
+ }
8408
+
8409
+ function scheduleScrollReport(event) {
8410
+ const surface = getScrollSurface(event?.target);
8411
+ if (!surface) {
8412
+ return;
8413
+ }
8414
+ latestScrollSurface = surface;
8415
+ if (isScrollReportScheduled) {
8416
+ return;
8417
+ }
8418
+ isScrollReportScheduled = true;
8419
+ if (typeof window.requestAnimationFrame === "function") {
8420
+ window.requestAnimationFrame(reportScroll);
8421
+ return;
8422
+ }
8423
+ reportScroll();
8424
+ }
8425
+
8426
+ function applyScrollPosition(target, x, y) {
8427
+ const element = resolveScrollSurface(target);
8428
+ if (element === undefined) {
8429
+ return false;
8430
+ }
8431
+ if (element && typeof element.scrollTo === "function") {
8432
+ element.scrollTo(x, y);
8433
+ } else if (element) {
8434
+ element.scrollLeft = x;
8435
+ element.scrollTop = y;
8436
+ } else {
8437
+ window.scrollTo(x, y);
8438
+ }
8439
+ const position = readScrollPosition(element);
8440
+ return position && Math.abs(position.x - x) <= 1 && Math.abs(position.y - y) <= 1;
8441
+ }
8442
+
8443
+ function restoreScroll(target, x, y) {
8444
+ if (applyScrollPosition(target, x, y)) {
8445
+ return;
8446
+ }
8447
+ let resizeObserver;
8448
+ let mutationObserver;
8449
+ let timeoutId;
8450
+ const stop = () => {
8451
+ resizeObserver?.disconnect();
8452
+ mutationObserver?.disconnect();
8453
+ if (timeoutId !== undefined && typeof window.clearTimeout === "function") {
8454
+ window.clearTimeout(timeoutId);
8455
+ }
8456
+ };
8457
+ const retry = () => {
8458
+ if (applyScrollPosition(target, x, y)) {
8459
+ stop();
8460
+ }
8461
+ };
8462
+ if (typeof window.ResizeObserver === "function") {
8463
+ resizeObserver = new window.ResizeObserver(retry);
8464
+ resizeObserver.observe(window.document.documentElement);
8465
+ if (window.document.body) {
8466
+ resizeObserver.observe(window.document.body);
8467
+ }
8468
+ }
8469
+ if (typeof window.MutationObserver === "function" && window.document.body) {
8470
+ mutationObserver = new window.MutationObserver(retry);
8471
+ mutationObserver.observe(window.document.body, { childList: true, subtree: true });
8472
+ }
8473
+ if (typeof window.setTimeout === "function") {
8474
+ timeoutId = window.setTimeout(stop, 10000);
8475
+ }
8476
+ }
8477
+
8478
+ if (typeof window.document.addEventListener === "function") {
8479
+ window.document.addEventListener("scroll", scheduleScrollReport, true);
8480
+ }
8481
+ window.addEventListener("message", (event) => {
8482
+ const data = event.data;
8483
+ if (
8484
+ event.source !== window.parent ||
8485
+ !data ||
8486
+ data.type !== scrollContract.restoreScrollMessageType ||
8487
+ data.version !== scrollContract.version ||
8488
+ !isScrollTarget(data.target) ||
8489
+ !Number.isFinite(data.x) ||
8490
+ !Number.isFinite(data.y) ||
8491
+ data.x < 0 ||
8492
+ data.y < 0
8493
+ ) {
8494
+ return;
8495
+ }
8496
+ restoreScroll(data.target, data.x, data.y);
8497
+ });
8498
+ }`.trim();
7907
8499
  }
7908
- function encodePanelAppAssetPath(path) {
7909
- return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
8500
+ function injectPanelAppBridgeScript(html, params) {
8501
+ if (html.includes(PANEL_APP_BRIDGE_MARKER)) return html;
8502
+ const script = `<script>${getPanelAppBridgeScript(params)}<\/script>`;
8503
+ const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
8504
+ if (headMatch?.index !== void 0) {
8505
+ const insertAt = headMatch.index + headMatch[0].length;
8506
+ return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
8507
+ }
8508
+ return `${script}${html}`;
7910
8509
  }
7911
- function hasSafeBaseName(name) {
7912
- return !name.includes("/") && !name.includes("\\") && !name.includes("\0");
8510
+ function getPanelAppBridgeScript(params = {
8511
+ appId: "",
8512
+ runtimeToken: ""
8513
+ }) {
8514
+ const appId = JSON.stringify(params.appId);
8515
+ const runtimeToken = JSON.stringify(params.runtimeToken);
8516
+ return `
8517
+ ${getUiContentParamsBootstrapScript()}
8518
+ (() => {
8519
+ const requestType = "nextclaw:panel-app-service-actions:request";
8520
+ const responseType = "nextclaw:panel-app-service-actions:response";
8521
+ const appId = ${appId};
8522
+ const runtimeToken = ${runtimeToken};
8523
+ const pending = new Map();
8524
+ let counter = 0;
8525
+
8526
+ function createRequestId() {
8527
+ counter += 1;
8528
+ return "panel-bridge-" + Date.now().toString(36) + "-" + counter.toString(36);
8529
+ }
8530
+
8531
+ function request(method, payload) {
8532
+ const requestId = createRequestId();
8533
+ return new Promise((resolve, reject) => {
8534
+ pending.set(requestId, { method, resolve, reject });
8535
+ window.parent.postMessage({ type: requestType, requestId, appId, runtimeToken, method, payload }, "*");
8536
+ });
8537
+ }
8538
+
8539
+ ${getPanelAppInlineContentHeightReporterScript()}
8540
+ ${getPanelAppScrollRestorationScript()}
8541
+
8542
+ function resolveApiFetchUrl(input) {
8543
+ const raw = typeof input === "string" || input instanceof URL ? input.toString() : input?.url;
8544
+ if (typeof raw !== "string") {
8545
+ return null;
8546
+ }
8547
+ try {
8548
+ const url = new URL(raw, window.location.href);
8549
+ return url.origin === window.location.origin && url.pathname.startsWith("/api/") ? url : null;
8550
+ } catch {
8551
+ return null;
8552
+ }
8553
+ }
8554
+
8555
+ function createFetchInitWithRuntimeToken(input, init) {
8556
+ if (!resolveApiFetchUrl(input)) {
8557
+ return init;
8558
+ }
8559
+ const headers = new Headers(init?.headers || (typeof input === "object" && input ? input.headers : undefined));
8560
+ if (!headers.has("x-nextclaw-panel-bridge-session")) {
8561
+ headers.set("x-nextclaw-panel-bridge-session", runtimeToken);
8562
+ }
8563
+ return { ...init, headers };
8564
+ }
8565
+
8566
+ const nativeFetch = window.fetch?.bind(window);
8567
+ if (nativeFetch) {
8568
+ window.fetch = (input, init) => nativeFetch(input, createFetchInitWithRuntimeToken(input, init));
8569
+ }
8570
+
8571
+ function unwrapServiceActionResult(result) {
8572
+ if (!result || typeof result !== "object") {
8573
+ return result;
8574
+ }
8575
+ if (Object.prototype.hasOwnProperty.call(result, "structuredContent") && result.structuredContent !== undefined) {
8576
+ return result.structuredContent;
8577
+ }
8578
+ const content = Array.isArray(result.content) ? result.content : undefined;
8579
+ if (content && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string") {
8580
+ try {
8581
+ return JSON.parse(content[0].text);
8582
+ } catch {
8583
+ return content[0].text;
8584
+ }
8585
+ }
8586
+ return result;
8587
+ }
8588
+
8589
+ function resolveBridgeData(entry, data) {
8590
+ if (entry.method === "list") {
8591
+ return Array.isArray(data.data?.actions) ? data.data.actions : [];
8592
+ }
8593
+ if (entry.method === "invoke") {
8594
+ return unwrapServiceActionResult(data.data?.result);
8595
+ }
8596
+ if (entry.method === "agent.generateObject") {
8597
+ return data.data?.result;
8598
+ }
8599
+ return data.data;
8600
+ }
8601
+
8602
+ window.addEventListener("message", (event) => {
8603
+ const data = event.data;
8604
+ if (!data || data.type !== responseType || typeof data.requestId !== "string") {
8605
+ return;
8606
+ }
8607
+ const entry = pending.get(data.requestId);
8608
+ if (!entry) {
8609
+ return;
8610
+ }
8611
+ pending.delete(data.requestId);
8612
+ if (data.ok) {
8613
+ entry.resolve(resolveBridgeData(entry, data));
8614
+ return;
8615
+ }
8616
+ const error = new Error(data.error?.message || "NextClaw panel bridge request failed.");
8617
+ error.code = data.error?.code;
8618
+ error.details = data.error?.details;
8619
+ entry.reject(error);
8620
+ });
8621
+
8622
+ const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
8623
+ Object.defineProperty(window, "nextclaw", {
8624
+ configurable: true,
8625
+ value: {
8626
+ ...existing,
8627
+ serviceActions: {
8628
+ list: () => request("list", {}),
8629
+ invoke: (actionId, input) => request("invoke", { actionId, input }),
8630
+ requestGrant: (actionId) => request("requestGrant", { actionId }),
8631
+ revokeGrant: (actionId) => request("revokeGrant", { actionId })
8632
+ },
8633
+ agent: {
8634
+ send: (input) => request("agent.send", { request: input }),
8635
+ generateObject: (input) => request("agent.generateObject", { input })
8636
+ }
8637
+ }
8638
+ });
8639
+ installInlineContentHeightReporter();
8640
+ installScrollRestoration();
8641
+ })();
8642
+ `.trim();
7913
8643
  }
7914
- function isLikelyTextIcon(icon) {
7915
- return !icon.includes("/") && !icon.includes(".") && icon.length <= 4;
8644
+ //#endregion
8645
+ //#region src/utils/panel-app-client-injection.utils.ts
8646
+ const PANEL_APP_CLIENT_MARKER = "nextclaw:panel-app-client:init";
8647
+ const PANEL_APP_CLIENT_SDK_PATH = "/api/panel-app-client-sdk.js";
8648
+ function injectPanelAppClientScript(html, params) {
8649
+ if (html.includes(PANEL_APP_CLIENT_MARKER)) return html;
8650
+ const script = [`<script src="${PANEL_APP_CLIENT_SDK_PATH}" crossorigin="anonymous"><\/script>`, `<script>${getPanelAppClientInitScript(params)}<\/script>`].join("");
8651
+ const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
8652
+ if (headMatch?.index !== void 0) {
8653
+ const insertAt = headMatch.index + headMatch[0].length;
8654
+ return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
8655
+ }
8656
+ return `${script}${html}`;
7916
8657
  }
7917
- function escapeRegExp(value) {
7918
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8658
+ function getPanelAppClientInitScript(params) {
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();
7919
8692
  }
7920
8693
  //#endregion
7921
8694
  //#region src/services/panel-app-source.service.ts
@@ -8016,84 +8789,6 @@ function isMissingFileError$1(error) {
8016
8789
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
8017
8790
  }
8018
8791
  //#endregion
8019
- //#region src/utils/panel-app-time.utils.ts
8020
- function resolvePanelAppCreatedAt(fileStat) {
8021
- return fileStat.birthtimeMs > 0 ? fileStat.birthtime.toISOString() : fileStat.mtime.toISOString();
8022
- }
8023
- function resolvePanelAppActivityMs(entry) {
8024
- return Math.max(new Date(entry.lastOpenedAt ?? 0).getTime(), new Date(entry.createdAt).getTime(), new Date(entry.updatedAt).getTime());
8025
- }
8026
- //#endregion
8027
- //#region src/utils/panel-app-content-source.utils.ts
8028
- async function readPanelAppContentSource(params) {
8029
- const { createAssetBaseHref, id, panelsPath, sourceService } = params;
8030
- return await readResolvedPanelAppContentSource(await sourceService.resolveSource(panelsPath, id), createAssetBaseHref);
8031
- }
8032
- async function readPanelAppContentSourceByPath(params) {
8033
- const { createAssetBaseHref, path, sourceService } = params;
8034
- return await readResolvedPanelAppContentSource(await sourceService.resolveSourcePath(path), createAssetBaseHref);
8035
- }
8036
- async function readPanelAppContentSourceByIdOrPath(params) {
8037
- const { sourcePath, ...standardSourceParams } = params;
8038
- return sourcePath ? await readPanelAppContentSourceByPath({
8039
- createAssetBaseHref: params.createAssetBaseHref,
8040
- path: sourcePath,
8041
- sourceService: params.sourceService
8042
- }) : await readPanelAppContentSource(standardSourceParams);
8043
- }
8044
- async function readResolvedPanelAppContentSource(source, createAssetBaseHref) {
8045
- const html = await readFile(source.entryPath, "utf8");
8046
- const manifest = source.manifest ?? parsePanelAppManifest(html);
8047
- const sourceId = encodePanelAppId(source.sourceName);
8048
- return {
8049
- appId: resolvePanelAppAppId(source, manifest),
8050
- sourceId,
8051
- source,
8052
- manifest,
8053
- html,
8054
- htmlWithBase: source.kind === "folder" ? injectPanelAppAssetBase(html, createAssetBaseHref(source)) : html
8055
- };
8056
- }
8057
- async function readPanelAppContentSourceByIdOrAppId(params) {
8058
- const { appIdOrSourceId, createAssetBaseHref, panelsPath, sourceService } = params;
8059
- try {
8060
- return await readPanelAppContentSource({
8061
- createAssetBaseHref,
8062
- id: appIdOrSourceId,
8063
- panelsPath,
8064
- sourceService
8065
- });
8066
- } catch (error) {
8067
- if (!isPanelAppError(error)) throw error;
8068
- if (error.code !== "PANEL_APP_INVALID_ID" && error.code !== "PANEL_APP_NOT_FOUND") throw error;
8069
- }
8070
- const sources = await sourceService.listSources(panelsPath);
8071
- for (const source of sources) {
8072
- const html = await readFile(source.entryPath, "utf8");
8073
- if (resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(html)) === appIdOrSourceId) return await readPanelAppContentSource({
8074
- createAssetBaseHref,
8075
- id: encodePanelAppId(source.sourceName),
8076
- panelsPath,
8077
- sourceService
8078
- });
8079
- }
8080
- throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
8081
- }
8082
- function resolvePanelAppAppId(source, manifest) {
8083
- return manifest.id ?? encodePanelAppId(source.sourceName);
8084
- }
8085
- async function assertPanelAppDeclaresClient(params) {
8086
- const { appId, panelsPath, sourceService } = params;
8087
- const sources = await sourceService.listSources(panelsPath);
8088
- for (const source of sources) {
8089
- const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
8090
- if (resolvePanelAppAppId(source, manifest) !== appId) continue;
8091
- if (!manifest.client) throw new PanelAppError("PANEL_APP_CLIENT_NOT_DECLARED", "panel app did not declare client access");
8092
- return;
8093
- }
8094
- throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
8095
- }
8096
- //#endregion
8097
8792
  //#region src/managers/panel-app.manager.ts
8098
8793
  const PANEL_APP_CONTENT_BASE_PATH = "/api/panel-apps";
8099
8794
  const PANEL_APP_TOKENIZED_ASSET_BASE_PATH = "/api/panel-app-assets";
@@ -8107,6 +8802,8 @@ var PanelAppManager = class {
8107
8802
  agentBridgeService;
8108
8803
  assetTokenService = new PanelAppAssetTokenService();
8109
8804
  sourceService = new PanelAppSourceService();
8805
+ packageStateManager;
8806
+ entryPresenter;
8110
8807
  constructor(params) {
8111
8808
  this.params = params;
8112
8809
  this.agentRunClient = params.agentRunClient ?? (params.eventBus && params.ingress ? new AgentRunClient({
@@ -8117,16 +8814,31 @@ var PanelAppManager = class {
8117
8814
  agentRunClient: this.agentRunClient,
8118
8815
  createCapabilityGrantStore: this.createCapabilityGrantStore
8119
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
+ });
8120
8832
  }
8121
8833
  listPanelApps = async () => {
8122
8834
  const workspacePath = this.getWorkspacePath();
8123
8835
  const panelsPath = this.getPanelsPath(workspacePath);
8124
- const sources = await this.sourceService.listSources(panelsPath);
8836
+ const sources = await this.packageStateManager.listSources();
8125
8837
  const appState = await this.createStateStore(panelsPath).load();
8126
8838
  return {
8127
8839
  workspacePath,
8128
8840
  panelsPath,
8129
- 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)
8130
8842
  };
8131
8843
  };
8132
8844
  getPanelAppContent = async (id, sourcePath) => {
@@ -8201,12 +8913,7 @@ var PanelAppManager = class {
8201
8913
  return session;
8202
8914
  };
8203
8915
  createPanelAppBridgeSession = async (params) => {
8204
- const resolved = await readPanelAppContentSourceByIdOrAppId({
8205
- appIdOrSourceId: params.id,
8206
- createAssetBaseHref: this.createAssetBaseHref,
8207
- panelsPath: this.getPanelsPath(this.getWorkspacePath()),
8208
- sourceService: this.sourceService
8209
- });
8916
+ const resolved = await this.packageStateManager.readContentSourceByIdOrAppId(params.id);
8210
8917
  return this.createPanelAppRuntimeTokenSession({
8211
8918
  appId: resolved.appId,
8212
8919
  clientDeclared: resolved.manifest.client,
@@ -8215,11 +8922,7 @@ var PanelAppManager = class {
8215
8922
  });
8216
8923
  };
8217
8924
  grantPanelAppClient = async (appId) => {
8218
- await assertPanelAppDeclaresClient({
8219
- appId,
8220
- panelsPath: this.getPanelsPath(this.getWorkspacePath()),
8221
- sourceService: this.sourceService
8222
- });
8925
+ await this.packageStateManager.assertDeclaresClient(appId);
8223
8926
  return await this.createClientGrantStore().grant({
8224
8927
  appId,
8225
8928
  grantedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8253,17 +8956,19 @@ var PanelAppManager = class {
8253
8956
  const fileName = await this.resolvePanelAppFileName(id);
8254
8957
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
8255
8958
  const state = await this.createStateStore(panelsPath).updatePreferences(encodePanelAppId(fileName), preferences);
8256
- 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));
8257
8960
  };
8258
8961
  recordPanelAppOpened = async (id) => {
8259
8962
  const fileName = await this.resolvePanelAppFileName(id);
8260
8963
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
8261
8964
  const state = await this.createStateStore(panelsPath).recordOpened(encodePanelAppId(fileName));
8262
- 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));
8263
8966
  };
8264
8967
  deletePanelApp = async (id) => {
8265
8968
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
8266
- 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}`);
8267
8972
  const panelAppId = encodePanelAppId(source.sourceName);
8268
8973
  const appId = resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8")));
8269
8974
  await rm(source.sourcePath, { recursive: source.kind === "folder" });
@@ -8293,36 +8998,12 @@ var PanelAppManager = class {
8293
8998
  createStateStore = (panelsPath) => new PanelAppStateStore(panelsPath);
8294
8999
  createCapabilityGrantStore = () => new PanelAppCapabilityGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CAPABILITY_GRANTS_FILE_NAME));
8295
9000
  createClientGrantStore = () => new PanelAppClientGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CLIENT_GRANTS_FILE_NAME));
8296
- buildPanelAppEntry = async (source, state) => {
8297
- const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
8298
- const id = encodePanelAppId(source.sourceName);
8299
- const appId = resolvePanelAppAppId(source, manifest);
8300
- const createdAt = resolvePanelAppCreatedAt(source.sourceStat);
8301
- const updatedAt = source.sourceStat.mtime.toISOString();
8302
- const entry = {
8303
- id,
8304
- appId,
8305
- fileName: source.sourceName,
8306
- kind: source.kind,
8307
- title: manifest.title ?? toPanelAppTitle(source.sourceName),
8308
- contentPath: `${PANEL_APP_CONTENT_BASE_PATH}/${encodeURIComponent(id)}/content`,
8309
- createdAt,
8310
- updatedAt,
8311
- sizeBytes: source.sourceStat.size,
8312
- favorite: state.favorite ?? false,
8313
- clientDeclared: manifest.client,
8314
- clientGranted: await this.isPanelAppClientGranted(appId, manifest.client),
8315
- openCount: state.openCount ?? 0
8316
- };
8317
- if (manifest.description) entry.description = manifest.description;
8318
- if (manifest.icon) entry.icon = source.kind === "folder" ? resolvePanelAppIconUrl(id, manifest.icon) : manifest.icon;
8319
- if (state.lastOpenedAt) entry.lastOpenedAt = state.lastOpenedAt;
8320
- return entry;
8321
- };
8322
9001
  resolvePanelAppFileName = async (id) => {
8323
- return (await this.sourceService.resolveSource(this.getPanelsPath(this.getWorkspacePath()), id)).sourceName;
9002
+ return (await this.packageStateManager.resolveSource(id)).sourceName;
8324
9003
  };
8325
- 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);
8326
9007
  deleteExpiredBridgeSessions = () => {
8327
9008
  const now = Date.now();
8328
9009
  for (const [token, session] of this.bridgeSessions) if (new Date(session.expiresAt).getTime() <= now) this.bridgeSessions.delete(token);
@@ -8778,7 +9459,7 @@ var McpServiceAppRuntimeService = class {
8778
9459
  command: manifest.command,
8779
9460
  args: manifest.args,
8780
9461
  cwd: app.dirPath,
8781
- env: createRuntimeChildEnv(process.env),
9462
+ env: createRuntimeChildEnv(process.env, this.createAppRuntimeEnv(app)),
8782
9463
  stderr: "pipe"
8783
9464
  },
8784
9465
  scope: {
@@ -8791,6 +9472,15 @@ var McpServiceAppRuntimeService = class {
8791
9472
  }
8792
9473
  }
8793
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
+ };
8794
9484
  toServiceAction = (manifest, tool) => {
8795
9485
  const actionId = buildServiceActionId(manifest.id, tool.toolName);
8796
9486
  const manifestAction = manifest.actions[tool.toolName];
@@ -8885,12 +9575,12 @@ var ServiceActionGrantStore = class {
8885
9575
  };
8886
9576
  };
8887
9577
  function normalizeStoreData(value) {
8888
- 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);
8889
9579
  const grants = {};
8890
9580
  for (const [callerKey, callerValue] of Object.entries(value.grants)) {
8891
- if (!isRecord$7(callerValue) || !isRecord$7(callerValue.actions)) continue;
9581
+ if (!isRecord$8(callerValue) || !isRecord$8(callerValue.actions)) continue;
8892
9582
  const actions = {};
8893
- 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] = {
8894
9584
  grantedAt: actionValue.grantedAt,
8895
9585
  risk: actionValue.risk
8896
9586
  };
@@ -8904,11 +9594,11 @@ function normalizeStoreData(value) {
8904
9594
  function isServiceActionRisk(value) {
8905
9595
  return value === "read" || value === "write" || value === "external" || value === "dangerous";
8906
9596
  }
8907
- function isRecord$7(value) {
9597
+ function isRecord$8(value) {
8908
9598
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8909
9599
  }
8910
9600
  function isMissingFileError(error) {
8911
- return isRecord$7(error) && error.code === "ENOENT";
9601
+ return isRecord$8(error) && error.code === "ENOENT";
8912
9602
  }
8913
9603
  //#endregion
8914
9604
  //#region src/utils/service-app-manifest.utils.ts
@@ -8933,7 +9623,7 @@ function parseServiceAppManifest(raw) {
8933
9623
  } catch (error) {
8934
9624
  throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
8935
9625
  }
8936
- 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.");
8937
9627
  const id = readRequiredString$6(parsed, "id");
8938
9628
  if (!SERVICE_APP_ID_PATTERN.test(id)) throw new Error("service app id must be kebab-case.");
8939
9629
  const protocol = readOptionalString$7(parsed, "protocol") ?? "mcp";
@@ -8951,16 +9641,16 @@ function parseServiceAppManifest(raw) {
8951
9641
  }
8952
9642
  function readManifestActions(value) {
8953
9643
  if (value === void 0) throw new Error("service app actions are required.");
8954
- 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.");
8955
9645
  if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
8956
9646
  const actions = {};
8957
9647
  for (const [name, action] of Object.entries(value)) {
8958
9648
  if (!name.trim()) throw new Error("service app action name cannot be empty.");
8959
- 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.`);
8960
9650
  const risk = readOptionalString$7(action, "risk");
8961
9651
  if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
8962
9652
  const inputSchema = action.inputSchema;
8963
- 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.`);
8964
9654
  actions[name] = {
8965
9655
  risk,
8966
9656
  title: readOptionalString$7(action, "title"),
@@ -8988,7 +9678,7 @@ function readStringArray(value, key) {
8988
9678
  if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
8989
9679
  return value;
8990
9680
  }
8991
- function isRecord$6(value) {
9681
+ function isRecord$7(value) {
8992
9682
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8993
9683
  }
8994
9684
  //#endregion
@@ -9046,10 +9736,13 @@ var ServiceAppManager = class {
9046
9736
  const workspacePath = this.getWorkspacePath();
9047
9737
  const serviceAppsPath = this.getServiceAppsPath(workspacePath);
9048
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)));
9049
9742
  return {
9050
9743
  workspacePath,
9051
9744
  serviceAppsPath,
9052
- 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))
9053
9746
  };
9054
9747
  };
9055
9748
  getServiceApp = async (appId) => {
@@ -9127,6 +9820,7 @@ var ServiceAppManager = class {
9127
9820
  };
9128
9821
  deleteServiceApp = async (appId) => {
9129
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}`);
9130
9824
  await this.runtimeService.restart(record.id);
9131
9825
  await rm(record.dirPath, { recursive: true });
9132
9826
  await this.createGrantStore().revokeActionsByPrefix(`${record.id}.`);
@@ -9138,6 +9832,31 @@ var ServiceAppManager = class {
9138
9832
  dispose = async () => {
9139
9833
  await this.runtimeService.dispose();
9140
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
+ };
9141
9860
  withGrantState = async (action, params) => {
9142
9861
  if (!params.caller) return action;
9143
9862
  const granted = await this.createGrantStore().isGranted(params.caller, action.id);
@@ -9162,14 +9881,23 @@ var ServiceAppManager = class {
9162
9881
  return await this.requireServiceApp(appId);
9163
9882
  };
9164
9883
  requireServiceApp = async (appId) => {
9165
- 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
+ }
9166
9894
  try {
9167
9895
  if (!(await stat(dirPath)).isDirectory()) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
9168
9896
  const manifest = await readServiceAppManifest(dirPath);
9169
9897
  if (manifest.id !== appId) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app manifest id must match directory name");
9170
9898
  return {
9171
9899
  manifest,
9172
- record: this.toServiceAppRecord(dirPath, manifest)
9900
+ record: this.toServiceAppRecord(dirPath, manifest, packageSource)
9173
9901
  };
9174
9902
  } catch (error) {
9175
9903
  if (isServiceAppError(error)) throw error;
@@ -9181,7 +9909,7 @@ var ServiceAppManager = class {
9181
9909
  const workspacePath = this.getWorkspacePath();
9182
9910
  const serviceAppsPath = this.getServiceAppsPath(workspacePath);
9183
9911
  const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
9184
- return (await Promise.all(dirNames.map(async (dirName) => {
9912
+ const workspaceEntries = await Promise.all(dirNames.map(async (dirName) => {
9185
9913
  const dirPath = join(serviceAppsPath, dirName);
9186
9914
  try {
9187
9915
  const manifest = await readServiceAppManifest(dirPath);
@@ -9192,7 +9920,19 @@ var ServiceAppManager = class {
9192
9920
  } catch {
9193
9921
  return null;
9194
9922
  }
9195
- }))).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));
9196
9936
  };
9197
9937
  buildServiceAppRecord = async (serviceAppsPath, dirName) => {
9198
9938
  const dirPath = join(serviceAppsPath, dirName);
@@ -9214,7 +9954,7 @@ var ServiceAppManager = class {
9214
9954
  };
9215
9955
  }
9216
9956
  };
9217
- toServiceAppRecord = (dirPath, manifest) => {
9957
+ toServiceAppRecord = (dirPath, manifest, packageSource) => {
9218
9958
  const runtimeStatus = this.runtimeService.getStatus(manifest.id);
9219
9959
  return {
9220
9960
  id: manifest.id,
@@ -9231,7 +9971,12 @@ var ServiceAppManager = class {
9231
9971
  lastError: runtimeStatus.lastError,
9232
9972
  lastStartedAt: runtimeStatus.lastStartedAt,
9233
9973
  lastReadyAt: runtimeStatus.lastReadyAt,
9234
- 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
9235
9980
  };
9236
9981
  };
9237
9982
  assertCaller = (caller) => {
@@ -9244,6 +9989,31 @@ var ServiceAppManager = class {
9244
9989
  getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
9245
9990
  getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
9246
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
+ };
9247
10017
  listServiceAppDirNames = async (serviceAppsPath) => {
9248
10018
  try {
9249
10019
  return (await readdir(serviceAppsPath, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
@@ -9457,7 +10227,7 @@ function parseFrontmatterBlock(raw) {
9457
10227
  function parseYamlFrontmatter(raw) {
9458
10228
  try {
9459
10229
  const parsed = parse(raw);
9460
- return isRecord$5(parsed) ? parsed : {};
10230
+ return isRecord$6(parsed) ? parsed : {};
9461
10231
  } catch (error) {
9462
10232
  const message = error instanceof Error ? error.message : String(error);
9463
10233
  throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
@@ -9469,7 +10239,7 @@ function readString$5(record, ...names) {
9469
10239
  }
9470
10240
  function readLocalizedTextMap(record, ...names) {
9471
10241
  const value = readValue(record, names);
9472
- if (!isRecord$5(value)) return;
10242
+ if (!isRecord$6(value)) return;
9473
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()]));
9474
10244
  return Object.keys(localized).length > 0 ? localized : void 0;
9475
10245
  }
@@ -9493,7 +10263,7 @@ function normalizeFrontmatterKey(raw) {
9493
10263
  function normalizeLocaleTag(raw) {
9494
10264
  return raw.trim().toLowerCase();
9495
10265
  }
9496
- function isRecord$5(value) {
10266
+ function isRecord$6(value) {
9497
10267
  return typeof value === "object" && value !== null && !Array.isArray(value);
9498
10268
  }
9499
10269
  //#endregion
@@ -9681,7 +10451,7 @@ var NcpAgentUnfinishedRunStore = class {
9681
10451
  } catch {
9682
10452
  continue;
9683
10453
  }
9684
- if (!isRecord$12(parsed) || parsed._type !== "event" || !isRecord$12(parsed.event)) continue;
10454
+ if (!isRecord$13(parsed) || parsed._type !== "event" || !isRecord$13(parsed.event)) continue;
9685
10455
  activeRun = applyNcpAgentRunLifecycleEvent(sessionId, activeRun, parsed.event);
9686
10456
  }
9687
10457
  return activeRun;
@@ -9702,11 +10472,11 @@ function isNcpAgentSessionMessageProjectionBoundaryEvent(event) {
9702
10472
  function serializeNcpAgentSessionJournalEntry(entry) {
9703
10473
  const serialized = JSON.stringify(entry);
9704
10474
  if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
9705
- 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");
9706
10476
  return serialized;
9707
10477
  }
9708
10478
  function attachNcpAgentSessionJournalTimestamp(event, timestamp) {
9709
- if (!("payload" in event) || !isRecord$12(event.payload)) return event;
10479
+ if (!("payload" in event) || !isRecord$13(event.payload)) return event;
9710
10480
  return {
9711
10481
  ...event,
9712
10482
  payload: {
@@ -9731,7 +10501,7 @@ var NcpAgentSessionJournalParser = class {
9731
10501
  this.applyMetadata(parsed);
9732
10502
  return;
9733
10503
  }
9734
- 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);
9735
10505
  };
9736
10506
  finish = () => ({
9737
10507
  metadata: this.metadata,
@@ -9745,14 +10515,14 @@ var NcpAgentSessionJournalParser = class {
9745
10515
  parseLine = (line, index) => {
9746
10516
  try {
9747
10517
  const parsed = JSON.parse(line);
9748
- return isRecord$12(parsed) ? parsed : null;
10518
+ return isRecord$13(parsed) ? parsed : null;
9749
10519
  } catch (error) {
9750
10520
  console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
9751
10521
  return null;
9752
10522
  }
9753
10523
  };
9754
10524
  applyMetadata = (entry) => {
9755
- this.metadata = isRecord$12(entry.metadata) ? structuredClone(entry.metadata) : {};
10525
+ this.metadata = isRecord$13(entry.metadata) ? structuredClone(entry.metadata) : {};
9756
10526
  this.agentId = normalizeNcpAgentId(typeof entry.agent_id === "string" ? entry.agent_id : void 0);
9757
10527
  this.createdAt = toIsoString(entry.created_at, this.createdAt);
9758
10528
  this.updatedAt = toIsoString(entry.updated_at, this.updatedAt);
@@ -9799,7 +10569,7 @@ var NcpAgentSessionMetadataStore = class {
9799
10569
  read = async (sessionId, activitySnapshot) => {
9800
10570
  try {
9801
10571
  const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
9802
- 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}`);
9803
10573
  const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
9804
10574
  const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
9805
10575
  return {
@@ -9869,7 +10639,7 @@ function deduplicateNcpAgentSessionTailMessages(messages) {
9869
10639
  function isNcpAgentSessionMessageProjectionMeta(value, sessionId) {
9870
10640
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
9871
10641
  const meta = value;
9872
- 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));
9873
10643
  }
9874
10644
  function readActiveAssistantMessageId(messages) {
9875
10645
  for (let index = messages.length - 1; index >= 0; index -= 1) {
@@ -9892,12 +10662,12 @@ function mergePendingCompactionMessageIds(current, messages) {
9892
10662
  }
9893
10663
  return pending;
9894
10664
  }
9895
- function isRecord$4(value) {
10665
+ function isRecord$5(value) {
9896
10666
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9897
10667
  }
9898
10668
  function readCompactionStatus(message) {
9899
10669
  const checkpoint = message.metadata?.checkpoint;
9900
- 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;
9901
10671
  return typeof checkpoint.status === "string" ? checkpoint.status : null;
9902
10672
  }
9903
10673
  //#endregion
@@ -11914,7 +12684,7 @@ var CurrentSessionContextProvider = class {
11914
12684
  const MAX_EXCERPT_COUNT = 16;
11915
12685
  const MAX_EXCERPT_CHARACTERS$1 = 8e3;
11916
12686
  const MAX_TOTAL_CONTEXT_CHARACTERS$1 = 96e3;
11917
- function isRecord$3(value) {
12687
+ function isRecord$4(value) {
11918
12688
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11919
12689
  }
11920
12690
  function readString$3(value) {
@@ -11925,11 +12695,11 @@ function escapeAttribute$1(value) {
11925
12695
  }
11926
12696
  function readConversationExcerpts(metadata) {
11927
12697
  const raw = metadata?.[CHAT_INLINE_TOKENS_METADATA_KEY];
11928
- 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 : [];
11929
12699
  const excerpts = [];
11930
12700
  const seen = /* @__PURE__ */ new Set();
11931
12701
  for (const entry of entries) {
11932
- 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;
11933
12703
  const key = readString$3(entry.key);
11934
12704
  const messageId = readString$3(entry.messageId);
11935
12705
  const label = readString$3(entry.label);
@@ -11974,27 +12744,63 @@ var ConversationExcerptContextProvider = class {
11974
12744
  };
11975
12745
  };
11976
12746
  //#endregion
11977
- //#region src/contributions/context-provider/providers/inbox-delivery-context.provider.ts
11978
- var InboxDeliveryContextProvider = class {
11979
- constructor(deliveryManager, sessionManager) {
11980
- this.deliveryManager = deliveryManager;
11981
- 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;
11982
12768
  }
11983
12769
  provide = async (request) => {
11984
- if (!request.sessionId) return [];
11985
- const rawDeliveryId = (await this.sessionManager.getSessionRecord(request.sessionId))?.metadata?.[INBOX_DELIVERY_SESSION_METADATA_KEY];
11986
- if (typeof rawDeliveryId !== "string" || !rawDeliveryId.trim()) return [];
11987
- const delivery = await this.deliveryManager.getDelivery(rawDeliveryId);
11988
- if (!delivery) return [];
11989
- const lines = [
11990
- "## Inbox Delivery Context",
11991
- "This chat was opened from a durable inbox delivery. Treat the following content as the shared subject for this conversation.",
11992
- `Delivery ID: ${delivery.id}`,
11993
- `Title: ${delivery.title}`
11994
- ];
11995
- if (delivery.summary) lines.push(`Summary: ${delivery.summary}`);
11996
- lines.push("", "### Delivered content", delivery.content);
11997
- 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")];
11998
12804
  };
11999
12805
  };
12000
12806
  //#endregion
@@ -12851,7 +13657,7 @@ var ContextProviderContribution = class {
12851
13657
  new SkillsContextProvider(context),
12852
13658
  createSessionOrchestrationContextProvider(),
12853
13659
  new ExecutionPolicyContextProvider(context),
12854
- new InboxDeliveryContextProvider(this.kernel.inboxDeliveryManager, this.kernel.sessionManager),
13660
+ new SystemObjectReferenceContextProvider(this.kernel.assetStore),
12855
13661
  new CurrentSessionContextProvider(context),
12856
13662
  new ReplyFormatContextProvider()
12857
13663
  ]) this.cleanups.push(this.kernel.contextProviderManager.register(provider));
@@ -14207,6 +15013,10 @@ var ToolProviderContribution = class {
14207
15013
  };
14208
15014
  //#endregion
14209
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
+ }
14210
15020
  function resolveKernelSessionsDir(options) {
14211
15021
  const homeDir = options.homeDir?.trim();
14212
15022
  if (homeDir) return ensureDir(resolve(expandHome(homeDir), "sessions"));
@@ -14255,6 +15065,7 @@ var NextclawKernel = class {
14255
15065
  control;
14256
15066
  skills;
14257
15067
  automation;
15068
+ appPackageManager;
14258
15069
  channels;
14259
15070
  sessionRequests;
14260
15071
  sessionSearch;
@@ -14262,6 +15073,7 @@ var NextclawKernel = class {
14262
15073
  mcpManager;
14263
15074
  sessionManager;
14264
15075
  inboxDeliveryManager;
15076
+ systemObjectReferenceManager;
14265
15077
  panelAppManager;
14266
15078
  preferenceManager;
14267
15079
  projectManager;
@@ -14316,16 +15128,26 @@ var NextclawKernel = class {
14316
15128
  });
14317
15129
  this.inboxDeliveryManager = new InboxDeliveryManager({
14318
15130
  eventBus: this.eventBus,
14319
- sessionManager: this.sessionManager,
14320
15131
  storePath: resolveKernelInboxDeliveryStorePath(options)
14321
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
+ });
14322
15139
  this.panelAppManager = new PanelAppManager({
14323
15140
  configManager: this.configManager,
14324
15141
  eventBus: this.eventBus,
14325
- ingress: this.ingress
15142
+ ingress: this.ingress,
15143
+ listPackageComponentSources: this.appPackageManager.listActiveComponentSources
14326
15144
  });
14327
15145
  this.preferenceManager = new PreferenceManager({ storePath: resolveKernelPreferenceStorePath(options) });
14328
- 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();
14329
15151
  this.extensions = new ExtensionManager({
14330
15152
  configManager: this.configManager,
14331
15153
  eventBus: this.eventBus,
@@ -14365,6 +15187,22 @@ var NextclawKernel = class {
14365
15187
  new ContextWindowContribution(this)
14366
15188
  ];
14367
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
+ };
14368
15206
  listSessionTypes = (params) => this.agentRuntimeManager.listSessionTypes(params);
14369
15207
  isSessionRunning = (sessionId) => this.sessionRunManager.isSessionRunning(sessionId);
14370
15208
  provideGatewayController = (gatewayController) => {
@@ -14965,6 +15803,6 @@ function resolveLegacyEventType(message) {
14965
15803
  return `message.${role || "other"}`;
14966
15804
  }
14967
15805
  //#endregion
14968
- 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 };
14969
15807
 
14970
15808
  //# sourceMappingURL=index.js.map