@nextclaw/kernel 0.3.3 → 0.4.0-beta.1
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 +42 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +880 -563
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
|
|
|
15
15
|
import { McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
|
|
16
16
|
import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
|
|
17
17
|
import { DefaultNcpAgentConversationStateManager } from "@nextclaw/ncp-toolkit";
|
|
18
|
+
import { createRuntimeChildEnv } from "@nextclaw/core/child-process-env";
|
|
18
19
|
import { parse } from "yaml";
|
|
19
20
|
import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
|
|
20
21
|
import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRuntime } from "@nextclaw/nextclaw-ncp-runtime-stdio-client";
|
|
@@ -627,16 +628,6 @@ function readOptionalString$9(value) {
|
|
|
627
628
|
if (typeof value !== "string") return;
|
|
628
629
|
return value.trim() || void 0;
|
|
629
630
|
}
|
|
630
|
-
function toSessionMessageRequest(payload) {
|
|
631
|
-
return {
|
|
632
|
-
sessionId: payload.sessionId,
|
|
633
|
-
message: {
|
|
634
|
-
...payload.message,
|
|
635
|
-
sessionId: payload.sessionId
|
|
636
|
-
},
|
|
637
|
-
correlationId: payload.requestId
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
631
|
function toRunHandle(accepted) {
|
|
641
632
|
return {
|
|
642
633
|
sessionId: accepted.sessionId,
|
|
@@ -684,7 +675,14 @@ var AgentRunRequestManager = class {
|
|
|
684
675
|
};
|
|
685
676
|
handleSessionMessageRequest = async (envelope) => {
|
|
686
677
|
if (!envelope.payload) throw new Error("Invalid agent run session message request.");
|
|
687
|
-
return toRunHandle(await this.send(
|
|
678
|
+
return toRunHandle(await this.send({
|
|
679
|
+
sessionId: envelope.payload.sessionId,
|
|
680
|
+
message: {
|
|
681
|
+
...envelope.payload.message,
|
|
682
|
+
sessionId: envelope.payload.sessionId
|
|
683
|
+
},
|
|
684
|
+
correlationId: envelope.payload.requestId
|
|
685
|
+
}));
|
|
688
686
|
};
|
|
689
687
|
send = async (request) => {
|
|
690
688
|
const session = await this.sessionManager.getOrCreateAgentRunSession({
|
|
@@ -710,7 +708,26 @@ var AgentRunRequestManager = class {
|
|
|
710
708
|
message
|
|
711
709
|
};
|
|
712
710
|
sessionRun.inbox.enqueue(message);
|
|
713
|
-
|
|
711
|
+
let stopPublishingRunStatus = () => void 0;
|
|
712
|
+
stopPublishingRunStatus = sessionRun.onStatusChange((status) => {
|
|
713
|
+
this.eventBus.emit(eventKeys.sessionRunStatus, {
|
|
714
|
+
sessionKey: sessionRun.sessionId,
|
|
715
|
+
status
|
|
716
|
+
}, {
|
|
717
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
718
|
+
source: "agent-run-request"
|
|
719
|
+
});
|
|
720
|
+
if (status === "idle") stopPublishingRunStatus();
|
|
721
|
+
});
|
|
722
|
+
this.cleanups.push(stopPublishingRunStatus);
|
|
723
|
+
const activeRun = (() => {
|
|
724
|
+
try {
|
|
725
|
+
return sessionRun.beginRun();
|
|
726
|
+
} catch (error) {
|
|
727
|
+
stopPublishingRunStatus();
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
})();
|
|
714
731
|
const model = request.model ?? session.model ?? this.configManager.getDefaultModel();
|
|
715
732
|
const agentId = request.agentId ?? session.agentId ?? resolveDefaultAgentProfileId(this.configManager.loadConfig());
|
|
716
733
|
const spec = {
|
|
@@ -2511,11 +2528,11 @@ var ExtensionManager = class {
|
|
|
2511
2528
|
//#endregion
|
|
2512
2529
|
//#region src/utils/model-message-vision.utils.ts
|
|
2513
2530
|
const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
|
|
2514
|
-
function isRecord$
|
|
2531
|
+
function isRecord$11(value) {
|
|
2515
2532
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2516
2533
|
}
|
|
2517
2534
|
function isImageContentPart(value) {
|
|
2518
|
-
if (!isRecord$
|
|
2535
|
+
if (!isRecord$11(value)) return false;
|
|
2519
2536
|
const type = value.type;
|
|
2520
2537
|
return type === "image_url" || type === "input_image";
|
|
2521
2538
|
}
|
|
@@ -2531,7 +2548,7 @@ function normalizeContentWithoutVision(content) {
|
|
|
2531
2548
|
};
|
|
2532
2549
|
});
|
|
2533
2550
|
if (!sawImage) return content;
|
|
2534
|
-
const textParts = parts.filter((part) => isRecord$
|
|
2551
|
+
const textParts = parts.filter((part) => isRecord$11(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
|
|
2535
2552
|
if (textParts.length === parts.length) return textParts.join("\n\n");
|
|
2536
2553
|
return parts;
|
|
2537
2554
|
}
|
|
@@ -3070,7 +3087,7 @@ function safeNcpSessionFilename(value) {
|
|
|
3070
3087
|
function normalizeNcpAgentId(agentId) {
|
|
3071
3088
|
return agentId?.trim().toLowerCase() || void 0;
|
|
3072
3089
|
}
|
|
3073
|
-
function isRecord$
|
|
3090
|
+
function isRecord$10(value) {
|
|
3074
3091
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3075
3092
|
}
|
|
3076
3093
|
function toIsoString(value, fallback) {
|
|
@@ -3690,7 +3707,7 @@ var PanelAppStateStore = class {
|
|
|
3690
3707
|
};
|
|
3691
3708
|
//#endregion
|
|
3692
3709
|
//#region src/stores/panel-app-capability-grant.store.ts
|
|
3693
|
-
const EMPTY_GRANTS$
|
|
3710
|
+
const EMPTY_GRANTS$2 = {
|
|
3694
3711
|
version: 1,
|
|
3695
3712
|
grants: {}
|
|
3696
3713
|
};
|
|
@@ -3725,9 +3742,9 @@ var PanelAppCapabilityGrantStore = class {
|
|
|
3725
3742
|
};
|
|
3726
3743
|
load = async () => {
|
|
3727
3744
|
try {
|
|
3728
|
-
return normalizeStoreData$
|
|
3745
|
+
return normalizeStoreData$2(JSON.parse(await readFile(this.filePath, "utf8")));
|
|
3729
3746
|
} catch (error) {
|
|
3730
|
-
if (isMissingFileError$
|
|
3747
|
+
if (isMissingFileError$3(error)) return structuredClone(EMPTY_GRANTS$2);
|
|
3731
3748
|
throw error;
|
|
3732
3749
|
}
|
|
3733
3750
|
};
|
|
@@ -3736,13 +3753,13 @@ var PanelAppCapabilityGrantStore = class {
|
|
|
3736
3753
|
await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
3737
3754
|
};
|
|
3738
3755
|
};
|
|
3739
|
-
function normalizeStoreData$
|
|
3740
|
-
if (!isRecord$
|
|
3756
|
+
function normalizeStoreData$2(value) {
|
|
3757
|
+
if (!isRecord$9(value) || value.version !== 1 || !isRecord$9(value.grants)) return structuredClone(EMPTY_GRANTS$2);
|
|
3741
3758
|
const grants = {};
|
|
3742
3759
|
for (const [callerKey, callerValue] of Object.entries(value.grants)) {
|
|
3743
|
-
if (!isRecord$
|
|
3760
|
+
if (!isRecord$9(callerValue) || !isRecord$9(callerValue.capabilities)) continue;
|
|
3744
3761
|
const capabilities = {};
|
|
3745
|
-
for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$
|
|
3762
|
+
for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$9(grantValue) && typeof grantValue.grantedAt === "string") capabilities[capability] = { grantedAt: grantValue.grantedAt };
|
|
3746
3763
|
grants[callerKey] = { capabilities };
|
|
3747
3764
|
}
|
|
3748
3765
|
return {
|
|
@@ -3753,11 +3770,65 @@ function normalizeStoreData$1(value) {
|
|
|
3753
3770
|
function getCallerKey(caller) {
|
|
3754
3771
|
return `${caller.surface}:${caller.appId}`;
|
|
3755
3772
|
}
|
|
3773
|
+
function isRecord$9(value) {
|
|
3774
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3775
|
+
}
|
|
3776
|
+
function isMissingFileError$3(error) {
|
|
3777
|
+
return Boolean(error) && typeof error === "object" && error.code === "ENOENT";
|
|
3778
|
+
}
|
|
3779
|
+
//#endregion
|
|
3780
|
+
//#region src/stores/panel-app-client-grant.store.ts
|
|
3781
|
+
const EMPTY_GRANTS$1 = {
|
|
3782
|
+
grants: {},
|
|
3783
|
+
version: 1
|
|
3784
|
+
};
|
|
3785
|
+
var PanelAppClientGrantStore = class {
|
|
3786
|
+
constructor(filePath) {
|
|
3787
|
+
this.filePath = filePath;
|
|
3788
|
+
}
|
|
3789
|
+
isGranted = async (appId) => {
|
|
3790
|
+
const data = await this.load();
|
|
3791
|
+
return Boolean(data.grants[appId]);
|
|
3792
|
+
};
|
|
3793
|
+
grant = async (params) => {
|
|
3794
|
+
const data = await this.load();
|
|
3795
|
+
data.grants[params.appId] = { grantedAt: params.grantedAt };
|
|
3796
|
+
await this.save(data);
|
|
3797
|
+
return params;
|
|
3798
|
+
};
|
|
3799
|
+
revoke = async (appId) => {
|
|
3800
|
+
const data = await this.load();
|
|
3801
|
+
if (!data.grants[appId]) return;
|
|
3802
|
+
delete data.grants[appId];
|
|
3803
|
+
await this.save(data);
|
|
3804
|
+
};
|
|
3805
|
+
load = async () => {
|
|
3806
|
+
try {
|
|
3807
|
+
return normalizeStoreData$1(JSON.parse(await readFile(this.filePath, "utf8")));
|
|
3808
|
+
} catch (error) {
|
|
3809
|
+
if (isMissingFileError$2(error)) return structuredClone(EMPTY_GRANTS$1);
|
|
3810
|
+
throw error;
|
|
3811
|
+
}
|
|
3812
|
+
};
|
|
3813
|
+
save = async (data) => {
|
|
3814
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
3815
|
+
await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
3816
|
+
};
|
|
3817
|
+
};
|
|
3818
|
+
function normalizeStoreData$1(value) {
|
|
3819
|
+
if (!isRecord$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS$1);
|
|
3820
|
+
const grants = {};
|
|
3821
|
+
for (const [appId, grant] of Object.entries(value.grants)) if (isRecord$8(grant) && typeof grant.grantedAt === "string") grants[appId] = { grantedAt: grant.grantedAt };
|
|
3822
|
+
return {
|
|
3823
|
+
grants,
|
|
3824
|
+
version: 1
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3756
3827
|
function isRecord$8(value) {
|
|
3757
3828
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3758
3829
|
}
|
|
3759
3830
|
function isMissingFileError$2(error) {
|
|
3760
|
-
return
|
|
3831
|
+
return isRecord$8(error) && error.code === "ENOENT";
|
|
3761
3832
|
}
|
|
3762
3833
|
//#endregion
|
|
3763
3834
|
//#region src/services/agent-run-client.service.ts
|
|
@@ -3953,162 +4024,465 @@ var AgentRunClient = class {
|
|
|
3953
4024
|
};
|
|
3954
4025
|
};
|
|
3955
4026
|
//#endregion
|
|
3956
|
-
//#region src/
|
|
3957
|
-
const
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
const insertAt = headMatch.index + headMatch[0].length;
|
|
3964
|
-
return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
|
|
4027
|
+
//#region src/tools/structured-result.tools.ts
|
|
4028
|
+
const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
|
|
4029
|
+
var StructuredResultSubmitTool = class {
|
|
4030
|
+
name = STRUCTURED_RESULT_TOOL_NAME;
|
|
4031
|
+
description = "Submit the structured object result for this request.";
|
|
4032
|
+
constructor(contract) {
|
|
4033
|
+
this.contract = contract;
|
|
3965
4034
|
}
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
function getPanelAppBridgeScript() {
|
|
3969
|
-
return `
|
|
3970
|
-
(() => {
|
|
3971
|
-
const requestType = "nextclaw:panel-app-service-actions:request";
|
|
3972
|
-
const responseType = "nextclaw:panel-app-service-actions:response";
|
|
3973
|
-
const pending = new Map();
|
|
3974
|
-
let counter = 0;
|
|
3975
|
-
|
|
3976
|
-
function createRequestId() {
|
|
3977
|
-
counter += 1;
|
|
3978
|
-
return "panel-bridge-" + Date.now().toString(36) + "-" + counter.toString(36);
|
|
3979
|
-
}
|
|
3980
|
-
|
|
3981
|
-
function request(method, payload) {
|
|
3982
|
-
const requestId = createRequestId();
|
|
3983
|
-
return new Promise((resolve, reject) => {
|
|
3984
|
-
pending.set(requestId, { method, resolve, reject });
|
|
3985
|
-
window.parent.postMessage({ type: requestType, requestId, method, payload }, "*");
|
|
3986
|
-
});
|
|
3987
|
-
}
|
|
3988
|
-
|
|
3989
|
-
function unwrapServiceActionResult(result) {
|
|
3990
|
-
if (!result || typeof result !== "object") {
|
|
3991
|
-
return result;
|
|
3992
|
-
}
|
|
3993
|
-
if (Object.prototype.hasOwnProperty.call(result, "structuredContent") && result.structuredContent !== undefined) {
|
|
3994
|
-
return result.structuredContent;
|
|
3995
|
-
}
|
|
3996
|
-
const content = Array.isArray(result.content) ? result.content : undefined;
|
|
3997
|
-
if (content && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string") {
|
|
3998
|
-
try {
|
|
3999
|
-
return JSON.parse(content[0].text);
|
|
4000
|
-
} catch {
|
|
4001
|
-
return content[0].text;
|
|
4002
|
-
}
|
|
4003
|
-
}
|
|
4004
|
-
return result;
|
|
4005
|
-
}
|
|
4006
|
-
|
|
4007
|
-
function resolveBridgeData(entry, data) {
|
|
4008
|
-
if (entry.method === "list") {
|
|
4009
|
-
return Array.isArray(data.data?.actions) ? data.data.actions : [];
|
|
4010
|
-
}
|
|
4011
|
-
if (entry.method === "invoke") {
|
|
4012
|
-
return unwrapServiceActionResult(data.data?.result);
|
|
4013
|
-
}
|
|
4014
|
-
if (entry.method === "agent.generateObject") {
|
|
4015
|
-
return data.data?.result;
|
|
4016
|
-
}
|
|
4017
|
-
return data.data;
|
|
4018
|
-
}
|
|
4019
|
-
|
|
4020
|
-
window.addEventListener("message", (event) => {
|
|
4021
|
-
const data = event.data;
|
|
4022
|
-
if (!data || data.type !== responseType || typeof data.requestId !== "string") {
|
|
4023
|
-
return;
|
|
4024
|
-
}
|
|
4025
|
-
const entry = pending.get(data.requestId);
|
|
4026
|
-
if (!entry) {
|
|
4027
|
-
return;
|
|
4028
|
-
}
|
|
4029
|
-
pending.delete(data.requestId);
|
|
4030
|
-
if (data.ok) {
|
|
4031
|
-
entry.resolve(resolveBridgeData(entry, data));
|
|
4032
|
-
return;
|
|
4033
|
-
}
|
|
4034
|
-
const error = new Error(data.error?.message || "NextClaw panel bridge request failed.");
|
|
4035
|
-
error.code = data.error?.code;
|
|
4036
|
-
error.details = data.error?.details;
|
|
4037
|
-
entry.reject(error);
|
|
4038
|
-
});
|
|
4039
|
-
|
|
4040
|
-
const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
|
|
4041
|
-
Object.defineProperty(window, "nextclaw", {
|
|
4042
|
-
configurable: true,
|
|
4043
|
-
value: {
|
|
4044
|
-
...existing,
|
|
4045
|
-
serviceActions: {
|
|
4046
|
-
list: () => request("list", {}),
|
|
4047
|
-
invoke: (actionId, input) => request("invoke", { actionId, input }),
|
|
4048
|
-
requestGrant: (actionId) => request("requestGrant", { actionId }),
|
|
4049
|
-
revokeGrant: (actionId) => request("revokeGrant", { actionId })
|
|
4050
|
-
},
|
|
4051
|
-
agent: {
|
|
4052
|
-
send: (input) => request("agent.send", { request: input }),
|
|
4053
|
-
generateObject: (input) => request("agent.generateObject", { input })
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
});
|
|
4057
|
-
})();
|
|
4058
|
-
`.trim();
|
|
4059
|
-
}
|
|
4060
|
-
//#endregion
|
|
4061
|
-
//#region src/utils/panel-app-manifest.utils.ts
|
|
4062
|
-
const PANEL_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4063
|
-
function parsePanelAppManifest(html) {
|
|
4064
|
-
return {
|
|
4065
|
-
...readHtmlTitle(html),
|
|
4066
|
-
...readStandardIcon(html),
|
|
4067
|
-
...readPanelAppMeta(html),
|
|
4068
|
-
capabilities: readPanelAppCapabilities(html),
|
|
4069
|
-
serviceActions: readPanelAppServiceActions(html)
|
|
4070
|
-
};
|
|
4071
|
-
}
|
|
4072
|
-
function parsePanelAppFolderManifest(raw) {
|
|
4073
|
-
let parsed;
|
|
4074
|
-
try {
|
|
4075
|
-
parsed = JSON.parse(raw);
|
|
4076
|
-
} catch (error) {
|
|
4077
|
-
throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4035
|
+
get parameters() {
|
|
4036
|
+
return this.contract.schema;
|
|
4078
4037
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4038
|
+
validateArgs = (args) => validateToolArgs(args, this.contract.schema);
|
|
4039
|
+
execute = async (args) => args;
|
|
4040
|
+
};
|
|
4041
|
+
//#endregion
|
|
4042
|
+
//#region src/utils/panel-app-agent.utils.ts
|
|
4043
|
+
const PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS = 6e4;
|
|
4044
|
+
const PANEL_APP_AGENT_MAX_TIMEOUT_MS = 12e4;
|
|
4045
|
+
const PANEL_APP_AGENT_MAX_PROMPT_CHARS = 2e4;
|
|
4046
|
+
const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
|
|
4047
|
+
function normalizePanelAppGenerateObjectInput(input) {
|
|
4048
|
+
const peerId = input.peerId.trim();
|
|
4049
|
+
const prompt = input.prompt.trim();
|
|
4050
|
+
if (!peerId || !prompt || !isRecord$7(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
|
|
4051
|
+
if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
|
|
4052
|
+
if (stringifyJsonValue(input.context ?? null).length > PANEL_APP_AGENT_MAX_CONTEXT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject context is too large");
|
|
4082
4053
|
return {
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
serviceActions: readStringArray$1(parsed.actions, "actions")
|
|
4054
|
+
context: input.context,
|
|
4055
|
+
peerId,
|
|
4056
|
+
prompt,
|
|
4057
|
+
schema: input.schema,
|
|
4058
|
+
timeoutMs: normalizeTimeoutMs(input.timeoutMs),
|
|
4059
|
+
title: input.title?.trim() || void 0
|
|
4090
4060
|
};
|
|
4091
4061
|
}
|
|
4092
|
-
function
|
|
4062
|
+
function createPanelAppGenerateObjectMessage(params) {
|
|
4063
|
+
const { bridgeSession, request, requestId } = params;
|
|
4093
4064
|
return {
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4065
|
+
id: `panel-app-agent-message-${randomUUID()}`,
|
|
4066
|
+
metadata: {
|
|
4067
|
+
...createPanelAppAgentMetadata(bridgeSession),
|
|
4068
|
+
panel_app_peer_id: request.peerId,
|
|
4069
|
+
structured_result: {
|
|
4070
|
+
request_id: requestId,
|
|
4071
|
+
schema: structuredClone(request.schema),
|
|
4072
|
+
tool_name: STRUCTURED_RESULT_TOOL_NAME
|
|
4073
|
+
}
|
|
4074
|
+
},
|
|
4075
|
+
parts: [{
|
|
4076
|
+
type: "text",
|
|
4077
|
+
text: buildGenerateObjectPrompt(bridgeSession, request)
|
|
4078
|
+
}],
|
|
4079
|
+
role: "user",
|
|
4080
|
+
status: "final",
|
|
4081
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4097
4082
|
};
|
|
4098
4083
|
}
|
|
4099
|
-
function
|
|
4100
|
-
const
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4084
|
+
async function waitForPanelAppStructuredResult(agentRunClient, params) {
|
|
4085
|
+
const iterator = agentRunClient.sendAndStreamEvents(params.payload)[Symbol.asyncIterator]();
|
|
4086
|
+
let timeoutId;
|
|
4087
|
+
const timeout = new Promise((_, reject) => {
|
|
4088
|
+
timeoutId = setTimeout(() => reject(new PanelAppError("AGENT_OBJECT_RESULT_TIMEOUT", "agent object result timed out")), params.timeoutMs);
|
|
4089
|
+
});
|
|
4090
|
+
let resultToolCallId = null;
|
|
4091
|
+
try {
|
|
4092
|
+
while (true) {
|
|
4093
|
+
const next = await Promise.race([iterator.next(), timeout]);
|
|
4094
|
+
if (next.done) break;
|
|
4095
|
+
const result = readStructuredResultEvent(next.value, resultToolCallId);
|
|
4096
|
+
if (result.matchedToolCallId) resultToolCallId = result.matchedToolCallId;
|
|
4097
|
+
if (result.submitted) return result.content;
|
|
4098
|
+
throwIfTerminalError(next.value);
|
|
4099
|
+
}
|
|
4100
|
+
} finally {
|
|
4101
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
4102
|
+
await iterator.return?.(void 0);
|
|
4103
|
+
}
|
|
4104
|
+
throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
|
|
4105
|
+
}
|
|
4106
|
+
function withPanelAppAgentMetadata(payload, bridgeSession) {
|
|
4107
|
+
const panelAppMetadata = createPanelAppAgentMetadata(bridgeSession);
|
|
4108
|
+
const peerId = readOptionalPeerId(payload.peerId);
|
|
4109
|
+
const sessionId = readOptionalSessionId(payload.sessionId);
|
|
4110
|
+
if (peerId && sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request cannot include both sessionId and peerId");
|
|
4111
|
+
const metadata = {
|
|
4112
|
+
...payload.metadata ?? {},
|
|
4113
|
+
...panelAppMetadata
|
|
4114
|
+
};
|
|
4115
|
+
if (peerId) metadata.panel_app_peer_id = peerId;
|
|
4116
|
+
if (Array.isArray(payload.content)) return {
|
|
4117
|
+
content: structuredClone(payload.content),
|
|
4118
|
+
metadata,
|
|
4119
|
+
peerId,
|
|
4120
|
+
sessionId
|
|
4121
|
+
};
|
|
4122
|
+
if (!payload.message) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request is required");
|
|
4123
|
+
return {
|
|
4124
|
+
message: {
|
|
4125
|
+
...structuredClone(payload.message),
|
|
4126
|
+
metadata: {
|
|
4127
|
+
...payload.message.metadata ?? {},
|
|
4128
|
+
...panelAppMetadata
|
|
4129
|
+
}
|
|
4130
|
+
},
|
|
4131
|
+
metadata,
|
|
4132
|
+
peerId,
|
|
4133
|
+
sessionId
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
function createPanelAppAgentMetadata(bridgeSession) {
|
|
4137
|
+
return {
|
|
4138
|
+
agent_peer_scope: `panel-app:${bridgeSession.appId}`,
|
|
4139
|
+
panel_app_bridge_request_id: randomUUID(),
|
|
4140
|
+
panel_app_id: bridgeSession.appId,
|
|
4141
|
+
source_kind: "panel_app"
|
|
4142
|
+
};
|
|
4143
|
+
}
|
|
4144
|
+
function normalizeTimeoutMs(timeoutMs) {
|
|
4145
|
+
if (!Number.isFinite(timeoutMs) || typeof timeoutMs !== "number") return PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS;
|
|
4146
|
+
return Math.min(PANEL_APP_AGENT_MAX_TIMEOUT_MS, Math.max(1e3, Math.trunc(timeoutMs)));
|
|
4147
|
+
}
|
|
4148
|
+
function readOptionalPeerId(value) {
|
|
4149
|
+
if (typeof value !== "string") return;
|
|
4150
|
+
const peerId = value.trim();
|
|
4151
|
+
if (!peerId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent peerId cannot be empty");
|
|
4152
|
+
return peerId;
|
|
4153
|
+
}
|
|
4154
|
+
function readOptionalSessionId(value) {
|
|
4155
|
+
if (typeof value !== "string") return;
|
|
4156
|
+
const sessionId = value.trim();
|
|
4157
|
+
if (!sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent sessionId cannot be empty");
|
|
4158
|
+
return sessionId;
|
|
4159
|
+
}
|
|
4160
|
+
function buildGenerateObjectPrompt(bridgeSession, request) {
|
|
4161
|
+
return [
|
|
4162
|
+
"Panel App generateObject request",
|
|
4163
|
+
"",
|
|
4164
|
+
`Panel App ID: ${bridgeSession.appId}`,
|
|
4165
|
+
`Peer ID: ${request.peerId}`,
|
|
4166
|
+
"",
|
|
4167
|
+
"Context JSON:",
|
|
4168
|
+
stringifyJsonValue(request.context ?? null),
|
|
4169
|
+
"",
|
|
4170
|
+
"Task:",
|
|
4171
|
+
request.prompt,
|
|
4172
|
+
"",
|
|
4173
|
+
"Result contract:",
|
|
4174
|
+
`Call the ${STRUCTURED_RESULT_TOOL_NAME} tool exactly once with an object matching the provided schema.`,
|
|
4175
|
+
"Do not use natural language as the result."
|
|
4176
|
+
].join("\n");
|
|
4177
|
+
}
|
|
4178
|
+
function readStructuredResultEvent(event, resultToolCallId) {
|
|
4179
|
+
if (event.type === NcpEventType.MessageToolCallStart && event.payload.toolName === "nextclaw_submit_result") return {
|
|
4180
|
+
matchedToolCallId: event.payload.toolCallId,
|
|
4181
|
+
submitted: false
|
|
4182
|
+
};
|
|
4183
|
+
if (event.type !== NcpEventType.MessageToolCallResult || event.payload.toolCallId !== resultToolCallId) return { submitted: false };
|
|
4184
|
+
assertToolResultContent(event.payload.content);
|
|
4185
|
+
return {
|
|
4186
|
+
content: event.payload.content,
|
|
4187
|
+
submitted: true
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
function assertToolResultContent(content) {
|
|
4191
|
+
if (!isRecord$7(content) || content.ok !== false || !isRecord$7(content.error)) return;
|
|
4192
|
+
if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
|
|
4193
|
+
throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
|
|
4194
|
+
}
|
|
4195
|
+
function throwIfTerminalError(event) {
|
|
4196
|
+
if (event.type === NcpEventType.MessageFailed) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error.message);
|
|
4197
|
+
if (event.type === NcpEventType.RunError) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error ?? "agent object request failed");
|
|
4198
|
+
if (event.type === NcpEventType.RunFinished) throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
|
|
4199
|
+
}
|
|
4200
|
+
function stringifyJsonValue(value) {
|
|
4201
|
+
try {
|
|
4202
|
+
return JSON.stringify(value, null, 2);
|
|
4203
|
+
} catch {
|
|
4204
|
+
throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "value is not JSON serializable");
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
function isRecord$7(value) {
|
|
4208
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4209
|
+
}
|
|
4210
|
+
//#endregion
|
|
4211
|
+
//#region src/services/panel-app-agent-bridge.service.ts
|
|
4212
|
+
var PanelAppAgentBridgeService = class {
|
|
4213
|
+
constructor(params) {
|
|
4214
|
+
this.params = params;
|
|
4215
|
+
}
|
|
4216
|
+
sendAgentMessage = async (bridgeSession, payload) => {
|
|
4217
|
+
await this.assertAgentCapabilityGranted(bridgeSession, "agent:send");
|
|
4218
|
+
return await this.requireAgentRunClient().send(withPanelAppAgentMetadata(payload, bridgeSession));
|
|
4219
|
+
};
|
|
4220
|
+
generateAgentObject = async (bridgeSession, input) => {
|
|
4221
|
+
await this.assertAgentCapabilityGranted(bridgeSession, "agent:generateObject");
|
|
4222
|
+
const request = normalizePanelAppGenerateObjectInput(input);
|
|
4223
|
+
const message = createPanelAppGenerateObjectMessage({
|
|
4224
|
+
bridgeSession,
|
|
4225
|
+
request,
|
|
4226
|
+
requestId: randomUUID()
|
|
4227
|
+
});
|
|
4228
|
+
return { result: await waitForPanelAppStructuredResult(this.requireAgentRunClient(), {
|
|
4229
|
+
payload: {
|
|
4230
|
+
message,
|
|
4231
|
+
metadata: {
|
|
4232
|
+
...createPanelAppAgentMetadata(bridgeSession),
|
|
4233
|
+
panel_app_peer_id: request.peerId
|
|
4234
|
+
},
|
|
4235
|
+
peerId: request.peerId
|
|
4236
|
+
},
|
|
4237
|
+
timeoutMs: request.timeoutMs
|
|
4238
|
+
}) };
|
|
4239
|
+
};
|
|
4240
|
+
grantAgentCapability = async (bridgeSession, capability) => {
|
|
4241
|
+
if (!isPanelAppAgentCapability(capability)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "unknown panel app agent capability");
|
|
4242
|
+
this.assertDeclaredCapability(bridgeSession, capability);
|
|
4243
|
+
return await this.params.createCapabilityGrantStore().grant({
|
|
4244
|
+
caller: bridgeSession.caller,
|
|
4245
|
+
capability,
|
|
4246
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4247
|
+
});
|
|
4248
|
+
};
|
|
4249
|
+
assertAgentCapabilityGranted = async (bridgeSession, capability) => {
|
|
4250
|
+
this.assertDeclaredCapability(bridgeSession, capability);
|
|
4251
|
+
if (!await this.params.createCapabilityGrantStore().isGranted(bridgeSession.caller, capability)) throw new PanelAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to use ${capability}.`);
|
|
4252
|
+
};
|
|
4253
|
+
assertDeclaredCapability = (bridgeSession, capability) => {
|
|
4254
|
+
if (!bridgeSession.declaredCapabilities.includes(capability)) throw new PanelAppError("PANEL_APP_CAPABILITY_NOT_DECLARED", this.describeMissingAgentCapability(bridgeSession.declaredCapabilities, capability));
|
|
4255
|
+
};
|
|
4256
|
+
describeMissingAgentCapability = (declaredCapabilities, capability) => {
|
|
4257
|
+
const declared = declaredCapabilities.length > 0 ? declaredCapabilities.join(", ") : "none";
|
|
4258
|
+
const valid = PANEL_APP_AGENT_CAPABILITIES.join(", ");
|
|
4259
|
+
const hint = declaredCapabilities.includes(capability.replace(":", ".")) ? ` Use ${capability}, not ${capability.replace(":", ".")}.` : "";
|
|
4260
|
+
return [
|
|
4261
|
+
`panel app did not declare ${capability}.`,
|
|
4262
|
+
`Declared: ${declared}.`,
|
|
4263
|
+
`Valid capabilities: ${valid}.`,
|
|
4264
|
+
`Declare it with nextclaw-panel-capabilities or panel-app.json capabilities.`,
|
|
4265
|
+
hint.trim()
|
|
4266
|
+
].filter(Boolean).join(" ");
|
|
4267
|
+
};
|
|
4268
|
+
requireAgentRunClient = () => {
|
|
4269
|
+
if (!this.params.agentRunClient) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "panel app agent client is not configured");
|
|
4270
|
+
return this.params.agentRunClient;
|
|
4271
|
+
};
|
|
4272
|
+
};
|
|
4273
|
+
//#endregion
|
|
4274
|
+
//#region src/utils/panel-app-bridge.utils.ts
|
|
4275
|
+
const PANEL_APP_BRIDGE_MARKER = "nextclaw:panel-app-service-actions:request";
|
|
4276
|
+
function injectPanelAppBridgeScript(html, params) {
|
|
4277
|
+
if (html.includes(PANEL_APP_BRIDGE_MARKER)) return html;
|
|
4278
|
+
const script = `<script>${getPanelAppBridgeScript(params)}<\/script>`;
|
|
4279
|
+
const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
4280
|
+
if (headMatch?.index !== void 0) {
|
|
4281
|
+
const insertAt = headMatch.index + headMatch[0].length;
|
|
4282
|
+
return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
|
|
4283
|
+
}
|
|
4284
|
+
return `${script}${html}`;
|
|
4285
|
+
}
|
|
4286
|
+
function getPanelAppBridgeScript(params = {
|
|
4287
|
+
appId: "",
|
|
4288
|
+
runtimeToken: ""
|
|
4289
|
+
}) {
|
|
4290
|
+
return `
|
|
4291
|
+
(() => {
|
|
4292
|
+
const requestType = "nextclaw:panel-app-service-actions:request";
|
|
4293
|
+
const responseType = "nextclaw:panel-app-service-actions:response";
|
|
4294
|
+
const appId = ${JSON.stringify(params.appId)};
|
|
4295
|
+
const runtimeToken = ${JSON.stringify(params.runtimeToken)};
|
|
4296
|
+
const pending = new Map();
|
|
4297
|
+
let counter = 0;
|
|
4298
|
+
|
|
4299
|
+
function createRequestId() {
|
|
4300
|
+
counter += 1;
|
|
4301
|
+
return "panel-bridge-" + Date.now().toString(36) + "-" + counter.toString(36);
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
function request(method, payload) {
|
|
4305
|
+
const requestId = createRequestId();
|
|
4306
|
+
return new Promise((resolve, reject) => {
|
|
4307
|
+
pending.set(requestId, { method, resolve, reject });
|
|
4308
|
+
window.parent.postMessage({ type: requestType, requestId, appId, runtimeToken, method, payload }, "*");
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
function unwrapServiceActionResult(result) {
|
|
4313
|
+
if (!result || typeof result !== "object") {
|
|
4314
|
+
return result;
|
|
4315
|
+
}
|
|
4316
|
+
if (Object.prototype.hasOwnProperty.call(result, "structuredContent") && result.structuredContent !== undefined) {
|
|
4317
|
+
return result.structuredContent;
|
|
4318
|
+
}
|
|
4319
|
+
const content = Array.isArray(result.content) ? result.content : undefined;
|
|
4320
|
+
if (content && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string") {
|
|
4321
|
+
try {
|
|
4322
|
+
return JSON.parse(content[0].text);
|
|
4323
|
+
} catch {
|
|
4324
|
+
return content[0].text;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
return result;
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
function resolveBridgeData(entry, data) {
|
|
4331
|
+
if (entry.method === "list") {
|
|
4332
|
+
return Array.isArray(data.data?.actions) ? data.data.actions : [];
|
|
4333
|
+
}
|
|
4334
|
+
if (entry.method === "invoke") {
|
|
4335
|
+
return unwrapServiceActionResult(data.data?.result);
|
|
4336
|
+
}
|
|
4337
|
+
if (entry.method === "agent.generateObject") {
|
|
4338
|
+
return data.data?.result;
|
|
4339
|
+
}
|
|
4340
|
+
return data.data;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
window.addEventListener("message", (event) => {
|
|
4344
|
+
const data = event.data;
|
|
4345
|
+
if (!data || data.type !== responseType || typeof data.requestId !== "string") {
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
const entry = pending.get(data.requestId);
|
|
4349
|
+
if (!entry) {
|
|
4350
|
+
return;
|
|
4351
|
+
}
|
|
4352
|
+
pending.delete(data.requestId);
|
|
4353
|
+
if (data.ok) {
|
|
4354
|
+
entry.resolve(resolveBridgeData(entry, data));
|
|
4355
|
+
return;
|
|
4356
|
+
}
|
|
4357
|
+
const error = new Error(data.error?.message || "NextClaw panel bridge request failed.");
|
|
4358
|
+
error.code = data.error?.code;
|
|
4359
|
+
error.details = data.error?.details;
|
|
4360
|
+
entry.reject(error);
|
|
4361
|
+
});
|
|
4362
|
+
|
|
4363
|
+
const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
|
|
4364
|
+
Object.defineProperty(window, "nextclaw", {
|
|
4365
|
+
configurable: true,
|
|
4366
|
+
value: {
|
|
4367
|
+
...existing,
|
|
4368
|
+
serviceActions: {
|
|
4369
|
+
list: () => request("list", {}),
|
|
4370
|
+
invoke: (actionId, input) => request("invoke", { actionId, input }),
|
|
4371
|
+
requestGrant: (actionId) => request("requestGrant", { actionId }),
|
|
4372
|
+
revokeGrant: (actionId) => request("revokeGrant", { actionId })
|
|
4373
|
+
},
|
|
4374
|
+
agent: {
|
|
4375
|
+
send: (input) => request("agent.send", { request: input }),
|
|
4376
|
+
generateObject: (input) => request("agent.generateObject", { input })
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
});
|
|
4380
|
+
})();
|
|
4381
|
+
`.trim();
|
|
4382
|
+
}
|
|
4383
|
+
//#endregion
|
|
4384
|
+
//#region src/utils/panel-app-client-injection.utils.ts
|
|
4385
|
+
const PANEL_APP_CLIENT_MARKER = "nextclaw:panel-app-client:init";
|
|
4386
|
+
const PANEL_APP_CLIENT_SDK_PATH = "/api/panel-app-client-sdk.js";
|
|
4387
|
+
function injectPanelAppClientScript(html, params) {
|
|
4388
|
+
if (html.includes(PANEL_APP_CLIENT_MARKER)) return html;
|
|
4389
|
+
const script = [`<script src="${PANEL_APP_CLIENT_SDK_PATH}"><\/script>`, `<script>${getPanelAppClientInitScript(params)}<\/script>`].join("");
|
|
4390
|
+
const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
4391
|
+
if (headMatch?.index !== void 0) {
|
|
4392
|
+
const insertAt = headMatch.index + headMatch[0].length;
|
|
4393
|
+
return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
|
|
4394
|
+
}
|
|
4395
|
+
return `${script}${html}`;
|
|
4396
|
+
}
|
|
4397
|
+
function getPanelAppClientInitScript(params) {
|
|
4398
|
+
return `
|
|
4399
|
+
(() => {
|
|
4400
|
+
const marker = "${PANEL_APP_CLIENT_MARKER}";
|
|
4401
|
+
if (typeof window.NextClawClient !== "function") {
|
|
4402
|
+
console.error("[NextClaw] Panel App client SDK failed to load.");
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
if (typeof window.createNextClawAppClient !== "function") {
|
|
4406
|
+
console.error("[NextClaw] Panel App client projection failed to load.");
|
|
4407
|
+
return;
|
|
4408
|
+
}
|
|
4409
|
+
const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
|
|
4410
|
+
const hostClient = new window.NextClawClient({
|
|
4411
|
+
baseUrl: window.location.origin,
|
|
4412
|
+
headers: {
|
|
4413
|
+
"x-nextclaw-panel-bridge-session": ${JSON.stringify(params.runtimeToken)}
|
|
4414
|
+
}
|
|
4415
|
+
});
|
|
4416
|
+
const client = window.createNextClawAppClient(hostClient);
|
|
4417
|
+
Object.defineProperty(window, "nextclaw", {
|
|
4418
|
+
configurable: true,
|
|
4419
|
+
value: {
|
|
4420
|
+
...existing,
|
|
4421
|
+
client
|
|
4422
|
+
}
|
|
4423
|
+
});
|
|
4424
|
+
Object.defineProperty(window.nextclaw, "__clientInitMarker", {
|
|
4425
|
+
configurable: true,
|
|
4426
|
+
enumerable: false,
|
|
4427
|
+
value: marker
|
|
4428
|
+
});
|
|
4429
|
+
})();
|
|
4430
|
+
`.trim();
|
|
4431
|
+
}
|
|
4432
|
+
//#endregion
|
|
4433
|
+
//#region src/utils/panel-app-manifest.utils.ts
|
|
4434
|
+
const PANEL_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4435
|
+
function parsePanelAppManifest(html) {
|
|
4436
|
+
return {
|
|
4437
|
+
...readHtmlTitle(html),
|
|
4438
|
+
...readStandardIcon(html),
|
|
4439
|
+
...readPanelAppMeta(html),
|
|
4440
|
+
capabilities: readPanelAppCapabilities(html),
|
|
4441
|
+
client: false,
|
|
4442
|
+
serviceActions: readPanelAppServiceActions(html)
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
function parsePanelAppFolderManifest(raw) {
|
|
4446
|
+
let parsed;
|
|
4447
|
+
try {
|
|
4448
|
+
parsed = JSON.parse(raw);
|
|
4449
|
+
} catch (error) {
|
|
4450
|
+
throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4451
|
+
}
|
|
4452
|
+
if (!isRecord$6(parsed)) throw new Error("panel-app.json must contain an object.");
|
|
4453
|
+
const id = readOptionalString$6(parsed, "id");
|
|
4454
|
+
if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
|
|
4455
|
+
return {
|
|
4456
|
+
id,
|
|
4457
|
+
title: readRequiredString$3(parsed, "title"),
|
|
4458
|
+
description: readOptionalString$6(parsed, "description"),
|
|
4459
|
+
icon: readOptionalString$6(parsed, "icon"),
|
|
4460
|
+
entry: readRequiredString$3(parsed, "entry"),
|
|
4461
|
+
capabilities: readStringArray$1(parsed.capabilities, "capabilities"),
|
|
4462
|
+
client: readOptionalBoolean$2(parsed, "client"),
|
|
4463
|
+
serviceActions: readStringArray$1(parsed.actions, "actions")
|
|
4464
|
+
};
|
|
4465
|
+
}
|
|
4466
|
+
function readPanelAppMeta(html) {
|
|
4467
|
+
return {
|
|
4468
|
+
...readPanelAppMetaField(html, "title"),
|
|
4469
|
+
...readPanelAppMetaField(html, "description"),
|
|
4470
|
+
...readPanelAppMetaField(html, "icon")
|
|
4471
|
+
};
|
|
4472
|
+
}
|
|
4473
|
+
function readPanelAppMetaField(html, field) {
|
|
4474
|
+
const content = readMetaContent(html, `nextclaw-panel-${field}`, field === "icon" ? "attribute" : "text");
|
|
4475
|
+
const manifest = {};
|
|
4476
|
+
if (content) manifest[field] = content;
|
|
4477
|
+
return manifest;
|
|
4478
|
+
}
|
|
4479
|
+
function readHtmlTitle(html) {
|
|
4480
|
+
const title = normalizeTextValue(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]);
|
|
4481
|
+
return title ? { title } : {};
|
|
4482
|
+
}
|
|
4483
|
+
function readStandardIcon(html) {
|
|
4484
|
+
const icon = readLinkHref(html, (relTokens) => relTokens.includes("icon"));
|
|
4485
|
+
const appleTouchIcon = readLinkHref(html, (relTokens) => relTokens.some((token) => token === "apple-touch-icon" || token === "apple-touch-icon-precomposed"));
|
|
4112
4486
|
const href = normalizeIconHref(icon ?? appleTouchIcon);
|
|
4113
4487
|
return href ? { icon: href } : {};
|
|
4114
4488
|
}
|
|
@@ -4151,12 +4525,18 @@ function readRequiredString$3(record, key) {
|
|
|
4151
4525
|
function readOptionalString$6(record, key) {
|
|
4152
4526
|
return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
|
|
4153
4527
|
}
|
|
4528
|
+
function readOptionalBoolean$2(record, key) {
|
|
4529
|
+
const value = record[key];
|
|
4530
|
+
if (value === void 0) return false;
|
|
4531
|
+
if (typeof value !== "boolean") throw new Error(`panel app ${key} must be a boolean.`);
|
|
4532
|
+
return value;
|
|
4533
|
+
}
|
|
4154
4534
|
function readStringArray$1(value, key) {
|
|
4155
4535
|
if (value === void 0) return [];
|
|
4156
4536
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
|
|
4157
4537
|
return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
|
|
4158
4538
|
}
|
|
4159
|
-
function isRecord$
|
|
4539
|
+
function isRecord$6(value) {
|
|
4160
4540
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4161
4541
|
}
|
|
4162
4542
|
function normalizeTextValue(value) {
|
|
@@ -4236,296 +4616,168 @@ function resolvePanelAppIconUrl(id, icon) {
|
|
|
4236
4616
|
if (icon.startsWith("data:image/") || icon.startsWith("http://") || icon.startsWith("https://") || icon.startsWith("/") || isLikelyTextIcon(icon)) return icon;
|
|
4237
4617
|
return `/api/panel-apps/${encodeURIComponent(id)}/assets/${encodePanelAppAssetPath(icon)}`;
|
|
4238
4618
|
}
|
|
4239
|
-
function injectPanelAppAssetBase(html, baseHref) {
|
|
4240
|
-
const base = `<base href="${baseHref}">`;
|
|
4241
|
-
if (/<base\b/i.test(html)) return html;
|
|
4242
|
-
if (/<head[^>]*>/i.test(html)) return html.replace(/<head[^>]*>/i, (head) => `${head}${base}`);
|
|
4243
|
-
return `${base}${html}`;
|
|
4244
|
-
}
|
|
4245
|
-
function encodePanelAppAssetPath(path) {
|
|
4246
|
-
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
4247
|
-
}
|
|
4248
|
-
function hasSafeBaseName(name) {
|
|
4249
|
-
return !name.includes("/") && !name.includes("\\") && !name.includes("\0");
|
|
4250
|
-
}
|
|
4251
|
-
function isLikelyTextIcon(icon) {
|
|
4252
|
-
return !icon.includes("/") && !icon.includes(".") && icon.length <= 4;
|
|
4253
|
-
}
|
|
4254
|
-
function escapeRegExp(value) {
|
|
4255
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4256
|
-
}
|
|
4257
|
-
//#endregion
|
|
4258
|
-
//#region src/services/panel-app-source.service.ts
|
|
4259
|
-
var PanelAppSourceService = class {
|
|
4260
|
-
listSources = async (panelsPath) => {
|
|
4261
|
-
try {
|
|
4262
|
-
const entries = await readdir(panelsPath, { withFileTypes: true });
|
|
4263
|
-
const sources = [];
|
|
4264
|
-
for (const entry of entries.filter(isPanelAppSourceEntry)) try {
|
|
4265
|
-
sources.push(await this.readSource(panelsPath, entry.name));
|
|
4266
|
-
} catch (error) {
|
|
4267
|
-
if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND") throw error;
|
|
4268
|
-
}
|
|
4269
|
-
return sources;
|
|
4270
|
-
} catch (error) {
|
|
4271
|
-
if (isPanelAppError(error)) throw error;
|
|
4272
|
-
if (isMissingFileError$1(error)) return [];
|
|
4273
|
-
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4274
|
-
}
|
|
4275
|
-
};
|
|
4276
|
-
resolveSource = async (panelsPath, id) => {
|
|
4277
|
-
const sourceName = decodePanelAppId(id);
|
|
4278
|
-
try {
|
|
4279
|
-
return await this.readSource(panelsPath, sourceName);
|
|
4280
|
-
} catch (error) {
|
|
4281
|
-
if (isPanelAppError(error)) throw error;
|
|
4282
|
-
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4283
|
-
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4284
|
-
}
|
|
4285
|
-
};
|
|
4286
|
-
getAsset = async (panelsPath, id, assetPath) => {
|
|
4287
|
-
const source = await this.resolveSource(panelsPath, id);
|
|
4288
|
-
if (source.kind !== "folder") throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4289
|
-
const filePath = resolvePanelAppRelativePath(source.sourcePath, assetPath);
|
|
4290
|
-
try {
|
|
4291
|
-
if (!(await stat(filePath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4292
|
-
return {
|
|
4293
|
-
content: await readFile(filePath),
|
|
4294
|
-
contentType: resolvePanelAppAssetContentType(filePath)
|
|
4295
|
-
};
|
|
4296
|
-
} catch (error) {
|
|
4297
|
-
if (isPanelAppError(error)) throw error;
|
|
4298
|
-
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4299
|
-
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4300
|
-
}
|
|
4301
|
-
};
|
|
4302
|
-
readSource = async (panelsPath, sourceName) => {
|
|
4303
|
-
const sourcePath = join(panelsPath, sourceName);
|
|
4304
|
-
const sourceStat = await stat(sourcePath);
|
|
4305
|
-
if (sourceStat.isFile() && isPanelAppFileName(sourceName)) return {
|
|
4306
|
-
kind: "single-file",
|
|
4307
|
-
sourceName,
|
|
4308
|
-
sourcePath,
|
|
4309
|
-
entryPath: sourcePath,
|
|
4310
|
-
sourceStat
|
|
4311
|
-
};
|
|
4312
|
-
if (sourceStat.isDirectory() && isPanelAppDirName(sourceName)) return await this.readFolderSource(sourcePath, sourceName, sourceStat);
|
|
4313
|
-
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4314
|
-
};
|
|
4315
|
-
readFolderSource = async (sourcePath, sourceName, sourceStat) => {
|
|
4316
|
-
try {
|
|
4317
|
-
const manifest = await readPanelAppFolderManifest(sourcePath, sourceName);
|
|
4318
|
-
const entryPath = resolvePanelAppRelativePath(sourcePath, manifest.entry);
|
|
4319
|
-
if (!(await stat(entryPath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app entry not found");
|
|
4320
|
-
return {
|
|
4321
|
-
kind: "folder",
|
|
4322
|
-
sourceName,
|
|
4323
|
-
sourcePath,
|
|
4324
|
-
entryPath,
|
|
4325
|
-
manifest,
|
|
4326
|
-
sourceStat
|
|
4327
|
-
};
|
|
4328
|
-
} catch (error) {
|
|
4329
|
-
if (isPanelAppError(error)) throw error;
|
|
4330
|
-
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4331
|
-
throw new PanelAppError("PANEL_APP_MANIFEST_INVALID", error instanceof Error ? error.message : String(error));
|
|
4332
|
-
}
|
|
4333
|
-
};
|
|
4334
|
-
};
|
|
4335
|
-
function isMissingFileError$1(error) {
|
|
4336
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4337
|
-
}
|
|
4338
|
-
//#endregion
|
|
4339
|
-
//#region src/utils/panel-app-time.utils.ts
|
|
4340
|
-
function resolvePanelAppCreatedAt(fileStat) {
|
|
4341
|
-
return fileStat.birthtimeMs > 0 ? fileStat.birthtime.toISOString() : fileStat.mtime.toISOString();
|
|
4342
|
-
}
|
|
4343
|
-
function resolvePanelAppActivityMs(entry) {
|
|
4344
|
-
return Math.max(new Date(entry.lastOpenedAt ?? 0).getTime(), new Date(entry.createdAt).getTime(), new Date(entry.updatedAt).getTime());
|
|
4345
|
-
}
|
|
4346
|
-
//#endregion
|
|
4347
|
-
//#region src/tools/structured-result.tools.ts
|
|
4348
|
-
const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
|
|
4349
|
-
var StructuredResultSubmitTool = class {
|
|
4350
|
-
name = STRUCTURED_RESULT_TOOL_NAME;
|
|
4351
|
-
description = "Submit the structured object result for this request.";
|
|
4352
|
-
constructor(contract) {
|
|
4353
|
-
this.contract = contract;
|
|
4354
|
-
}
|
|
4355
|
-
get parameters() {
|
|
4356
|
-
return this.contract.schema;
|
|
4357
|
-
}
|
|
4358
|
-
validateArgs = (args) => validateToolArgs(args, this.contract.schema);
|
|
4359
|
-
execute = async (args) => args;
|
|
4360
|
-
};
|
|
4361
|
-
//#endregion
|
|
4362
|
-
//#region src/utils/panel-app-agent.utils.ts
|
|
4363
|
-
const PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS = 6e4;
|
|
4364
|
-
const PANEL_APP_AGENT_MAX_TIMEOUT_MS = 12e4;
|
|
4365
|
-
const PANEL_APP_AGENT_MAX_PROMPT_CHARS = 2e4;
|
|
4366
|
-
const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
|
|
4367
|
-
function normalizePanelAppGenerateObjectInput(input) {
|
|
4368
|
-
const peerId = input.peerId.trim();
|
|
4369
|
-
const prompt = input.prompt.trim();
|
|
4370
|
-
if (!peerId || !prompt || !isRecord$6(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
|
|
4371
|
-
if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
|
|
4372
|
-
if (stringifyJsonValue(input.context ?? null).length > PANEL_APP_AGENT_MAX_CONTEXT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject context is too large");
|
|
4373
|
-
return {
|
|
4374
|
-
context: input.context,
|
|
4375
|
-
peerId,
|
|
4376
|
-
prompt,
|
|
4377
|
-
schema: input.schema,
|
|
4378
|
-
timeoutMs: normalizeTimeoutMs(input.timeoutMs),
|
|
4379
|
-
title: input.title?.trim() || void 0
|
|
4380
|
-
};
|
|
4381
|
-
}
|
|
4382
|
-
function createPanelAppGenerateObjectMessage(params) {
|
|
4383
|
-
const { bridgeSession, request, requestId } = params;
|
|
4384
|
-
return {
|
|
4385
|
-
id: `panel-app-agent-message-${randomUUID()}`,
|
|
4386
|
-
metadata: {
|
|
4387
|
-
...createPanelAppAgentMetadata(bridgeSession),
|
|
4388
|
-
panel_app_peer_id: request.peerId,
|
|
4389
|
-
structured_result: {
|
|
4390
|
-
request_id: requestId,
|
|
4391
|
-
schema: structuredClone(request.schema),
|
|
4392
|
-
tool_name: STRUCTURED_RESULT_TOOL_NAME
|
|
4393
|
-
}
|
|
4394
|
-
},
|
|
4395
|
-
parts: [{
|
|
4396
|
-
type: "text",
|
|
4397
|
-
text: buildGenerateObjectPrompt(bridgeSession, request)
|
|
4398
|
-
}],
|
|
4399
|
-
role: "user",
|
|
4400
|
-
status: "final",
|
|
4401
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4402
|
-
};
|
|
4403
|
-
}
|
|
4404
|
-
async function waitForPanelAppStructuredResult(agentRunClient, params) {
|
|
4405
|
-
const iterator = agentRunClient.sendAndStreamEvents(params.payload)[Symbol.asyncIterator]();
|
|
4406
|
-
let timeoutId;
|
|
4407
|
-
const timeout = new Promise((_, reject) => {
|
|
4408
|
-
timeoutId = setTimeout(() => reject(new PanelAppError("AGENT_OBJECT_RESULT_TIMEOUT", "agent object result timed out")), params.timeoutMs);
|
|
4409
|
-
});
|
|
4410
|
-
let resultToolCallId = null;
|
|
4411
|
-
try {
|
|
4412
|
-
while (true) {
|
|
4413
|
-
const next = await Promise.race([iterator.next(), timeout]);
|
|
4414
|
-
if (next.done) break;
|
|
4415
|
-
const result = readStructuredResultEvent(next.value, resultToolCallId);
|
|
4416
|
-
if (result.matchedToolCallId) resultToolCallId = result.matchedToolCallId;
|
|
4417
|
-
if (result.submitted) return result.content;
|
|
4418
|
-
throwIfTerminalError(next.value);
|
|
4419
|
-
}
|
|
4420
|
-
} finally {
|
|
4421
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
4422
|
-
await iterator.return?.(void 0);
|
|
4423
|
-
}
|
|
4424
|
-
throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
|
|
4425
|
-
}
|
|
4426
|
-
function withPanelAppAgentMetadata(payload, bridgeSession) {
|
|
4427
|
-
const panelAppMetadata = createPanelAppAgentMetadata(bridgeSession);
|
|
4428
|
-
const peerId = readOptionalPeerId(payload.peerId);
|
|
4429
|
-
const sessionId = readOptionalSessionId(payload.sessionId);
|
|
4430
|
-
if (peerId && sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request cannot include both sessionId and peerId");
|
|
4431
|
-
const metadata = {
|
|
4432
|
-
...payload.metadata ?? {},
|
|
4433
|
-
...panelAppMetadata
|
|
4434
|
-
};
|
|
4435
|
-
if (peerId) metadata.panel_app_peer_id = peerId;
|
|
4436
|
-
if (Array.isArray(payload.content)) return {
|
|
4437
|
-
content: structuredClone(payload.content),
|
|
4438
|
-
metadata,
|
|
4439
|
-
peerId,
|
|
4440
|
-
sessionId
|
|
4441
|
-
};
|
|
4442
|
-
if (!payload.message) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request is required");
|
|
4443
|
-
return {
|
|
4444
|
-
message: {
|
|
4445
|
-
...structuredClone(payload.message),
|
|
4446
|
-
metadata: {
|
|
4447
|
-
...payload.message.metadata ?? {},
|
|
4448
|
-
...panelAppMetadata
|
|
4449
|
-
}
|
|
4450
|
-
},
|
|
4451
|
-
metadata,
|
|
4452
|
-
peerId,
|
|
4453
|
-
sessionId
|
|
4454
|
-
};
|
|
4455
|
-
}
|
|
4456
|
-
function createPanelAppAgentMetadata(bridgeSession) {
|
|
4457
|
-
return {
|
|
4458
|
-
agent_peer_scope: `panel-app:${bridgeSession.panelAppId}`,
|
|
4459
|
-
panel_app_bridge_request_id: randomUUID(),
|
|
4460
|
-
panel_app_id: bridgeSession.panelAppId,
|
|
4461
|
-
source_kind: "panel_app"
|
|
4462
|
-
};
|
|
4619
|
+
function injectPanelAppAssetBase(html, baseHref) {
|
|
4620
|
+
const base = `<base href="${baseHref}">`;
|
|
4621
|
+
if (/<base\b/i.test(html)) return html;
|
|
4622
|
+
if (/<head[^>]*>/i.test(html)) return html.replace(/<head[^>]*>/i, (head) => `${head}${base}`);
|
|
4623
|
+
return `${base}${html}`;
|
|
4463
4624
|
}
|
|
4464
|
-
function
|
|
4465
|
-
|
|
4466
|
-
return Math.min(PANEL_APP_AGENT_MAX_TIMEOUT_MS, Math.max(1e3, Math.trunc(timeoutMs)));
|
|
4625
|
+
function encodePanelAppAssetPath(path) {
|
|
4626
|
+
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
4467
4627
|
}
|
|
4468
|
-
function
|
|
4469
|
-
|
|
4470
|
-
const peerId = value.trim();
|
|
4471
|
-
if (!peerId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent peerId cannot be empty");
|
|
4472
|
-
return peerId;
|
|
4628
|
+
function hasSafeBaseName(name) {
|
|
4629
|
+
return !name.includes("/") && !name.includes("\\") && !name.includes("\0");
|
|
4473
4630
|
}
|
|
4474
|
-
function
|
|
4475
|
-
|
|
4476
|
-
const sessionId = value.trim();
|
|
4477
|
-
if (!sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent sessionId cannot be empty");
|
|
4478
|
-
return sessionId;
|
|
4631
|
+
function isLikelyTextIcon(icon) {
|
|
4632
|
+
return !icon.includes("/") && !icon.includes(".") && icon.length <= 4;
|
|
4479
4633
|
}
|
|
4480
|
-
function
|
|
4481
|
-
return [
|
|
4482
|
-
"Panel App generateObject request",
|
|
4483
|
-
"",
|
|
4484
|
-
`Panel App ID: ${bridgeSession.panelAppId}`,
|
|
4485
|
-
`Peer ID: ${request.peerId}`,
|
|
4486
|
-
"",
|
|
4487
|
-
"Context JSON:",
|
|
4488
|
-
stringifyJsonValue(request.context ?? null),
|
|
4489
|
-
"",
|
|
4490
|
-
"Task:",
|
|
4491
|
-
request.prompt,
|
|
4492
|
-
"",
|
|
4493
|
-
"Result contract:",
|
|
4494
|
-
`Call the ${STRUCTURED_RESULT_TOOL_NAME} tool exactly once with an object matching the provided schema.`,
|
|
4495
|
-
"Do not use natural language as the result."
|
|
4496
|
-
].join("\n");
|
|
4634
|
+
function escapeRegExp(value) {
|
|
4635
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4497
4636
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4637
|
+
//#endregion
|
|
4638
|
+
//#region src/services/panel-app-source.service.ts
|
|
4639
|
+
var PanelAppSourceService = class {
|
|
4640
|
+
listSources = async (panelsPath) => {
|
|
4641
|
+
try {
|
|
4642
|
+
const entries = await readdir(panelsPath, { withFileTypes: true });
|
|
4643
|
+
const sources = [];
|
|
4644
|
+
for (const entry of entries.filter(isPanelAppSourceEntry)) try {
|
|
4645
|
+
sources.push(await this.readSource(panelsPath, entry.name));
|
|
4646
|
+
} catch (error) {
|
|
4647
|
+
if (!isPanelAppError(error) || error.code !== "PANEL_APP_NOT_FOUND") throw error;
|
|
4648
|
+
}
|
|
4649
|
+
return sources;
|
|
4650
|
+
} catch (error) {
|
|
4651
|
+
if (isPanelAppError(error)) throw error;
|
|
4652
|
+
if (isMissingFileError$1(error)) return [];
|
|
4653
|
+
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4654
|
+
}
|
|
4502
4655
|
};
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4656
|
+
resolveSource = async (panelsPath, id) => {
|
|
4657
|
+
const sourceName = decodePanelAppId(id);
|
|
4658
|
+
try {
|
|
4659
|
+
return await this.readSource(panelsPath, sourceName);
|
|
4660
|
+
} catch (error) {
|
|
4661
|
+
if (isPanelAppError(error)) throw error;
|
|
4662
|
+
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4663
|
+
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4664
|
+
}
|
|
4665
|
+
};
|
|
4666
|
+
getAsset = async (panelsPath, id, assetPath) => {
|
|
4667
|
+
const source = await this.resolveSource(panelsPath, id);
|
|
4668
|
+
if (source.kind !== "folder") throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4669
|
+
const filePath = resolvePanelAppRelativePath(source.sourcePath, assetPath);
|
|
4670
|
+
try {
|
|
4671
|
+
if (!(await stat(filePath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4672
|
+
return {
|
|
4673
|
+
content: await readFile(filePath),
|
|
4674
|
+
contentType: resolvePanelAppAssetContentType(filePath)
|
|
4675
|
+
};
|
|
4676
|
+
} catch (error) {
|
|
4677
|
+
if (isPanelAppError(error)) throw error;
|
|
4678
|
+
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4679
|
+
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4680
|
+
}
|
|
4681
|
+
};
|
|
4682
|
+
readSource = async (panelsPath, sourceName) => {
|
|
4683
|
+
const sourcePath = join(panelsPath, sourceName);
|
|
4684
|
+
const sourceStat = await stat(sourcePath);
|
|
4685
|
+
if (sourceStat.isFile() && isPanelAppFileName(sourceName)) return {
|
|
4686
|
+
kind: "single-file",
|
|
4687
|
+
sourceName,
|
|
4688
|
+
sourcePath,
|
|
4689
|
+
entryPath: sourcePath,
|
|
4690
|
+
sourceStat
|
|
4691
|
+
};
|
|
4692
|
+
if (sourceStat.isDirectory() && isPanelAppDirName(sourceName)) return await this.readFolderSource(sourcePath, sourceName, sourceStat);
|
|
4693
|
+
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4694
|
+
};
|
|
4695
|
+
readFolderSource = async (sourcePath, sourceName, sourceStat) => {
|
|
4696
|
+
try {
|
|
4697
|
+
const manifest = await readPanelAppFolderManifest(sourcePath, sourceName);
|
|
4698
|
+
const entryPath = resolvePanelAppRelativePath(sourcePath, manifest.entry);
|
|
4699
|
+
if (!(await stat(entryPath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app entry not found");
|
|
4700
|
+
return {
|
|
4701
|
+
kind: "folder",
|
|
4702
|
+
sourceName,
|
|
4703
|
+
sourcePath,
|
|
4704
|
+
entryPath,
|
|
4705
|
+
manifest,
|
|
4706
|
+
sourceStat
|
|
4707
|
+
};
|
|
4708
|
+
} catch (error) {
|
|
4709
|
+
if (isPanelAppError(error)) throw error;
|
|
4710
|
+
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4711
|
+
throw new PanelAppError("PANEL_APP_MANIFEST_INVALID", error instanceof Error ? error.message : String(error));
|
|
4712
|
+
}
|
|
4508
4713
|
};
|
|
4714
|
+
};
|
|
4715
|
+
function isMissingFileError$1(error) {
|
|
4716
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4509
4717
|
}
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4718
|
+
//#endregion
|
|
4719
|
+
//#region src/utils/panel-app-time.utils.ts
|
|
4720
|
+
function resolvePanelAppCreatedAt(fileStat) {
|
|
4721
|
+
return fileStat.birthtimeMs > 0 ? fileStat.birthtime.toISOString() : fileStat.mtime.toISOString();
|
|
4514
4722
|
}
|
|
4515
|
-
function
|
|
4516
|
-
|
|
4517
|
-
if (event.type === NcpEventType.RunError) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error ?? "agent object request failed");
|
|
4518
|
-
if (event.type === NcpEventType.RunFinished) throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
|
|
4723
|
+
function resolvePanelAppActivityMs(entry) {
|
|
4724
|
+
return Math.max(new Date(entry.lastOpenedAt ?? 0).getTime(), new Date(entry.createdAt).getTime(), new Date(entry.updatedAt).getTime());
|
|
4519
4725
|
}
|
|
4520
|
-
|
|
4726
|
+
//#endregion
|
|
4727
|
+
//#region src/utils/panel-app-content-source.utils.ts
|
|
4728
|
+
async function readPanelAppContentSource(params) {
|
|
4729
|
+
const { createAssetBaseHref, id, panelsPath, sourceService } = params;
|
|
4730
|
+
const source = await sourceService.resolveSource(panelsPath, id);
|
|
4731
|
+
const html = await readFile(source.entryPath, "utf8");
|
|
4732
|
+
const manifest = source.manifest ?? parsePanelAppManifest(html);
|
|
4733
|
+
const sourceId = encodePanelAppId(source.sourceName);
|
|
4734
|
+
return {
|
|
4735
|
+
appId: resolvePanelAppAppId(source, manifest),
|
|
4736
|
+
sourceId,
|
|
4737
|
+
source,
|
|
4738
|
+
manifest,
|
|
4739
|
+
html,
|
|
4740
|
+
htmlWithBase: source.kind === "folder" ? injectPanelAppAssetBase(html, createAssetBaseHref(source)) : html
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
async function readPanelAppContentSourceByIdOrAppId(params) {
|
|
4744
|
+
const { appIdOrSourceId, createAssetBaseHref, panelsPath, sourceService } = params;
|
|
4521
4745
|
try {
|
|
4522
|
-
return
|
|
4523
|
-
|
|
4524
|
-
|
|
4746
|
+
return await readPanelAppContentSource({
|
|
4747
|
+
createAssetBaseHref,
|
|
4748
|
+
id: appIdOrSourceId,
|
|
4749
|
+
panelsPath,
|
|
4750
|
+
sourceService
|
|
4751
|
+
});
|
|
4752
|
+
} catch (error) {
|
|
4753
|
+
if (!isPanelAppError(error)) throw error;
|
|
4754
|
+
if (error.code !== "PANEL_APP_INVALID_ID" && error.code !== "PANEL_APP_NOT_FOUND") throw error;
|
|
4755
|
+
}
|
|
4756
|
+
const sources = await sourceService.listSources(panelsPath);
|
|
4757
|
+
for (const source of sources) {
|
|
4758
|
+
const html = await readFile(source.entryPath, "utf8");
|
|
4759
|
+
if (resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(html)) === appIdOrSourceId) return await readPanelAppContentSource({
|
|
4760
|
+
createAssetBaseHref,
|
|
4761
|
+
id: encodePanelAppId(source.sourceName),
|
|
4762
|
+
panelsPath,
|
|
4763
|
+
sourceService
|
|
4764
|
+
});
|
|
4525
4765
|
}
|
|
4766
|
+
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4526
4767
|
}
|
|
4527
|
-
function
|
|
4528
|
-
return
|
|
4768
|
+
function resolvePanelAppAppId(source, manifest) {
|
|
4769
|
+
return manifest.id ?? encodePanelAppId(source.sourceName);
|
|
4770
|
+
}
|
|
4771
|
+
async function assertPanelAppDeclaresClient(params) {
|
|
4772
|
+
const { appId, panelsPath, sourceService } = params;
|
|
4773
|
+
const sources = await sourceService.listSources(panelsPath);
|
|
4774
|
+
for (const source of sources) {
|
|
4775
|
+
const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
|
|
4776
|
+
if (resolvePanelAppAppId(source, manifest) !== appId) continue;
|
|
4777
|
+
if (!manifest.client) throw new PanelAppError("PANEL_APP_CLIENT_NOT_DECLARED", "panel app did not declare client access");
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4529
4781
|
}
|
|
4530
4782
|
//#endregion
|
|
4531
4783
|
//#region src/managers/panel-app.manager.ts
|
|
@@ -4533,9 +4785,12 @@ const PANEL_APP_CONTENT_BASE_PATH = "/api/panel-apps";
|
|
|
4533
4785
|
const PANEL_APP_TOKENIZED_ASSET_BASE_PATH = "/api/panel-app-assets";
|
|
4534
4786
|
const PANEL_APP_CONTENT_TYPE = "text/html; charset=utf-8";
|
|
4535
4787
|
const PANEL_APP_CAPABILITY_GRANTS_FILE_NAME = ".panel-app-capability-grants.json";
|
|
4788
|
+
const PANEL_APP_CLIENT_GRANTS_FILE_NAME = ".panel-app-client-grants.json";
|
|
4789
|
+
const PANEL_APP_RUNTIME_TOKEN_TTL_MS = 1440 * 60 * 1e3;
|
|
4536
4790
|
var PanelAppManager = class {
|
|
4537
4791
|
bridgeSessions = /* @__PURE__ */ new Map();
|
|
4538
4792
|
agentRunClient;
|
|
4793
|
+
agentBridgeService;
|
|
4539
4794
|
assetTokenService = new PanelAppAssetTokenService();
|
|
4540
4795
|
sourceService = new PanelAppSourceService();
|
|
4541
4796
|
constructor(params) {
|
|
@@ -4544,6 +4799,10 @@ var PanelAppManager = class {
|
|
|
4544
4799
|
eventBus: params.eventBus,
|
|
4545
4800
|
ingress: params.ingress
|
|
4546
4801
|
}) : null);
|
|
4802
|
+
this.agentBridgeService = new PanelAppAgentBridgeService({
|
|
4803
|
+
agentRunClient: this.agentRunClient,
|
|
4804
|
+
createCapabilityGrantStore: this.createCapabilityGrantStore
|
|
4805
|
+
});
|
|
4547
4806
|
}
|
|
4548
4807
|
listPanelApps = async () => {
|
|
4549
4808
|
const workspacePath = this.getWorkspacePath();
|
|
@@ -4557,20 +4816,35 @@ var PanelAppManager = class {
|
|
|
4557
4816
|
};
|
|
4558
4817
|
};
|
|
4559
4818
|
getPanelAppContent = async (id) => {
|
|
4560
|
-
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
4561
4819
|
try {
|
|
4562
|
-
const
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4820
|
+
const resolved = await readPanelAppContentSource({
|
|
4821
|
+
createAssetBaseHref: this.createAssetBaseHref,
|
|
4822
|
+
id,
|
|
4823
|
+
panelsPath: this.getPanelsPath(this.getWorkspacePath()),
|
|
4824
|
+
sourceService: this.sourceService
|
|
4825
|
+
});
|
|
4826
|
+
const clientGranted = await this.isPanelAppClientGranted(resolved.appId, resolved.manifest.client);
|
|
4827
|
+
const session = this.createPanelAppRuntimeTokenSession({
|
|
4828
|
+
appId: resolved.appId,
|
|
4829
|
+
clientDeclared: resolved.manifest.client,
|
|
4830
|
+
declaredActions: resolved.manifest.serviceActions,
|
|
4831
|
+
declaredCapabilities: resolved.manifest.capabilities
|
|
4832
|
+
});
|
|
4833
|
+
const htmlWithBridge = injectPanelAppBridgeScript(resolved.htmlWithBase, {
|
|
4834
|
+
appId: resolved.appId,
|
|
4835
|
+
runtimeToken: session.token
|
|
4836
|
+
});
|
|
4837
|
+
const html = resolved.manifest.client && clientGranted ? injectPanelAppClientScript(htmlWithBridge, { runtimeToken: session.token }) : htmlWithBridge;
|
|
4567
4838
|
return {
|
|
4568
|
-
id: sourceId,
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4839
|
+
id: resolved.sourceId,
|
|
4840
|
+
appId: resolved.appId,
|
|
4841
|
+
fileName: resolved.source.sourceName,
|
|
4842
|
+
html,
|
|
4843
|
+
capabilities: resolved.manifest.capabilities,
|
|
4844
|
+
clientDeclared: resolved.manifest.client,
|
|
4845
|
+
clientGranted,
|
|
4572
4846
|
contentType: PANEL_APP_CONTENT_TYPE,
|
|
4573
|
-
serviceActions: manifest.serviceActions
|
|
4847
|
+
serviceActions: resolved.manifest.serviceActions
|
|
4574
4848
|
};
|
|
4575
4849
|
} catch (error) {
|
|
4576
4850
|
if (isPanelAppError(error)) throw error;
|
|
@@ -4588,27 +4862,58 @@ var PanelAppManager = class {
|
|
|
4588
4862
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
4589
4863
|
return await this.sourceService.getAsset(panelsPath, claims.panelAppId, assetPath);
|
|
4590
4864
|
};
|
|
4591
|
-
getPanelAppBridgeScript = () => getPanelAppBridgeScript(
|
|
4592
|
-
|
|
4593
|
-
|
|
4865
|
+
getPanelAppBridgeScript = () => getPanelAppBridgeScript({
|
|
4866
|
+
appId: "",
|
|
4867
|
+
runtimeToken: ""
|
|
4868
|
+
});
|
|
4869
|
+
createPanelAppRuntimeTokenSession = (params) => {
|
|
4870
|
+
const { appId, clientDeclared, declaredActions, declaredCapabilities } = params;
|
|
4594
4871
|
const now = /* @__PURE__ */ new Date();
|
|
4595
4872
|
const session = {
|
|
4596
4873
|
id: randomUUID(),
|
|
4597
4874
|
token: randomUUID(),
|
|
4598
|
-
|
|
4599
|
-
tabId: params.tabId,
|
|
4875
|
+
appId,
|
|
4600
4876
|
caller: {
|
|
4601
4877
|
surface: "panel-app",
|
|
4602
|
-
appId
|
|
4878
|
+
appId
|
|
4603
4879
|
},
|
|
4604
|
-
declaredCapabilities
|
|
4605
|
-
declaredActions
|
|
4880
|
+
declaredCapabilities,
|
|
4881
|
+
declaredActions,
|
|
4882
|
+
clientDeclared,
|
|
4606
4883
|
createdAt: now.toISOString(),
|
|
4607
|
-
expiresAt: new Date(now.getTime() +
|
|
4884
|
+
expiresAt: new Date(now.getTime() + PANEL_APP_RUNTIME_TOKEN_TTL_MS).toISOString()
|
|
4608
4885
|
};
|
|
4609
4886
|
this.bridgeSessions.set(session.token, session);
|
|
4610
4887
|
return session;
|
|
4611
4888
|
};
|
|
4889
|
+
createPanelAppBridgeSession = async (params) => {
|
|
4890
|
+
const resolved = await readPanelAppContentSourceByIdOrAppId({
|
|
4891
|
+
appIdOrSourceId: params.id,
|
|
4892
|
+
createAssetBaseHref: this.createAssetBaseHref,
|
|
4893
|
+
panelsPath: this.getPanelsPath(this.getWorkspacePath()),
|
|
4894
|
+
sourceService: this.sourceService
|
|
4895
|
+
});
|
|
4896
|
+
return this.createPanelAppRuntimeTokenSession({
|
|
4897
|
+
appId: resolved.appId,
|
|
4898
|
+
clientDeclared: resolved.manifest.client,
|
|
4899
|
+
declaredActions: resolved.manifest.serviceActions,
|
|
4900
|
+
declaredCapabilities: resolved.manifest.capabilities
|
|
4901
|
+
});
|
|
4902
|
+
};
|
|
4903
|
+
grantPanelAppClient = async (appId) => {
|
|
4904
|
+
await assertPanelAppDeclaresClient({
|
|
4905
|
+
appId,
|
|
4906
|
+
panelsPath: this.getPanelsPath(this.getWorkspacePath()),
|
|
4907
|
+
sourceService: this.sourceService
|
|
4908
|
+
});
|
|
4909
|
+
return await this.createClientGrantStore().grant({
|
|
4910
|
+
appId,
|
|
4911
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4912
|
+
});
|
|
4913
|
+
};
|
|
4914
|
+
revokePanelAppClient = async (appId) => {
|
|
4915
|
+
await this.createClientGrantStore().revoke(appId);
|
|
4916
|
+
};
|
|
4612
4917
|
resolvePanelAppBridgeSession = (token) => {
|
|
4613
4918
|
this.deleteExpiredBridgeSessions();
|
|
4614
4919
|
const session = this.bridgeSessions.get(token.trim());
|
|
@@ -4620,39 +4925,15 @@ var PanelAppManager = class {
|
|
|
4620
4925
|
};
|
|
4621
4926
|
sendAgentMessage = async (bridgeSessionToken, payload) => {
|
|
4622
4927
|
const bridgeSession = this.resolvePanelAppBridgeSession(bridgeSessionToken);
|
|
4623
|
-
await this.
|
|
4624
|
-
return await this.requireAgentRunClient().send(withPanelAppAgentMetadata(payload, bridgeSession));
|
|
4928
|
+
return await this.agentBridgeService.sendAgentMessage(bridgeSession, payload);
|
|
4625
4929
|
};
|
|
4626
4930
|
generateAgentObject = async (bridgeSessionToken, input) => {
|
|
4627
4931
|
const bridgeSession = this.resolvePanelAppBridgeSession(bridgeSessionToken);
|
|
4628
|
-
await this.
|
|
4629
|
-
const request = normalizePanelAppGenerateObjectInput(input);
|
|
4630
|
-
const message = createPanelAppGenerateObjectMessage({
|
|
4631
|
-
bridgeSession,
|
|
4632
|
-
request,
|
|
4633
|
-
requestId: randomUUID()
|
|
4634
|
-
});
|
|
4635
|
-
return { result: await waitForPanelAppStructuredResult(this.requireAgentRunClient(), {
|
|
4636
|
-
payload: {
|
|
4637
|
-
message,
|
|
4638
|
-
metadata: {
|
|
4639
|
-
...createPanelAppAgentMetadata(bridgeSession),
|
|
4640
|
-
panel_app_peer_id: request.peerId
|
|
4641
|
-
},
|
|
4642
|
-
peerId: request.peerId
|
|
4643
|
-
},
|
|
4644
|
-
timeoutMs: request.timeoutMs
|
|
4645
|
-
}) };
|
|
4932
|
+
return await this.agentBridgeService.generateAgentObject(bridgeSession, input);
|
|
4646
4933
|
};
|
|
4647
4934
|
grantAgentCapability = async (bridgeSessionToken, capability) => {
|
|
4648
4935
|
const bridgeSession = this.resolvePanelAppBridgeSession(bridgeSessionToken);
|
|
4649
|
-
|
|
4650
|
-
this.assertDeclaredCapability(bridgeSession, capability);
|
|
4651
|
-
return await this.createCapabilityGrantStore().grant({
|
|
4652
|
-
caller: bridgeSession.caller,
|
|
4653
|
-
capability,
|
|
4654
|
-
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4655
|
-
});
|
|
4936
|
+
return await this.agentBridgeService.grantAgentCapability(bridgeSession, capability);
|
|
4656
4937
|
};
|
|
4657
4938
|
updatePanelAppPreferences = async (id, preferences) => {
|
|
4658
4939
|
const fileName = await this.resolvePanelAppFileName(id);
|
|
@@ -4670,42 +4951,21 @@ var PanelAppManager = class {
|
|
|
4670
4951
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
4671
4952
|
const source = await this.sourceService.resolveSource(panelsPath, id);
|
|
4672
4953
|
const panelAppId = encodePanelAppId(source.sourceName);
|
|
4954
|
+
const appId = resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8")));
|
|
4673
4955
|
await rm(source.sourcePath, { recursive: source.kind === "folder" });
|
|
4674
4956
|
await this.createStateStore(panelsPath).deleteEntry(panelAppId);
|
|
4675
4957
|
await this.createCapabilityGrantStore().deleteCaller({
|
|
4676
4958
|
surface: "panel-app",
|
|
4677
|
-
appId
|
|
4959
|
+
appId
|
|
4678
4960
|
});
|
|
4679
|
-
this.
|
|
4961
|
+
await this.createClientGrantStore().revoke(appId);
|
|
4962
|
+
this.deleteBridgeSessionsByPanelAppId(appId);
|
|
4680
4963
|
return {
|
|
4681
4964
|
deleted: true,
|
|
4682
4965
|
fileName: source.sourceName,
|
|
4683
4966
|
id: panelAppId
|
|
4684
4967
|
};
|
|
4685
4968
|
};
|
|
4686
|
-
assertAgentCapabilityGranted = async (bridgeSession, capability) => {
|
|
4687
|
-
this.assertDeclaredCapability(bridgeSession, capability);
|
|
4688
|
-
if (!await this.createCapabilityGrantStore().isGranted(bridgeSession.caller, capability)) throw new PanelAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to use ${capability}.`);
|
|
4689
|
-
};
|
|
4690
|
-
assertDeclaredCapability = (bridgeSession, capability) => {
|
|
4691
|
-
if (!bridgeSession.declaredCapabilities.includes(capability)) throw new PanelAppError("PANEL_APP_CAPABILITY_NOT_DECLARED", this.describeMissingAgentCapability(bridgeSession.declaredCapabilities, capability));
|
|
4692
|
-
};
|
|
4693
|
-
describeMissingAgentCapability = (declaredCapabilities, capability) => {
|
|
4694
|
-
const declared = declaredCapabilities.length > 0 ? declaredCapabilities.join(", ") : "none";
|
|
4695
|
-
const valid = PANEL_APP_AGENT_CAPABILITIES.join(", ");
|
|
4696
|
-
const hint = declaredCapabilities.includes(capability.replace(":", ".")) ? ` Use ${capability}, not ${capability.replace(":", ".")}.` : "";
|
|
4697
|
-
return [
|
|
4698
|
-
`panel app did not declare ${capability}.`,
|
|
4699
|
-
`Declared: ${declared}.`,
|
|
4700
|
-
`Valid capabilities: ${valid}.`,
|
|
4701
|
-
`Declare it with nextclaw-panel-capabilities or panel-app.json capabilities.`,
|
|
4702
|
-
hint.trim()
|
|
4703
|
-
].filter(Boolean).join(" ");
|
|
4704
|
-
};
|
|
4705
|
-
requireAgentRunClient = () => {
|
|
4706
|
-
if (!this.agentRunClient) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "panel app agent client is not configured");
|
|
4707
|
-
return this.agentRunClient;
|
|
4708
|
-
};
|
|
4709
4969
|
getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
|
|
4710
4970
|
getPanelsPath = (workspacePath) => join(workspacePath, DEFAULT_PANELS_DIR);
|
|
4711
4971
|
createAssetBaseHref = (source) => {
|
|
@@ -4717,13 +4977,16 @@ var PanelAppManager = class {
|
|
|
4717
4977
|
};
|
|
4718
4978
|
createStateStore = (panelsPath) => new PanelAppStateStore(panelsPath);
|
|
4719
4979
|
createCapabilityGrantStore = () => new PanelAppCapabilityGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CAPABILITY_GRANTS_FILE_NAME));
|
|
4980
|
+
createClientGrantStore = () => new PanelAppClientGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CLIENT_GRANTS_FILE_NAME));
|
|
4720
4981
|
buildPanelAppEntry = async (source, state) => {
|
|
4721
4982
|
const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
|
|
4722
4983
|
const id = encodePanelAppId(source.sourceName);
|
|
4984
|
+
const appId = resolvePanelAppAppId(source, manifest);
|
|
4723
4985
|
const createdAt = resolvePanelAppCreatedAt(source.sourceStat);
|
|
4724
4986
|
const updatedAt = source.sourceStat.mtime.toISOString();
|
|
4725
4987
|
const entry = {
|
|
4726
4988
|
id,
|
|
4989
|
+
appId,
|
|
4727
4990
|
fileName: source.sourceName,
|
|
4728
4991
|
kind: source.kind,
|
|
4729
4992
|
title: manifest.title ?? toPanelAppTitle(source.sourceName),
|
|
@@ -4732,6 +4995,8 @@ var PanelAppManager = class {
|
|
|
4732
4995
|
updatedAt,
|
|
4733
4996
|
sizeBytes: source.sourceStat.size,
|
|
4734
4997
|
favorite: state.favorite ?? false,
|
|
4998
|
+
clientDeclared: manifest.client,
|
|
4999
|
+
clientGranted: await this.isPanelAppClientGranted(appId, manifest.client),
|
|
4735
5000
|
openCount: state.openCount ?? 0
|
|
4736
5001
|
};
|
|
4737
5002
|
if (manifest.description) entry.description = manifest.description;
|
|
@@ -4748,7 +5013,11 @@ var PanelAppManager = class {
|
|
|
4748
5013
|
for (const [token, session] of this.bridgeSessions) if (new Date(session.expiresAt).getTime() <= now) this.bridgeSessions.delete(token);
|
|
4749
5014
|
};
|
|
4750
5015
|
deleteBridgeSessionsByPanelAppId = (panelAppId) => {
|
|
4751
|
-
for (const [token, session] of this.bridgeSessions) if (session.
|
|
5016
|
+
for (const [token, session] of this.bridgeSessions) if (session.appId === panelAppId) this.bridgeSessions.delete(token);
|
|
5017
|
+
};
|
|
5018
|
+
isPanelAppClientGranted = async (appId, clientDeclared) => {
|
|
5019
|
+
if (!clientDeclared) return false;
|
|
5020
|
+
return await this.createClientGrantStore().isGranted(appId);
|
|
4752
5021
|
};
|
|
4753
5022
|
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4754
5023
|
};
|
|
@@ -4860,7 +5129,7 @@ var McpServiceAppRuntimeService = class {
|
|
|
4860
5129
|
command: manifest.command,
|
|
4861
5130
|
args: manifest.args,
|
|
4862
5131
|
cwd: app.dirPath,
|
|
4863
|
-
env:
|
|
5132
|
+
env: createRuntimeChildEnv(process.env),
|
|
4864
5133
|
stderr: "pipe"
|
|
4865
5134
|
},
|
|
4866
5135
|
scope: {
|
|
@@ -5139,7 +5408,7 @@ var ServiceAppManager = class {
|
|
|
5139
5408
|
return record;
|
|
5140
5409
|
};
|
|
5141
5410
|
listServiceActions = async (params = {}) => {
|
|
5142
|
-
const actions = (params.appId ? [await this.requireServiceApp(params.appId)] : await this.listValidServiceApps()).flatMap(({ manifest, record }) =>
|
|
5411
|
+
const actions = (params.appId ? [await this.requireServiceApp(params.appId)] : await this.listValidServiceApps()).flatMap(({ manifest, record }) => listServiceAppManifestActions(record, manifest));
|
|
5143
5412
|
return await Promise.all(actions.map(async (action) => await this.withGrantState(action, params)));
|
|
5144
5413
|
};
|
|
5145
5414
|
discoverServiceAppActions = async (appId) => {
|
|
@@ -5234,7 +5503,7 @@ var ServiceAppManager = class {
|
|
|
5234
5503
|
};
|
|
5235
5504
|
requireServiceAction = async (actionId) => {
|
|
5236
5505
|
const { manifest, record } = await this.requireServiceAppForAction(actionId);
|
|
5237
|
-
const action =
|
|
5506
|
+
const action = listServiceAppManifestActions(record, manifest).find((entry) => entry.id === actionId);
|
|
5238
5507
|
if (!action) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_FOUND", "service action not found");
|
|
5239
5508
|
return action;
|
|
5240
5509
|
};
|
|
@@ -5243,7 +5512,6 @@ var ServiceAppManager = class {
|
|
|
5243
5512
|
if (!appId) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
|
|
5244
5513
|
return await this.requireServiceApp(appId);
|
|
5245
5514
|
};
|
|
5246
|
-
listManifestActions = (record, manifest) => listServiceAppManifestActions(record, manifest);
|
|
5247
5515
|
requireServiceApp = async (appId) => {
|
|
5248
5516
|
const dirPath = join(this.getServiceAppsPath(this.getWorkspacePath()), appId);
|
|
5249
5517
|
try {
|
|
@@ -5299,9 +5567,10 @@ var ServiceAppManager = class {
|
|
|
5299
5567
|
};
|
|
5300
5568
|
toServiceAppRecord = (dirPath, manifest) => {
|
|
5301
5569
|
const runtimeStatus = this.runtimeService.getStatus(manifest.id);
|
|
5302
|
-
|
|
5570
|
+
return {
|
|
5303
5571
|
id: manifest.id,
|
|
5304
5572
|
title: manifest.title,
|
|
5573
|
+
description: manifest.description,
|
|
5305
5574
|
dirPath,
|
|
5306
5575
|
manifestPath: getServiceAppManifestPath(dirPath),
|
|
5307
5576
|
command: manifest.command,
|
|
@@ -5309,14 +5578,12 @@ var ServiceAppManager = class {
|
|
|
5309
5578
|
cwd: dirPath,
|
|
5310
5579
|
enabled: manifest.enabled,
|
|
5311
5580
|
protocol: manifest.protocol,
|
|
5312
|
-
status: manifest.enabled ? runtimeStatus.status : "stopped"
|
|
5581
|
+
status: manifest.enabled ? runtimeStatus.status : "stopped",
|
|
5582
|
+
lastError: runtimeStatus.lastError,
|
|
5583
|
+
lastStartedAt: runtimeStatus.lastStartedAt,
|
|
5584
|
+
lastReadyAt: runtimeStatus.lastReadyAt,
|
|
5585
|
+
lastFailedAt: runtimeStatus.lastFailedAt
|
|
5313
5586
|
};
|
|
5314
|
-
if (manifest.description) record.description = manifest.description;
|
|
5315
|
-
if (runtimeStatus.lastError) record.lastError = runtimeStatus.lastError;
|
|
5316
|
-
if (runtimeStatus.lastStartedAt) record.lastStartedAt = runtimeStatus.lastStartedAt;
|
|
5317
|
-
if (runtimeStatus.lastReadyAt) record.lastReadyAt = runtimeStatus.lastReadyAt;
|
|
5318
|
-
if (runtimeStatus.lastFailedAt) record.lastFailedAt = runtimeStatus.lastFailedAt;
|
|
5319
|
-
return record;
|
|
5320
5587
|
};
|
|
5321
5588
|
assertCaller = (caller) => {
|
|
5322
5589
|
if (caller.surface !== "panel-app" || !caller.appId.trim()) throw new ServiceAppError("SERVICE_APP_INVALID_CALLER", "service action caller is invalid");
|
|
@@ -5336,7 +5603,7 @@ var ServiceAppManager = class {
|
|
|
5336
5603
|
throw new ServiceAppError("SERVICE_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
5337
5604
|
}
|
|
5338
5605
|
};
|
|
5339
|
-
isMissingFileError = (error) =>
|
|
5606
|
+
isMissingFileError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
5340
5607
|
};
|
|
5341
5608
|
function toTitle(value) {
|
|
5342
5609
|
return basename(value).replace(/[-_]+/g, " ").trim() || value;
|
|
@@ -5351,7 +5618,6 @@ var MessageInbox = class {
|
|
|
5351
5618
|
drain = () => {
|
|
5352
5619
|
return this.messages.splice(0, this.messages.length);
|
|
5353
5620
|
};
|
|
5354
|
-
isEmpty = () => this.messages.length === 0;
|
|
5355
5621
|
};
|
|
5356
5622
|
function isConversationStateEvent(event) {
|
|
5357
5623
|
return event.type !== NcpEventType.ContextWindowUpdated;
|
|
@@ -5359,10 +5625,10 @@ function isConversationStateEvent(event) {
|
|
|
5359
5625
|
var SessionRun = class {
|
|
5360
5626
|
inbox = new MessageInbox();
|
|
5361
5627
|
sessionId;
|
|
5628
|
+
statusListeners = /* @__PURE__ */ new Set();
|
|
5362
5629
|
activeRunId = null;
|
|
5363
5630
|
activeRunController = null;
|
|
5364
|
-
constructor(seed,
|
|
5365
|
-
this.eventBus = eventBus;
|
|
5631
|
+
constructor(seed, stateManager = new DefaultNcpAgentConversationStateManager()) {
|
|
5366
5632
|
this.stateManager = stateManager;
|
|
5367
5633
|
this.sessionId = seed.sessionId;
|
|
5368
5634
|
this.stateManager.hydrate({
|
|
@@ -5379,19 +5645,20 @@ var SessionRun = class {
|
|
|
5379
5645
|
const conversationEvents = events.filter(isConversationStateEvent);
|
|
5380
5646
|
if (conversationEvents.length > 0) await this.stateManager.dispatchBatch(conversationEvents);
|
|
5381
5647
|
};
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
});
|
|
5648
|
+
onStatusChange = (listener) => {
|
|
5649
|
+
this.statusListeners.add(listener);
|
|
5650
|
+
return () => {
|
|
5651
|
+
this.statusListeners.delete(listener);
|
|
5652
|
+
};
|
|
5388
5653
|
};
|
|
5389
5654
|
beginRun = () => {
|
|
5390
5655
|
if (this.activeRunId) throw new Error(`Session ${this.sessionId} already has an active run.`);
|
|
5656
|
+
const wasRunning = this.isRunning();
|
|
5391
5657
|
const runId = `agent-run-${randomUUID()}`;
|
|
5392
5658
|
const controller = new AbortController();
|
|
5393
5659
|
this.activeRunId = runId;
|
|
5394
5660
|
this.activeRunController = controller;
|
|
5661
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5395
5662
|
return {
|
|
5396
5663
|
runId,
|
|
5397
5664
|
signal: controller.signal
|
|
@@ -5400,30 +5667,43 @@ var SessionRun = class {
|
|
|
5400
5667
|
abortRun = (runId) => {
|
|
5401
5668
|
if (!this.activeRunId || !this.activeRunController) return false;
|
|
5402
5669
|
if (runId && this.activeRunId !== runId) return false;
|
|
5670
|
+
const wasRunning = this.isRunning();
|
|
5403
5671
|
this.activeRunController.abort();
|
|
5672
|
+
this.activeRunController = null;
|
|
5673
|
+
this.activeRunId = null;
|
|
5674
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5404
5675
|
return true;
|
|
5405
5676
|
};
|
|
5406
5677
|
isRunning = () => this.activeRunId !== null;
|
|
5407
5678
|
dispose = () => {
|
|
5679
|
+
const wasRunning = this.isRunning();
|
|
5408
5680
|
this.activeRunController?.abort();
|
|
5409
5681
|
this.activeRunController = null;
|
|
5410
5682
|
this.activeRunId = null;
|
|
5683
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5684
|
+
this.statusListeners.clear();
|
|
5411
5685
|
};
|
|
5412
5686
|
applyRunEvents = (events) => {
|
|
5687
|
+
const wasRunning = this.isRunning();
|
|
5413
5688
|
for (const event of events) {
|
|
5414
5689
|
if (event.type === NcpEventType.RunStarted && event.payload.runId) this.activeRunId = event.payload.runId;
|
|
5415
|
-
if (
|
|
5690
|
+
if ((event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError) && (!event.payload.runId || event.payload.runId === this.activeRunId)) {
|
|
5416
5691
|
this.activeRunId = null;
|
|
5417
5692
|
this.activeRunController = null;
|
|
5418
5693
|
}
|
|
5419
5694
|
}
|
|
5695
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5696
|
+
};
|
|
5697
|
+
emitStatusChangeIfNeeded = (wasRunning) => {
|
|
5698
|
+
const running = this.isRunning();
|
|
5699
|
+
if (running === wasRunning) return;
|
|
5700
|
+
for (const listener of [...this.statusListeners]) listener(running ? "running" : "idle");
|
|
5420
5701
|
};
|
|
5421
5702
|
};
|
|
5422
5703
|
var SessionRunManager = class {
|
|
5423
5704
|
runs = /* @__PURE__ */ new Map();
|
|
5424
|
-
constructor(sessionManager
|
|
5705
|
+
constructor(sessionManager) {
|
|
5425
5706
|
this.sessionManager = sessionManager;
|
|
5426
|
-
this.eventBus = eventBus;
|
|
5427
5707
|
}
|
|
5428
5708
|
getSessionRun = (sessionId) => this.runs.get(sessionId) ?? null;
|
|
5429
5709
|
isSessionRunning = (sessionId) => this.runs.get(sessionId.trim())?.isRunning() ?? false;
|
|
@@ -5432,7 +5712,7 @@ var SessionRunManager = class {
|
|
|
5432
5712
|
const run = new SessionRun({
|
|
5433
5713
|
messages: await this.sessionManager.listSessionMessages(sessionId),
|
|
5434
5714
|
sessionId
|
|
5435
|
-
}
|
|
5715
|
+
});
|
|
5436
5716
|
this.runs.set(sessionId, run);
|
|
5437
5717
|
return run;
|
|
5438
5718
|
};
|
|
@@ -5684,7 +5964,7 @@ var NcpAgentSessionMetadataStore = class {
|
|
|
5684
5964
|
read = async (sessionId, activitySnapshot) => {
|
|
5685
5965
|
try {
|
|
5686
5966
|
const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
|
|
5687
|
-
if (!isRecord$
|
|
5967
|
+
if (!isRecord$10(parsed) || parsed._type !== "metadata" || !isRecord$10(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
|
|
5688
5968
|
const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
|
|
5689
5969
|
const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
5690
5970
|
return {
|
|
@@ -5785,7 +6065,7 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
5785
6065
|
function serializeJournalEntry(entry) {
|
|
5786
6066
|
const serialized = JSON.stringify(entry);
|
|
5787
6067
|
if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
|
|
5788
|
-
if (!isRecord$
|
|
6068
|
+
if (!isRecord$10(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
|
|
5789
6069
|
return serialized;
|
|
5790
6070
|
}
|
|
5791
6071
|
var NcpAgentSessionJournalStore = class {
|
|
@@ -6001,15 +6281,15 @@ var NcpAgentSessionJournalStore = class {
|
|
|
6001
6281
|
console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
6002
6282
|
continue;
|
|
6003
6283
|
}
|
|
6004
|
-
if (!isRecord$
|
|
6284
|
+
if (!isRecord$10(parsed)) continue;
|
|
6005
6285
|
if (parsed._type === "metadata") {
|
|
6006
|
-
metadata = isRecord$
|
|
6286
|
+
metadata = isRecord$10(parsed.metadata) ? structuredClone(parsed.metadata) : {};
|
|
6007
6287
|
agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
6008
6288
|
createdAt = toIsoString(parsed.created_at, createdAt);
|
|
6009
6289
|
updatedAt = toIsoString(parsed.updated_at, updatedAt);
|
|
6010
6290
|
continue;
|
|
6011
6291
|
}
|
|
6012
|
-
if (parsed._type === "event" && isRecord$
|
|
6292
|
+
if (parsed._type === "event" && isRecord$10(parsed.event)) {
|
|
6013
6293
|
const seq = Number(parsed.seq);
|
|
6014
6294
|
nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
|
|
6015
6295
|
updatedAt = toIsoString(parsed.timestamp, updatedAt);
|
|
@@ -7785,11 +8065,19 @@ function formatErrorStatus(error) {
|
|
|
7785
8065
|
}
|
|
7786
8066
|
return "运行出错";
|
|
7787
8067
|
}
|
|
7788
|
-
function
|
|
8068
|
+
function readToolCallId(value) {
|
|
8069
|
+
if (typeof value !== "string") return null;
|
|
8070
|
+
const trimmed = value.trim();
|
|
8071
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
8072
|
+
}
|
|
8073
|
+
function formatToolDoneStatus(toolName) {
|
|
8074
|
+
return toolName ? `工具调用完成:${toolName}` : "工具调用完成";
|
|
8075
|
+
}
|
|
8076
|
+
function createSessionActivityPreviewFromNcpEvent(event, timestamp, options = {}) {
|
|
7789
8077
|
switch (event.type) {
|
|
7790
8078
|
case NcpEventType.RunStarted: return createProjection(readSessionId(event.payload.sessionId), {
|
|
7791
8079
|
state: "running",
|
|
7792
|
-
statusText: "
|
|
8080
|
+
statusText: "正在思考",
|
|
7793
8081
|
timestamp
|
|
7794
8082
|
});
|
|
7795
8083
|
case NcpEventType.RunFinished: return createProjection(readSessionId(event.payload.sessionId), {
|
|
@@ -7830,11 +8118,15 @@ function createSessionActivityPreviewFromNcpEvent(event, timestamp) {
|
|
|
7830
8118
|
timestamp
|
|
7831
8119
|
});
|
|
7832
8120
|
case NcpEventType.MessageToolCallEnd:
|
|
7833
|
-
case NcpEventType.MessageToolCallResult:
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
8121
|
+
case NcpEventType.MessageToolCallResult: {
|
|
8122
|
+
const sessionId = readSessionId(event.payload.sessionId);
|
|
8123
|
+
const toolCallId = readToolCallId(event.payload.toolCallId);
|
|
8124
|
+
return createProjection(sessionId, {
|
|
8125
|
+
state: "running",
|
|
8126
|
+
statusText: formatToolDoneStatus(sessionId && toolCallId ? options.readToolName?.(sessionId, toolCallId) ?? null : null),
|
|
8127
|
+
timestamp
|
|
8128
|
+
});
|
|
8129
|
+
}
|
|
7838
8130
|
default: return null;
|
|
7839
8131
|
}
|
|
7840
8132
|
}
|
|
@@ -7902,13 +8194,16 @@ function writeSessionActivityPreviewMetadata(metadata, projection) {
|
|
|
7902
8194
|
var SessionActivityPreviewContribution = class {
|
|
7903
8195
|
unsubscribeNcpEvent = null;
|
|
7904
8196
|
metadataWriteChains = /* @__PURE__ */ new Map();
|
|
8197
|
+
toolNames = /* @__PURE__ */ new Map();
|
|
7905
8198
|
constructor(kernel) {
|
|
7906
8199
|
this.kernel = kernel;
|
|
7907
8200
|
}
|
|
7908
8201
|
start = () => {
|
|
7909
8202
|
if (this.unsubscribeNcpEvent) return;
|
|
7910
8203
|
this.unsubscribeNcpEvent = this.kernel.eventBus.on(eventKeys.ncpEvent, (event) => {
|
|
7911
|
-
|
|
8204
|
+
this.rememberToolName(event);
|
|
8205
|
+
const projection = createSessionActivityPreviewFromNcpEvent(event, (/* @__PURE__ */ new Date()).toISOString(), { readToolName: this.readToolName });
|
|
8206
|
+
this.clearFinishedRunToolNames(event);
|
|
7912
8207
|
if (!projection) return;
|
|
7913
8208
|
this.updatePreview(projection);
|
|
7914
8209
|
});
|
|
@@ -7917,7 +8212,29 @@ var SessionActivityPreviewContribution = class {
|
|
|
7917
8212
|
this.unsubscribeNcpEvent?.();
|
|
7918
8213
|
this.unsubscribeNcpEvent = null;
|
|
7919
8214
|
this.metadataWriteChains.clear();
|
|
8215
|
+
this.toolNames.clear();
|
|
8216
|
+
};
|
|
8217
|
+
rememberToolName = (event) => {
|
|
8218
|
+
if (event.type !== NcpEventType.MessageToolCallStart) return;
|
|
8219
|
+
const sessionId = event.payload.sessionId.trim();
|
|
8220
|
+
const toolCallId = event.payload.toolCallId.trim();
|
|
8221
|
+
const toolName = event.payload.toolName.trim();
|
|
8222
|
+
if (!sessionId || !toolCallId || !toolName) return;
|
|
8223
|
+
this.toolNames.set(this.createToolNameKey(sessionId, toolCallId), toolName);
|
|
8224
|
+
};
|
|
8225
|
+
readToolName = (sessionId, toolCallId) => this.toolNames.get(this.createToolNameKey(sessionId, toolCallId)) ?? null;
|
|
8226
|
+
clearFinishedRunToolNames = (event) => {
|
|
8227
|
+
if (event.type !== NcpEventType.RunFinished && event.type !== NcpEventType.RunError) return;
|
|
8228
|
+
const sessionId = this.readNonEmptyString(event.payload.sessionId);
|
|
8229
|
+
if (!sessionId) return;
|
|
8230
|
+
for (const key of this.toolNames.keys()) if (key.startsWith(`${sessionId}:`)) this.toolNames.delete(key);
|
|
8231
|
+
};
|
|
8232
|
+
readNonEmptyString = (value) => {
|
|
8233
|
+
if (typeof value !== "string") return null;
|
|
8234
|
+
const trimmed = value.trim();
|
|
8235
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
7920
8236
|
};
|
|
8237
|
+
createToolNameKey = (sessionId, toolCallId) => `${sessionId}:${toolCallId}`;
|
|
7921
8238
|
updatePreview = (projection) => {
|
|
7922
8239
|
const next = (this.metadataWriteChains.get(projection.sessionId) ?? Promise.resolve()).then(() => this.updatePreviewMetadata(projection));
|
|
7923
8240
|
this.metadataWriteChains.set(projection.sessionId, next.catch(() => void 0));
|
|
@@ -8630,7 +8947,7 @@ var NextclawKernel = class {
|
|
|
8630
8947
|
})
|
|
8631
8948
|
});
|
|
8632
8949
|
this.contextCompactionManager = new AgentRunContextCompactionManager(this.configManager, this.llmProviders, this.sessionManager);
|
|
8633
|
-
this.sessionRunManager = new SessionRunManager(this.sessionManager
|
|
8950
|
+
this.sessionRunManager = new SessionRunManager(this.sessionManager);
|
|
8634
8951
|
this.agentRunRequestManager = new AgentRunRequestManager(this.agentRuntimeManager, this.configManager, this.contextProviderManager, this.eventBus, this.ingress, this.sessionManager, this.sessionRunManager, this.toolProviderManager);
|
|
8635
8952
|
this.contributions = [
|
|
8636
8953
|
new ToolProviderContribution(this),
|