@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.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 fs, { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { open, readFile, readdir, realpath, stat } from "node:fs/promises";
7
- import path, { dirname, extname, isAbsolute, join, parse, resolve } from "node:path";
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, normalizeThinkingLevels, parseThinkingLevel, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig, updateAgentProfile } from "@nextclaw/core";
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
- pluginHydration: {
653
+ extensionLoading: {
653
654
  state: "pending",
654
- loadedPluginCount: 0,
655
- totalPluginCount: 0
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/plugin-channel-config-projection.utils.ts
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 normalizePluginProjectionOptions(options) {
690
+ function normalizeExtensionProjectionOptions(options) {
690
691
  return {
691
- pluginChannelBindings: options?.pluginChannelBindings ?? [],
692
- pluginUiMetadata: options?.pluginUiMetadata ?? []
692
+ extensionChannelBindings: options?.extensionChannelBindings ?? [],
693
+ extensionUiMetadata: options?.extensionUiMetadata ?? []
693
694
  };
694
695
  }
695
696
  function getProjectedConfigView(config, options) {
696
- return toPluginConfigView(config, normalizePluginProjectionOptions(options).pluginChannelBindings);
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 buildPluginChannelUiHints(options) {
709
- const normalized = normalizePluginProjectionOptions(options);
710
- if (normalized.pluginChannelBindings.length === 0) return {};
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.pluginUiMetadata.map((item) => [item.id, item]));
713
- for (const binding of normalized.pluginChannelBindings) {
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 pluginHints = metadataById.get(binding.pluginId)?.configUiHints ?? {};
723
- for (const [key, hint] of Object.entries(pluginHints)) hints[`${channelScope}.${key}`] = {
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 = normalizePluginProjectionOptions(options);
736
+ const normalized = normalizeExtensionProjectionOptions(options);
735
737
  const projectedChannelMap = getProjectedChannelMap(config, normalized);
736
- const bindingByChannelId = new Map(normalized.pluginChannelBindings.map((binding) => [binding.channelId, binding]));
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 mergeProjectedPluginChannelConfig(config, channelName, mergedChannel, options) {
755
- const normalized = normalizePluginProjectionOptions(options);
756
- if (!normalized.pluginChannelBindings.some((binding) => binding.channelId === channelName)) return null;
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 mergePluginConfigView(config, {
760
+ return mergeExtensionConfigView(config, {
759
761
  ...currentView,
760
762
  channels: {
761
763
  ...currentView.channels ?? {},
762
764
  [channelName]: mergedChannel
763
765
  }
764
- }, normalized.pluginChannelBindings);
766
+ });
765
767
  }
766
768
  //#endregion
767
769
  //#region src/features/config/utils/channel-auth.utils.ts
768
- function clonePluginConfig(value) {
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 findPluginChannelBinding(bindings, channelId) {
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.pluginConfig) return;
790
+ if (result.status !== "authorized" || !result.channelConfig) return;
789
791
  const currentConfig = loadConfigOrDefault(configPath);
790
- saveConfig(enablePluginInConfig({
792
+ saveConfig({
791
793
  ...currentConfig,
792
794
  channels: {
793
795
  ...currentConfig.channels,
794
- [binding.channelId]: result.pluginConfig
796
+ [binding.channelId]: result.channelConfig
795
797
  }
796
- }, binding.pluginId), configPath);
798
+ }, configPath);
797
799
  }
798
800
  async function startChannelAuth(params) {
799
801
  const { configPath, channelId, request, bindings } = params;
800
- const binding = findPluginChannelBinding(bindings, channelId);
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), { pluginChannelBindings: bindings });
805
+ const configView = getProjectedConfigView(loadConfigOrDefault(configPath), { extensionChannelBindings: bindings });
804
806
  return await start({
805
807
  cfg: configView,
806
- pluginId: binding.pluginId,
808
+ extensionId: binding.extensionId,
807
809
  channelId: binding.channelId,
808
- pluginConfig: clonePluginConfig(configView.channels?.[binding.channelId]),
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 = findPluginChannelBinding(bindings, channelId);
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), { pluginChannelBindings: bindings });
821
+ const configView = getProjectedConfigView(loadConfigOrDefault(configPath), { extensionChannelBindings: bindings });
820
822
  const result = await poll({
821
823
  cfg: configView,
822
- pluginId: binding.pluginId,
824
+ extensionId: binding.extensionId,
823
825
  channelId: binding.channelId,
824
- pluginConfig: clonePluginConfig(configView.channels?.[binding.channelId]),
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
- modelThinking: {}
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
- getPluginConfigOptions = () => {
1380
+ getExtensionConfigProjectionOptions = () => {
1357
1381
  return {
1358
- pluginChannelBindings: this.options.plugins?.getChannelBindings() ?? [],
1359
- pluginUiMetadata: this.options.plugins?.getUiMetadata() ?? []
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.getPluginConfigOptions())));
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.getPluginConfigOptions())));
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.getPluginConfigOptions())));
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.getPluginConfigOptions());
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.plugins?.getChannelBindings() ?? []
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.plugins?.getChannelBindings() ?? []
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
- modelThinking: normalizeModelThinkingConfig(provider.modelThinking ?? {})
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 pluginUiHints = buildPluginChannelUiHints(options);
2252
- if (Object.keys(pluginUiHints).length === 0) return base;
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
- ...pluginUiHints
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, "modelThinking")) provider.modelThinking = normalizeModelThinkingConfig(patch.modelThinking ?? {});
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
- modelThinking: normalizeModelThinkingConfig(patch.modelThinking ?? {})
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 = normalizePluginProjectionOptions(options);
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 mergedPluginConfig = mergeProjectedPluginChannelConfig(config, channelName, mergedChannel, normalizedOptions);
2506
- if (mergedPluginConfig) {
2507
- const next = ConfigSchema.parse(mergedPluginConfig);
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: "OpenClaw 社区技能,用于 X/Twitter 读取/搜索/发布工作流。",
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-plugin-discord": {
2934
- summary: "NextClaw 官方插件,用于 Discord 渠道集成。",
2935
- description: "通过 NextClaw 插件运行时提供 Discord 渠道的入站/出站支持。"
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.agentRuntimeManager.listSessionTypes({ describeMode: "observation" });
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 createNcpEventStreamResponse(kernel.sessionRunManager.streamSessionEvents(payload, { signal: c.req.raw.signal }), c.req.raw.signal);
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
  });