@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.d.ts +189 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +698 -37
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
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$
|
|
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$
|
|
638
|
-
chatId: readRequiredString$
|
|
639
|
-
senderId: readRequiredString$
|
|
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$
|
|
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$
|
|
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$
|
|
824
|
-
const chatId = readRequiredString$
|
|
825
|
-
const senderId = readRequiredString$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2189
|
+
if (readOptionalString$6(model)) {
|
|
2190
2190
|
metadata.model = model?.trim();
|
|
2191
2191
|
metadata.preferred_model = model?.trim();
|
|
2192
2192
|
}
|
|
2193
|
-
if (readOptionalString$
|
|
2193
|
+
if (readOptionalString$6(thinkingLevel)) {
|
|
2194
2194
|
metadata.thinking = thinkingLevel?.trim();
|
|
2195
2195
|
metadata.preferred_thinking = thinkingLevel?.trim();
|
|
2196
2196
|
}
|
|
2197
|
-
if (readOptionalString$
|
|
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$
|
|
2221
|
-
const parentSessionId = readOptionalString$
|
|
2222
|
-
const requestId = readOptionalString$
|
|
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$
|
|
2242
|
-
const sessionId = readOptionalString$
|
|
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$
|
|
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$
|
|
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$
|
|
3816
|
+
if (!isRecord$5(parsed)) continue;
|
|
3159
3817
|
if (parsed._type === "metadata") {
|
|
3160
|
-
metadata = isRecord$
|
|
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$
|
|
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
|