@nextclaw/server 0.12.25 → 0.12.26-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 +107 -94
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +173 -636
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
package/dist/index.js
CHANGED
|
@@ -2,13 +2,12 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { compress } from "hono/compress";
|
|
3
3
|
import { serve } from "@hono/node-server";
|
|
4
4
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5
|
-
import
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { open, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
7
|
-
import
|
|
7
|
+
import { dirname, extname, isAbsolute, join, parse, resolve } from "node:path";
|
|
8
8
|
import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
9
9
|
import * as NextclawCore from "@nextclaw/core";
|
|
10
|
-
import { ConfigSchema, DEFAULT_WORKSPACE_PATH, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, hasSecretRef, isSensitiveConfigPath, loadConfig,
|
|
11
|
-
import { discoverPluginStatusReport, enablePluginInConfig, mergePluginConfigView, toPluginConfigView } from "@nextclaw/openclaw-compat";
|
|
10
|
+
import { ConfigSchema, DEFAULT_WORKSPACE_PATH, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, hasSecretRef, isSensitiveConfigPath, loadConfig, mergeExtensionConfigView, normalizeProviderModelConfig, parseThinkingLevel, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
|
|
12
11
|
import { homedir } from "node:os";
|
|
13
12
|
import { findBuiltinProviderByName, listBuiltinProviders } from "@nextclaw/runtime";
|
|
14
13
|
import { McpInstalledViewService } from "@nextclaw/mcp";
|
|
@@ -474,6 +473,8 @@ const ingressKeys = {
|
|
|
474
473
|
extension: {
|
|
475
474
|
channelConfigGet: createTypedKey("extension.channel.config.get"),
|
|
476
475
|
channelMessageSubmit: createTypedKey("extension.channel.message.submit"),
|
|
476
|
+
channelCommandList: createTypedKey("extension.channel.command.list"),
|
|
477
|
+
channelCommandExecute: createTypedKey("extension.channel.command.execute"),
|
|
477
478
|
response: createTypedKey("extension.response")
|
|
478
479
|
},
|
|
479
480
|
agentRun: {
|
|
@@ -649,10 +650,10 @@ function buildFallbackBootstrapStatus() {
|
|
|
649
650
|
return {
|
|
650
651
|
phase: "kernel-starting",
|
|
651
652
|
ncpAgent: { state: "pending" },
|
|
652
|
-
|
|
653
|
+
extensionLoading: {
|
|
653
654
|
state: "pending",
|
|
654
|
-
|
|
655
|
-
|
|
655
|
+
loadedExtensionCount: 0,
|
|
656
|
+
totalExtensionCount: 0
|
|
656
657
|
},
|
|
657
658
|
channels: {
|
|
658
659
|
state: "pending",
|
|
@@ -676,7 +677,7 @@ var AppRoutesController = class {
|
|
|
676
677
|
bootstrapStatus = (c) => c.json(ok(this.options.bootstrapStatus?.getStatus() ?? buildFallbackBootstrapStatus()));
|
|
677
678
|
};
|
|
678
679
|
//#endregion
|
|
679
|
-
//#region src/features/config/utils/
|
|
680
|
+
//#region src/features/config/utils/extension-channel-config-projection.utils.ts
|
|
680
681
|
const DOCS_BASE_URL = "https://docs.nextclaw.io";
|
|
681
682
|
const CHANNEL_TUTORIAL_URLS = {
|
|
682
683
|
feishu: {
|
|
@@ -686,14 +687,15 @@ const CHANNEL_TUTORIAL_URLS = {
|
|
|
686
687
|
},
|
|
687
688
|
weixin: { default: "https://npmx.dev/package/@nextclaw/channel-extension-weixin" }
|
|
688
689
|
};
|
|
689
|
-
function
|
|
690
|
+
function normalizeExtensionProjectionOptions(options) {
|
|
690
691
|
return {
|
|
691
|
-
|
|
692
|
-
|
|
692
|
+
extensionChannelBindings: options?.extensionChannelBindings ?? [],
|
|
693
|
+
extensionUiMetadata: options?.extensionUiMetadata ?? []
|
|
693
694
|
};
|
|
694
695
|
}
|
|
695
696
|
function getProjectedConfigView(config, options) {
|
|
696
|
-
|
|
697
|
+
normalizeExtensionProjectionOptions(options);
|
|
698
|
+
return toExtensionConfigView(config);
|
|
697
699
|
}
|
|
698
700
|
function getProjectedChannelMap(config, options) {
|
|
699
701
|
const channels = getProjectedConfigView(config, options).channels;
|
|
@@ -705,12 +707,12 @@ function getProjectedChannelConfig(config, channelName, options) {
|
|
|
705
707
|
if (!channel || typeof channel !== "object" || Array.isArray(channel)) return null;
|
|
706
708
|
return channel;
|
|
707
709
|
}
|
|
708
|
-
function
|
|
709
|
-
const normalized =
|
|
710
|
-
if (normalized.
|
|
710
|
+
function buildExtensionChannelUiHints(options) {
|
|
711
|
+
const normalized = normalizeExtensionProjectionOptions(options);
|
|
712
|
+
if (normalized.extensionChannelBindings.length === 0) return {};
|
|
711
713
|
const hints = {};
|
|
712
|
-
const metadataById = new Map(normalized.
|
|
713
|
-
for (const binding of normalized.
|
|
714
|
+
const metadataById = new Map(normalized.extensionUiMetadata.map((item) => [item.id, item]));
|
|
715
|
+
for (const binding of normalized.extensionChannelBindings) {
|
|
714
716
|
const channelScope = `channels.${binding.channelId}`;
|
|
715
717
|
const channelMeta = binding.channel.meta;
|
|
716
718
|
const channelLabel = typeof channelMeta?.selectionLabel === "string" ? channelMeta.selectionLabel : typeof channelMeta?.label === "string" ? channelMeta.label : binding.channelId;
|
|
@@ -719,8 +721,8 @@ function buildPluginChannelUiHints(options) {
|
|
|
719
721
|
...channelLabel ? { label: channelLabel } : {},
|
|
720
722
|
...channelHelp ? { help: channelHelp } : {}
|
|
721
723
|
};
|
|
722
|
-
const
|
|
723
|
-
for (const [key, hint] of Object.entries(
|
|
724
|
+
const extensionHints = metadataById.get(binding.extensionId)?.configUiHints ?? {};
|
|
725
|
+
for (const [key, hint] of Object.entries(extensionHints)) hints[`${channelScope}.${key}`] = {
|
|
724
726
|
label: hint.label,
|
|
725
727
|
help: hint.help,
|
|
726
728
|
advanced: hint.advanced,
|
|
@@ -731,9 +733,9 @@ function buildPluginChannelUiHints(options) {
|
|
|
731
733
|
return hints;
|
|
732
734
|
}
|
|
733
735
|
function buildProjectedChannelMeta(config, options) {
|
|
734
|
-
const normalized =
|
|
736
|
+
const normalized = normalizeExtensionProjectionOptions(options);
|
|
735
737
|
const projectedChannelMap = getProjectedChannelMap(config, normalized);
|
|
736
|
-
const bindingByChannelId = new Map(normalized.
|
|
738
|
+
const bindingByChannelId = new Map(normalized.extensionChannelBindings.map((binding) => [binding.channelId, binding]));
|
|
737
739
|
return [...new Set([
|
|
738
740
|
...Object.keys(config.channels),
|
|
739
741
|
...Object.keys(projectedChannelMap),
|
|
@@ -751,25 +753,25 @@ function buildProjectedChannelMeta(config, options) {
|
|
|
751
753
|
};
|
|
752
754
|
});
|
|
753
755
|
}
|
|
754
|
-
function
|
|
755
|
-
const normalized =
|
|
756
|
-
if (!normalized.
|
|
756
|
+
function mergeProjectedExtensionChannelConfig(config, channelName, mergedChannel, options) {
|
|
757
|
+
const normalized = normalizeExtensionProjectionOptions(options);
|
|
758
|
+
if (!normalized.extensionChannelBindings.some((binding) => binding.channelId === channelName)) return null;
|
|
757
759
|
const currentView = getProjectedConfigView(config, normalized);
|
|
758
|
-
return
|
|
760
|
+
return mergeExtensionConfigView(config, {
|
|
759
761
|
...currentView,
|
|
760
762
|
channels: {
|
|
761
763
|
...currentView.channels ?? {},
|
|
762
764
|
[channelName]: mergedChannel
|
|
763
765
|
}
|
|
764
|
-
}
|
|
766
|
+
});
|
|
765
767
|
}
|
|
766
768
|
//#endregion
|
|
767
769
|
//#region src/features/config/utils/channel-auth.utils.ts
|
|
768
|
-
function
|
|
770
|
+
function cloneChannelConfig(value) {
|
|
769
771
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
770
772
|
return JSON.parse(JSON.stringify(value));
|
|
771
773
|
}
|
|
772
|
-
function
|
|
774
|
+
function findExtensionChannelBinding(bindings, channelId) {
|
|
773
775
|
const normalizedChannelId = channelId.trim().toLowerCase();
|
|
774
776
|
return bindings.find((binding) => binding.channelId.trim().toLowerCase() === normalizedChannelId) ?? null;
|
|
775
777
|
}
|
|
@@ -785,27 +787,27 @@ function toPublicChannelAuthPollResult(result) {
|
|
|
785
787
|
}
|
|
786
788
|
function applyAuthorizedChannelAuthResult(params) {
|
|
787
789
|
const { configPath, binding, result } = params;
|
|
788
|
-
if (result.status !== "authorized" || !result.
|
|
790
|
+
if (result.status !== "authorized" || !result.channelConfig) return;
|
|
789
791
|
const currentConfig = loadConfigOrDefault(configPath);
|
|
790
|
-
saveConfig(
|
|
792
|
+
saveConfig({
|
|
791
793
|
...currentConfig,
|
|
792
794
|
channels: {
|
|
793
795
|
...currentConfig.channels,
|
|
794
|
-
[binding.channelId]: result.
|
|
796
|
+
[binding.channelId]: result.channelConfig
|
|
795
797
|
}
|
|
796
|
-
},
|
|
798
|
+
}, configPath);
|
|
797
799
|
}
|
|
798
800
|
async function startChannelAuth(params) {
|
|
799
801
|
const { configPath, channelId, request, bindings } = params;
|
|
800
|
-
const binding =
|
|
802
|
+
const binding = findExtensionChannelBinding(bindings, channelId);
|
|
801
803
|
const start = binding?.channel.auth?.start;
|
|
802
804
|
if (!binding || !start) return null;
|
|
803
|
-
const configView = getProjectedConfigView(loadConfigOrDefault(configPath), {
|
|
805
|
+
const configView = getProjectedConfigView(loadConfigOrDefault(configPath), { extensionChannelBindings: bindings });
|
|
804
806
|
return await start({
|
|
805
807
|
cfg: configView,
|
|
806
|
-
|
|
808
|
+
extensionId: binding.extensionId,
|
|
807
809
|
channelId: binding.channelId,
|
|
808
|
-
|
|
810
|
+
channelConfig: cloneChannelConfig(configView.channels?.[binding.channelId]),
|
|
809
811
|
accountId: request.accountId?.trim() || null,
|
|
810
812
|
baseUrl: request.baseUrl?.trim() || null,
|
|
811
813
|
domain: request.domain?.trim() || null
|
|
@@ -813,15 +815,15 @@ async function startChannelAuth(params) {
|
|
|
813
815
|
}
|
|
814
816
|
async function pollChannelAuth(params) {
|
|
815
817
|
const { configPath, channelId, sessionId, bindings } = params;
|
|
816
|
-
const binding =
|
|
818
|
+
const binding = findExtensionChannelBinding(bindings, channelId);
|
|
817
819
|
const poll = binding?.channel.auth?.poll;
|
|
818
820
|
if (!binding || !poll) return null;
|
|
819
|
-
const configView = getProjectedConfigView(loadConfigOrDefault(configPath), {
|
|
821
|
+
const configView = getProjectedConfigView(loadConfigOrDefault(configPath), { extensionChannelBindings: bindings });
|
|
820
822
|
const result = await poll({
|
|
821
823
|
cfg: configView,
|
|
822
|
-
|
|
824
|
+
extensionId: binding.extensionId,
|
|
823
825
|
channelId: binding.channelId,
|
|
824
|
-
|
|
826
|
+
channelConfig: cloneChannelConfig(configView.channels?.[binding.channelId]),
|
|
825
827
|
sessionId
|
|
826
828
|
});
|
|
827
829
|
if (!result) return null;
|
|
@@ -832,9 +834,31 @@ async function pollChannelAuth(params) {
|
|
|
832
834
|
});
|
|
833
835
|
return toPublicChannelAuthPollResult(result);
|
|
834
836
|
}
|
|
837
|
+
async function connectChannelAuth(params) {
|
|
838
|
+
const { configPath, channelId, request, bindings } = params;
|
|
839
|
+
const binding = findExtensionChannelBinding(bindings, channelId);
|
|
840
|
+
const connect = binding?.channel.auth?.connect;
|
|
841
|
+
if (!binding || !connect) return null;
|
|
842
|
+
const configView = getProjectedConfigView(loadConfigOrDefault(configPath), { extensionChannelBindings: bindings });
|
|
843
|
+
const result = await connect({
|
|
844
|
+
cfg: configView,
|
|
845
|
+
extensionId: binding.extensionId,
|
|
846
|
+
channelId: binding.channelId,
|
|
847
|
+
channelConfig: cloneChannelConfig(configView.channels?.[binding.channelId]),
|
|
848
|
+
accountId: request.accountId?.trim() || null,
|
|
849
|
+
domain: request.domain?.trim() || null,
|
|
850
|
+
fields: request.fields
|
|
851
|
+
});
|
|
852
|
+
applyAuthorizedChannelAuthResult({
|
|
853
|
+
configPath,
|
|
854
|
+
binding,
|
|
855
|
+
result
|
|
856
|
+
});
|
|
857
|
+
return toPublicChannelAuthPollResult(result);
|
|
858
|
+
}
|
|
835
859
|
//#endregion
|
|
836
860
|
//#region src/features/config/utils/default-provider-config.utils.ts
|
|
837
|
-
function createDefaultProviderConfig(defaultWireApi = "auto", defaultModels = []) {
|
|
861
|
+
function createDefaultProviderConfig(defaultWireApi = "auto", defaultModels = [], modelConfig = {}) {
|
|
838
862
|
return {
|
|
839
863
|
enabled: true,
|
|
840
864
|
displayName: "",
|
|
@@ -843,11 +867,11 @@ function createDefaultProviderConfig(defaultWireApi = "auto", defaultModels = []
|
|
|
843
867
|
extraHeaders: null,
|
|
844
868
|
wireApi: defaultWireApi,
|
|
845
869
|
models: [...defaultModels],
|
|
846
|
-
|
|
870
|
+
modelConfig
|
|
847
871
|
};
|
|
848
872
|
}
|
|
849
873
|
function createDefaultProviderConfigFromSpec(spec) {
|
|
850
|
-
return createDefaultProviderConfig(spec?.defaultWireApi ?? "auto", spec?.defaultModels ?? []);
|
|
874
|
+
return createDefaultProviderConfig(spec?.defaultWireApi ?? "auto", spec?.defaultModels ?? [], normalizeProviderModelConfig(spec?.modelConfig ?? {}));
|
|
851
875
|
}
|
|
852
876
|
//#endregion
|
|
853
877
|
//#region src/features/config/providers/server-builtin-provider.provider.ts
|
|
@@ -1353,10 +1377,10 @@ var ConfigRoutesController = class {
|
|
|
1353
1377
|
constructor(options) {
|
|
1354
1378
|
this.options = options;
|
|
1355
1379
|
}
|
|
1356
|
-
|
|
1380
|
+
getExtensionConfigProjectionOptions = () => {
|
|
1357
1381
|
return {
|
|
1358
|
-
|
|
1359
|
-
|
|
1382
|
+
extensionChannelBindings: this.options.extensions?.getChannelBindings() ?? [],
|
|
1383
|
+
extensionUiMetadata: this.options.extensions?.getUiMetadata() ?? []
|
|
1360
1384
|
};
|
|
1361
1385
|
};
|
|
1362
1386
|
publishConfigUpdatedPaths = (paths) => {
|
|
@@ -1400,15 +1424,15 @@ var ConfigRoutesController = class {
|
|
|
1400
1424
|
};
|
|
1401
1425
|
getConfig = (c) => {
|
|
1402
1426
|
const config = loadConfigOrDefault(this.options.configPath);
|
|
1403
|
-
return c.json(ok(buildConfigView(config, this.
|
|
1427
|
+
return c.json(ok(buildConfigView(config, this.getExtensionConfigProjectionOptions())));
|
|
1404
1428
|
};
|
|
1405
1429
|
getConfigMeta = (c) => {
|
|
1406
1430
|
const config = loadConfigOrDefault(this.options.configPath);
|
|
1407
|
-
return c.json(ok(buildConfigMeta(config, this.
|
|
1431
|
+
return c.json(ok(buildConfigMeta(config, this.getExtensionConfigProjectionOptions())));
|
|
1408
1432
|
};
|
|
1409
1433
|
getConfigSchema = (c) => {
|
|
1410
1434
|
const config = loadConfigOrDefault(this.options.configPath);
|
|
1411
|
-
return c.json(ok(buildConfigSchemaView(config, this.
|
|
1435
|
+
return c.json(ok(buildConfigSchemaView(config, this.getExtensionConfigProjectionOptions())));
|
|
1412
1436
|
};
|
|
1413
1437
|
updateConfigModel = async (c) => {
|
|
1414
1438
|
const body = await readJson(c.req.raw);
|
|
@@ -1521,7 +1545,7 @@ var ConfigRoutesController = class {
|
|
|
1521
1545
|
const channel = c.req.param("channel");
|
|
1522
1546
|
const body = await readJson(c.req.raw);
|
|
1523
1547
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1524
|
-
const result = updateChannel(this.options.configPath, channel, body.data, this.
|
|
1548
|
+
const result = updateChannel(this.options.configPath, channel, body.data, this.getExtensionConfigProjectionOptions());
|
|
1525
1549
|
if (!result) return c.json(err("NOT_FOUND", `unknown channel: ${channel}`), 404);
|
|
1526
1550
|
this.publishConfigUpdatedPaths([`channels.${channel}`]);
|
|
1527
1551
|
this.enqueueChannelConfigApply(channel);
|
|
@@ -1545,7 +1569,7 @@ var ConfigRoutesController = class {
|
|
|
1545
1569
|
baseUrl: typeof payload.baseUrl === "string" ? payload.baseUrl : void 0,
|
|
1546
1570
|
domain: typeof payload.domain === "string" ? payload.domain : void 0
|
|
1547
1571
|
},
|
|
1548
|
-
bindings: this.options.
|
|
1572
|
+
bindings: this.options.extensions?.getChannelBindings() ?? []
|
|
1549
1573
|
});
|
|
1550
1574
|
if (!result) return c.json(err("NOT_SUPPORTED", `channel auth is not supported: ${channel}`), 404);
|
|
1551
1575
|
return c.json(ok(result));
|
|
@@ -1564,12 +1588,36 @@ var ConfigRoutesController = class {
|
|
|
1564
1588
|
configPath: this.options.configPath,
|
|
1565
1589
|
channelId: channel,
|
|
1566
1590
|
sessionId,
|
|
1567
|
-
bindings: this.options.
|
|
1591
|
+
bindings: this.options.extensions?.getChannelBindings() ?? []
|
|
1568
1592
|
});
|
|
1569
1593
|
if (!result) return c.json(err("NOT_FOUND", "channel auth session not found"), 404);
|
|
1570
1594
|
if (result.status === "authorized") await this.publishConfigUpdates([`channels.${channel}`]);
|
|
1571
1595
|
return c.json(ok(result));
|
|
1572
1596
|
};
|
|
1597
|
+
connectChannelAuth = async (c) => {
|
|
1598
|
+
const channel = c.req.param("channel");
|
|
1599
|
+
const body = await readJson(c.req.raw);
|
|
1600
|
+
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
1601
|
+
const fields = body.data.fields && typeof body.data.fields === "object" && !Array.isArray(body.data.fields) ? body.data.fields : {};
|
|
1602
|
+
try {
|
|
1603
|
+
const result = await connectChannelAuth({
|
|
1604
|
+
configPath: this.options.configPath,
|
|
1605
|
+
channelId: channel,
|
|
1606
|
+
request: {
|
|
1607
|
+
accountId: typeof body.data.accountId === "string" ? body.data.accountId : void 0,
|
|
1608
|
+
domain: typeof body.data.domain === "string" ? body.data.domain : void 0,
|
|
1609
|
+
fields
|
|
1610
|
+
},
|
|
1611
|
+
bindings: this.options.extensions?.getChannelBindings() ?? []
|
|
1612
|
+
});
|
|
1613
|
+
if (!result) return c.json(err("NOT_SUPPORTED", `channel auth connect is not supported: ${channel}`), 404);
|
|
1614
|
+
if (result.status === "authorized") await this.publishConfigUpdates([`channels.${channel}`]);
|
|
1615
|
+
return c.json(ok(result));
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1618
|
+
return c.json(err("AUTH_CONNECT_FAILED", message), 400);
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1573
1621
|
updateSecrets = async (c) => {
|
|
1574
1622
|
const body = await readJson(c.req.raw);
|
|
1575
1623
|
if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
@@ -2059,23 +2107,6 @@ function normalizeModelList(input) {
|
|
|
2059
2107
|
}
|
|
2060
2108
|
return [...deduped];
|
|
2061
2109
|
}
|
|
2062
|
-
function normalizeModelThinkingConfig(input) {
|
|
2063
|
-
if (!input || typeof input !== "object") return {};
|
|
2064
|
-
const normalized = {};
|
|
2065
|
-
for (const [rawModel, rawValue] of Object.entries(input)) {
|
|
2066
|
-
const model = rawModel.trim();
|
|
2067
|
-
if (!model || !rawValue || typeof rawValue !== "object") continue;
|
|
2068
|
-
const supported = normalizeThinkingLevels(rawValue.supported);
|
|
2069
|
-
if (supported.length === 0) continue;
|
|
2070
|
-
const defaultLevel = parseThinkingLevel(rawValue.default);
|
|
2071
|
-
if (defaultLevel && supported.includes(defaultLevel)) normalized[model] = {
|
|
2072
|
-
supported,
|
|
2073
|
-
default: defaultLevel
|
|
2074
|
-
};
|
|
2075
|
-
else normalized[model] = { supported };
|
|
2076
|
-
}
|
|
2077
|
-
return normalized;
|
|
2078
|
-
}
|
|
2079
2110
|
function toProviderView(config, provider, providerName, uiHints, spec) {
|
|
2080
2111
|
const apiKeyRefSet = hasSecretRef(config, `providers.${providerName}.apiKey`);
|
|
2081
2112
|
const masked = maskApiKey(provider.apiKey);
|
|
@@ -2088,7 +2119,7 @@ function toProviderView(config, provider, providerName, uiHints, spec) {
|
|
|
2088
2119
|
apiBase: provider.apiBase ?? null,
|
|
2089
2120
|
extraHeaders: extraHeaders && Object.keys(extraHeaders).length > 0 ? extraHeaders : null,
|
|
2090
2121
|
models: normalizeModelList(provider.models ?? []),
|
|
2091
|
-
|
|
2122
|
+
modelConfig: normalizeProviderModelConfig(provider.modelConfig ?? {})
|
|
2092
2123
|
};
|
|
2093
2124
|
if (Boolean(spec?.supportsWireApi) || isCustomProviderName(providerName)) view.wireApi = provider.wireApi ?? spec?.defaultWireApi ?? "auto";
|
|
2094
2125
|
return view;
|
|
@@ -2204,6 +2235,7 @@ function buildConfigMeta(config, options) {
|
|
|
2204
2235
|
supportsCliImport: Boolean(spec.auth.cliCredential)
|
|
2205
2236
|
} : void 0,
|
|
2206
2237
|
defaultModels: normalizeModelList(spec.defaultModels ?? []),
|
|
2238
|
+
modelConfig: normalizeProviderModelConfig(spec.modelConfig ?? {}),
|
|
2207
2239
|
supportsWireApi: spec.supportsWireApi,
|
|
2208
2240
|
wireApiOptions: spec.wireApiOptions,
|
|
2209
2241
|
defaultWireApi: spec.defaultWireApi
|
|
@@ -2237,6 +2269,7 @@ function buildConfigMeta(config, options) {
|
|
|
2237
2269
|
apiBaseHelp: void 0,
|
|
2238
2270
|
auth: void 0,
|
|
2239
2271
|
defaultModels: [],
|
|
2272
|
+
modelConfig: {},
|
|
2240
2273
|
supportsWireApi: true,
|
|
2241
2274
|
wireApiOptions: CUSTOM_PROVIDER_WIRE_API_OPTIONS,
|
|
2242
2275
|
defaultWireApi: "auto"
|
|
@@ -2248,13 +2281,13 @@ function buildConfigMeta(config, options) {
|
|
|
2248
2281
|
}
|
|
2249
2282
|
function buildConfigSchemaView(_config, options) {
|
|
2250
2283
|
const base = buildConfigSchema({ version: getPackageVersion() });
|
|
2251
|
-
const
|
|
2252
|
-
if (Object.keys(
|
|
2284
|
+
const extensionUiHints = buildExtensionChannelUiHints(options);
|
|
2285
|
+
if (Object.keys(extensionUiHints).length === 0) return base;
|
|
2253
2286
|
return {
|
|
2254
2287
|
...base,
|
|
2255
2288
|
uiHints: {
|
|
2256
2289
|
...base.uiHints,
|
|
2257
|
-
...
|
|
2290
|
+
...extensionUiHints
|
|
2258
2291
|
}
|
|
2259
2292
|
};
|
|
2260
2293
|
}
|
|
@@ -2323,7 +2356,7 @@ function updateProvider(configPath, providerName, patch) {
|
|
|
2323
2356
|
if (Object.prototype.hasOwnProperty.call(patch, "extraHeaders")) provider.extraHeaders = patch.extraHeaders ?? null;
|
|
2324
2357
|
if (Object.prototype.hasOwnProperty.call(patch, "wireApi") && (spec?.supportsWireApi || isCustom)) provider.wireApi = patch.wireApi ?? spec?.defaultWireApi ?? "auto";
|
|
2325
2358
|
if (Object.prototype.hasOwnProperty.call(patch, "models")) provider.models = normalizeModelList(patch.models ?? []);
|
|
2326
|
-
if (Object.prototype.hasOwnProperty.call(patch, "
|
|
2359
|
+
if (Object.prototype.hasOwnProperty.call(patch, "modelConfig")) provider.modelConfig = normalizeProviderModelConfig(patch.modelConfig ?? {});
|
|
2327
2360
|
const next = ConfigSchema.parse(config);
|
|
2328
2361
|
saveConfig(next, configPath);
|
|
2329
2362
|
const uiHints = buildUiHints(next);
|
|
@@ -2343,7 +2376,7 @@ function createCustomProvider(configPath, patch = {}) {
|
|
|
2343
2376
|
extraHeaders: normalizeHeaders(patch.extraHeaders ?? null),
|
|
2344
2377
|
wireApi: patch.wireApi ?? "auto",
|
|
2345
2378
|
models: normalizeModelList(patch.models ?? []),
|
|
2346
|
-
|
|
2379
|
+
modelConfig: normalizeProviderModelConfig(patch.modelConfig ?? {})
|
|
2347
2380
|
};
|
|
2348
2381
|
const next = ConfigSchema.parse(config);
|
|
2349
2382
|
saveConfig(next, configPath);
|
|
@@ -2491,7 +2524,7 @@ async function testProviderConnection(configPath, providerName, patch, providerM
|
|
|
2491
2524
|
}
|
|
2492
2525
|
function updateChannel(configPath, channelName, patch, options) {
|
|
2493
2526
|
const config = loadConfigOrDefault(configPath);
|
|
2494
|
-
const normalizedOptions =
|
|
2527
|
+
const normalizedOptions = normalizeExtensionProjectionOptions(options);
|
|
2495
2528
|
const channel = getProjectedChannelConfig(config, channelName, normalizedOptions);
|
|
2496
2529
|
if (!channel) return null;
|
|
2497
2530
|
for (const key of Object.keys(patch)) {
|
|
@@ -2502,9 +2535,9 @@ function updateChannel(configPath, channelName, patch, options) {
|
|
|
2502
2535
|
...channel,
|
|
2503
2536
|
...patch
|
|
2504
2537
|
};
|
|
2505
|
-
const
|
|
2506
|
-
if (
|
|
2507
|
-
const next = ConfigSchema.parse(
|
|
2538
|
+
const mergedExtensionConfig = mergeProjectedExtensionChannelConfig(config, channelName, mergedChannel, normalizedOptions);
|
|
2539
|
+
if (mergedExtensionConfig) {
|
|
2540
|
+
const next = ConfigSchema.parse(mergedExtensionConfig);
|
|
2508
2541
|
saveConfig(next, configPath);
|
|
2509
2542
|
return sanitizePublicConfigValue(getProjectedChannelConfig(next, channelName, normalizedOptions) ?? {}, `channels.${channelName}`, buildUiHints(next, normalizedOptions));
|
|
2510
2543
|
}
|
|
@@ -2884,7 +2917,6 @@ function isSessionProjectRootValidationError(error) {
|
|
|
2884
2917
|
//#endregion
|
|
2885
2918
|
//#region src/features/marketplace/configs/marketplace.constants.config.ts
|
|
2886
2919
|
const DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
|
|
2887
|
-
const NEXTCLAW_PLUGIN_NPM_PREFIX = "@nextclaw/channel-plugin-";
|
|
2888
2920
|
const MARKETPLACE_ZH_COPY_BY_SLUG = {
|
|
2889
2921
|
weather: {
|
|
2890
2922
|
summary: "NextClaw 内置技能,用于天气查询工作流。",
|
|
@@ -2923,40 +2955,16 @@ const MARKETPLACE_ZH_COPY_BY_SLUG = {
|
|
|
2923
2955
|
description: "使用该技能可打开、编辑、清洗并转换 .xlsx 与 .csv 等表格文件。"
|
|
2924
2956
|
},
|
|
2925
2957
|
bird: {
|
|
2926
|
-
summary: "
|
|
2958
|
+
summary: "社区技能,用于 X/Twitter 读取/搜索/发布工作流。",
|
|
2927
2959
|
description: "使用 bird CLI 在代理工作流中读取线程、搜索帖子并起草推文/回复。"
|
|
2928
2960
|
},
|
|
2929
2961
|
"cloudflare-deploy": {
|
|
2930
2962
|
summary: "OpenAI 精选技能,用于在 Cloudflare 上部署应用与基础设施。",
|
|
2931
2963
|
description: "使用该技能可选择 Cloudflare 产品并部署 Workers、Pages 及相关服务。"
|
|
2932
2964
|
},
|
|
2933
|
-
"channel-
|
|
2934
|
-
summary: "
|
|
2935
|
-
description: "通过 NextClaw
|
|
2936
|
-
},
|
|
2937
|
-
"channel-plugin-telegram": {
|
|
2938
|
-
summary: "NextClaw 官方插件,用于 Telegram 渠道集成。",
|
|
2939
|
-
description: "通过 NextClaw 插件运行时提供 Telegram 渠道的入站/出站支持。"
|
|
2940
|
-
},
|
|
2941
|
-
"channel-plugin-slack": {
|
|
2942
|
-
summary: "NextClaw 官方插件,用于 Slack 渠道集成。",
|
|
2943
|
-
description: "通过 NextClaw 插件运行时提供 Slack 渠道的入站/出站支持。"
|
|
2944
|
-
},
|
|
2945
|
-
"channel-plugin-wecom": {
|
|
2946
|
-
summary: "NextClaw 官方插件,用于企业微信渠道集成。",
|
|
2947
|
-
description: "通过 NextClaw 插件运行时提供企业微信渠道的入站/出站支持。"
|
|
2948
|
-
},
|
|
2949
|
-
"channel-plugin-email": {
|
|
2950
|
-
summary: "NextClaw 官方插件,用于 Email 渠道集成。",
|
|
2951
|
-
description: "通过 NextClaw 插件运行时提供 Email 渠道的入站/出站支持。"
|
|
2952
|
-
},
|
|
2953
|
-
"channel-plugin-whatsapp": {
|
|
2954
|
-
summary: "NextClaw 官方插件,用于 WhatsApp 渠道集成。",
|
|
2955
|
-
description: "通过 NextClaw 插件运行时提供 WhatsApp 渠道的入站/出站支持。"
|
|
2956
|
-
},
|
|
2957
|
-
"channel-plugin-clawbay": {
|
|
2958
|
-
summary: "Clawbay 官方渠道插件,用于 NextClaw 集成。",
|
|
2959
|
-
description: "通过插件运行时为 NextClaw 提供 Clawbay 渠道能力。"
|
|
2965
|
+
"channel-extension-clawbay": {
|
|
2966
|
+
summary: "Clawbay 官方渠道扩展,用于 NextClaw 集成。",
|
|
2967
|
+
description: "通过 extension channel 机制为 NextClaw 提供 Clawbay 渠道能力。"
|
|
2960
2968
|
}
|
|
2961
2969
|
};
|
|
2962
2970
|
//#endregion
|
|
@@ -3321,105 +3329,6 @@ var McpMarketplaceController = class {
|
|
|
3321
3329
|
};
|
|
3322
3330
|
};
|
|
3323
3331
|
//#endregion
|
|
3324
|
-
//#region src/features/marketplace/utils/marketplace-spec.utils.ts
|
|
3325
|
-
function readPluginPackageNameFromSource(source) {
|
|
3326
|
-
const trimmed = source?.trim();
|
|
3327
|
-
if (!trimmed) return;
|
|
3328
|
-
let cursor = path.dirname(path.resolve(trimmed));
|
|
3329
|
-
for (let index = 0; index < 8; index += 1) {
|
|
3330
|
-
try {
|
|
3331
|
-
const manifestPath = path.join(cursor, "package.json");
|
|
3332
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
3333
|
-
if (typeof manifest.name === "string" && manifest.name.trim().length > 0) return manifest.name.trim();
|
|
3334
|
-
} catch {}
|
|
3335
|
-
const parent = path.dirname(cursor);
|
|
3336
|
-
if (parent === cursor) break;
|
|
3337
|
-
cursor = parent;
|
|
3338
|
-
}
|
|
3339
|
-
}
|
|
3340
|
-
function normalizePluginNpmSpec(rawSpec) {
|
|
3341
|
-
const spec = rawSpec.trim();
|
|
3342
|
-
if (!spec.startsWith("@")) return spec;
|
|
3343
|
-
const versionDelimiterIndex = spec.lastIndexOf("@");
|
|
3344
|
-
if (versionDelimiterIndex <= 0) return spec;
|
|
3345
|
-
const packageName = spec.slice(0, versionDelimiterIndex).trim();
|
|
3346
|
-
if (!packageName.includes("/")) return spec;
|
|
3347
|
-
return packageName;
|
|
3348
|
-
}
|
|
3349
|
-
function isSupportedMarketplacePluginSpec(rawSpec) {
|
|
3350
|
-
return normalizePluginNpmSpec(rawSpec).length > 0;
|
|
3351
|
-
}
|
|
3352
|
-
function resolvePluginCanonicalSpec(params) {
|
|
3353
|
-
const { installSpec, pluginId } = params;
|
|
3354
|
-
const rawInstallSpec = typeof installSpec === "string" ? installSpec.trim() : "";
|
|
3355
|
-
if (rawInstallSpec.length > 0) return normalizePluginNpmSpec(rawInstallSpec);
|
|
3356
|
-
if (pluginId.startsWith("builtin-channel-")) {
|
|
3357
|
-
const channelSlug = pluginId.slice(16).trim();
|
|
3358
|
-
if (channelSlug.length > 0) return `${NEXTCLAW_PLUGIN_NPM_PREFIX}${channelSlug}`;
|
|
3359
|
-
}
|
|
3360
|
-
return pluginId;
|
|
3361
|
-
}
|
|
3362
|
-
function resolveDiscoveredPluginCanonicalSpec(params) {
|
|
3363
|
-
return resolvePluginCanonicalSpec({
|
|
3364
|
-
pluginId: params.pluginId,
|
|
3365
|
-
installSpec: params.installSpec ?? readPluginPackageNameFromSource(params.source)
|
|
3366
|
-
});
|
|
3367
|
-
}
|
|
3368
|
-
function readPluginRuntimeStatusPriority(status) {
|
|
3369
|
-
if (status === "loaded") return 400;
|
|
3370
|
-
if (status === "disabled") return 300;
|
|
3371
|
-
if (status === "unresolved") return 200;
|
|
3372
|
-
return 100;
|
|
3373
|
-
}
|
|
3374
|
-
function readPluginOriginPriority(origin) {
|
|
3375
|
-
if (origin === "bundled") return 80;
|
|
3376
|
-
if (origin === "workspace") return 70;
|
|
3377
|
-
if (origin === "global") return 60;
|
|
3378
|
-
if (origin === "config") return 50;
|
|
3379
|
-
return 10;
|
|
3380
|
-
}
|
|
3381
|
-
function readInstalledPluginRecordPriority(record) {
|
|
3382
|
-
const installScore = record.installPath ? 20 : 0;
|
|
3383
|
-
const timestampScore = record.installedAt ? 10 : 0;
|
|
3384
|
-
return readPluginRuntimeStatusPriority(record.runtimeStatus) + readPluginOriginPriority(record.origin) + installScore + timestampScore;
|
|
3385
|
-
}
|
|
3386
|
-
function mergeInstalledPluginRecords(primary, secondary) {
|
|
3387
|
-
return {
|
|
3388
|
-
...primary,
|
|
3389
|
-
id: primary.id ?? secondary.id,
|
|
3390
|
-
label: primary.label ?? secondary.label,
|
|
3391
|
-
source: primary.source ?? secondary.source,
|
|
3392
|
-
installedAt: primary.installedAt ?? secondary.installedAt,
|
|
3393
|
-
enabled: primary.enabled ?? secondary.enabled,
|
|
3394
|
-
runtimeStatus: primary.runtimeStatus ?? secondary.runtimeStatus,
|
|
3395
|
-
origin: primary.origin ?? secondary.origin,
|
|
3396
|
-
installPath: primary.installPath ?? secondary.installPath
|
|
3397
|
-
};
|
|
3398
|
-
}
|
|
3399
|
-
function dedupeInstalledPluginRecordsByCanonicalSpec(records) {
|
|
3400
|
-
const deduped = /* @__PURE__ */ new Map();
|
|
3401
|
-
for (const record of records) {
|
|
3402
|
-
const canonicalSpec = normalizePluginNpmSpec(record.spec).trim();
|
|
3403
|
-
if (!canonicalSpec) continue;
|
|
3404
|
-
const key = canonicalSpec.toLowerCase();
|
|
3405
|
-
const normalizedRecord = {
|
|
3406
|
-
...record,
|
|
3407
|
-
spec: canonicalSpec
|
|
3408
|
-
};
|
|
3409
|
-
const existing = deduped.get(key);
|
|
3410
|
-
if (!existing) {
|
|
3411
|
-
deduped.set(key, normalizedRecord);
|
|
3412
|
-
continue;
|
|
3413
|
-
}
|
|
3414
|
-
if (readInstalledPluginRecordPriority(normalizedRecord) > readInstalledPluginRecordPriority(existing)) {
|
|
3415
|
-
deduped.set(key, mergeInstalledPluginRecords(normalizedRecord, existing));
|
|
3416
|
-
continue;
|
|
3417
|
-
}
|
|
3418
|
-
deduped.set(key, mergeInstalledPluginRecords(existing, normalizedRecord));
|
|
3419
|
-
}
|
|
3420
|
-
return Array.from(deduped.values());
|
|
3421
|
-
}
|
|
3422
|
-
//#endregion
|
|
3423
3332
|
//#region src/features/marketplace/utils/marketplace-installed.utils.ts
|
|
3424
3333
|
const getWorkspacePathFromConfig = NextclawCore.getWorkspacePathFromConfig;
|
|
3425
3334
|
function createSkillsLoader(workspace) {
|
|
@@ -3427,154 +3336,6 @@ function createSkillsLoader(workspace) {
|
|
|
3427
3336
|
if (!ctor) return null;
|
|
3428
3337
|
return new ctor(workspace);
|
|
3429
3338
|
}
|
|
3430
|
-
function readPluginStatusScore(plugin) {
|
|
3431
|
-
if (plugin.status === "loaded") return 300;
|
|
3432
|
-
if (plugin.status === "disabled") return 200;
|
|
3433
|
-
return 100;
|
|
3434
|
-
}
|
|
3435
|
-
function readPluginOriginScore(plugin, hasInstallRecord) {
|
|
3436
|
-
if (hasInstallRecord) {
|
|
3437
|
-
if (plugin.origin === "workspace") return 40;
|
|
3438
|
-
if (plugin.origin === "global") return 30;
|
|
3439
|
-
if (plugin.origin === "config") return 20;
|
|
3440
|
-
return 10;
|
|
3441
|
-
}
|
|
3442
|
-
if (plugin.origin === "bundled") return 40;
|
|
3443
|
-
if (plugin.origin === "workspace") return 30;
|
|
3444
|
-
if (plugin.origin === "global") return 20;
|
|
3445
|
-
return 10;
|
|
3446
|
-
}
|
|
3447
|
-
function readPluginPriority(plugin, installedPluginIds) {
|
|
3448
|
-
return readPluginStatusScore(plugin) + readPluginOriginScore(plugin, installedPluginIds.has(plugin.id));
|
|
3449
|
-
}
|
|
3450
|
-
function buildDiscoveredPluginMap(params) {
|
|
3451
|
-
const discoveredById = /* @__PURE__ */ new Map();
|
|
3452
|
-
for (const plugin of params.discoveredPlugins) {
|
|
3453
|
-
const existing = discoveredById.get(plugin.id);
|
|
3454
|
-
if (!existing) {
|
|
3455
|
-
discoveredById.set(plugin.id, plugin);
|
|
3456
|
-
continue;
|
|
3457
|
-
}
|
|
3458
|
-
if (readPluginPriority(plugin, params.installedPluginIds) > readPluginPriority(existing, params.installedPluginIds)) discoveredById.set(plugin.id, plugin);
|
|
3459
|
-
}
|
|
3460
|
-
return discoveredById;
|
|
3461
|
-
}
|
|
3462
|
-
function collectDiscoveredPluginRecords(params) {
|
|
3463
|
-
return Array.from(params.discoveredById.values()).map((plugin) => {
|
|
3464
|
-
const installRecord = params.pluginRecordsMap[plugin.id];
|
|
3465
|
-
const entry = params.pluginEntries[plugin.id];
|
|
3466
|
-
return {
|
|
3467
|
-
type: "plugin",
|
|
3468
|
-
id: plugin.id,
|
|
3469
|
-
spec: resolveDiscoveredPluginCanonicalSpec({
|
|
3470
|
-
pluginId: plugin.id,
|
|
3471
|
-
installSpec: installRecord?.spec,
|
|
3472
|
-
source: plugin.source
|
|
3473
|
-
}),
|
|
3474
|
-
label: plugin.name && plugin.name.trim().length > 0 ? plugin.name : plugin.id,
|
|
3475
|
-
source: plugin.source,
|
|
3476
|
-
installedAt: installRecord?.installedAt,
|
|
3477
|
-
enabled: entry?.enabled === false ? false : plugin.enabled,
|
|
3478
|
-
runtimeStatus: entry?.enabled === false ? "disabled" : plugin.status,
|
|
3479
|
-
origin: plugin.origin,
|
|
3480
|
-
installPath: installRecord?.installPath
|
|
3481
|
-
};
|
|
3482
|
-
});
|
|
3483
|
-
}
|
|
3484
|
-
function collectInstalledOnlyPluginRecords(params) {
|
|
3485
|
-
return Object.entries(params.pluginRecordsMap).filter(([pluginId]) => !params.seenPluginIds.has(pluginId)).map(([pluginId, installRecord]) => {
|
|
3486
|
-
const entry = params.pluginEntries[pluginId];
|
|
3487
|
-
return {
|
|
3488
|
-
type: "plugin",
|
|
3489
|
-
id: pluginId,
|
|
3490
|
-
spec: resolvePluginCanonicalSpec({
|
|
3491
|
-
pluginId,
|
|
3492
|
-
installSpec: installRecord.spec
|
|
3493
|
-
}),
|
|
3494
|
-
label: pluginId,
|
|
3495
|
-
source: installRecord.source,
|
|
3496
|
-
installedAt: installRecord.installedAt,
|
|
3497
|
-
enabled: entry?.enabled !== false,
|
|
3498
|
-
runtimeStatus: entry?.enabled === false ? "disabled" : "unresolved",
|
|
3499
|
-
installPath: installRecord.installPath
|
|
3500
|
-
};
|
|
3501
|
-
});
|
|
3502
|
-
}
|
|
3503
|
-
function collectConfigOnlyPluginRecords(params) {
|
|
3504
|
-
return Object.entries(params.pluginEntries).filter(([pluginId]) => !params.seenPluginIds.has(pluginId)).map(([pluginId, entry]) => {
|
|
3505
|
-
return {
|
|
3506
|
-
type: "plugin",
|
|
3507
|
-
id: pluginId,
|
|
3508
|
-
spec: resolvePluginCanonicalSpec({ pluginId }),
|
|
3509
|
-
label: pluginId,
|
|
3510
|
-
source: "config",
|
|
3511
|
-
enabled: entry?.enabled !== false,
|
|
3512
|
-
runtimeStatus: entry?.enabled === false ? "disabled" : "unresolved"
|
|
3513
|
-
};
|
|
3514
|
-
});
|
|
3515
|
-
}
|
|
3516
|
-
function findPluginIdByExactId(pluginRecords, lowerTargetId) {
|
|
3517
|
-
for (const record of pluginRecords) {
|
|
3518
|
-
const recordId = record.id?.trim();
|
|
3519
|
-
if (recordId && recordId.toLowerCase() === lowerTargetId) return recordId;
|
|
3520
|
-
}
|
|
3521
|
-
}
|
|
3522
|
-
function findPluginIdByNormalizedSpec(pluginRecords, normalizedSpec) {
|
|
3523
|
-
if (!normalizedSpec) return;
|
|
3524
|
-
for (const record of pluginRecords) {
|
|
3525
|
-
const recordId = record.id?.trim();
|
|
3526
|
-
if (!recordId) continue;
|
|
3527
|
-
if (normalizePluginNpmSpec(record.spec).toLowerCase() === normalizedSpec) return recordId;
|
|
3528
|
-
}
|
|
3529
|
-
}
|
|
3530
|
-
function collectDefinedRecordIds(records) {
|
|
3531
|
-
return new Set(records.map((record) => readNonEmptyString(record.id)).filter((recordId) => Boolean(recordId)));
|
|
3532
|
-
}
|
|
3533
|
-
function collectInstalledPluginRecords(options) {
|
|
3534
|
-
const config = loadConfigOrDefault(options.configPath);
|
|
3535
|
-
const pluginRecordsMap = config.plugins.installs ?? {};
|
|
3536
|
-
const pluginEntries = config.plugins.entries ?? {};
|
|
3537
|
-
const installedPluginIds = new Set(Object.keys(pluginRecordsMap));
|
|
3538
|
-
let discoveredPlugins = [];
|
|
3539
|
-
try {
|
|
3540
|
-
discoveredPlugins = discoverPluginStatusReport({
|
|
3541
|
-
config,
|
|
3542
|
-
workspaceDir: getWorkspacePathFromConfig(config)
|
|
3543
|
-
}).plugins;
|
|
3544
|
-
} catch {
|
|
3545
|
-
discoveredPlugins = [];
|
|
3546
|
-
}
|
|
3547
|
-
const discoveredRecords = collectDiscoveredPluginRecords({
|
|
3548
|
-
discoveredById: buildDiscoveredPluginMap({
|
|
3549
|
-
discoveredPlugins,
|
|
3550
|
-
installedPluginIds
|
|
3551
|
-
}),
|
|
3552
|
-
pluginRecordsMap,
|
|
3553
|
-
pluginEntries
|
|
3554
|
-
});
|
|
3555
|
-
const seenDiscoveredPluginIds = collectDefinedRecordIds(discoveredRecords);
|
|
3556
|
-
const installedOnlyRecords = collectInstalledOnlyPluginRecords({
|
|
3557
|
-
pluginRecordsMap,
|
|
3558
|
-
pluginEntries,
|
|
3559
|
-
seenPluginIds: seenDiscoveredPluginIds
|
|
3560
|
-
});
|
|
3561
|
-
const configOnlyRecords = collectConfigOnlyPluginRecords({
|
|
3562
|
-
pluginEntries,
|
|
3563
|
-
seenPluginIds: new Set([...seenDiscoveredPluginIds, ...collectDefinedRecordIds(installedOnlyRecords)])
|
|
3564
|
-
});
|
|
3565
|
-
const dedupedPluginRecords = dedupeInstalledPluginRecordsByCanonicalSpec([
|
|
3566
|
-
...discoveredRecords,
|
|
3567
|
-
...installedOnlyRecords,
|
|
3568
|
-
...configOnlyRecords
|
|
3569
|
-
]);
|
|
3570
|
-
dedupedPluginRecords.sort((left, right) => {
|
|
3571
|
-
return left.spec.localeCompare(right.spec);
|
|
3572
|
-
});
|
|
3573
|
-
return {
|
|
3574
|
-
specs: dedupedPluginRecords.map((record) => record.spec),
|
|
3575
|
-
records: dedupedPluginRecords
|
|
3576
|
-
};
|
|
3577
|
-
}
|
|
3578
3339
|
function collectInstalledSkillRecords(options) {
|
|
3579
3340
|
const skillsLoader = createSkillsLoader(getWorkspacePathFromConfig(loadConfigOrDefault(options.configPath)));
|
|
3580
3341
|
const availableSkillSet = new Set((skillsLoader?.listSkills(true) ?? []).map((skill) => skill.name));
|
|
@@ -3600,15 +3361,6 @@ function collectInstalledSkillRecords(options) {
|
|
|
3600
3361
|
records
|
|
3601
3362
|
};
|
|
3602
3363
|
}
|
|
3603
|
-
function collectPluginMarketplaceInstalledView(options) {
|
|
3604
|
-
const installed = collectInstalledPluginRecords(options);
|
|
3605
|
-
return {
|
|
3606
|
-
type: "plugin",
|
|
3607
|
-
total: installed.records.length,
|
|
3608
|
-
specs: installed.specs,
|
|
3609
|
-
records: installed.records
|
|
3610
|
-
};
|
|
3611
|
-
}
|
|
3612
3364
|
function collectSkillMarketplaceInstalledView(options) {
|
|
3613
3365
|
const installed = collectInstalledSkillRecords(options);
|
|
3614
3366
|
return {
|
|
@@ -3618,27 +3370,10 @@ function collectSkillMarketplaceInstalledView(options) {
|
|
|
3618
3370
|
records: installed.records
|
|
3619
3371
|
};
|
|
3620
3372
|
}
|
|
3621
|
-
function resolvePluginManageTargetId(options, rawTargetId, rawSpec) {
|
|
3622
|
-
const targetId = rawTargetId.trim();
|
|
3623
|
-
if (!targetId && !rawSpec) return rawTargetId;
|
|
3624
|
-
const normalizedTarget = targetId ? normalizePluginNpmSpec(targetId).toLowerCase() : "";
|
|
3625
|
-
const normalizedSpec = rawSpec ? normalizePluginNpmSpec(rawSpec).toLowerCase() : "";
|
|
3626
|
-
const pluginRecords = collectInstalledPluginRecords(options).records;
|
|
3627
|
-
const matchedRecordId = findPluginIdByExactId(pluginRecords, targetId.toLowerCase());
|
|
3628
|
-
if (matchedRecordId) return matchedRecordId;
|
|
3629
|
-
const matchedByTargetSpec = findPluginIdByNormalizedSpec(pluginRecords, normalizedTarget);
|
|
3630
|
-
if (matchedByTargetSpec) return matchedByTargetSpec;
|
|
3631
|
-
const matchedByRawSpec = normalizedSpec && normalizedSpec !== normalizedTarget ? findPluginIdByNormalizedSpec(pluginRecords, normalizedSpec) : void 0;
|
|
3632
|
-
if (matchedByRawSpec) return matchedByRawSpec;
|
|
3633
|
-
return targetId || rawSpec || rawTargetId;
|
|
3634
|
-
}
|
|
3635
3373
|
function collectKnownSkillNames(options) {
|
|
3636
3374
|
const loader = createSkillsLoader(getWorkspacePathFromConfig(loadConfigOrDefault(options.configPath)));
|
|
3637
3375
|
return new Set((loader?.listSkills(false) ?? []).map((skill) => skill.name));
|
|
3638
3376
|
}
|
|
3639
|
-
function isSupportedMarketplacePluginItem(item) {
|
|
3640
|
-
return item.type === "plugin" && item.install.kind === "npm" && isSupportedMarketplacePluginSpec(item.install.spec);
|
|
3641
|
-
}
|
|
3642
3377
|
function isSupportedMarketplaceSkillItem(item, knownSkillNames) {
|
|
3643
3378
|
if (item.type !== "skill") return false;
|
|
3644
3379
|
if (item.install.kind === "marketplace") return true;
|
|
@@ -3653,214 +3388,6 @@ function findUnsupportedSkillInstallKind(items) {
|
|
|
3653
3388
|
return null;
|
|
3654
3389
|
}
|
|
3655
3390
|
//#endregion
|
|
3656
|
-
//#region src/features/marketplace/controllers/plugin-marketplace.controller.ts
|
|
3657
|
-
async function loadPluginReadmeFromNpm(spec) {
|
|
3658
|
-
const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(spec)}`;
|
|
3659
|
-
try {
|
|
3660
|
-
const response = await fetch(registryUrl, { headers: { Accept: "application/json" } });
|
|
3661
|
-
if (!response.ok) return null;
|
|
3662
|
-
const payload = await response.json();
|
|
3663
|
-
const readme = typeof payload.readme === "string" ? payload.readme : "";
|
|
3664
|
-
const latest = isRecord$1(payload["dist-tags"]) && typeof payload["dist-tags"].latest === "string" ? payload["dist-tags"].latest : void 0;
|
|
3665
|
-
const metadata = {
|
|
3666
|
-
name: typeof payload.name === "string" ? payload.name : spec,
|
|
3667
|
-
version: latest,
|
|
3668
|
-
description: typeof payload.description === "string" ? payload.description : void 0,
|
|
3669
|
-
homepage: typeof payload.homepage === "string" ? payload.homepage : void 0
|
|
3670
|
-
};
|
|
3671
|
-
if (readme.trim().length === 0) return null;
|
|
3672
|
-
return {
|
|
3673
|
-
readme,
|
|
3674
|
-
sourceUrl: registryUrl,
|
|
3675
|
-
metadataRaw: JSON.stringify(metadata, null, 2)
|
|
3676
|
-
};
|
|
3677
|
-
} catch {
|
|
3678
|
-
return null;
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
async function buildPluginContentView(item) {
|
|
3682
|
-
if (item.install.kind === "npm") {
|
|
3683
|
-
const npm = await loadPluginReadmeFromNpm(item.install.spec);
|
|
3684
|
-
if (npm) return {
|
|
3685
|
-
type: "plugin",
|
|
3686
|
-
slug: item.slug,
|
|
3687
|
-
name: item.name,
|
|
3688
|
-
install: item.install,
|
|
3689
|
-
source: "npm",
|
|
3690
|
-
raw: npm.readme,
|
|
3691
|
-
bodyRaw: npm.readme,
|
|
3692
|
-
metadataRaw: npm.metadataRaw,
|
|
3693
|
-
sourceUrl: npm.sourceUrl
|
|
3694
|
-
};
|
|
3695
|
-
}
|
|
3696
|
-
return {
|
|
3697
|
-
type: "plugin",
|
|
3698
|
-
slug: item.slug,
|
|
3699
|
-
name: item.name,
|
|
3700
|
-
install: item.install,
|
|
3701
|
-
source: "remote",
|
|
3702
|
-
bodyRaw: item.description || item.summary || "",
|
|
3703
|
-
metadataRaw: JSON.stringify({
|
|
3704
|
-
name: item.name,
|
|
3705
|
-
author: item.author,
|
|
3706
|
-
sourceRepo: item.sourceRepo,
|
|
3707
|
-
homepage: item.homepage
|
|
3708
|
-
}, null, 2)
|
|
3709
|
-
};
|
|
3710
|
-
}
|
|
3711
|
-
async function installMarketplacePlugin(params) {
|
|
3712
|
-
const { body, options } = params;
|
|
3713
|
-
const spec = typeof body.spec === "string" ? body.spec.trim() : "";
|
|
3714
|
-
if (!spec) throw new Error("INVALID_BODY:non-empty spec is required");
|
|
3715
|
-
const installer = options.marketplace?.installer;
|
|
3716
|
-
if (!installer) throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
|
|
3717
|
-
if (!installer.installPlugin) throw new Error("NOT_AVAILABLE:plugin installer is not configured");
|
|
3718
|
-
const result = await installer.installPlugin(spec);
|
|
3719
|
-
emitConfigUpdated(options, "plugins");
|
|
3720
|
-
return {
|
|
3721
|
-
type: "plugin",
|
|
3722
|
-
spec,
|
|
3723
|
-
message: result.message,
|
|
3724
|
-
output: result.output
|
|
3725
|
-
};
|
|
3726
|
-
}
|
|
3727
|
-
async function manageMarketplacePlugin(params) {
|
|
3728
|
-
const { body, options } = params;
|
|
3729
|
-
const action = body.action;
|
|
3730
|
-
const targetId = resolvePluginManageTargetId(options, typeof body.id === "string" && body.id.trim().length > 0 ? body.id.trim() : typeof body.spec === "string" && body.spec.trim().length > 0 ? body.spec.trim() : "", typeof body.spec === "string" ? body.spec.trim() : "");
|
|
3731
|
-
if (action !== "enable" && action !== "disable" && action !== "uninstall" || !targetId) throw new Error("INVALID_BODY:action and non-empty id/spec are required");
|
|
3732
|
-
const installer = options.marketplace?.installer;
|
|
3733
|
-
if (!installer) throw new Error("NOT_AVAILABLE:marketplace installer is not configured");
|
|
3734
|
-
let result;
|
|
3735
|
-
if (action === "enable") {
|
|
3736
|
-
if (!installer.enablePlugin) throw new Error("NOT_AVAILABLE:plugin enable is not configured");
|
|
3737
|
-
result = await installer.enablePlugin(targetId);
|
|
3738
|
-
} else if (action === "disable") {
|
|
3739
|
-
if (!installer.disablePlugin) throw new Error("NOT_AVAILABLE:plugin disable is not configured");
|
|
3740
|
-
result = await installer.disablePlugin(targetId);
|
|
3741
|
-
} else {
|
|
3742
|
-
if (!installer.uninstallPlugin) throw new Error("NOT_AVAILABLE:plugin uninstall is not configured");
|
|
3743
|
-
result = await installer.uninstallPlugin(targetId);
|
|
3744
|
-
}
|
|
3745
|
-
emitConfigUpdated(options, "plugins");
|
|
3746
|
-
return {
|
|
3747
|
-
type: "plugin",
|
|
3748
|
-
action,
|
|
3749
|
-
id: targetId,
|
|
3750
|
-
message: result.message,
|
|
3751
|
-
output: result.output
|
|
3752
|
-
};
|
|
3753
|
-
}
|
|
3754
|
-
var PluginMarketplaceController = class {
|
|
3755
|
-
constructor(options, marketplaceBaseUrl) {
|
|
3756
|
-
this.options = options;
|
|
3757
|
-
this.marketplaceBaseUrl = marketplaceBaseUrl;
|
|
3758
|
-
}
|
|
3759
|
-
getInstalled = (c) => {
|
|
3760
|
-
return c.json(ok(collectPluginMarketplaceInstalledView(this.options)));
|
|
3761
|
-
};
|
|
3762
|
-
listItems = async (c) => {
|
|
3763
|
-
const query = c.req.query();
|
|
3764
|
-
const result = await fetchMarketplaceData({
|
|
3765
|
-
baseUrl: this.marketplaceBaseUrl,
|
|
3766
|
-
path: "/api/v1/plugins/items",
|
|
3767
|
-
query: {
|
|
3768
|
-
q: query.q,
|
|
3769
|
-
tag: query.tag,
|
|
3770
|
-
sort: query.sort,
|
|
3771
|
-
page: query.page,
|
|
3772
|
-
pageSize: query.pageSize
|
|
3773
|
-
}
|
|
3774
|
-
});
|
|
3775
|
-
if (!result.ok) return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
3776
|
-
const items = sanitizeMarketplaceListItems(result.data.items).map((item) => normalizeMarketplaceItemForUi(item)).filter((item) => isSupportedMarketplacePluginItem(item));
|
|
3777
|
-
return c.json(ok({
|
|
3778
|
-
total: result.data.total,
|
|
3779
|
-
page: result.data.page,
|
|
3780
|
-
pageSize: result.data.pageSize,
|
|
3781
|
-
totalPages: result.data.totalPages,
|
|
3782
|
-
sort: result.data.sort,
|
|
3783
|
-
query: result.data.query,
|
|
3784
|
-
items
|
|
3785
|
-
}));
|
|
3786
|
-
};
|
|
3787
|
-
getItem = async (c) => {
|
|
3788
|
-
const slug = encodeURIComponent(c.req.param("slug"));
|
|
3789
|
-
const result = await fetchMarketplaceData({
|
|
3790
|
-
baseUrl: this.marketplaceBaseUrl,
|
|
3791
|
-
path: `/api/v1/plugins/items/${slug}`
|
|
3792
|
-
});
|
|
3793
|
-
if (!result.ok) return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
3794
|
-
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItemView(result.data));
|
|
3795
|
-
if (!isSupportedMarketplacePluginItem(sanitized)) return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
3796
|
-
return c.json(ok(sanitized));
|
|
3797
|
-
};
|
|
3798
|
-
getItemContent = async (c) => {
|
|
3799
|
-
const slug = encodeURIComponent(c.req.param("slug"));
|
|
3800
|
-
const result = await fetchMarketplaceData({
|
|
3801
|
-
baseUrl: this.marketplaceBaseUrl,
|
|
3802
|
-
path: `/api/v1/plugins/items/${slug}`
|
|
3803
|
-
});
|
|
3804
|
-
if (!result.ok) return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
3805
|
-
const sanitized = normalizeMarketplaceItemForUi(sanitizeMarketplaceItemView(result.data));
|
|
3806
|
-
if (!isSupportedMarketplacePluginItem(sanitized)) return c.json(err("NOT_FOUND", "marketplace item not supported by nextclaw"), 404);
|
|
3807
|
-
const content = await buildPluginContentView(sanitized);
|
|
3808
|
-
return c.json(ok(content));
|
|
3809
|
-
};
|
|
3810
|
-
install = async (c) => {
|
|
3811
|
-
const body = await readJson(c.req.raw);
|
|
3812
|
-
if (!body.ok || !body.data || typeof body.data !== "object") return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
3813
|
-
if (body.data.type && body.data.type !== "plugin") return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
|
|
3814
|
-
try {
|
|
3815
|
-
const payload = await installMarketplacePlugin({
|
|
3816
|
-
options: this.options,
|
|
3817
|
-
body: body.data
|
|
3818
|
-
});
|
|
3819
|
-
return c.json(ok(payload));
|
|
3820
|
-
} catch (error) {
|
|
3821
|
-
const message = String(error);
|
|
3822
|
-
if (message.startsWith("INVALID_BODY:")) return c.json(err("INVALID_BODY", message.slice(13)), 400);
|
|
3823
|
-
if (message.startsWith("NOT_AVAILABLE:")) return c.json(err("NOT_AVAILABLE", message.slice(14)), 503);
|
|
3824
|
-
return c.json(err("INSTALL_FAILED", message), 400);
|
|
3825
|
-
}
|
|
3826
|
-
};
|
|
3827
|
-
manage = async (c) => {
|
|
3828
|
-
const body = await readJson(c.req.raw);
|
|
3829
|
-
if (!body.ok || !body.data || typeof body.data !== "object") return c.json(err("INVALID_BODY", "invalid json body"), 400);
|
|
3830
|
-
if (body.data.type && body.data.type !== "plugin") return c.json(err("INVALID_BODY", "body.type does not match route type"), 400);
|
|
3831
|
-
try {
|
|
3832
|
-
const payload = await manageMarketplacePlugin({
|
|
3833
|
-
options: this.options,
|
|
3834
|
-
body: body.data
|
|
3835
|
-
});
|
|
3836
|
-
return c.json(ok(payload));
|
|
3837
|
-
} catch (error) {
|
|
3838
|
-
const message = String(error);
|
|
3839
|
-
if (message.startsWith("INVALID_BODY:")) return c.json(err("INVALID_BODY", message.slice(13)), 400);
|
|
3840
|
-
if (message.startsWith("NOT_AVAILABLE:")) return c.json(err("NOT_AVAILABLE", message.slice(14)), 503);
|
|
3841
|
-
return c.json(err("MANAGE_FAILED", message), 400);
|
|
3842
|
-
}
|
|
3843
|
-
};
|
|
3844
|
-
getRecommendations = async (c) => {
|
|
3845
|
-
const query = c.req.query();
|
|
3846
|
-
const result = await fetchMarketplaceData({
|
|
3847
|
-
baseUrl: this.marketplaceBaseUrl,
|
|
3848
|
-
path: "/api/v1/plugins/recommendations",
|
|
3849
|
-
query: {
|
|
3850
|
-
scene: query.scene,
|
|
3851
|
-
limit: query.limit
|
|
3852
|
-
}
|
|
3853
|
-
});
|
|
3854
|
-
if (!result.ok) return c.json(err("MARKETPLACE_UNAVAILABLE", result.message), result.status);
|
|
3855
|
-
const filteredItems = sanitizeMarketplaceListItems(result.data.items).map((item) => normalizeMarketplaceItemForUi(item)).filter((item) => isSupportedMarketplacePluginItem(item));
|
|
3856
|
-
return c.json(ok({
|
|
3857
|
-
...result.data,
|
|
3858
|
-
total: filteredItems.length,
|
|
3859
|
-
items: filteredItems
|
|
3860
|
-
}));
|
|
3861
|
-
};
|
|
3862
|
-
};
|
|
3863
|
-
//#endregion
|
|
3864
3391
|
//#region src/features/marketplace/controllers/skill-marketplace.controller.ts
|
|
3865
3392
|
async function installMarketplaceSkill(params) {
|
|
3866
3393
|
const { body, options } = params;
|
|
@@ -4051,13 +3578,6 @@ var SkillMarketplaceController = class {
|
|
|
4051
3578
|
//#endregion
|
|
4052
3579
|
//#region src/features/marketplace/routes/marketplace.route.ts
|
|
4053
3580
|
function mountMarketplaceRoutes(app, controllers) {
|
|
4054
|
-
app.get("/api/marketplace/plugins/installed", controllers.plugin.getInstalled);
|
|
4055
|
-
app.get("/api/marketplace/plugins/items", controllers.plugin.listItems);
|
|
4056
|
-
app.get("/api/marketplace/plugins/items/:slug", controllers.plugin.getItem);
|
|
4057
|
-
app.get("/api/marketplace/plugins/items/:slug/content", controllers.plugin.getItemContent);
|
|
4058
|
-
app.post("/api/marketplace/plugins/install", controllers.plugin.install);
|
|
4059
|
-
app.post("/api/marketplace/plugins/manage", controllers.plugin.manage);
|
|
4060
|
-
app.get("/api/marketplace/plugins/recommendations", controllers.plugin.getRecommendations);
|
|
4061
3581
|
app.get("/api/marketplace/skills/installed", controllers.skill.getInstalled);
|
|
4062
3582
|
app.get("/api/marketplace/skills/scenes", controllers.skill.listScenes);
|
|
4063
3583
|
app.get("/api/marketplace/skills/items", controllers.skill.listItems);
|
|
@@ -4190,7 +3710,7 @@ var NcpSessionRoutesController = class {
|
|
|
4190
3710
|
this.sessionSkillsViewBuilder = new SessionSkillsViewBuilder(options);
|
|
4191
3711
|
}
|
|
4192
3712
|
getSessionTypes = async (c) => {
|
|
4193
|
-
const payload = await this.options.kernel.
|
|
3713
|
+
const payload = await this.options.kernel.listSessionTypes({ describeMode: "observation" });
|
|
4194
3714
|
return c.json(ok(payload));
|
|
4195
3715
|
};
|
|
4196
3716
|
listSessions = async (c) => {
|
|
@@ -4481,6 +4001,56 @@ var RuntimeUpdateRoutesController = class {
|
|
|
4481
4001
|
};
|
|
4482
4002
|
};
|
|
4483
4003
|
//#endregion
|
|
4004
|
+
//#region src/app/utils/ncp-session-event-stream.utils.ts
|
|
4005
|
+
function readEventSessionId(event) {
|
|
4006
|
+
const payload = "payload" in event ? event.payload : null;
|
|
4007
|
+
if (!payload || typeof payload !== "object") return null;
|
|
4008
|
+
return "sessionId" in payload && typeof payload.sessionId === "string" ? payload.sessionId : null;
|
|
4009
|
+
}
|
|
4010
|
+
function toSseFrame(eventName, data) {
|
|
4011
|
+
return `event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
4012
|
+
}
|
|
4013
|
+
function createNcpSessionEventStreamResponse(eventBus, payload, signal) {
|
|
4014
|
+
const encoder = new TextEncoder();
|
|
4015
|
+
let controller = null;
|
|
4016
|
+
let closed = false;
|
|
4017
|
+
let unsubscribe = () => void 0;
|
|
4018
|
+
const cleanup = () => {
|
|
4019
|
+
unsubscribe();
|
|
4020
|
+
signal.removeEventListener("abort", close);
|
|
4021
|
+
};
|
|
4022
|
+
const close = () => {
|
|
4023
|
+
if (closed) return;
|
|
4024
|
+
closed = true;
|
|
4025
|
+
cleanup();
|
|
4026
|
+
controller?.close();
|
|
4027
|
+
};
|
|
4028
|
+
const push = (event) => {
|
|
4029
|
+
if (closed || signal.aborted) return;
|
|
4030
|
+
if (readEventSessionId(event) === payload.sessionId) controller?.enqueue(encoder.encode(toSseFrame("ncp-event", event)));
|
|
4031
|
+
};
|
|
4032
|
+
const stream = new ReadableStream({
|
|
4033
|
+
start: (streamController) => {
|
|
4034
|
+
controller = streamController;
|
|
4035
|
+
unsubscribe = eventBus.on(eventKeys.ncpEvent, push);
|
|
4036
|
+
signal.addEventListener("abort", close, { once: true });
|
|
4037
|
+
if (signal.aborted) close();
|
|
4038
|
+
},
|
|
4039
|
+
cancel: () => {
|
|
4040
|
+
if (!closed) {
|
|
4041
|
+
closed = true;
|
|
4042
|
+
cleanup();
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
});
|
|
4046
|
+
return new Response(stream, { headers: {
|
|
4047
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
4048
|
+
"Cache-Control": "no-cache, no-transform",
|
|
4049
|
+
Connection: "keep-alive",
|
|
4050
|
+
"X-Accel-Buffering": "no"
|
|
4051
|
+
} });
|
|
4052
|
+
}
|
|
4053
|
+
//#endregion
|
|
4484
4054
|
//#region src/features/server-path/utils/server-path-browse.utils.ts
|
|
4485
4055
|
var ServerPathBrowseError = class extends Error {
|
|
4486
4056
|
constructor(code, message) {
|
|
@@ -4748,7 +4318,6 @@ function createUiRouteControllers(options, authService, marketplaceBaseUrl) {
|
|
|
4748
4318
|
remote: remoteAccess ? new RemoteRoutesController(remoteAccess) : null,
|
|
4749
4319
|
runtimeControl: runtimeControl ? new RuntimeControlRoutesController(runtimeControl) : null,
|
|
4750
4320
|
runtimeUpdate: runtimeUpdate ? new RuntimeUpdateRoutesController(runtimeUpdate) : null,
|
|
4751
|
-
pluginMarketplace: new PluginMarketplaceController(options, marketplaceBaseUrl),
|
|
4752
4321
|
skillMarketplace: new SkillMarketplaceController(options, marketplaceBaseUrl),
|
|
4753
4322
|
mcpMarketplace: new McpMarketplaceController(options, marketplaceBaseUrl)
|
|
4754
4323
|
};
|
|
@@ -4773,42 +4342,6 @@ function readStreamPayload(url) {
|
|
|
4773
4342
|
function isAbortPayload(value) {
|
|
4774
4343
|
return isRecord(value) && typeof value.sessionId === "string" && value.sessionId.trim().length > 0;
|
|
4775
4344
|
}
|
|
4776
|
-
function toSseFrame(eventName, data) {
|
|
4777
|
-
return `event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
4778
|
-
}
|
|
4779
|
-
function createNcpEventStreamResponse(events, signal) {
|
|
4780
|
-
const encoder = new TextEncoder();
|
|
4781
|
-
const stream = new ReadableStream({ start: async (controller) => {
|
|
4782
|
-
let closed = false;
|
|
4783
|
-
const close = () => {
|
|
4784
|
-
if (!closed) {
|
|
4785
|
-
closed = true;
|
|
4786
|
-
controller.close();
|
|
4787
|
-
}
|
|
4788
|
-
};
|
|
4789
|
-
signal.addEventListener("abort", close, { once: true });
|
|
4790
|
-
try {
|
|
4791
|
-
for await (const event of events) {
|
|
4792
|
-
if (closed || signal.aborted) break;
|
|
4793
|
-
controller.enqueue(encoder.encode(toSseFrame("ncp-event", event)));
|
|
4794
|
-
}
|
|
4795
|
-
} catch (error) {
|
|
4796
|
-
if (!closed && !signal.aborted) controller.enqueue(encoder.encode(toSseFrame("error", {
|
|
4797
|
-
code: "STREAM_SOURCE_FAILED",
|
|
4798
|
-
message: error instanceof Error ? error.message : String(error)
|
|
4799
|
-
})));
|
|
4800
|
-
} finally {
|
|
4801
|
-
signal.removeEventListener("abort", close);
|
|
4802
|
-
close();
|
|
4803
|
-
}
|
|
4804
|
-
} });
|
|
4805
|
-
return new Response(stream, { headers: {
|
|
4806
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
4807
|
-
"Cache-Control": "no-cache, no-transform",
|
|
4808
|
-
Connection: "keep-alive",
|
|
4809
|
-
"X-Accel-Buffering": "no"
|
|
4810
|
-
} });
|
|
4811
|
-
}
|
|
4812
4345
|
var UiRouteRegistry = class {
|
|
4813
4346
|
constructor(app, options, controllers) {
|
|
4814
4347
|
this.app = app;
|
|
@@ -4831,7 +4364,7 @@ var UiRouteRegistry = class {
|
|
|
4831
4364
|
this.app.get(`${NCP_AGENT_BASE_PATH}/stream`, (c) => {
|
|
4832
4365
|
const payload = readStreamPayload(c.req.raw.url);
|
|
4833
4366
|
if (!payload) return c.json(err("INVALID_QUERY", "sessionId is required."), 400);
|
|
4834
|
-
return
|
|
4367
|
+
return createNcpSessionEventStreamResponse(kernel.eventBus, payload, c.req.raw.signal);
|
|
4835
4368
|
});
|
|
4836
4369
|
this.app.post(`${NCP_AGENT_BASE_PATH}/abort`, async (c) => {
|
|
4837
4370
|
const body = await readJson(c.req.raw);
|
|
@@ -5002,6 +4535,11 @@ var UiRouteRegistry = class {
|
|
|
5002
4535
|
"/api/config/channels/:channel/auth/start",
|
|
5003
4536
|
config.startChannelAuth
|
|
5004
4537
|
],
|
|
4538
|
+
[
|
|
4539
|
+
"post",
|
|
4540
|
+
"/api/config/channels/:channel/auth/connect",
|
|
4541
|
+
config.connectChannelAuth
|
|
4542
|
+
],
|
|
5005
4543
|
[
|
|
5006
4544
|
"post",
|
|
5007
4545
|
"/api/config/channels/:channel/auth/poll",
|
|
@@ -5216,7 +4754,6 @@ var UiRouteRegistry = class {
|
|
|
5216
4754
|
}
|
|
5217
4755
|
});
|
|
5218
4756
|
mountMarketplaceRoutes(this.app, {
|
|
5219
|
-
plugin: this.controllers.pluginMarketplace,
|
|
5220
4757
|
skill: this.controllers.skillMarketplace,
|
|
5221
4758
|
mcp: this.controllers.mcpMarketplace
|
|
5222
4759
|
});
|