@nextclaw/kernel 0.1.15-beta.3 → 0.1.15-beta.4

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
@@ -1,6 +1,6 @@
1
1
  import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t as UpdateManifestReader } from "./update-manifest.types-C0qPrjGQ.js";
2
2
  import { createRequire } from "node:module";
3
- import { AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, ChannelManager as ChannelManager$1, CommandRegistry, ConfigSchema, ContextBuilder, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, EditFileTool, ExecTool, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionSearchManager, SkillsLoader, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createTypingStopControlMessage, diffConfigPaths, ensureDir, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeInlineSecretRefs, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, summarizeSessionRequestTask, toDisposable, toExtensionConfigView } from "@nextclaw/core";
3
+ import { AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, ChannelManager as ChannelManager$1, CommandRegistry, ConfigSchema, ContextBuilder, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, EditFileTool, ExecTool, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionSearchManager, SkillsLoader, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createTypingStopControlMessage, diffConfigPaths, ensureDir, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeInlineSecretRefs, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, summarizeSessionRequestTask, toDisposable, toExtensionConfigView } from "@nextclaw/core";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
5
  import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { EventBus, Ingress, eventKeys, ingressKeys } from "@nextclaw/shared";
@@ -443,7 +443,7 @@ const runtimeRequire = createRequire(import.meta.url);
443
443
  function readString$7(value) {
444
444
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
445
445
  }
446
- function readStringArray(value) {
446
+ function readStringArray$1(value) {
447
447
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
448
448
  }
449
449
  function readStringRecord(value) {
@@ -502,7 +502,7 @@ function toManifest(value, rootDir) {
502
502
  server: {
503
503
  type: "stdio",
504
504
  command,
505
- ...readStringArray(server.args) ? { args: readStringArray(server.args) } : {},
505
+ ...readStringArray$1(server.args) ? { args: readStringArray$1(server.args) } : {},
506
506
  ...readStringRecord(server.env) ? { env: readStringRecord(server.env) } : {}
507
507
  },
508
508
  ...record.contributes && typeof record.contributes === "object" && !Array.isArray(record.contributes) ? { contributes: record.contributes } : {}
@@ -603,7 +603,7 @@ function readString$6(value) {
603
603
  function readRecord$2(value) {
604
604
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
605
605
  }
606
- function readRequiredString$2(value, name) {
606
+ function readRequiredString$3(value, name) {
607
607
  const trimmed = readString$6(value);
608
608
  if (!trimmed) throw new Error(`${name} is required`);
609
609
  return trimmed;
@@ -634,9 +634,9 @@ function readInboundAttachments(value) {
634
634
  function toInboundMessage(value) {
635
635
  const payload = readRecord$2(value);
636
636
  return {
637
- channel: readRequiredString$2(payload.channelId, "channelId"),
638
- chatId: readRequiredString$2(payload.conversationId, "conversationId"),
639
- senderId: readRequiredString$2(payload.senderId, "senderId"),
637
+ channel: readRequiredString$3(payload.channelId, "channelId"),
638
+ chatId: readRequiredString$3(payload.conversationId, "conversationId"),
639
+ senderId: readRequiredString$3(payload.senderId, "senderId"),
640
640
  content: readTextContent(payload.content),
641
641
  timestamp: /* @__PURE__ */ new Date(),
642
642
  attachments: readInboundAttachments(payload.attachments),
@@ -658,7 +658,7 @@ function normalizeAuthPollResult(value) {
658
658
  if (!value) return null;
659
659
  const record = readRecord$2(value);
660
660
  return {
661
- channel: readRequiredString$2(record.channel, "channel"),
661
+ channel: readRequiredString$3(record.channel, "channel"),
662
662
  status: record.status,
663
663
  message: readString$6(record.message),
664
664
  nextPollMs: readOptionalNumber(record.nextPollMs),
@@ -805,7 +805,7 @@ var ExtensionRuntimeService = class {
805
805
  };
806
806
  handleChannelConfigGet = (envelope, context) => {
807
807
  this.assertAuthorized(context);
808
- const channelId = readRequiredString$2(readRecord$2(envelope.payload).channelId, "channelId");
808
+ const channelId = readRequiredString$3(readRecord$2(envelope.payload).channelId, "channelId");
809
809
  return { config: this.options.getConfig().channels[channelId] ?? {} };
810
810
  };
811
811
  handleChannelMessageSubmit = async (envelope, context) => {
@@ -820,9 +820,9 @@ var ExtensionRuntimeService = class {
820
820
  handleChannelCommandExecute = async (envelope, context) => {
821
821
  this.assertAuthorized(context);
822
822
  const payload = readRecord$2(envelope.payload);
823
- const channel = readRequiredString$2(payload.channelId, "channelId");
824
- const chatId = readRequiredString$2(payload.conversationId, "conversationId");
825
- const senderId = readRequiredString$2(payload.senderId, "senderId");
823
+ const channel = readRequiredString$3(payload.channelId, "channelId");
824
+ const chatId = readRequiredString$3(payload.conversationId, "conversationId");
825
+ const senderId = readRequiredString$3(payload.senderId, "senderId");
826
826
  const metadata = readRecord$2(payload.metadata);
827
827
  const config = this.options.getConfig();
828
828
  const registry = new CommandRegistry(config, this.options.sessionManager);
@@ -849,7 +849,7 @@ var ExtensionRuntimeService = class {
849
849
  content: "",
850
850
  ephemeral: true
851
851
  };
852
- return await registry.execute(readRequiredString$2(payload.commandName, "commandName"), readRecord$2(payload.args), {
852
+ return await registry.execute(readRequiredString$3(payload.commandName, "commandName"), readRecord$2(payload.args), {
853
853
  channel,
854
854
  chatId,
855
855
  senderId,
@@ -859,7 +859,7 @@ var ExtensionRuntimeService = class {
859
859
  handleExtensionResponse = (envelope, context) => {
860
860
  this.assertAuthorized(context);
861
861
  const payload = readRecord$2(envelope.payload);
862
- const requestId = readRequiredString$2(payload.requestId, "requestId");
862
+ const requestId = readRequiredString$3(payload.requestId, "requestId");
863
863
  const pending = this.pendingRequests.get(requestId);
864
864
  if (!pending) return { accepted: false };
865
865
  this.pendingRequests.delete(requestId);
@@ -1033,11 +1033,11 @@ var ExtensionManager = class {
1033
1033
  //#endregion
1034
1034
  //#region src/utils/model-message-vision.utils.ts
1035
1035
  const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
1036
- function isRecord$4(value) {
1036
+ function isRecord$6(value) {
1037
1037
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1038
1038
  }
1039
1039
  function isImageContentPart(value) {
1040
- if (!isRecord$4(value)) return false;
1040
+ if (!isRecord$6(value)) return false;
1041
1041
  const type = value.type;
1042
1042
  return type === "image_url" || type === "input_image";
1043
1043
  }
@@ -1053,7 +1053,7 @@ function normalizeContentWithoutVision(content) {
1053
1053
  };
1054
1054
  });
1055
1055
  if (!sawImage) return content;
1056
- const textParts = parts.filter((part) => isRecord$4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
1056
+ const textParts = parts.filter((part) => isRecord$6(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
1057
1057
  if (textParts.length === parts.length) return textParts.join("\n\n");
1058
1058
  return parts;
1059
1059
  }
@@ -2030,7 +2030,7 @@ function safeNcpSessionFilename(value) {
2030
2030
  function normalizeNcpAgentId(agentId) {
2031
2031
  return agentId?.trim().toLowerCase() || void 0;
2032
2032
  }
2033
- function isRecord$3(value) {
2033
+ function isRecord$5(value) {
2034
2034
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2035
2035
  }
2036
2036
  function toIsoString(value, fallback) {
@@ -2140,7 +2140,7 @@ function applyLimit(items, limit) {
2140
2140
  if (!Number.isFinite(limit) || typeof limit !== "number" || limit <= 0) return items;
2141
2141
  return items.slice(0, Math.trunc(limit));
2142
2142
  }
2143
- function readOptionalString$5(value) {
2143
+ function readOptionalString$6(value) {
2144
2144
  if (typeof value !== "string") return null;
2145
2145
  const trimmed = value.trim();
2146
2146
  return trimmed.length > 0 ? trimmed : null;
@@ -2176,7 +2176,7 @@ function mergeMetadataOverrides(metadata, overrides) {
2176
2176
  }
2177
2177
  function resolveSessionType(params) {
2178
2178
  const { metadata, runtime, sessionType } = params;
2179
- return readOptionalString$5(runtime) ?? readOptionalString$5(metadata.runtime) ?? readOptionalString$5(sessionType) ?? readOptionalString$5(metadata.session_type) ?? DEFAULT_SESSION_TYPE;
2179
+ return readOptionalString$6(runtime) ?? readOptionalString$6(metadata.runtime) ?? readOptionalString$6(sessionType) ?? readOptionalString$6(metadata.session_type) ?? DEFAULT_SESSION_TYPE;
2180
2180
  }
2181
2181
  function applySessionOverrides(params) {
2182
2182
  const { lifecycle, metadata, model, parentSessionId, projectRoot, requestId, sessionType, thinkingLevel, title } = params;
@@ -2186,15 +2186,15 @@ function applySessionOverrides(params) {
2186
2186
  metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = lifecycle;
2187
2187
  if (parentSessionId) metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
2188
2188
  if (requestId) metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
2189
- if (readOptionalString$5(model)) {
2189
+ if (readOptionalString$6(model)) {
2190
2190
  metadata.model = model?.trim();
2191
2191
  metadata.preferred_model = model?.trim();
2192
2192
  }
2193
- if (readOptionalString$5(thinkingLevel)) {
2193
+ if (readOptionalString$6(thinkingLevel)) {
2194
2194
  metadata.thinking = thinkingLevel?.trim();
2195
2195
  metadata.preferred_thinking = thinkingLevel?.trim();
2196
2196
  }
2197
- if (readOptionalString$5(projectRoot)) metadata.project_root = projectRoot?.trim();
2197
+ if (readOptionalString$6(projectRoot)) metadata.project_root = projectRoot?.trim();
2198
2198
  }
2199
2199
  function isSessionSummaryRefreshEvent(event) {
2200
2200
  switch (event.type) {
@@ -2217,9 +2217,9 @@ var NcpSessionManager = class {
2217
2217
  const { agentId: requestedAgentId, metadataOverrides, model, parentSessionId: rawParentSessionId, projectRoot, requestId: rawRequestId, runtime, sessionId: requestedSessionId, sessionType: requestedSessionType, sourceSessionId, sourceSessionMetadata, task, thinkingLevel, title: requestedTitle } = params;
2218
2218
  const sourceRecord = sourceSessionId ? await this.getSessionRecord(sourceSessionId) : null;
2219
2219
  const metadata = cloneInheritedMetadata(sourceSessionMetadata);
2220
- const title = readOptionalString$5(requestedTitle) ?? summarizeTask(task);
2221
- const parentSessionId = readOptionalString$5(rawParentSessionId);
2222
- const requestId = readOptionalString$5(rawRequestId);
2220
+ const title = readOptionalString$6(requestedTitle) ?? summarizeTask(task);
2221
+ const parentSessionId = readOptionalString$6(rawParentSessionId);
2222
+ const requestId = readOptionalString$6(rawRequestId);
2223
2223
  const sessionType = resolveSessionType({
2224
2224
  runtime,
2225
2225
  sessionType: requestedSessionType,
@@ -2238,8 +2238,8 @@ var NcpSessionManager = class {
2238
2238
  });
2239
2239
  const now = (/* @__PURE__ */ new Date()).toISOString();
2240
2240
  const nextMetadata = mergeMetadataOverrides(metadata, metadataOverrides);
2241
- const agentId = readOptionalString$5(requestedAgentId) ?? readOptionalString$5(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
2242
- const sessionId = readOptionalString$5(requestedSessionId) ?? buildSessionId();
2241
+ const agentId = readOptionalString$6(requestedAgentId) ?? readOptionalString$6(sourceRecord?.agentId) ?? BUILTIN_MAIN_AGENT_ID;
2242
+ const sessionId = readOptionalString$6(requestedSessionId) ?? buildSessionId();
2243
2243
  const record = {
2244
2244
  sessionId,
2245
2245
  ...agentId ? { agentId } : {},
@@ -2444,12 +2444,83 @@ var PanelAppStateStore = class {
2444
2444
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2445
2445
  };
2446
2446
  //#endregion
2447
+ //#region src/utils/panel-app-bridge.utils.ts
2448
+ const PANEL_APP_BRIDGE_MARKER = "nextclaw:panel-app-service-actions:request";
2449
+ function injectPanelAppBridgeScript(html) {
2450
+ if (html.includes(PANEL_APP_BRIDGE_MARKER)) return html;
2451
+ const script = `<script>${getPanelAppBridgeScript()}<\/script>`;
2452
+ const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
2453
+ if (headMatch?.index !== void 0) {
2454
+ const insertAt = headMatch.index + headMatch[0].length;
2455
+ return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
2456
+ }
2457
+ return `${script}${html}`;
2458
+ }
2459
+ function getPanelAppBridgeScript() {
2460
+ return `
2461
+ (() => {
2462
+ const requestType = "nextclaw:panel-app-service-actions:request";
2463
+ const responseType = "nextclaw:panel-app-service-actions:response";
2464
+ const pending = new Map();
2465
+ let counter = 0;
2466
+
2467
+ function createRequestId() {
2468
+ counter += 1;
2469
+ return "panel-bridge-" + Date.now().toString(36) + "-" + counter.toString(36);
2470
+ }
2471
+
2472
+ function request(method, payload) {
2473
+ const requestId = createRequestId();
2474
+ return new Promise((resolve, reject) => {
2475
+ pending.set(requestId, { resolve, reject });
2476
+ window.parent.postMessage({ type: requestType, requestId, method, payload }, "*");
2477
+ });
2478
+ }
2479
+
2480
+ window.addEventListener("message", (event) => {
2481
+ const data = event.data;
2482
+ if (!data || data.type !== responseType || typeof data.requestId !== "string") {
2483
+ return;
2484
+ }
2485
+ const entry = pending.get(data.requestId);
2486
+ if (!entry) {
2487
+ return;
2488
+ }
2489
+ pending.delete(data.requestId);
2490
+ if (data.ok) {
2491
+ entry.resolve(data.data);
2492
+ return;
2493
+ }
2494
+ const error = new Error(data.error?.message || "NextClaw panel bridge request failed.");
2495
+ error.code = data.error?.code;
2496
+ error.details = data.error?.details;
2497
+ entry.reject(error);
2498
+ });
2499
+
2500
+ const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
2501
+ Object.defineProperty(window, "nextclaw", {
2502
+ configurable: true,
2503
+ value: {
2504
+ ...existing,
2505
+ serviceActions: {
2506
+ list: () => request("list", {}),
2507
+ invoke: (actionId, input) => request("invoke", { actionId, input }),
2508
+ requestGrant: (actionId) => request("requestGrant", { actionId }),
2509
+ revokeGrant: (actionId) => request("revokeGrant", { actionId })
2510
+ }
2511
+ }
2512
+ });
2513
+ })();
2514
+ `.trim();
2515
+ }
2516
+ //#endregion
2447
2517
  //#region src/utils/panel-app-manifest.utils.ts
2448
2518
  function parsePanelAppManifest(html) {
2449
2519
  return {
2450
2520
  ...readHtmlTitle(html),
2451
2521
  ...readStandardIcon(html),
2452
- ...readPanelAppMeta(html)
2522
+ ...readPanelAppMeta(html),
2523
+ serviceActions: readPanelAppServiceActions(html)
2453
2524
  };
2454
2525
  }
2455
2526
  function readPanelAppMeta(html) {
@@ -2496,6 +2567,11 @@ function readHtmlAttribute(attributes, attribute) {
2496
2567
  const match = attributes.match(new RegExp(`(?:^|\\s)${attribute}\\s*=\\s*(["'])(.*?)\\1`, "i"));
2497
2568
  return match?.[2] ? decodeHtmlAttribute(match[2]) : void 0;
2498
2569
  }
2570
+ function readPanelAppServiceActions(html) {
2571
+ const content = readMetaContent(html, "nextclaw-panel-actions", "attribute");
2572
+ if (!content) return [];
2573
+ return [...new Set(content.split(/[,\s]+/).map((entry) => entry.trim()).filter(Boolean))];
2574
+ }
2499
2575
  function normalizeTextValue(value) {
2500
2576
  return decodeHtmlAttribute(value ?? "").replace(/\s+/g, " ").trim() || void 0;
2501
2577
  }
@@ -2525,6 +2601,7 @@ function isPanelAppError(error) {
2525
2601
  return error instanceof PanelAppError;
2526
2602
  }
2527
2603
  var PanelAppManager = class {
2604
+ bridgeSessions = /* @__PURE__ */ new Map();
2528
2605
  constructor(params) {
2529
2606
  this.params = params;
2530
2607
  }
@@ -2545,11 +2622,13 @@ var PanelAppManager = class {
2545
2622
  try {
2546
2623
  if (!(await stat(filePath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
2547
2624
  const html = await readFile(filePath, "utf8");
2625
+ const manifest = parsePanelAppManifest(html);
2548
2626
  return {
2549
2627
  id: this.encodePanelAppId(fileName),
2550
2628
  fileName,
2551
- html,
2552
- contentType: PANEL_APP_CONTENT_TYPE
2629
+ html: injectPanelAppBridgeScript(html),
2630
+ contentType: PANEL_APP_CONTENT_TYPE,
2631
+ serviceActions: manifest.serviceActions
2553
2632
  };
2554
2633
  } catch (error) {
2555
2634
  if (isPanelAppError(error)) throw error;
@@ -2557,6 +2636,35 @@ var PanelAppManager = class {
2557
2636
  throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
2558
2637
  }
2559
2638
  };
2639
+ getPanelAppBridgeScript = () => getPanelAppBridgeScript();
2640
+ createPanelAppBridgeSession = async (params) => {
2641
+ const content = await this.getPanelAppContent(params.id);
2642
+ const now = /* @__PURE__ */ new Date();
2643
+ const session = {
2644
+ id: randomUUID(),
2645
+ token: randomUUID(),
2646
+ panelAppId: content.id,
2647
+ tabId: params.tabId,
2648
+ caller: {
2649
+ surface: "panel-app",
2650
+ appId: content.id
2651
+ },
2652
+ declaredActions: content.serviceActions,
2653
+ createdAt: now.toISOString(),
2654
+ expiresAt: new Date(now.getTime() + 3600 * 1e3).toISOString()
2655
+ };
2656
+ this.bridgeSessions.set(session.token, session);
2657
+ return session;
2658
+ };
2659
+ resolvePanelAppBridgeSession = (token) => {
2660
+ this.deleteExpiredBridgeSessions();
2661
+ const session = this.bridgeSessions.get(token.trim());
2662
+ if (!session) throw new PanelAppError("PANEL_APP_BRIDGE_SESSION_NOT_FOUND", "panel app bridge session not found");
2663
+ return session;
2664
+ };
2665
+ deletePanelAppBridgeSession = (token) => {
2666
+ this.bridgeSessions.delete(token.trim());
2667
+ };
2560
2668
  updatePanelAppPreferences = async (id, preferences) => {
2561
2669
  const fileName = await this.resolvePanelAppFileName(id);
2562
2670
  const panelsPath = this.getPanelsPath(this.getWorkspacePath());
@@ -2628,9 +2736,559 @@ var PanelAppManager = class {
2628
2736
  toPanelAppTitle = (fileName) => fileName.slice(0, -11).replace(/[-_]+/g, " ").trim() || fileName;
2629
2737
  comparePanelApps = (left, right) => Number(right.favorite) - Number(left.favorite) || this.compareIsoDesc(left.lastOpenedAt, right.lastOpenedAt) || this.compareIsoDesc(left.updatedAt, right.updatedAt) || left.title.localeCompare(right.title);
2630
2738
  compareIsoDesc = (left, right) => new Date(right ?? 0).getTime() - new Date(left ?? 0).getTime();
2739
+ deleteExpiredBridgeSessions = () => {
2740
+ const now = Date.now();
2741
+ for (const [token, session] of this.bridgeSessions) if (new Date(session.expiresAt).getTime() <= now) this.bridgeSessions.delete(token);
2742
+ };
2631
2743
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2632
2744
  };
2633
2745
  //#endregion
2746
+ //#region src/utils/service-action.utils.ts
2747
+ const DEFAULT_SERVICE_ACTION_RISK = "dangerous";
2748
+ function buildServiceActionId(appId, actionName) {
2749
+ return `${appId}.${actionName}`;
2750
+ }
2751
+ function getServiceActionName(actionId, appId) {
2752
+ const prefix = `${appId}.`;
2753
+ if (!actionId.startsWith(prefix)) throw new Error("service action does not belong to service app.");
2754
+ const name = actionId.slice(prefix.length).trim();
2755
+ if (!name) throw new Error("service action name is required.");
2756
+ return name;
2757
+ }
2758
+ function getServiceActionCallerKey(caller) {
2759
+ return `${caller.surface}:${caller.appId}`;
2760
+ }
2761
+ function parseServiceActionCallerKey(key) {
2762
+ const [surface, appId, ...rest] = key.split(":");
2763
+ if (surface !== "panel-app" || !appId || rest.length > 0) return null;
2764
+ return {
2765
+ surface,
2766
+ appId
2767
+ };
2768
+ }
2769
+ function resolveServiceActionGrantState({ actionId, declaredActions, granted }) {
2770
+ if (declaredActions && !declaredActions.includes(actionId)) return "not-declared";
2771
+ return granted ? "granted" : "not-granted";
2772
+ }
2773
+ //#endregion
2774
+ //#region src/services/mcp-service-app-runtime.service.ts
2775
+ var McpServiceAppRuntimeService = class {
2776
+ lifecycleManager;
2777
+ states = /* @__PURE__ */ new Map();
2778
+ constructor(params) {
2779
+ this.params = params;
2780
+ this.lifecycleManager = new McpServerLifecycleManager({ getConfig: params.getConfig });
2781
+ }
2782
+ getStatus = (appId) => {
2783
+ return this.states.get(appId) ?? { status: "idle" };
2784
+ };
2785
+ listActions = async ({ app, manifest }) => {
2786
+ if (!app.enabled) return [];
2787
+ const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
2788
+ this.states.set(app.id, {
2789
+ status: "starting",
2790
+ lastStartedAt
2791
+ });
2792
+ try {
2793
+ const state = await this.lifecycleManager.warmServer(this.toMcpServerRecord(app, manifest));
2794
+ this.states.set(app.id, {
2795
+ status: "running",
2796
+ lastStartedAt,
2797
+ lastReadyAt: state.lastReadyAt
2798
+ });
2799
+ return state.tools.map((tool) => this.toServiceAction(manifest, tool));
2800
+ } catch (error) {
2801
+ const lastError = error instanceof Error ? error.message : String(error);
2802
+ this.states.set(app.id, {
2803
+ status: "failed",
2804
+ lastError,
2805
+ lastStartedAt,
2806
+ lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
2807
+ });
2808
+ return [];
2809
+ }
2810
+ };
2811
+ invokeAction = async ({ app, manifest, actionName, input }) => {
2812
+ const lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
2813
+ this.states.set(app.id, {
2814
+ status: "starting",
2815
+ lastStartedAt
2816
+ });
2817
+ try {
2818
+ const result = await this.lifecycleManager.callTool(this.toMcpServerRecord(app, manifest), actionName, input);
2819
+ this.states.set(app.id, {
2820
+ status: "running",
2821
+ lastStartedAt,
2822
+ lastReadyAt: (/* @__PURE__ */ new Date()).toISOString()
2823
+ });
2824
+ return result;
2825
+ } catch (error) {
2826
+ const lastError = error instanceof Error ? error.message : String(error);
2827
+ this.states.set(app.id, {
2828
+ status: "failed",
2829
+ lastError,
2830
+ lastStartedAt,
2831
+ lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
2832
+ });
2833
+ throw error;
2834
+ }
2835
+ };
2836
+ restart = async (appId) => {
2837
+ await this.lifecycleManager.closeServer(appId);
2838
+ this.states.set(appId, { status: "idle" });
2839
+ };
2840
+ dispose = async () => {
2841
+ await this.lifecycleManager.closeAll();
2842
+ this.states.clear();
2843
+ };
2844
+ toMcpServerRecord = (app, manifest) => ({
2845
+ name: app.id,
2846
+ definition: {
2847
+ enabled: manifest.enabled,
2848
+ transport: {
2849
+ type: "stdio",
2850
+ command: manifest.command,
2851
+ args: manifest.args,
2852
+ cwd: app.dirPath,
2853
+ env: {},
2854
+ stderr: "pipe"
2855
+ },
2856
+ scope: {
2857
+ allAgents: false,
2858
+ agents: []
2859
+ },
2860
+ policy: {
2861
+ trust: "explicit",
2862
+ start: "eager"
2863
+ }
2864
+ }
2865
+ });
2866
+ toServiceAction = (manifest, tool) => {
2867
+ const actionId = buildServiceActionId(manifest.id, tool.toolName);
2868
+ const manifestAction = manifest.actions[tool.toolName];
2869
+ const risk = manifestAction?.risk ?? "dangerous";
2870
+ return {
2871
+ id: actionId,
2872
+ appId: manifest.id,
2873
+ name: tool.toolName,
2874
+ title: manifestAction?.title ?? tool.toolName,
2875
+ description: manifestAction?.description ?? tool.description,
2876
+ inputSchema: tool.parameters,
2877
+ risk
2878
+ };
2879
+ };
2880
+ };
2881
+ //#endregion
2882
+ //#region src/stores/service-action-grant.store.ts
2883
+ const EMPTY_GRANTS = {
2884
+ version: 1,
2885
+ grants: {}
2886
+ };
2887
+ var ServiceActionGrantStore = class {
2888
+ constructor(storePath) {
2889
+ this.storePath = storePath;
2890
+ }
2891
+ isGranted = async (caller, actionId) => {
2892
+ const data = await this.load();
2893
+ return Boolean(data.grants[getServiceActionCallerKey(caller)]?.actions[actionId]);
2894
+ };
2895
+ grant = async ({ actionId, caller, grantedAt, risk }) => {
2896
+ const data = await this.load();
2897
+ const callerKey = getServiceActionCallerKey(caller);
2898
+ const callerGrants = data.grants[callerKey] ?? { actions: {} };
2899
+ callerGrants.actions[actionId] = {
2900
+ grantedAt,
2901
+ risk
2902
+ };
2903
+ data.grants[callerKey] = callerGrants;
2904
+ await this.save(data);
2905
+ return {
2906
+ caller,
2907
+ actionId,
2908
+ risk,
2909
+ grantedAt
2910
+ };
2911
+ };
2912
+ list = async () => {
2913
+ const data = await this.load();
2914
+ return Object.entries(data.grants).flatMap(([callerKey, callerGrants]) => {
2915
+ const caller = parseServiceActionCallerKey(callerKey);
2916
+ if (!caller) return [];
2917
+ return Object.entries(callerGrants.actions).map(([actionId, grant]) => ({
2918
+ caller,
2919
+ actionId,
2920
+ risk: grant.risk,
2921
+ grantedAt: grant.grantedAt
2922
+ }));
2923
+ }).sort((left, right) => new Date(right.grantedAt).getTime() - new Date(left.grantedAt).getTime());
2924
+ };
2925
+ revoke = async (caller, actionId) => {
2926
+ const data = await this.load();
2927
+ const callerKey = getServiceActionCallerKey(caller);
2928
+ const callerGrants = data.grants[callerKey];
2929
+ if (!callerGrants) return;
2930
+ delete callerGrants.actions[actionId];
2931
+ if (Object.keys(callerGrants.actions).length === 0) delete data.grants[callerKey];
2932
+ await this.save(data);
2933
+ };
2934
+ load = async () => {
2935
+ try {
2936
+ return normalizeStoreData(JSON.parse(await readFile(this.storePath, "utf8")));
2937
+ } catch (error) {
2938
+ if (isMissingFileError(error)) return structuredClone(EMPTY_GRANTS);
2939
+ throw error;
2940
+ }
2941
+ };
2942
+ save = async (data) => {
2943
+ await mkdir(dirname(this.storePath), { recursive: true });
2944
+ await writeFile(this.storePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
2945
+ };
2946
+ };
2947
+ function normalizeStoreData(value) {
2948
+ if (!isRecord$4(value) || value.version !== 1 || !isRecord$4(value.grants)) return structuredClone(EMPTY_GRANTS);
2949
+ const grants = {};
2950
+ for (const [callerKey, callerValue] of Object.entries(value.grants)) {
2951
+ if (!isRecord$4(callerValue) || !isRecord$4(callerValue.actions)) continue;
2952
+ const actions = {};
2953
+ for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$4(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
2954
+ grantedAt: actionValue.grantedAt,
2955
+ risk: actionValue.risk
2956
+ };
2957
+ grants[callerKey] = { actions };
2958
+ }
2959
+ return {
2960
+ version: 1,
2961
+ grants
2962
+ };
2963
+ }
2964
+ function isServiceActionRisk(value) {
2965
+ return value === "read" || value === "write" || value === "external" || value === "dangerous";
2966
+ }
2967
+ function isRecord$4(value) {
2968
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2969
+ }
2970
+ function isMissingFileError(error) {
2971
+ return isRecord$4(error) && error.code === "ENOENT";
2972
+ }
2973
+ //#endregion
2974
+ //#region src/utils/service-app-manifest.utils.ts
2975
+ const SERVICE_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2976
+ const SERVICE_ACTION_RISKS = new Set([
2977
+ "read",
2978
+ "write",
2979
+ "external",
2980
+ "dangerous"
2981
+ ]);
2982
+ const SERVICE_APP_MANIFEST_FILE_NAME = "service-app.json";
2983
+ function getServiceAppManifestPath(dirPath) {
2984
+ return join(dirPath, SERVICE_APP_MANIFEST_FILE_NAME);
2985
+ }
2986
+ async function readServiceAppManifest(dirPath) {
2987
+ return parseServiceAppManifest(await readFile(getServiceAppManifestPath(dirPath), "utf8"));
2988
+ }
2989
+ function parseServiceAppManifest(raw) {
2990
+ let parsed;
2991
+ try {
2992
+ parsed = JSON.parse(raw);
2993
+ } catch (error) {
2994
+ throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
2995
+ }
2996
+ if (!isRecord$3(parsed)) throw new Error("service-app.json must contain an object.");
2997
+ const id = readRequiredString$2(parsed, "id");
2998
+ if (!SERVICE_APP_ID_PATTERN.test(id)) throw new Error("service app id must be kebab-case.");
2999
+ const protocol = readOptionalString$5(parsed, "protocol") ?? "mcp";
3000
+ if (protocol !== "mcp") throw new Error("service app protocol must be mcp.");
3001
+ return {
3002
+ id,
3003
+ title: readRequiredString$2(parsed, "title"),
3004
+ description: readOptionalString$5(parsed, "description"),
3005
+ enabled: readOptionalBoolean$1(parsed, "enabled") ?? true,
3006
+ protocol,
3007
+ command: readRequiredString$2(parsed, "command"),
3008
+ args: readStringArray(parsed.args, "args"),
3009
+ actions: readManifestActions(parsed.actions)
3010
+ };
3011
+ }
3012
+ function readManifestActions(value) {
3013
+ if (value === void 0) throw new Error("service app actions are required.");
3014
+ if (!isRecord$3(value)) throw new Error("service app actions must be an object.");
3015
+ if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
3016
+ const actions = {};
3017
+ for (const [name, action] of Object.entries(value)) {
3018
+ if (!name.trim()) throw new Error("service app action name cannot be empty.");
3019
+ if (!isRecord$3(action)) throw new Error(`service app action ${name} must be an object.`);
3020
+ const risk = readOptionalString$5(action, "risk");
3021
+ if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
3022
+ const inputSchema = action.inputSchema;
3023
+ if (inputSchema !== void 0 && !isRecord$3(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
3024
+ actions[name] = {
3025
+ risk,
3026
+ title: readOptionalString$5(action, "title"),
3027
+ description: readOptionalString$5(action, "description"),
3028
+ inputSchema
3029
+ };
3030
+ }
3031
+ return actions;
3032
+ }
3033
+ function readRequiredString$2(record, key) {
3034
+ const value = readOptionalString$5(record, key);
3035
+ if (!value) throw new Error(`service app ${key} is required.`);
3036
+ return value;
3037
+ }
3038
+ function readOptionalString$5(record, key) {
3039
+ return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
3040
+ }
3041
+ function readOptionalBoolean$1(record, key) {
3042
+ if (record[key] === void 0) return;
3043
+ if (typeof record[key] !== "boolean") throw new Error(`service app ${key} must be boolean.`);
3044
+ return record[key];
3045
+ }
3046
+ function readStringArray(value, key) {
3047
+ if (value === void 0) return [];
3048
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
3049
+ return value;
3050
+ }
3051
+ function isRecord$3(value) {
3052
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3053
+ }
3054
+ //#endregion
3055
+ //#region src/managers/service-app.manager.ts
3056
+ const SERVICE_ACTION_GRANTS_FILE_NAME = ".service-action-grants.json";
3057
+ var ServiceAppError = class extends Error {
3058
+ constructor(code, message) {
3059
+ super(message);
3060
+ this.code = code;
3061
+ this.name = "ServiceAppError";
3062
+ }
3063
+ };
3064
+ function isServiceAppError(error) {
3065
+ return error instanceof ServiceAppError;
3066
+ }
3067
+ var ServiceAppManager = class {
3068
+ runtimeService;
3069
+ constructor(params) {
3070
+ this.params = params;
3071
+ this.runtimeService = params.runtimeService ?? new McpServiceAppRuntimeService({ getConfig: () => params.configManager.config });
3072
+ }
3073
+ listServiceApps = async () => {
3074
+ const workspacePath = this.getWorkspacePath();
3075
+ const serviceAppsPath = this.getServiceAppsPath(workspacePath);
3076
+ const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
3077
+ return {
3078
+ workspacePath,
3079
+ serviceAppsPath,
3080
+ entries: (await Promise.all(dirNames.map((dirName) => this.buildServiceAppRecord(serviceAppsPath, dirName)))).filter((entry) => Boolean(entry)).sort((left, right) => left.title.localeCompare(right.title))
3081
+ };
3082
+ };
3083
+ getServiceApp = async (appId) => {
3084
+ const { record } = await this.requireServiceApp(appId);
3085
+ return record;
3086
+ };
3087
+ listServiceActions = async (params = {}) => {
3088
+ const actions = (params.appId ? [await this.requireServiceApp(params.appId)] : await this.listValidServiceApps()).flatMap(({ manifest, record }) => this.listManifestActions(record, manifest));
3089
+ return await Promise.all(actions.map(async (action) => await this.withGrantState(action, params)));
3090
+ };
3091
+ discoverServiceAppActions = async (appId) => {
3092
+ const { manifest, record } = await this.requireServiceApp(appId);
3093
+ const runtimeActions = await this.runtimeService.listActions({
3094
+ app: record,
3095
+ manifest
3096
+ });
3097
+ return this.mergeRuntimeActions(record, manifest, runtimeActions);
3098
+ };
3099
+ invokeServiceAction = async (actionId, request) => {
3100
+ this.assertCaller(request.caller);
3101
+ this.assertDeclaredAction(actionId, request.declaredActions);
3102
+ const { manifest, record } = await this.requireServiceAppForAction(actionId);
3103
+ const actionName = getServiceActionName(actionId, record.id);
3104
+ if (!Object.hasOwn(manifest.actions, actionName)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
3105
+ if (!await this.createGrantStore().isGranted(request.caller, actionId)) throw new ServiceAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to call ${actionId}.`);
3106
+ return {
3107
+ actionId,
3108
+ result: await this.runtimeService.invokeAction({
3109
+ app: record,
3110
+ manifest,
3111
+ actionName,
3112
+ input: request.input ?? {}
3113
+ })
3114
+ };
3115
+ };
3116
+ grantServiceAction = async (actionId, request) => {
3117
+ this.assertCaller(request.caller);
3118
+ this.assertDeclaredAction(actionId, request.declaredActions);
3119
+ const action = await this.requireServiceAction(actionId);
3120
+ return await this.createGrantStore().grant({
3121
+ caller: request.caller,
3122
+ actionId,
3123
+ risk: action.risk,
3124
+ grantedAt: (/* @__PURE__ */ new Date()).toISOString()
3125
+ });
3126
+ };
3127
+ listServiceActionGrants = async () => {
3128
+ return await this.createGrantStore().list();
3129
+ };
3130
+ revokeServiceAction = async (caller, actionId) => {
3131
+ this.assertCaller(caller);
3132
+ await this.createGrantStore().revoke(caller, actionId);
3133
+ };
3134
+ restartServiceApp = async (appId) => {
3135
+ const { record } = await this.requireServiceApp(appId);
3136
+ await this.runtimeService.restart(record.id);
3137
+ return await this.getServiceApp(appId);
3138
+ };
3139
+ dispose = async () => {
3140
+ await this.runtimeService.dispose();
3141
+ };
3142
+ withGrantState = async (action, params) => {
3143
+ if (!params.caller) return action;
3144
+ const granted = await this.createGrantStore().isGranted(params.caller, action.id);
3145
+ return {
3146
+ ...action,
3147
+ grantState: resolveServiceActionGrantState({
3148
+ actionId: action.id,
3149
+ declaredActions: params.declaredActions,
3150
+ granted
3151
+ })
3152
+ };
3153
+ };
3154
+ requireServiceAction = async (actionId) => {
3155
+ const { manifest, record } = await this.requireServiceAppForAction(actionId);
3156
+ const action = this.listManifestActions(record, manifest).find((entry) => entry.id === actionId);
3157
+ if (!action) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
3158
+ return action;
3159
+ };
3160
+ requireServiceAppForAction = async (actionId) => {
3161
+ const appId = actionId.split(".")[0]?.trim();
3162
+ if (!appId) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
3163
+ return await this.requireServiceApp(appId);
3164
+ };
3165
+ listManifestActions = (record, manifest) => {
3166
+ if (!record.enabled) return [];
3167
+ return Object.entries(manifest.actions).map(([name, action]) => ({
3168
+ id: buildServiceActionId(record.id, name),
3169
+ appId: record.id,
3170
+ name,
3171
+ title: action.title ?? name,
3172
+ description: action.description,
3173
+ inputSchema: action.inputSchema,
3174
+ risk: action.risk ?? "dangerous"
3175
+ })).sort((left, right) => left.id.localeCompare(right.id));
3176
+ };
3177
+ mergeRuntimeActions = (record, manifest, runtimeActions) => {
3178
+ const runtimeByName = new Map(runtimeActions.map((action) => [action.name, action]));
3179
+ const declared = this.listManifestActions(record, manifest).map((action) => {
3180
+ const runtimeAction = runtimeByName.get(action.name);
3181
+ return {
3182
+ ...action,
3183
+ description: runtimeAction?.description ?? action.description,
3184
+ inputSchema: runtimeAction?.inputSchema ?? action.inputSchema,
3185
+ runtimeState: runtimeAction ? "matched" : "missing"
3186
+ };
3187
+ });
3188
+ const undeclared = runtimeActions.filter((action) => !Object.hasOwn(manifest.actions, action.name)).map((action) => ({
3189
+ ...action,
3190
+ risk: DEFAULT_SERVICE_ACTION_RISK,
3191
+ runtimeState: "undeclared"
3192
+ }));
3193
+ return [...declared, ...undeclared].sort((left, right) => left.id.localeCompare(right.id));
3194
+ };
3195
+ requireServiceApp = async (appId) => {
3196
+ const dirPath = join(this.getServiceAppsPath(this.getWorkspacePath()), appId);
3197
+ try {
3198
+ if (!(await stat(dirPath)).isDirectory()) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
3199
+ const manifest = await readServiceAppManifest(dirPath);
3200
+ if (manifest.id !== appId) throw new ServiceAppError("SERVICE_APP_INVALID_MANIFEST", "service app manifest id must match directory name");
3201
+ return {
3202
+ manifest,
3203
+ record: this.toServiceAppRecord(dirPath, manifest)
3204
+ };
3205
+ } catch (error) {
3206
+ if (isServiceAppError(error)) throw error;
3207
+ if (this.isMissingFileError(error)) throw new ServiceAppError("SERVICE_APP_NOT_FOUND", "service app not found");
3208
+ throw new ServiceAppError("SERVICE_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
3209
+ }
3210
+ };
3211
+ listValidServiceApps = async () => {
3212
+ const workspacePath = this.getWorkspacePath();
3213
+ const serviceAppsPath = this.getServiceAppsPath(workspacePath);
3214
+ const dirNames = await this.listServiceAppDirNames(serviceAppsPath);
3215
+ return (await Promise.all(dirNames.map(async (dirName) => {
3216
+ const dirPath = join(serviceAppsPath, dirName);
3217
+ try {
3218
+ const manifest = await readServiceAppManifest(dirPath);
3219
+ return {
3220
+ manifest,
3221
+ record: this.toServiceAppRecord(dirPath, manifest)
3222
+ };
3223
+ } catch {
3224
+ return null;
3225
+ }
3226
+ }))).filter((entry) => Boolean(entry));
3227
+ };
3228
+ buildServiceAppRecord = async (serviceAppsPath, dirName) => {
3229
+ const dirPath = join(serviceAppsPath, dirName);
3230
+ try {
3231
+ const manifest = await readServiceAppManifest(dirPath);
3232
+ return this.toServiceAppRecord(dirPath, manifest);
3233
+ } catch (error) {
3234
+ if (this.isMissingFileError(error)) return null;
3235
+ return {
3236
+ id: dirName,
3237
+ title: toTitle(dirName),
3238
+ dirPath,
3239
+ manifestPath: getServiceAppManifestPath(dirPath),
3240
+ cwd: dirPath,
3241
+ enabled: false,
3242
+ protocol: "mcp",
3243
+ status: "failed",
3244
+ lastError: error instanceof Error ? error.message : String(error)
3245
+ };
3246
+ }
3247
+ };
3248
+ toServiceAppRecord = (dirPath, manifest) => {
3249
+ const runtimeStatus = this.runtimeService.getStatus(manifest.id);
3250
+ const record = {
3251
+ id: manifest.id,
3252
+ title: manifest.title,
3253
+ dirPath,
3254
+ manifestPath: getServiceAppManifestPath(dirPath),
3255
+ command: manifest.command,
3256
+ args: manifest.args,
3257
+ cwd: dirPath,
3258
+ enabled: manifest.enabled,
3259
+ protocol: manifest.protocol,
3260
+ status: manifest.enabled ? runtimeStatus.status : "stopped"
3261
+ };
3262
+ if (manifest.description) record.description = manifest.description;
3263
+ if (runtimeStatus.lastError) record.lastError = runtimeStatus.lastError;
3264
+ if (runtimeStatus.lastStartedAt) record.lastStartedAt = runtimeStatus.lastStartedAt;
3265
+ if (runtimeStatus.lastReadyAt) record.lastReadyAt = runtimeStatus.lastReadyAt;
3266
+ if (runtimeStatus.lastFailedAt) record.lastFailedAt = runtimeStatus.lastFailedAt;
3267
+ return record;
3268
+ };
3269
+ assertCaller = (caller) => {
3270
+ if (caller.surface !== "panel-app" || !caller.appId.trim()) throw new ServiceAppError("SERVICE_APP_INVALID_CALLER", "service action caller is invalid");
3271
+ };
3272
+ assertDeclaredAction = (actionId, declaredActions) => {
3273
+ if (!declaredActions.includes(actionId)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_DECLARED", "panel app did not declare this service action");
3274
+ };
3275
+ getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
3276
+ getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
3277
+ createGrantStore = () => new ServiceActionGrantStore(join(this.getServiceAppsPath(this.getWorkspacePath()), SERVICE_ACTION_GRANTS_FILE_NAME));
3278
+ listServiceAppDirNames = async (serviceAppsPath) => {
3279
+ try {
3280
+ return (await readdir(serviceAppsPath, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
3281
+ } catch (error) {
3282
+ if (this.isMissingFileError(error)) return [];
3283
+ throw new ServiceAppError("SERVICE_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
3284
+ }
3285
+ };
3286
+ isMissingFileError = (error) => Boolean(error) && typeof error === "object" && error.code === "ENOENT";
3287
+ };
3288
+ function toTitle(value) {
3289
+ return basename(value).replace(/[-_]+/g, " ").trim() || value;
3290
+ }
3291
+ //#endregion
2634
3292
  //#region src/utils/skill-frontmatter.utils.ts
2635
3293
  function parseSkillFrontmatter(raw) {
2636
3294
  const frontmatter = parseFrontmatterBlock(raw);
@@ -2838,7 +3496,7 @@ var NcpAgentSessionMetadataStore = class {
2838
3496
  read = async (sessionId, activitySnapshot) => {
2839
3497
  try {
2840
3498
  const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
2841
- if (!isRecord$3(parsed) || parsed._type !== "metadata" || !isRecord$3(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
3499
+ if (!isRecord$5(parsed) || parsed._type !== "metadata" || !isRecord$5(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
2842
3500
  const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
2843
3501
  const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
2844
3502
  return {
@@ -2939,7 +3597,7 @@ var NcpAgentSessionSummaryIndexStore = class {
2939
3597
  function serializeJournalEntry(entry) {
2940
3598
  const serialized = JSON.stringify(entry);
2941
3599
  if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
2942
- if (!isRecord$3(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
3600
+ if (!isRecord$5(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
2943
3601
  return serialized;
2944
3602
  }
2945
3603
  var NcpAgentSessionJournalStore = class {
@@ -3155,15 +3813,15 @@ var NcpAgentSessionJournalStore = class {
3155
3813
  console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
3156
3814
  continue;
3157
3815
  }
3158
- if (!isRecord$3(parsed)) continue;
3816
+ if (!isRecord$5(parsed)) continue;
3159
3817
  if (parsed._type === "metadata") {
3160
- metadata = isRecord$3(parsed.metadata) ? structuredClone(parsed.metadata) : {};
3818
+ metadata = isRecord$5(parsed.metadata) ? structuredClone(parsed.metadata) : {};
3161
3819
  agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
3162
3820
  createdAt = toIsoString(parsed.created_at, createdAt);
3163
3821
  updatedAt = toIsoString(parsed.updated_at, updatedAt);
3164
3822
  continue;
3165
3823
  }
3166
- if (parsed._type === "event" && isRecord$3(parsed.event)) {
3824
+ if (parsed._type === "event" && isRecord$5(parsed.event)) {
3167
3825
  const seq = Number(parsed.seq);
3168
3826
  nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
3169
3827
  updatedAt = toIsoString(parsed.timestamp, updatedAt);
@@ -6661,6 +7319,7 @@ var NextclawKernel = class {
6661
7319
  mcpManager;
6662
7320
  ncpSessionManager;
6663
7321
  panelAppManager;
7322
+ serviceAppManager;
6664
7323
  extensions;
6665
7324
  ncpAgentSessionJournalStore;
6666
7325
  kernelBranch;
@@ -6689,6 +7348,7 @@ var NextclawKernel = class {
6689
7348
  providerManager: this.llmProviders
6690
7349
  });
6691
7350
  this.panelAppManager = new PanelAppManager({ configManager: this.configManager });
7351
+ this.serviceAppManager = new ServiceAppManager({ configManager: this.configManager });
6692
7352
  this.extensions = new ExtensionManager({
6693
7353
  configManager: this.configManager,
6694
7354
  eventBus: this.eventBus,
@@ -6740,6 +7400,7 @@ var NextclawKernel = class {
6740
7400
  for (const contribution of [...this.contributions].reverse()) await contribution.dispose();
6741
7401
  this.ncpSessionManager.dispose();
6742
7402
  await this.mcpManager.dispose();
7403
+ await this.serviceAppManager.dispose();
6743
7404
  await this.sessionSearch.dispose();
6744
7405
  };
6745
7406
  };
@@ -7225,6 +7886,6 @@ function resolveLegacyEventType(message) {
7225
7886
  return `message.${role || "other"}`;
7226
7887
  }
7227
7888
  //#endregion
7228
- export { AgentManager, AgentRunClient, AgentRuntimeRegistry, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, ConfigManager, ContextCompactionPreflightService, ContextWindowPreviewManager, DEFAULT_AGENT_RUNTIME_ENTRY_ID, ExtensionManager, GatewayInboundProcessor, LlmProviderManager, LlmUsageManager, LlmUsageStore, McpManager, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NcpSessionManager, NextclawKernel, PanelAppError, PanelAppManager, ProviderManagerNcpLLMApi, SessionRequestManager, SkillManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildSessionOrchestrationSection, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextWindowSignature, createLlmUsageRecord, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isContextCompactionTimelineMessage, isContextWindowSnapshot, isPanelAppError, isReplyCapableChannel, listExtensionChannelIds, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseSkillFrontmatter, projectNcpMessagesWithContextCompaction, readContextWindowEventSessionId, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveNextclawNcpRunContext, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, upsertContextCompactionTimelineMessage, waitForAgentRuntimeSessionReply };
7889
+ export { AgentManager, AgentRunClient, AgentRuntimeRegistry, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, ConfigManager, ContextCompactionPreflightService, ContextWindowPreviewManager, DEFAULT_AGENT_RUNTIME_ENTRY_ID, ExtensionManager, GatewayInboundProcessor, LlmProviderManager, LlmUsageManager, LlmUsageStore, McpManager, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NcpSessionManager, NextclawKernel, PanelAppError, PanelAppManager, ProviderManagerNcpLLMApi, ServiceAppError, ServiceAppManager, SessionRequestManager, SkillManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildSessionOrchestrationSection, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextWindowSignature, createLlmUsageRecord, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isContextCompactionTimelineMessage, isContextWindowSnapshot, isPanelAppError, isReplyCapableChannel, isServiceAppError, listExtensionChannelIds, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseSkillFrontmatter, projectNcpMessagesWithContextCompaction, readContextWindowEventSessionId, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveNextclawNcpRunContext, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, upsertContextCompactionTimelineMessage, waitForAgentRuntimeSessionReply };
7229
7890
 
7230
7891
  //# sourceMappingURL=index.js.map