@nextclaw/kernel 0.3.2 → 0.3.4-beta.0
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 +44 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +847 -519
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -627,16 +627,6 @@ function readOptionalString$9(value) {
|
|
|
627
627
|
if (typeof value !== "string") return;
|
|
628
628
|
return value.trim() || void 0;
|
|
629
629
|
}
|
|
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
630
|
function toRunHandle(accepted) {
|
|
641
631
|
return {
|
|
642
632
|
sessionId: accepted.sessionId,
|
|
@@ -684,7 +674,14 @@ var AgentRunRequestManager = class {
|
|
|
684
674
|
};
|
|
685
675
|
handleSessionMessageRequest = async (envelope) => {
|
|
686
676
|
if (!envelope.payload) throw new Error("Invalid agent run session message request.");
|
|
687
|
-
return toRunHandle(await this.send(
|
|
677
|
+
return toRunHandle(await this.send({
|
|
678
|
+
sessionId: envelope.payload.sessionId,
|
|
679
|
+
message: {
|
|
680
|
+
...envelope.payload.message,
|
|
681
|
+
sessionId: envelope.payload.sessionId
|
|
682
|
+
},
|
|
683
|
+
correlationId: envelope.payload.requestId
|
|
684
|
+
}));
|
|
688
685
|
};
|
|
689
686
|
send = async (request) => {
|
|
690
687
|
const session = await this.sessionManager.getOrCreateAgentRunSession({
|
|
@@ -710,7 +707,26 @@ var AgentRunRequestManager = class {
|
|
|
710
707
|
message
|
|
711
708
|
};
|
|
712
709
|
sessionRun.inbox.enqueue(message);
|
|
713
|
-
|
|
710
|
+
let stopPublishingRunStatus = () => void 0;
|
|
711
|
+
stopPublishingRunStatus = sessionRun.onStatusChange((status) => {
|
|
712
|
+
this.eventBus.emit(eventKeys.sessionRunStatus, {
|
|
713
|
+
sessionKey: sessionRun.sessionId,
|
|
714
|
+
status
|
|
715
|
+
}, {
|
|
716
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
717
|
+
source: "agent-run-request"
|
|
718
|
+
});
|
|
719
|
+
if (status === "idle") stopPublishingRunStatus();
|
|
720
|
+
});
|
|
721
|
+
this.cleanups.push(stopPublishingRunStatus);
|
|
722
|
+
const activeRun = (() => {
|
|
723
|
+
try {
|
|
724
|
+
return sessionRun.beginRun();
|
|
725
|
+
} catch (error) {
|
|
726
|
+
stopPublishingRunStatus();
|
|
727
|
+
throw error;
|
|
728
|
+
}
|
|
729
|
+
})();
|
|
714
730
|
const model = request.model ?? session.model ?? this.configManager.getDefaultModel();
|
|
715
731
|
const agentId = request.agentId ?? session.agentId ?? resolveDefaultAgentProfileId(this.configManager.loadConfig());
|
|
716
732
|
const spec = {
|
|
@@ -2511,11 +2527,11 @@ var ExtensionManager = class {
|
|
|
2511
2527
|
//#endregion
|
|
2512
2528
|
//#region src/utils/model-message-vision.utils.ts
|
|
2513
2529
|
const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
|
|
2514
|
-
function isRecord$
|
|
2530
|
+
function isRecord$11(value) {
|
|
2515
2531
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2516
2532
|
}
|
|
2517
2533
|
function isImageContentPart(value) {
|
|
2518
|
-
if (!isRecord$
|
|
2534
|
+
if (!isRecord$11(value)) return false;
|
|
2519
2535
|
const type = value.type;
|
|
2520
2536
|
return type === "image_url" || type === "input_image";
|
|
2521
2537
|
}
|
|
@@ -2531,7 +2547,7 @@ function normalizeContentWithoutVision(content) {
|
|
|
2531
2547
|
};
|
|
2532
2548
|
});
|
|
2533
2549
|
if (!sawImage) return content;
|
|
2534
|
-
const textParts = parts.filter((part) => isRecord$
|
|
2550
|
+
const textParts = parts.filter((part) => isRecord$11(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
|
|
2535
2551
|
if (textParts.length === parts.length) return textParts.join("\n\n");
|
|
2536
2552
|
return parts;
|
|
2537
2553
|
}
|
|
@@ -3070,7 +3086,7 @@ function safeNcpSessionFilename(value) {
|
|
|
3070
3086
|
function normalizeNcpAgentId(agentId) {
|
|
3071
3087
|
return agentId?.trim().toLowerCase() || void 0;
|
|
3072
3088
|
}
|
|
3073
|
-
function isRecord$
|
|
3089
|
+
function isRecord$10(value) {
|
|
3074
3090
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3075
3091
|
}
|
|
3076
3092
|
function toIsoString(value, fallback) {
|
|
@@ -3690,7 +3706,7 @@ var PanelAppStateStore = class {
|
|
|
3690
3706
|
};
|
|
3691
3707
|
//#endregion
|
|
3692
3708
|
//#region src/stores/panel-app-capability-grant.store.ts
|
|
3693
|
-
const EMPTY_GRANTS$
|
|
3709
|
+
const EMPTY_GRANTS$2 = {
|
|
3694
3710
|
version: 1,
|
|
3695
3711
|
grants: {}
|
|
3696
3712
|
};
|
|
@@ -3725,9 +3741,9 @@ var PanelAppCapabilityGrantStore = class {
|
|
|
3725
3741
|
};
|
|
3726
3742
|
load = async () => {
|
|
3727
3743
|
try {
|
|
3728
|
-
return normalizeStoreData$
|
|
3744
|
+
return normalizeStoreData$2(JSON.parse(await readFile(this.filePath, "utf8")));
|
|
3729
3745
|
} catch (error) {
|
|
3730
|
-
if (isMissingFileError$
|
|
3746
|
+
if (isMissingFileError$3(error)) return structuredClone(EMPTY_GRANTS$2);
|
|
3731
3747
|
throw error;
|
|
3732
3748
|
}
|
|
3733
3749
|
};
|
|
@@ -3736,13 +3752,13 @@ var PanelAppCapabilityGrantStore = class {
|
|
|
3736
3752
|
await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
3737
3753
|
};
|
|
3738
3754
|
};
|
|
3739
|
-
function normalizeStoreData$
|
|
3740
|
-
if (!isRecord$
|
|
3755
|
+
function normalizeStoreData$2(value) {
|
|
3756
|
+
if (!isRecord$9(value) || value.version !== 1 || !isRecord$9(value.grants)) return structuredClone(EMPTY_GRANTS$2);
|
|
3741
3757
|
const grants = {};
|
|
3742
3758
|
for (const [callerKey, callerValue] of Object.entries(value.grants)) {
|
|
3743
|
-
if (!isRecord$
|
|
3759
|
+
if (!isRecord$9(callerValue) || !isRecord$9(callerValue.capabilities)) continue;
|
|
3744
3760
|
const capabilities = {};
|
|
3745
|
-
for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$
|
|
3761
|
+
for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$9(grantValue) && typeof grantValue.grantedAt === "string") capabilities[capability] = { grantedAt: grantValue.grantedAt };
|
|
3746
3762
|
grants[callerKey] = { capabilities };
|
|
3747
3763
|
}
|
|
3748
3764
|
return {
|
|
@@ -3753,11 +3769,65 @@ function normalizeStoreData$1(value) {
|
|
|
3753
3769
|
function getCallerKey(caller) {
|
|
3754
3770
|
return `${caller.surface}:${caller.appId}`;
|
|
3755
3771
|
}
|
|
3772
|
+
function isRecord$9(value) {
|
|
3773
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3774
|
+
}
|
|
3775
|
+
function isMissingFileError$3(error) {
|
|
3776
|
+
return Boolean(error) && typeof error === "object" && error.code === "ENOENT";
|
|
3777
|
+
}
|
|
3778
|
+
//#endregion
|
|
3779
|
+
//#region src/stores/panel-app-client-grant.store.ts
|
|
3780
|
+
const EMPTY_GRANTS$1 = {
|
|
3781
|
+
grants: {},
|
|
3782
|
+
version: 1
|
|
3783
|
+
};
|
|
3784
|
+
var PanelAppClientGrantStore = class {
|
|
3785
|
+
constructor(filePath) {
|
|
3786
|
+
this.filePath = filePath;
|
|
3787
|
+
}
|
|
3788
|
+
isGranted = async (appId) => {
|
|
3789
|
+
const data = await this.load();
|
|
3790
|
+
return Boolean(data.grants[appId]);
|
|
3791
|
+
};
|
|
3792
|
+
grant = async (params) => {
|
|
3793
|
+
const data = await this.load();
|
|
3794
|
+
data.grants[params.appId] = { grantedAt: params.grantedAt };
|
|
3795
|
+
await this.save(data);
|
|
3796
|
+
return params;
|
|
3797
|
+
};
|
|
3798
|
+
revoke = async (appId) => {
|
|
3799
|
+
const data = await this.load();
|
|
3800
|
+
if (!data.grants[appId]) return;
|
|
3801
|
+
delete data.grants[appId];
|
|
3802
|
+
await this.save(data);
|
|
3803
|
+
};
|
|
3804
|
+
load = async () => {
|
|
3805
|
+
try {
|
|
3806
|
+
return normalizeStoreData$1(JSON.parse(await readFile(this.filePath, "utf8")));
|
|
3807
|
+
} catch (error) {
|
|
3808
|
+
if (isMissingFileError$2(error)) return structuredClone(EMPTY_GRANTS$1);
|
|
3809
|
+
throw error;
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
save = async (data) => {
|
|
3813
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
3814
|
+
await writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
3815
|
+
};
|
|
3816
|
+
};
|
|
3817
|
+
function normalizeStoreData$1(value) {
|
|
3818
|
+
if (!isRecord$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS$1);
|
|
3819
|
+
const grants = {};
|
|
3820
|
+
for (const [appId, grant] of Object.entries(value.grants)) if (isRecord$8(grant) && typeof grant.grantedAt === "string") grants[appId] = { grantedAt: grant.grantedAt };
|
|
3821
|
+
return {
|
|
3822
|
+
grants,
|
|
3823
|
+
version: 1
|
|
3824
|
+
};
|
|
3825
|
+
}
|
|
3756
3826
|
function isRecord$8(value) {
|
|
3757
3827
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3758
3828
|
}
|
|
3759
3829
|
function isMissingFileError$2(error) {
|
|
3760
|
-
return
|
|
3830
|
+
return isRecord$8(error) && error.code === "ENOENT";
|
|
3761
3831
|
}
|
|
3762
3832
|
//#endregion
|
|
3763
3833
|
//#region src/services/agent-run-client.service.ts
|
|
@@ -3953,162 +4023,460 @@ var AgentRunClient = class {
|
|
|
3953
4023
|
};
|
|
3954
4024
|
};
|
|
3955
4025
|
//#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)}`;
|
|
4026
|
+
//#region src/tools/structured-result.tools.ts
|
|
4027
|
+
const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
|
|
4028
|
+
var StructuredResultSubmitTool = class {
|
|
4029
|
+
name = STRUCTURED_RESULT_TOOL_NAME;
|
|
4030
|
+
description = "Submit the structured object result for this request.";
|
|
4031
|
+
constructor(contract) {
|
|
4032
|
+
this.contract = contract;
|
|
3965
4033
|
}
|
|
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)}`);
|
|
4034
|
+
get parameters() {
|
|
4035
|
+
return this.contract.schema;
|
|
4078
4036
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4037
|
+
validateArgs = (args) => validateToolArgs(args, this.contract.schema);
|
|
4038
|
+
execute = async (args) => args;
|
|
4039
|
+
};
|
|
4040
|
+
//#endregion
|
|
4041
|
+
//#region src/utils/panel-app-agent.utils.ts
|
|
4042
|
+
const PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS = 6e4;
|
|
4043
|
+
const PANEL_APP_AGENT_MAX_TIMEOUT_MS = 12e4;
|
|
4044
|
+
const PANEL_APP_AGENT_MAX_PROMPT_CHARS = 2e4;
|
|
4045
|
+
const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
|
|
4046
|
+
function normalizePanelAppGenerateObjectInput(input) {
|
|
4047
|
+
const peerId = input.peerId.trim();
|
|
4048
|
+
const prompt = input.prompt.trim();
|
|
4049
|
+
if (!peerId || !prompt || !isRecord$7(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
|
|
4050
|
+
if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
|
|
4051
|
+
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
4052
|
return {
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
serviceActions: readStringArray$1(parsed.actions, "actions")
|
|
4053
|
+
context: input.context,
|
|
4054
|
+
peerId,
|
|
4055
|
+
prompt,
|
|
4056
|
+
schema: input.schema,
|
|
4057
|
+
timeoutMs: normalizeTimeoutMs(input.timeoutMs),
|
|
4058
|
+
title: input.title?.trim() || void 0
|
|
4090
4059
|
};
|
|
4091
4060
|
}
|
|
4092
|
-
function
|
|
4061
|
+
function createPanelAppGenerateObjectMessage(params) {
|
|
4062
|
+
const { bridgeSession, request, requestId } = params;
|
|
4093
4063
|
return {
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4064
|
+
id: `panel-app-agent-message-${randomUUID()}`,
|
|
4065
|
+
metadata: {
|
|
4066
|
+
...createPanelAppAgentMetadata(bridgeSession),
|
|
4067
|
+
panel_app_peer_id: request.peerId,
|
|
4068
|
+
structured_result: {
|
|
4069
|
+
request_id: requestId,
|
|
4070
|
+
schema: structuredClone(request.schema),
|
|
4071
|
+
tool_name: STRUCTURED_RESULT_TOOL_NAME
|
|
4072
|
+
}
|
|
4073
|
+
},
|
|
4074
|
+
parts: [{
|
|
4075
|
+
type: "text",
|
|
4076
|
+
text: buildGenerateObjectPrompt(bridgeSession, request)
|
|
4077
|
+
}],
|
|
4078
|
+
role: "user",
|
|
4079
|
+
status: "final",
|
|
4080
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4097
4081
|
};
|
|
4098
4082
|
}
|
|
4099
|
-
function
|
|
4100
|
-
const
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
}
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4083
|
+
async function waitForPanelAppStructuredResult(agentRunClient, params) {
|
|
4084
|
+
const iterator = agentRunClient.sendAndStreamEvents(params.payload)[Symbol.asyncIterator]();
|
|
4085
|
+
let timeoutId;
|
|
4086
|
+
const timeout = new Promise((_, reject) => {
|
|
4087
|
+
timeoutId = setTimeout(() => reject(new PanelAppError("AGENT_OBJECT_RESULT_TIMEOUT", "agent object result timed out")), params.timeoutMs);
|
|
4088
|
+
});
|
|
4089
|
+
let resultToolCallId = null;
|
|
4090
|
+
try {
|
|
4091
|
+
while (true) {
|
|
4092
|
+
const next = await Promise.race([iterator.next(), timeout]);
|
|
4093
|
+
if (next.done) break;
|
|
4094
|
+
const result = readStructuredResultEvent(next.value, resultToolCallId);
|
|
4095
|
+
if (result.matchedToolCallId) resultToolCallId = result.matchedToolCallId;
|
|
4096
|
+
if (result.submitted) return result.content;
|
|
4097
|
+
throwIfTerminalError(next.value);
|
|
4098
|
+
}
|
|
4099
|
+
} finally {
|
|
4100
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
4101
|
+
await iterator.return?.(void 0);
|
|
4102
|
+
}
|
|
4103
|
+
throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
|
|
4104
|
+
}
|
|
4105
|
+
function withPanelAppAgentMetadata(payload, bridgeSession) {
|
|
4106
|
+
const panelAppMetadata = createPanelAppAgentMetadata(bridgeSession);
|
|
4107
|
+
const peerId = readOptionalPeerId(payload.peerId);
|
|
4108
|
+
const sessionId = readOptionalSessionId(payload.sessionId);
|
|
4109
|
+
if (peerId && sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request cannot include both sessionId and peerId");
|
|
4110
|
+
const metadata = {
|
|
4111
|
+
...payload.metadata ?? {},
|
|
4112
|
+
...panelAppMetadata
|
|
4113
|
+
};
|
|
4114
|
+
if (peerId) metadata.panel_app_peer_id = peerId;
|
|
4115
|
+
if (Array.isArray(payload.content)) return {
|
|
4116
|
+
content: structuredClone(payload.content),
|
|
4117
|
+
metadata,
|
|
4118
|
+
peerId,
|
|
4119
|
+
sessionId
|
|
4120
|
+
};
|
|
4121
|
+
if (!payload.message) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent send request is required");
|
|
4122
|
+
return {
|
|
4123
|
+
message: {
|
|
4124
|
+
...structuredClone(payload.message),
|
|
4125
|
+
metadata: {
|
|
4126
|
+
...payload.message.metadata ?? {},
|
|
4127
|
+
...panelAppMetadata
|
|
4128
|
+
}
|
|
4129
|
+
},
|
|
4130
|
+
metadata,
|
|
4131
|
+
peerId,
|
|
4132
|
+
sessionId
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
function createPanelAppAgentMetadata(bridgeSession) {
|
|
4136
|
+
return {
|
|
4137
|
+
agent_peer_scope: `panel-app:${bridgeSession.appId}`,
|
|
4138
|
+
panel_app_bridge_request_id: randomUUID(),
|
|
4139
|
+
panel_app_id: bridgeSession.appId,
|
|
4140
|
+
source_kind: "panel_app"
|
|
4141
|
+
};
|
|
4142
|
+
}
|
|
4143
|
+
function normalizeTimeoutMs(timeoutMs) {
|
|
4144
|
+
if (!Number.isFinite(timeoutMs) || typeof timeoutMs !== "number") return PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS;
|
|
4145
|
+
return Math.min(PANEL_APP_AGENT_MAX_TIMEOUT_MS, Math.max(1e3, Math.trunc(timeoutMs)));
|
|
4146
|
+
}
|
|
4147
|
+
function readOptionalPeerId(value) {
|
|
4148
|
+
if (typeof value !== "string") return;
|
|
4149
|
+
const peerId = value.trim();
|
|
4150
|
+
if (!peerId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent peerId cannot be empty");
|
|
4151
|
+
return peerId;
|
|
4152
|
+
}
|
|
4153
|
+
function readOptionalSessionId(value) {
|
|
4154
|
+
if (typeof value !== "string") return;
|
|
4155
|
+
const sessionId = value.trim();
|
|
4156
|
+
if (!sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent sessionId cannot be empty");
|
|
4157
|
+
return sessionId;
|
|
4158
|
+
}
|
|
4159
|
+
function buildGenerateObjectPrompt(bridgeSession, request) {
|
|
4160
|
+
return [
|
|
4161
|
+
"Panel App generateObject request",
|
|
4162
|
+
"",
|
|
4163
|
+
`Panel App ID: ${bridgeSession.appId}`,
|
|
4164
|
+
`Peer ID: ${request.peerId}`,
|
|
4165
|
+
"",
|
|
4166
|
+
"Context JSON:",
|
|
4167
|
+
stringifyJsonValue(request.context ?? null),
|
|
4168
|
+
"",
|
|
4169
|
+
"Task:",
|
|
4170
|
+
request.prompt,
|
|
4171
|
+
"",
|
|
4172
|
+
"Result contract:",
|
|
4173
|
+
`Call the ${STRUCTURED_RESULT_TOOL_NAME} tool exactly once with an object matching the provided schema.`,
|
|
4174
|
+
"Do not use natural language as the result."
|
|
4175
|
+
].join("\n");
|
|
4176
|
+
}
|
|
4177
|
+
function readStructuredResultEvent(event, resultToolCallId) {
|
|
4178
|
+
if (event.type === NcpEventType.MessageToolCallStart && event.payload.toolName === "nextclaw_submit_result") return {
|
|
4179
|
+
matchedToolCallId: event.payload.toolCallId,
|
|
4180
|
+
submitted: false
|
|
4181
|
+
};
|
|
4182
|
+
if (event.type !== NcpEventType.MessageToolCallResult || event.payload.toolCallId !== resultToolCallId) return { submitted: false };
|
|
4183
|
+
assertToolResultContent(event.payload.content);
|
|
4184
|
+
return {
|
|
4185
|
+
content: event.payload.content,
|
|
4186
|
+
submitted: true
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
function assertToolResultContent(content) {
|
|
4190
|
+
if (!isRecord$7(content) || content.ok !== false || !isRecord$7(content.error)) return;
|
|
4191
|
+
if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
|
|
4192
|
+
throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
|
|
4193
|
+
}
|
|
4194
|
+
function throwIfTerminalError(event) {
|
|
4195
|
+
if (event.type === NcpEventType.MessageFailed) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error.message);
|
|
4196
|
+
if (event.type === NcpEventType.RunError) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error ?? "agent object request failed");
|
|
4197
|
+
if (event.type === NcpEventType.RunFinished) throw new PanelAppError("AGENT_OBJECT_RESULT_NOT_SUBMITTED", "agent did not submit a structured object result");
|
|
4198
|
+
}
|
|
4199
|
+
function stringifyJsonValue(value) {
|
|
4200
|
+
try {
|
|
4201
|
+
return JSON.stringify(value, null, 2);
|
|
4202
|
+
} catch {
|
|
4203
|
+
throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "value is not JSON serializable");
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
function isRecord$7(value) {
|
|
4207
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4208
|
+
}
|
|
4209
|
+
//#endregion
|
|
4210
|
+
//#region src/services/panel-app-agent-bridge.service.ts
|
|
4211
|
+
var PanelAppAgentBridgeService = class {
|
|
4212
|
+
constructor(params) {
|
|
4213
|
+
this.params = params;
|
|
4214
|
+
}
|
|
4215
|
+
sendAgentMessage = async (bridgeSession, payload) => {
|
|
4216
|
+
await this.assertAgentCapabilityGranted(bridgeSession, "agent:send");
|
|
4217
|
+
return await this.requireAgentRunClient().send(withPanelAppAgentMetadata(payload, bridgeSession));
|
|
4218
|
+
};
|
|
4219
|
+
generateAgentObject = async (bridgeSession, input) => {
|
|
4220
|
+
await this.assertAgentCapabilityGranted(bridgeSession, "agent:generateObject");
|
|
4221
|
+
const request = normalizePanelAppGenerateObjectInput(input);
|
|
4222
|
+
const message = createPanelAppGenerateObjectMessage({
|
|
4223
|
+
bridgeSession,
|
|
4224
|
+
request,
|
|
4225
|
+
requestId: randomUUID()
|
|
4226
|
+
});
|
|
4227
|
+
return { result: await waitForPanelAppStructuredResult(this.requireAgentRunClient(), {
|
|
4228
|
+
payload: {
|
|
4229
|
+
message,
|
|
4230
|
+
metadata: {
|
|
4231
|
+
...createPanelAppAgentMetadata(bridgeSession),
|
|
4232
|
+
panel_app_peer_id: request.peerId
|
|
4233
|
+
},
|
|
4234
|
+
peerId: request.peerId
|
|
4235
|
+
},
|
|
4236
|
+
timeoutMs: request.timeoutMs
|
|
4237
|
+
}) };
|
|
4238
|
+
};
|
|
4239
|
+
grantAgentCapability = async (bridgeSession, capability) => {
|
|
4240
|
+
if (!isPanelAppAgentCapability(capability)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "unknown panel app agent capability");
|
|
4241
|
+
this.assertDeclaredCapability(bridgeSession, capability);
|
|
4242
|
+
return await this.params.createCapabilityGrantStore().grant({
|
|
4243
|
+
caller: bridgeSession.caller,
|
|
4244
|
+
capability,
|
|
4245
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4246
|
+
});
|
|
4247
|
+
};
|
|
4248
|
+
assertAgentCapabilityGranted = async (bridgeSession, capability) => {
|
|
4249
|
+
this.assertDeclaredCapability(bridgeSession, capability);
|
|
4250
|
+
if (!await this.params.createCapabilityGrantStore().isGranted(bridgeSession.caller, capability)) throw new PanelAppError("AUTHORIZATION_REQUIRED", `This panel app needs permission to use ${capability}.`);
|
|
4251
|
+
};
|
|
4252
|
+
assertDeclaredCapability = (bridgeSession, capability) => {
|
|
4253
|
+
if (!bridgeSession.declaredCapabilities.includes(capability)) throw new PanelAppError("PANEL_APP_CAPABILITY_NOT_DECLARED", this.describeMissingAgentCapability(bridgeSession.declaredCapabilities, capability));
|
|
4254
|
+
};
|
|
4255
|
+
describeMissingAgentCapability = (declaredCapabilities, capability) => {
|
|
4256
|
+
const declared = declaredCapabilities.length > 0 ? declaredCapabilities.join(", ") : "none";
|
|
4257
|
+
const valid = PANEL_APP_AGENT_CAPABILITIES.join(", ");
|
|
4258
|
+
const hint = declaredCapabilities.includes(capability.replace(":", ".")) ? ` Use ${capability}, not ${capability.replace(":", ".")}.` : "";
|
|
4259
|
+
return [
|
|
4260
|
+
`panel app did not declare ${capability}.`,
|
|
4261
|
+
`Declared: ${declared}.`,
|
|
4262
|
+
`Valid capabilities: ${valid}.`,
|
|
4263
|
+
`Declare it with nextclaw-panel-capabilities or panel-app.json capabilities.`,
|
|
4264
|
+
hint.trim()
|
|
4265
|
+
].filter(Boolean).join(" ");
|
|
4266
|
+
};
|
|
4267
|
+
requireAgentRunClient = () => {
|
|
4268
|
+
if (!this.params.agentRunClient) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "panel app agent client is not configured");
|
|
4269
|
+
return this.params.agentRunClient;
|
|
4270
|
+
};
|
|
4271
|
+
};
|
|
4272
|
+
//#endregion
|
|
4273
|
+
//#region src/utils/panel-app-bridge.utils.ts
|
|
4274
|
+
const PANEL_APP_BRIDGE_MARKER = "nextclaw:panel-app-service-actions:request";
|
|
4275
|
+
function injectPanelAppBridgeScript(html, params) {
|
|
4276
|
+
if (html.includes(PANEL_APP_BRIDGE_MARKER)) return html;
|
|
4277
|
+
const script = `<script>${getPanelAppBridgeScript(params)}<\/script>`;
|
|
4278
|
+
const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
4279
|
+
if (headMatch?.index !== void 0) {
|
|
4280
|
+
const insertAt = headMatch.index + headMatch[0].length;
|
|
4281
|
+
return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
|
|
4282
|
+
}
|
|
4283
|
+
return `${script}${html}`;
|
|
4284
|
+
}
|
|
4285
|
+
function getPanelAppBridgeScript(params = {
|
|
4286
|
+
appId: "",
|
|
4287
|
+
runtimeToken: ""
|
|
4288
|
+
}) {
|
|
4289
|
+
return `
|
|
4290
|
+
(() => {
|
|
4291
|
+
const requestType = "nextclaw:panel-app-service-actions:request";
|
|
4292
|
+
const responseType = "nextclaw:panel-app-service-actions:response";
|
|
4293
|
+
const appId = ${JSON.stringify(params.appId)};
|
|
4294
|
+
const runtimeToken = ${JSON.stringify(params.runtimeToken)};
|
|
4295
|
+
const pending = new Map();
|
|
4296
|
+
let counter = 0;
|
|
4297
|
+
|
|
4298
|
+
function createRequestId() {
|
|
4299
|
+
counter += 1;
|
|
4300
|
+
return "panel-bridge-" + Date.now().toString(36) + "-" + counter.toString(36);
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4303
|
+
function request(method, payload) {
|
|
4304
|
+
const requestId = createRequestId();
|
|
4305
|
+
return new Promise((resolve, reject) => {
|
|
4306
|
+
pending.set(requestId, { method, resolve, reject });
|
|
4307
|
+
window.parent.postMessage({ type: requestType, requestId, appId, runtimeToken, method, payload }, "*");
|
|
4308
|
+
});
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
function unwrapServiceActionResult(result) {
|
|
4312
|
+
if (!result || typeof result !== "object") {
|
|
4313
|
+
return result;
|
|
4314
|
+
}
|
|
4315
|
+
if (Object.prototype.hasOwnProperty.call(result, "structuredContent") && result.structuredContent !== undefined) {
|
|
4316
|
+
return result.structuredContent;
|
|
4317
|
+
}
|
|
4318
|
+
const content = Array.isArray(result.content) ? result.content : undefined;
|
|
4319
|
+
if (content && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string") {
|
|
4320
|
+
try {
|
|
4321
|
+
return JSON.parse(content[0].text);
|
|
4322
|
+
} catch {
|
|
4323
|
+
return content[0].text;
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
return result;
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
function resolveBridgeData(entry, data) {
|
|
4330
|
+
if (entry.method === "list") {
|
|
4331
|
+
return Array.isArray(data.data?.actions) ? data.data.actions : [];
|
|
4332
|
+
}
|
|
4333
|
+
if (entry.method === "invoke") {
|
|
4334
|
+
return unwrapServiceActionResult(data.data?.result);
|
|
4335
|
+
}
|
|
4336
|
+
if (entry.method === "agent.generateObject") {
|
|
4337
|
+
return data.data?.result;
|
|
4338
|
+
}
|
|
4339
|
+
return data.data;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
window.addEventListener("message", (event) => {
|
|
4343
|
+
const data = event.data;
|
|
4344
|
+
if (!data || data.type !== responseType || typeof data.requestId !== "string") {
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4347
|
+
const entry = pending.get(data.requestId);
|
|
4348
|
+
if (!entry) {
|
|
4349
|
+
return;
|
|
4350
|
+
}
|
|
4351
|
+
pending.delete(data.requestId);
|
|
4352
|
+
if (data.ok) {
|
|
4353
|
+
entry.resolve(resolveBridgeData(entry, data));
|
|
4354
|
+
return;
|
|
4355
|
+
}
|
|
4356
|
+
const error = new Error(data.error?.message || "NextClaw panel bridge request failed.");
|
|
4357
|
+
error.code = data.error?.code;
|
|
4358
|
+
error.details = data.error?.details;
|
|
4359
|
+
entry.reject(error);
|
|
4360
|
+
});
|
|
4361
|
+
|
|
4362
|
+
const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
|
|
4363
|
+
Object.defineProperty(window, "nextclaw", {
|
|
4364
|
+
configurable: true,
|
|
4365
|
+
value: {
|
|
4366
|
+
...existing,
|
|
4367
|
+
serviceActions: {
|
|
4368
|
+
list: () => request("list", {}),
|
|
4369
|
+
invoke: (actionId, input) => request("invoke", { actionId, input }),
|
|
4370
|
+
requestGrant: (actionId) => request("requestGrant", { actionId }),
|
|
4371
|
+
revokeGrant: (actionId) => request("revokeGrant", { actionId })
|
|
4372
|
+
},
|
|
4373
|
+
agent: {
|
|
4374
|
+
send: (input) => request("agent.send", { request: input }),
|
|
4375
|
+
generateObject: (input) => request("agent.generateObject", { input })
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
});
|
|
4379
|
+
})();
|
|
4380
|
+
`.trim();
|
|
4381
|
+
}
|
|
4382
|
+
//#endregion
|
|
4383
|
+
//#region src/utils/panel-app-client-injection.utils.ts
|
|
4384
|
+
const PANEL_APP_CLIENT_MARKER = "nextclaw:panel-app-client:init";
|
|
4385
|
+
const PANEL_APP_CLIENT_SDK_PATH = "/api/panel-app-client-sdk.js";
|
|
4386
|
+
function injectPanelAppClientScript(html, params) {
|
|
4387
|
+
if (html.includes(PANEL_APP_CLIENT_MARKER)) return html;
|
|
4388
|
+
const script = [`<script src="${PANEL_APP_CLIENT_SDK_PATH}"><\/script>`, `<script>${getPanelAppClientInitScript(params)}<\/script>`].join("");
|
|
4389
|
+
const headMatch = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
4390
|
+
if (headMatch?.index !== void 0) {
|
|
4391
|
+
const insertAt = headMatch.index + headMatch[0].length;
|
|
4392
|
+
return `${html.slice(0, insertAt)}${script}${html.slice(insertAt)}`;
|
|
4393
|
+
}
|
|
4394
|
+
return `${script}${html}`;
|
|
4395
|
+
}
|
|
4396
|
+
function getPanelAppClientInitScript(params) {
|
|
4397
|
+
return `
|
|
4398
|
+
(() => {
|
|
4399
|
+
const marker = "${PANEL_APP_CLIENT_MARKER}";
|
|
4400
|
+
if (typeof window.NextClawClient !== "function") {
|
|
4401
|
+
console.error("[NextClaw] Panel App client SDK failed to load.");
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4404
|
+
const existing = window.nextclaw && typeof window.nextclaw === "object" ? window.nextclaw : {};
|
|
4405
|
+
const client = new window.NextClawClient({
|
|
4406
|
+
baseUrl: window.location.origin,
|
|
4407
|
+
headers: {
|
|
4408
|
+
"x-nextclaw-panel-bridge-session": ${JSON.stringify(params.runtimeToken)}
|
|
4409
|
+
}
|
|
4410
|
+
});
|
|
4411
|
+
Object.defineProperty(window, "nextclaw", {
|
|
4412
|
+
configurable: true,
|
|
4413
|
+
value: {
|
|
4414
|
+
...existing,
|
|
4415
|
+
client
|
|
4416
|
+
}
|
|
4417
|
+
});
|
|
4418
|
+
Object.defineProperty(window.nextclaw, "__clientInitMarker", {
|
|
4419
|
+
configurable: true,
|
|
4420
|
+
enumerable: false,
|
|
4421
|
+
value: marker
|
|
4422
|
+
});
|
|
4423
|
+
})();
|
|
4424
|
+
`.trim();
|
|
4425
|
+
}
|
|
4426
|
+
//#endregion
|
|
4427
|
+
//#region src/utils/panel-app-manifest.utils.ts
|
|
4428
|
+
const PANEL_APP_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4429
|
+
function parsePanelAppManifest(html) {
|
|
4430
|
+
return {
|
|
4431
|
+
...readHtmlTitle(html),
|
|
4432
|
+
...readStandardIcon(html),
|
|
4433
|
+
...readPanelAppMeta(html),
|
|
4434
|
+
capabilities: readPanelAppCapabilities(html),
|
|
4435
|
+
client: false,
|
|
4436
|
+
serviceActions: readPanelAppServiceActions(html)
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4439
|
+
function parsePanelAppFolderManifest(raw) {
|
|
4440
|
+
let parsed;
|
|
4441
|
+
try {
|
|
4442
|
+
parsed = JSON.parse(raw);
|
|
4443
|
+
} catch (error) {
|
|
4444
|
+
throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4445
|
+
}
|
|
4446
|
+
if (!isRecord$6(parsed)) throw new Error("panel-app.json must contain an object.");
|
|
4447
|
+
const id = readOptionalString$6(parsed, "id");
|
|
4448
|
+
if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
|
|
4449
|
+
return {
|
|
4450
|
+
id,
|
|
4451
|
+
title: readRequiredString$3(parsed, "title"),
|
|
4452
|
+
description: readOptionalString$6(parsed, "description"),
|
|
4453
|
+
icon: readOptionalString$6(parsed, "icon"),
|
|
4454
|
+
entry: readRequiredString$3(parsed, "entry"),
|
|
4455
|
+
capabilities: readStringArray$1(parsed.capabilities, "capabilities"),
|
|
4456
|
+
client: readOptionalBoolean$2(parsed, "client"),
|
|
4457
|
+
serviceActions: readStringArray$1(parsed.actions, "actions")
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
function readPanelAppMeta(html) {
|
|
4461
|
+
return {
|
|
4462
|
+
...readPanelAppMetaField(html, "title"),
|
|
4463
|
+
...readPanelAppMetaField(html, "description"),
|
|
4464
|
+
...readPanelAppMetaField(html, "icon")
|
|
4465
|
+
};
|
|
4466
|
+
}
|
|
4467
|
+
function readPanelAppMetaField(html, field) {
|
|
4468
|
+
const content = readMetaContent(html, `nextclaw-panel-${field}`, field === "icon" ? "attribute" : "text");
|
|
4469
|
+
const manifest = {};
|
|
4470
|
+
if (content) manifest[field] = content;
|
|
4471
|
+
return manifest;
|
|
4472
|
+
}
|
|
4473
|
+
function readHtmlTitle(html) {
|
|
4474
|
+
const title = normalizeTextValue(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]);
|
|
4475
|
+
return title ? { title } : {};
|
|
4476
|
+
}
|
|
4477
|
+
function readStandardIcon(html) {
|
|
4478
|
+
const icon = readLinkHref(html, (relTokens) => relTokens.includes("icon"));
|
|
4479
|
+
const appleTouchIcon = readLinkHref(html, (relTokens) => relTokens.some((token) => token === "apple-touch-icon" || token === "apple-touch-icon-precomposed"));
|
|
4112
4480
|
const href = normalizeIconHref(icon ?? appleTouchIcon);
|
|
4113
4481
|
return href ? { icon: href } : {};
|
|
4114
4482
|
}
|
|
@@ -4151,12 +4519,18 @@ function readRequiredString$3(record, key) {
|
|
|
4151
4519
|
function readOptionalString$6(record, key) {
|
|
4152
4520
|
return typeof record[key] === "string" && record[key].trim() ? record[key].trim() : void 0;
|
|
4153
4521
|
}
|
|
4522
|
+
function readOptionalBoolean$2(record, key) {
|
|
4523
|
+
const value = record[key];
|
|
4524
|
+
if (value === void 0) return false;
|
|
4525
|
+
if (typeof value !== "boolean") throw new Error(`panel app ${key} must be a boolean.`);
|
|
4526
|
+
return value;
|
|
4527
|
+
}
|
|
4154
4528
|
function readStringArray$1(value, key) {
|
|
4155
4529
|
if (value === void 0) return [];
|
|
4156
4530
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
|
|
4157
4531
|
return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
|
|
4158
4532
|
}
|
|
4159
|
-
function isRecord$
|
|
4533
|
+
function isRecord$6(value) {
|
|
4160
4534
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4161
4535
|
}
|
|
4162
4536
|
function normalizeTextValue(value) {
|
|
@@ -4276,256 +4650,128 @@ var PanelAppSourceService = class {
|
|
|
4276
4650
|
resolveSource = async (panelsPath, id) => {
|
|
4277
4651
|
const sourceName = decodePanelAppId(id);
|
|
4278
4652
|
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);
|
|
4653
|
+
return await this.readSource(panelsPath, sourceName);
|
|
4654
|
+
} catch (error) {
|
|
4655
|
+
if (isPanelAppError(error)) throw error;
|
|
4656
|
+
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4657
|
+
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4419
4658
|
}
|
|
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
4659
|
};
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4660
|
+
getAsset = async (panelsPath, id, assetPath) => {
|
|
4661
|
+
const source = await this.resolveSource(panelsPath, id);
|
|
4662
|
+
if (source.kind !== "folder") throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4663
|
+
const filePath = resolvePanelAppRelativePath(source.sourcePath, assetPath);
|
|
4664
|
+
try {
|
|
4665
|
+
if (!(await stat(filePath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4666
|
+
return {
|
|
4667
|
+
content: await readFile(filePath),
|
|
4668
|
+
contentType: resolvePanelAppAssetContentType(filePath)
|
|
4669
|
+
};
|
|
4670
|
+
} catch (error) {
|
|
4671
|
+
if (isPanelAppError(error)) throw error;
|
|
4672
|
+
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app asset not found");
|
|
4673
|
+
throw new PanelAppError("PANEL_APP_READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
4674
|
+
}
|
|
4441
4675
|
};
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4676
|
+
readSource = async (panelsPath, sourceName) => {
|
|
4677
|
+
const sourcePath = join(panelsPath, sourceName);
|
|
4678
|
+
const sourceStat = await stat(sourcePath);
|
|
4679
|
+
if (sourceStat.isFile() && isPanelAppFileName(sourceName)) return {
|
|
4680
|
+
kind: "single-file",
|
|
4681
|
+
sourceName,
|
|
4682
|
+
sourcePath,
|
|
4683
|
+
entryPath: sourcePath,
|
|
4684
|
+
sourceStat
|
|
4685
|
+
};
|
|
4686
|
+
if (sourceStat.isDirectory() && isPanelAppDirName(sourceName)) return await this.readFolderSource(sourcePath, sourceName, sourceStat);
|
|
4687
|
+
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4454
4688
|
};
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4689
|
+
readFolderSource = async (sourcePath, sourceName, sourceStat) => {
|
|
4690
|
+
try {
|
|
4691
|
+
const manifest = await readPanelAppFolderManifest(sourcePath, sourceName);
|
|
4692
|
+
const entryPath = resolvePanelAppRelativePath(sourcePath, manifest.entry);
|
|
4693
|
+
if (!(await stat(entryPath)).isFile()) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app entry not found");
|
|
4694
|
+
return {
|
|
4695
|
+
kind: "folder",
|
|
4696
|
+
sourceName,
|
|
4697
|
+
sourcePath,
|
|
4698
|
+
entryPath,
|
|
4699
|
+
manifest,
|
|
4700
|
+
sourceStat
|
|
4701
|
+
};
|
|
4702
|
+
} catch (error) {
|
|
4703
|
+
if (isPanelAppError(error)) throw error;
|
|
4704
|
+
if (isMissingFileError$1(error)) throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4705
|
+
throw new PanelAppError("PANEL_APP_MANIFEST_INVALID", error instanceof Error ? error.message : String(error));
|
|
4706
|
+
}
|
|
4462
4707
|
};
|
|
4708
|
+
};
|
|
4709
|
+
function isMissingFileError$1(error) {
|
|
4710
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4463
4711
|
}
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
function readOptionalPeerId(value) {
|
|
4469
|
-
if (typeof value !== "string") return;
|
|
4470
|
-
const peerId = value.trim();
|
|
4471
|
-
if (!peerId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent peerId cannot be empty");
|
|
4472
|
-
return peerId;
|
|
4473
|
-
}
|
|
4474
|
-
function readOptionalSessionId(value) {
|
|
4475
|
-
if (typeof value !== "string") return;
|
|
4476
|
-
const sessionId = value.trim();
|
|
4477
|
-
if (!sessionId) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "agent sessionId cannot be empty");
|
|
4478
|
-
return sessionId;
|
|
4712
|
+
//#endregion
|
|
4713
|
+
//#region src/utils/panel-app-time.utils.ts
|
|
4714
|
+
function resolvePanelAppCreatedAt(fileStat) {
|
|
4715
|
+
return fileStat.birthtimeMs > 0 ? fileStat.birthtime.toISOString() : fileStat.mtime.toISOString();
|
|
4479
4716
|
}
|
|
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");
|
|
4717
|
+
function resolvePanelAppActivityMs(entry) {
|
|
4718
|
+
return Math.max(new Date(entry.lastOpenedAt ?? 0).getTime(), new Date(entry.createdAt).getTime(), new Date(entry.updatedAt).getTime());
|
|
4497
4719
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4720
|
+
//#endregion
|
|
4721
|
+
//#region src/utils/panel-app-content-source.utils.ts
|
|
4722
|
+
async function readPanelAppContentSource(params) {
|
|
4723
|
+
const { createAssetBaseHref, id, panelsPath, sourceService } = params;
|
|
4724
|
+
const source = await sourceService.resolveSource(panelsPath, id);
|
|
4725
|
+
const html = await readFile(source.entryPath, "utf8");
|
|
4726
|
+
const manifest = source.manifest ?? parsePanelAppManifest(html);
|
|
4727
|
+
const sourceId = encodePanelAppId(source.sourceName);
|
|
4505
4728
|
return {
|
|
4506
|
-
|
|
4507
|
-
|
|
4729
|
+
appId: resolvePanelAppAppId(source, manifest),
|
|
4730
|
+
sourceId,
|
|
4731
|
+
source,
|
|
4732
|
+
manifest,
|
|
4733
|
+
html,
|
|
4734
|
+
htmlWithBase: source.kind === "folder" ? injectPanelAppAssetBase(html, createAssetBaseHref(source)) : html
|
|
4508
4735
|
};
|
|
4509
4736
|
}
|
|
4510
|
-
function
|
|
4511
|
-
|
|
4512
|
-
if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
|
|
4513
|
-
throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
|
|
4514
|
-
}
|
|
4515
|
-
function throwIfTerminalError(event) {
|
|
4516
|
-
if (event.type === NcpEventType.MessageFailed) throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", event.payload.error.message);
|
|
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");
|
|
4519
|
-
}
|
|
4520
|
-
function stringifyJsonValue(value) {
|
|
4737
|
+
async function readPanelAppContentSourceByIdOrAppId(params) {
|
|
4738
|
+
const { appIdOrSourceId, createAssetBaseHref, panelsPath, sourceService } = params;
|
|
4521
4739
|
try {
|
|
4522
|
-
return
|
|
4523
|
-
|
|
4524
|
-
|
|
4740
|
+
return await readPanelAppContentSource({
|
|
4741
|
+
createAssetBaseHref,
|
|
4742
|
+
id: appIdOrSourceId,
|
|
4743
|
+
panelsPath,
|
|
4744
|
+
sourceService
|
|
4745
|
+
});
|
|
4746
|
+
} catch (error) {
|
|
4747
|
+
if (!isPanelAppError(error)) throw error;
|
|
4748
|
+
if (error.code !== "PANEL_APP_INVALID_ID" && error.code !== "PANEL_APP_NOT_FOUND") throw error;
|
|
4749
|
+
}
|
|
4750
|
+
const sources = await sourceService.listSources(panelsPath);
|
|
4751
|
+
for (const source of sources) {
|
|
4752
|
+
const html = await readFile(source.entryPath, "utf8");
|
|
4753
|
+
if (resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(html)) === appIdOrSourceId) return await readPanelAppContentSource({
|
|
4754
|
+
createAssetBaseHref,
|
|
4755
|
+
id: encodePanelAppId(source.sourceName),
|
|
4756
|
+
panelsPath,
|
|
4757
|
+
sourceService
|
|
4758
|
+
});
|
|
4525
4759
|
}
|
|
4760
|
+
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4526
4761
|
}
|
|
4527
|
-
function
|
|
4528
|
-
return
|
|
4762
|
+
function resolvePanelAppAppId(source, manifest) {
|
|
4763
|
+
return manifest.id ?? encodePanelAppId(source.sourceName);
|
|
4764
|
+
}
|
|
4765
|
+
async function assertPanelAppDeclaresClient(params) {
|
|
4766
|
+
const { appId, panelsPath, sourceService } = params;
|
|
4767
|
+
const sources = await sourceService.listSources(panelsPath);
|
|
4768
|
+
for (const source of sources) {
|
|
4769
|
+
const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
|
|
4770
|
+
if (resolvePanelAppAppId(source, manifest) !== appId) continue;
|
|
4771
|
+
if (!manifest.client) throw new PanelAppError("PANEL_APP_CLIENT_NOT_DECLARED", "panel app did not declare client access");
|
|
4772
|
+
return;
|
|
4773
|
+
}
|
|
4774
|
+
throw new PanelAppError("PANEL_APP_NOT_FOUND", "panel app not found");
|
|
4529
4775
|
}
|
|
4530
4776
|
//#endregion
|
|
4531
4777
|
//#region src/managers/panel-app.manager.ts
|
|
@@ -4533,9 +4779,12 @@ const PANEL_APP_CONTENT_BASE_PATH = "/api/panel-apps";
|
|
|
4533
4779
|
const PANEL_APP_TOKENIZED_ASSET_BASE_PATH = "/api/panel-app-assets";
|
|
4534
4780
|
const PANEL_APP_CONTENT_TYPE = "text/html; charset=utf-8";
|
|
4535
4781
|
const PANEL_APP_CAPABILITY_GRANTS_FILE_NAME = ".panel-app-capability-grants.json";
|
|
4782
|
+
const PANEL_APP_CLIENT_GRANTS_FILE_NAME = ".panel-app-client-grants.json";
|
|
4783
|
+
const PANEL_APP_RUNTIME_TOKEN_TTL_MS = 1440 * 60 * 1e3;
|
|
4536
4784
|
var PanelAppManager = class {
|
|
4537
4785
|
bridgeSessions = /* @__PURE__ */ new Map();
|
|
4538
4786
|
agentRunClient;
|
|
4787
|
+
agentBridgeService;
|
|
4539
4788
|
assetTokenService = new PanelAppAssetTokenService();
|
|
4540
4789
|
sourceService = new PanelAppSourceService();
|
|
4541
4790
|
constructor(params) {
|
|
@@ -4544,6 +4793,10 @@ var PanelAppManager = class {
|
|
|
4544
4793
|
eventBus: params.eventBus,
|
|
4545
4794
|
ingress: params.ingress
|
|
4546
4795
|
}) : null);
|
|
4796
|
+
this.agentBridgeService = new PanelAppAgentBridgeService({
|
|
4797
|
+
agentRunClient: this.agentRunClient,
|
|
4798
|
+
createCapabilityGrantStore: this.createCapabilityGrantStore
|
|
4799
|
+
});
|
|
4547
4800
|
}
|
|
4548
4801
|
listPanelApps = async () => {
|
|
4549
4802
|
const workspacePath = this.getWorkspacePath();
|
|
@@ -4557,20 +4810,35 @@ var PanelAppManager = class {
|
|
|
4557
4810
|
};
|
|
4558
4811
|
};
|
|
4559
4812
|
getPanelAppContent = async (id) => {
|
|
4560
|
-
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
4561
4813
|
try {
|
|
4562
|
-
const
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4814
|
+
const resolved = await readPanelAppContentSource({
|
|
4815
|
+
createAssetBaseHref: this.createAssetBaseHref,
|
|
4816
|
+
id,
|
|
4817
|
+
panelsPath: this.getPanelsPath(this.getWorkspacePath()),
|
|
4818
|
+
sourceService: this.sourceService
|
|
4819
|
+
});
|
|
4820
|
+
const clientGranted = await this.isPanelAppClientGranted(resolved.appId, resolved.manifest.client);
|
|
4821
|
+
const session = this.createPanelAppRuntimeTokenSession({
|
|
4822
|
+
appId: resolved.appId,
|
|
4823
|
+
clientDeclared: resolved.manifest.client,
|
|
4824
|
+
declaredActions: resolved.manifest.serviceActions,
|
|
4825
|
+
declaredCapabilities: resolved.manifest.capabilities
|
|
4826
|
+
});
|
|
4827
|
+
const htmlWithBridge = injectPanelAppBridgeScript(resolved.htmlWithBase, {
|
|
4828
|
+
appId: resolved.appId,
|
|
4829
|
+
runtimeToken: session.token
|
|
4830
|
+
});
|
|
4831
|
+
const html = resolved.manifest.client && clientGranted ? injectPanelAppClientScript(htmlWithBridge, { runtimeToken: session.token }) : htmlWithBridge;
|
|
4567
4832
|
return {
|
|
4568
|
-
id: sourceId,
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4833
|
+
id: resolved.sourceId,
|
|
4834
|
+
appId: resolved.appId,
|
|
4835
|
+
fileName: resolved.source.sourceName,
|
|
4836
|
+
html,
|
|
4837
|
+
capabilities: resolved.manifest.capabilities,
|
|
4838
|
+
clientDeclared: resolved.manifest.client,
|
|
4839
|
+
clientGranted,
|
|
4572
4840
|
contentType: PANEL_APP_CONTENT_TYPE,
|
|
4573
|
-
serviceActions: manifest.serviceActions
|
|
4841
|
+
serviceActions: resolved.manifest.serviceActions
|
|
4574
4842
|
};
|
|
4575
4843
|
} catch (error) {
|
|
4576
4844
|
if (isPanelAppError(error)) throw error;
|
|
@@ -4588,27 +4856,58 @@ var PanelAppManager = class {
|
|
|
4588
4856
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
4589
4857
|
return await this.sourceService.getAsset(panelsPath, claims.panelAppId, assetPath);
|
|
4590
4858
|
};
|
|
4591
|
-
getPanelAppBridgeScript = () => getPanelAppBridgeScript(
|
|
4592
|
-
|
|
4593
|
-
|
|
4859
|
+
getPanelAppBridgeScript = () => getPanelAppBridgeScript({
|
|
4860
|
+
appId: "",
|
|
4861
|
+
runtimeToken: ""
|
|
4862
|
+
});
|
|
4863
|
+
createPanelAppRuntimeTokenSession = (params) => {
|
|
4864
|
+
const { appId, clientDeclared, declaredActions, declaredCapabilities } = params;
|
|
4594
4865
|
const now = /* @__PURE__ */ new Date();
|
|
4595
4866
|
const session = {
|
|
4596
4867
|
id: randomUUID(),
|
|
4597
4868
|
token: randomUUID(),
|
|
4598
|
-
|
|
4599
|
-
tabId: params.tabId,
|
|
4869
|
+
appId,
|
|
4600
4870
|
caller: {
|
|
4601
4871
|
surface: "panel-app",
|
|
4602
|
-
appId
|
|
4872
|
+
appId
|
|
4603
4873
|
},
|
|
4604
|
-
declaredCapabilities
|
|
4605
|
-
declaredActions
|
|
4874
|
+
declaredCapabilities,
|
|
4875
|
+
declaredActions,
|
|
4876
|
+
clientDeclared,
|
|
4606
4877
|
createdAt: now.toISOString(),
|
|
4607
|
-
expiresAt: new Date(now.getTime() +
|
|
4878
|
+
expiresAt: new Date(now.getTime() + PANEL_APP_RUNTIME_TOKEN_TTL_MS).toISOString()
|
|
4608
4879
|
};
|
|
4609
4880
|
this.bridgeSessions.set(session.token, session);
|
|
4610
4881
|
return session;
|
|
4611
4882
|
};
|
|
4883
|
+
createPanelAppBridgeSession = async (params) => {
|
|
4884
|
+
const resolved = await readPanelAppContentSourceByIdOrAppId({
|
|
4885
|
+
appIdOrSourceId: params.id,
|
|
4886
|
+
createAssetBaseHref: this.createAssetBaseHref,
|
|
4887
|
+
panelsPath: this.getPanelsPath(this.getWorkspacePath()),
|
|
4888
|
+
sourceService: this.sourceService
|
|
4889
|
+
});
|
|
4890
|
+
return this.createPanelAppRuntimeTokenSession({
|
|
4891
|
+
appId: resolved.appId,
|
|
4892
|
+
clientDeclared: resolved.manifest.client,
|
|
4893
|
+
declaredActions: resolved.manifest.serviceActions,
|
|
4894
|
+
declaredCapabilities: resolved.manifest.capabilities
|
|
4895
|
+
});
|
|
4896
|
+
};
|
|
4897
|
+
grantPanelAppClient = async (appId) => {
|
|
4898
|
+
await assertPanelAppDeclaresClient({
|
|
4899
|
+
appId,
|
|
4900
|
+
panelsPath: this.getPanelsPath(this.getWorkspacePath()),
|
|
4901
|
+
sourceService: this.sourceService
|
|
4902
|
+
});
|
|
4903
|
+
return await this.createClientGrantStore().grant({
|
|
4904
|
+
appId,
|
|
4905
|
+
grantedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4906
|
+
});
|
|
4907
|
+
};
|
|
4908
|
+
revokePanelAppClient = async (appId) => {
|
|
4909
|
+
await this.createClientGrantStore().revoke(appId);
|
|
4910
|
+
};
|
|
4612
4911
|
resolvePanelAppBridgeSession = (token) => {
|
|
4613
4912
|
this.deleteExpiredBridgeSessions();
|
|
4614
4913
|
const session = this.bridgeSessions.get(token.trim());
|
|
@@ -4620,39 +4919,15 @@ var PanelAppManager = class {
|
|
|
4620
4919
|
};
|
|
4621
4920
|
sendAgentMessage = async (bridgeSessionToken, payload) => {
|
|
4622
4921
|
const bridgeSession = this.resolvePanelAppBridgeSession(bridgeSessionToken);
|
|
4623
|
-
await this.
|
|
4624
|
-
return await this.requireAgentRunClient().send(withPanelAppAgentMetadata(payload, bridgeSession));
|
|
4922
|
+
return await this.agentBridgeService.sendAgentMessage(bridgeSession, payload);
|
|
4625
4923
|
};
|
|
4626
4924
|
generateAgentObject = async (bridgeSessionToken, input) => {
|
|
4627
4925
|
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
|
-
}) };
|
|
4926
|
+
return await this.agentBridgeService.generateAgentObject(bridgeSession, input);
|
|
4646
4927
|
};
|
|
4647
4928
|
grantAgentCapability = async (bridgeSessionToken, capability) => {
|
|
4648
4929
|
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
|
-
});
|
|
4930
|
+
return await this.agentBridgeService.grantAgentCapability(bridgeSession, capability);
|
|
4656
4931
|
};
|
|
4657
4932
|
updatePanelAppPreferences = async (id, preferences) => {
|
|
4658
4933
|
const fileName = await this.resolvePanelAppFileName(id);
|
|
@@ -4670,42 +4945,21 @@ var PanelAppManager = class {
|
|
|
4670
4945
|
const panelsPath = this.getPanelsPath(this.getWorkspacePath());
|
|
4671
4946
|
const source = await this.sourceService.resolveSource(panelsPath, id);
|
|
4672
4947
|
const panelAppId = encodePanelAppId(source.sourceName);
|
|
4948
|
+
const appId = resolvePanelAppAppId(source, source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8")));
|
|
4673
4949
|
await rm(source.sourcePath, { recursive: source.kind === "folder" });
|
|
4674
4950
|
await this.createStateStore(panelsPath).deleteEntry(panelAppId);
|
|
4675
4951
|
await this.createCapabilityGrantStore().deleteCaller({
|
|
4676
4952
|
surface: "panel-app",
|
|
4677
|
-
appId
|
|
4953
|
+
appId
|
|
4678
4954
|
});
|
|
4679
|
-
this.
|
|
4955
|
+
await this.createClientGrantStore().revoke(appId);
|
|
4956
|
+
this.deleteBridgeSessionsByPanelAppId(appId);
|
|
4680
4957
|
return {
|
|
4681
4958
|
deleted: true,
|
|
4682
4959
|
fileName: source.sourceName,
|
|
4683
4960
|
id: panelAppId
|
|
4684
4961
|
};
|
|
4685
4962
|
};
|
|
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
4963
|
getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
|
|
4710
4964
|
getPanelsPath = (workspacePath) => join(workspacePath, DEFAULT_PANELS_DIR);
|
|
4711
4965
|
createAssetBaseHref = (source) => {
|
|
@@ -4717,13 +4971,16 @@ var PanelAppManager = class {
|
|
|
4717
4971
|
};
|
|
4718
4972
|
createStateStore = (panelsPath) => new PanelAppStateStore(panelsPath);
|
|
4719
4973
|
createCapabilityGrantStore = () => new PanelAppCapabilityGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CAPABILITY_GRANTS_FILE_NAME));
|
|
4974
|
+
createClientGrantStore = () => new PanelAppClientGrantStore(join(this.getPanelsPath(this.getWorkspacePath()), PANEL_APP_CLIENT_GRANTS_FILE_NAME));
|
|
4720
4975
|
buildPanelAppEntry = async (source, state) => {
|
|
4721
4976
|
const manifest = source.manifest ?? parsePanelAppManifest(await readFile(source.entryPath, "utf8"));
|
|
4722
4977
|
const id = encodePanelAppId(source.sourceName);
|
|
4978
|
+
const appId = resolvePanelAppAppId(source, manifest);
|
|
4723
4979
|
const createdAt = resolvePanelAppCreatedAt(source.sourceStat);
|
|
4724
4980
|
const updatedAt = source.sourceStat.mtime.toISOString();
|
|
4725
4981
|
const entry = {
|
|
4726
4982
|
id,
|
|
4983
|
+
appId,
|
|
4727
4984
|
fileName: source.sourceName,
|
|
4728
4985
|
kind: source.kind,
|
|
4729
4986
|
title: manifest.title ?? toPanelAppTitle(source.sourceName),
|
|
@@ -4732,6 +4989,8 @@ var PanelAppManager = class {
|
|
|
4732
4989
|
updatedAt,
|
|
4733
4990
|
sizeBytes: source.sourceStat.size,
|
|
4734
4991
|
favorite: state.favorite ?? false,
|
|
4992
|
+
clientDeclared: manifest.client,
|
|
4993
|
+
clientGranted: await this.isPanelAppClientGranted(appId, manifest.client),
|
|
4735
4994
|
openCount: state.openCount ?? 0
|
|
4736
4995
|
};
|
|
4737
4996
|
if (manifest.description) entry.description = manifest.description;
|
|
@@ -4748,7 +5007,11 @@ var PanelAppManager = class {
|
|
|
4748
5007
|
for (const [token, session] of this.bridgeSessions) if (new Date(session.expiresAt).getTime() <= now) this.bridgeSessions.delete(token);
|
|
4749
5008
|
};
|
|
4750
5009
|
deleteBridgeSessionsByPanelAppId = (panelAppId) => {
|
|
4751
|
-
for (const [token, session] of this.bridgeSessions) if (session.
|
|
5010
|
+
for (const [token, session] of this.bridgeSessions) if (session.appId === panelAppId) this.bridgeSessions.delete(token);
|
|
5011
|
+
};
|
|
5012
|
+
isPanelAppClientGranted = async (appId, clientDeclared) => {
|
|
5013
|
+
if (!clientDeclared) return false;
|
|
5014
|
+
return await this.createClientGrantStore().isGranted(appId);
|
|
4752
5015
|
};
|
|
4753
5016
|
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
4754
5017
|
};
|
|
@@ -5171,15 +5434,29 @@ var ServiceAppManager = class {
|
|
|
5171
5434
|
};
|
|
5172
5435
|
};
|
|
5173
5436
|
grantServiceAction = async (actionId, request) => {
|
|
5437
|
+
const [grant] = await this.grantServiceActions([actionId], request);
|
|
5438
|
+
if (!grant) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
|
|
5439
|
+
return grant;
|
|
5440
|
+
};
|
|
5441
|
+
grantServiceActions = async (actionIds, request) => {
|
|
5174
5442
|
this.assertCaller(request.caller);
|
|
5175
|
-
this.
|
|
5176
|
-
|
|
5177
|
-
|
|
5443
|
+
const normalizedActionIds = this.normalizeActionIds(actionIds);
|
|
5444
|
+
if (normalizedActionIds.length === 0) throw new ServiceAppError("SERVICE_APP_INVALID_ACTION", "service action id is invalid");
|
|
5445
|
+
const actions = [];
|
|
5446
|
+
for (const actionId of normalizedActionIds) {
|
|
5447
|
+
this.assertDeclaredAction(actionId, request.declaredActions);
|
|
5448
|
+
actions.push(await this.requireServiceAction(actionId));
|
|
5449
|
+
}
|
|
5450
|
+
const grantedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5451
|
+
const grantStore = this.createGrantStore();
|
|
5452
|
+
const grants = [];
|
|
5453
|
+
for (const action of actions) grants.push(await grantStore.grant({
|
|
5178
5454
|
caller: request.caller,
|
|
5179
|
-
actionId,
|
|
5455
|
+
actionId: action.id,
|
|
5180
5456
|
risk: action.risk,
|
|
5181
|
-
grantedAt
|
|
5182
|
-
});
|
|
5457
|
+
grantedAt
|
|
5458
|
+
}));
|
|
5459
|
+
return grants;
|
|
5183
5460
|
};
|
|
5184
5461
|
listServiceActionGrants = async () => {
|
|
5185
5462
|
return await this.createGrantStore().list();
|
|
@@ -5310,6 +5587,7 @@ var ServiceAppManager = class {
|
|
|
5310
5587
|
assertDeclaredAction = (actionId, declaredActions) => {
|
|
5311
5588
|
if (!declaredActions.includes(actionId)) throw new ServiceAppError("SERVICE_APP_ACTION_NOT_DECLARED", "panel app did not declare this service action");
|
|
5312
5589
|
};
|
|
5590
|
+
normalizeActionIds = (actionIds) => Array.from(new Set(actionIds.map((actionId) => actionId.trim()).filter((actionId) => actionId.length > 0)));
|
|
5313
5591
|
getWorkspacePath = () => getWorkspacePathFromConfig(this.params.configManager.config);
|
|
5314
5592
|
getServiceAppsPath = (workspacePath) => join(workspacePath, DEFAULT_SERVICE_APPS_DIR);
|
|
5315
5593
|
createGrantStore = () => new ServiceActionGrantStore(join(this.getServiceAppsPath(this.getWorkspacePath()), SERVICE_ACTION_GRANTS_FILE_NAME));
|
|
@@ -5336,7 +5614,6 @@ var MessageInbox = class {
|
|
|
5336
5614
|
drain = () => {
|
|
5337
5615
|
return this.messages.splice(0, this.messages.length);
|
|
5338
5616
|
};
|
|
5339
|
-
isEmpty = () => this.messages.length === 0;
|
|
5340
5617
|
};
|
|
5341
5618
|
function isConversationStateEvent(event) {
|
|
5342
5619
|
return event.type !== NcpEventType.ContextWindowUpdated;
|
|
@@ -5344,10 +5621,10 @@ function isConversationStateEvent(event) {
|
|
|
5344
5621
|
var SessionRun = class {
|
|
5345
5622
|
inbox = new MessageInbox();
|
|
5346
5623
|
sessionId;
|
|
5624
|
+
statusListeners = /* @__PURE__ */ new Set();
|
|
5347
5625
|
activeRunId = null;
|
|
5348
5626
|
activeRunController = null;
|
|
5349
|
-
constructor(seed,
|
|
5350
|
-
this.eventBus = eventBus;
|
|
5627
|
+
constructor(seed, stateManager = new DefaultNcpAgentConversationStateManager()) {
|
|
5351
5628
|
this.stateManager = stateManager;
|
|
5352
5629
|
this.sessionId = seed.sessionId;
|
|
5353
5630
|
this.stateManager.hydrate({
|
|
@@ -5364,19 +5641,20 @@ var SessionRun = class {
|
|
|
5364
5641
|
const conversationEvents = events.filter(isConversationStateEvent);
|
|
5365
5642
|
if (conversationEvents.length > 0) await this.stateManager.dispatchBatch(conversationEvents);
|
|
5366
5643
|
};
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
});
|
|
5644
|
+
onStatusChange = (listener) => {
|
|
5645
|
+
this.statusListeners.add(listener);
|
|
5646
|
+
return () => {
|
|
5647
|
+
this.statusListeners.delete(listener);
|
|
5648
|
+
};
|
|
5373
5649
|
};
|
|
5374
5650
|
beginRun = () => {
|
|
5375
5651
|
if (this.activeRunId) throw new Error(`Session ${this.sessionId} already has an active run.`);
|
|
5652
|
+
const wasRunning = this.isRunning();
|
|
5376
5653
|
const runId = `agent-run-${randomUUID()}`;
|
|
5377
5654
|
const controller = new AbortController();
|
|
5378
5655
|
this.activeRunId = runId;
|
|
5379
5656
|
this.activeRunController = controller;
|
|
5657
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5380
5658
|
return {
|
|
5381
5659
|
runId,
|
|
5382
5660
|
signal: controller.signal
|
|
@@ -5385,30 +5663,43 @@ var SessionRun = class {
|
|
|
5385
5663
|
abortRun = (runId) => {
|
|
5386
5664
|
if (!this.activeRunId || !this.activeRunController) return false;
|
|
5387
5665
|
if (runId && this.activeRunId !== runId) return false;
|
|
5666
|
+
const wasRunning = this.isRunning();
|
|
5388
5667
|
this.activeRunController.abort();
|
|
5668
|
+
this.activeRunController = null;
|
|
5669
|
+
this.activeRunId = null;
|
|
5670
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5389
5671
|
return true;
|
|
5390
5672
|
};
|
|
5391
5673
|
isRunning = () => this.activeRunId !== null;
|
|
5392
5674
|
dispose = () => {
|
|
5675
|
+
const wasRunning = this.isRunning();
|
|
5393
5676
|
this.activeRunController?.abort();
|
|
5394
5677
|
this.activeRunController = null;
|
|
5395
5678
|
this.activeRunId = null;
|
|
5679
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5680
|
+
this.statusListeners.clear();
|
|
5396
5681
|
};
|
|
5397
5682
|
applyRunEvents = (events) => {
|
|
5683
|
+
const wasRunning = this.isRunning();
|
|
5398
5684
|
for (const event of events) {
|
|
5399
5685
|
if (event.type === NcpEventType.RunStarted && event.payload.runId) this.activeRunId = event.payload.runId;
|
|
5400
|
-
if (
|
|
5686
|
+
if ((event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError) && (!event.payload.runId || event.payload.runId === this.activeRunId)) {
|
|
5401
5687
|
this.activeRunId = null;
|
|
5402
5688
|
this.activeRunController = null;
|
|
5403
5689
|
}
|
|
5404
5690
|
}
|
|
5691
|
+
this.emitStatusChangeIfNeeded(wasRunning);
|
|
5692
|
+
};
|
|
5693
|
+
emitStatusChangeIfNeeded = (wasRunning) => {
|
|
5694
|
+
const running = this.isRunning();
|
|
5695
|
+
if (running === wasRunning) return;
|
|
5696
|
+
for (const listener of [...this.statusListeners]) listener(running ? "running" : "idle");
|
|
5405
5697
|
};
|
|
5406
5698
|
};
|
|
5407
5699
|
var SessionRunManager = class {
|
|
5408
5700
|
runs = /* @__PURE__ */ new Map();
|
|
5409
|
-
constructor(sessionManager
|
|
5701
|
+
constructor(sessionManager) {
|
|
5410
5702
|
this.sessionManager = sessionManager;
|
|
5411
|
-
this.eventBus = eventBus;
|
|
5412
5703
|
}
|
|
5413
5704
|
getSessionRun = (sessionId) => this.runs.get(sessionId) ?? null;
|
|
5414
5705
|
isSessionRunning = (sessionId) => this.runs.get(sessionId.trim())?.isRunning() ?? false;
|
|
@@ -5417,7 +5708,7 @@ var SessionRunManager = class {
|
|
|
5417
5708
|
const run = new SessionRun({
|
|
5418
5709
|
messages: await this.sessionManager.listSessionMessages(sessionId),
|
|
5419
5710
|
sessionId
|
|
5420
|
-
}
|
|
5711
|
+
});
|
|
5421
5712
|
this.runs.set(sessionId, run);
|
|
5422
5713
|
return run;
|
|
5423
5714
|
};
|
|
@@ -5669,7 +5960,7 @@ var NcpAgentSessionMetadataStore = class {
|
|
|
5669
5960
|
read = async (sessionId, activitySnapshot) => {
|
|
5670
5961
|
try {
|
|
5671
5962
|
const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
|
|
5672
|
-
if (!isRecord$
|
|
5963
|
+
if (!isRecord$10(parsed) || parsed._type !== "metadata" || !isRecord$10(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
|
|
5673
5964
|
const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
|
|
5674
5965
|
const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
5675
5966
|
return {
|
|
@@ -5770,7 +6061,7 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
5770
6061
|
function serializeJournalEntry(entry) {
|
|
5771
6062
|
const serialized = JSON.stringify(entry);
|
|
5772
6063
|
if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
|
|
5773
|
-
if (!isRecord$
|
|
6064
|
+
if (!isRecord$10(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
|
|
5774
6065
|
return serialized;
|
|
5775
6066
|
}
|
|
5776
6067
|
var NcpAgentSessionJournalStore = class {
|
|
@@ -5986,15 +6277,15 @@ var NcpAgentSessionJournalStore = class {
|
|
|
5986
6277
|
console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5987
6278
|
continue;
|
|
5988
6279
|
}
|
|
5989
|
-
if (!isRecord$
|
|
6280
|
+
if (!isRecord$10(parsed)) continue;
|
|
5990
6281
|
if (parsed._type === "metadata") {
|
|
5991
|
-
metadata = isRecord$
|
|
6282
|
+
metadata = isRecord$10(parsed.metadata) ? structuredClone(parsed.metadata) : {};
|
|
5992
6283
|
agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
5993
6284
|
createdAt = toIsoString(parsed.created_at, createdAt);
|
|
5994
6285
|
updatedAt = toIsoString(parsed.updated_at, updatedAt);
|
|
5995
6286
|
continue;
|
|
5996
6287
|
}
|
|
5997
|
-
if (parsed._type === "event" && isRecord$
|
|
6288
|
+
if (parsed._type === "event" && isRecord$10(parsed.event)) {
|
|
5998
6289
|
const seq = Number(parsed.seq);
|
|
5999
6290
|
nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
|
|
6000
6291
|
updatedAt = toIsoString(parsed.timestamp, updatedAt);
|
|
@@ -7770,11 +8061,19 @@ function formatErrorStatus(error) {
|
|
|
7770
8061
|
}
|
|
7771
8062
|
return "运行出错";
|
|
7772
8063
|
}
|
|
7773
|
-
function
|
|
8064
|
+
function readToolCallId(value) {
|
|
8065
|
+
if (typeof value !== "string") return null;
|
|
8066
|
+
const trimmed = value.trim();
|
|
8067
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
8068
|
+
}
|
|
8069
|
+
function formatToolDoneStatus(toolName) {
|
|
8070
|
+
return toolName ? `工具调用完成:${toolName}` : "工具调用完成";
|
|
8071
|
+
}
|
|
8072
|
+
function createSessionActivityPreviewFromNcpEvent(event, timestamp, options = {}) {
|
|
7774
8073
|
switch (event.type) {
|
|
7775
8074
|
case NcpEventType.RunStarted: return createProjection(readSessionId(event.payload.sessionId), {
|
|
7776
8075
|
state: "running",
|
|
7777
|
-
statusText: "
|
|
8076
|
+
statusText: "正在思考",
|
|
7778
8077
|
timestamp
|
|
7779
8078
|
});
|
|
7780
8079
|
case NcpEventType.RunFinished: return createProjection(readSessionId(event.payload.sessionId), {
|
|
@@ -7815,11 +8114,15 @@ function createSessionActivityPreviewFromNcpEvent(event, timestamp) {
|
|
|
7815
8114
|
timestamp
|
|
7816
8115
|
});
|
|
7817
8116
|
case NcpEventType.MessageToolCallEnd:
|
|
7818
|
-
case NcpEventType.MessageToolCallResult:
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
8117
|
+
case NcpEventType.MessageToolCallResult: {
|
|
8118
|
+
const sessionId = readSessionId(event.payload.sessionId);
|
|
8119
|
+
const toolCallId = readToolCallId(event.payload.toolCallId);
|
|
8120
|
+
return createProjection(sessionId, {
|
|
8121
|
+
state: "running",
|
|
8122
|
+
statusText: formatToolDoneStatus(sessionId && toolCallId ? options.readToolName?.(sessionId, toolCallId) ?? null : null),
|
|
8123
|
+
timestamp
|
|
8124
|
+
});
|
|
8125
|
+
}
|
|
7823
8126
|
default: return null;
|
|
7824
8127
|
}
|
|
7825
8128
|
}
|
|
@@ -7887,13 +8190,16 @@ function writeSessionActivityPreviewMetadata(metadata, projection) {
|
|
|
7887
8190
|
var SessionActivityPreviewContribution = class {
|
|
7888
8191
|
unsubscribeNcpEvent = null;
|
|
7889
8192
|
metadataWriteChains = /* @__PURE__ */ new Map();
|
|
8193
|
+
toolNames = /* @__PURE__ */ new Map();
|
|
7890
8194
|
constructor(kernel) {
|
|
7891
8195
|
this.kernel = kernel;
|
|
7892
8196
|
}
|
|
7893
8197
|
start = () => {
|
|
7894
8198
|
if (this.unsubscribeNcpEvent) return;
|
|
7895
8199
|
this.unsubscribeNcpEvent = this.kernel.eventBus.on(eventKeys.ncpEvent, (event) => {
|
|
7896
|
-
|
|
8200
|
+
this.rememberToolName(event);
|
|
8201
|
+
const projection = createSessionActivityPreviewFromNcpEvent(event, (/* @__PURE__ */ new Date()).toISOString(), { readToolName: this.readToolName });
|
|
8202
|
+
this.clearFinishedRunToolNames(event);
|
|
7897
8203
|
if (!projection) return;
|
|
7898
8204
|
this.updatePreview(projection);
|
|
7899
8205
|
});
|
|
@@ -7902,7 +8208,29 @@ var SessionActivityPreviewContribution = class {
|
|
|
7902
8208
|
this.unsubscribeNcpEvent?.();
|
|
7903
8209
|
this.unsubscribeNcpEvent = null;
|
|
7904
8210
|
this.metadataWriteChains.clear();
|
|
8211
|
+
this.toolNames.clear();
|
|
8212
|
+
};
|
|
8213
|
+
rememberToolName = (event) => {
|
|
8214
|
+
if (event.type !== NcpEventType.MessageToolCallStart) return;
|
|
8215
|
+
const sessionId = event.payload.sessionId.trim();
|
|
8216
|
+
const toolCallId = event.payload.toolCallId.trim();
|
|
8217
|
+
const toolName = event.payload.toolName.trim();
|
|
8218
|
+
if (!sessionId || !toolCallId || !toolName) return;
|
|
8219
|
+
this.toolNames.set(this.createToolNameKey(sessionId, toolCallId), toolName);
|
|
8220
|
+
};
|
|
8221
|
+
readToolName = (sessionId, toolCallId) => this.toolNames.get(this.createToolNameKey(sessionId, toolCallId)) ?? null;
|
|
8222
|
+
clearFinishedRunToolNames = (event) => {
|
|
8223
|
+
if (event.type !== NcpEventType.RunFinished && event.type !== NcpEventType.RunError) return;
|
|
8224
|
+
const sessionId = this.readNonEmptyString(event.payload.sessionId);
|
|
8225
|
+
if (!sessionId) return;
|
|
8226
|
+
for (const key of this.toolNames.keys()) if (key.startsWith(`${sessionId}:`)) this.toolNames.delete(key);
|
|
8227
|
+
};
|
|
8228
|
+
readNonEmptyString = (value) => {
|
|
8229
|
+
if (typeof value !== "string") return null;
|
|
8230
|
+
const trimmed = value.trim();
|
|
8231
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
7905
8232
|
};
|
|
8233
|
+
createToolNameKey = (sessionId, toolCallId) => `${sessionId}:${toolCallId}`;
|
|
7906
8234
|
updatePreview = (projection) => {
|
|
7907
8235
|
const next = (this.metadataWriteChains.get(projection.sessionId) ?? Promise.resolve()).then(() => this.updatePreviewMetadata(projection));
|
|
7908
8236
|
this.metadataWriteChains.set(projection.sessionId, next.catch(() => void 0));
|
|
@@ -8023,11 +8351,11 @@ var MessagingToolProvider = class {
|
|
|
8023
8351
|
});
|
|
8024
8352
|
const { channel, chatId, metadata } = toolRunContext;
|
|
8025
8353
|
const accountId = readMetadataAccountId(metadata);
|
|
8026
|
-
const messageTool = new MessageTool((message) =>
|
|
8354
|
+
const messageTool = new MessageTool(async (message) => {
|
|
8355
|
+
if (!await this.kernel.channels.deliver(message)) throw new Error(`channel "${message.channel}" is not available`);
|
|
8356
|
+
}, { resolveChannels: this.resolveMessageChannels });
|
|
8027
8357
|
messageTool.setContext(channel, chatId, accountId ?? null);
|
|
8028
|
-
|
|
8029
|
-
cronTool.setContext(channel, chatId, accountId ?? null);
|
|
8030
|
-
return [messageTool, cronTool];
|
|
8358
|
+
return [messageTool, new CronTool(this.kernel.automation)];
|
|
8031
8359
|
};
|
|
8032
8360
|
resolveMessageChannels = () => {
|
|
8033
8361
|
const channels = this.kernel.extensions.getExtensionRegistry()?.channels ?? [];
|
|
@@ -8615,7 +8943,7 @@ var NextclawKernel = class {
|
|
|
8615
8943
|
})
|
|
8616
8944
|
});
|
|
8617
8945
|
this.contextCompactionManager = new AgentRunContextCompactionManager(this.configManager, this.llmProviders, this.sessionManager);
|
|
8618
|
-
this.sessionRunManager = new SessionRunManager(this.sessionManager
|
|
8946
|
+
this.sessionRunManager = new SessionRunManager(this.sessionManager);
|
|
8619
8947
|
this.agentRunRequestManager = new AgentRunRequestManager(this.agentRuntimeManager, this.configManager, this.contextProviderManager, this.eventBus, this.ingress, this.sessionManager, this.sessionRunManager, this.toolProviderManager);
|
|
8620
8948
|
this.contributions = [
|
|
8621
8949
|
new ToolProviderContribution(this),
|