@nextclaw/server 0.12.1 → 0.12.2

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 CHANGED
@@ -486,8 +486,9 @@ type ProviderConnectionTestResult = {
486
486
  latencyMs: number;
487
487
  message: string;
488
488
  };
489
- type SearchProviderName = "bocha" | "brave";
489
+ type SearchProviderName = "bocha" | "tavily" | "brave";
490
490
  type BochaFreshnessValue = "noLimit" | "oneDay" | "oneWeek" | "oneMonth" | "oneYear" | string;
491
+ type TavilySearchDepthValue = "basic" | "advanced";
491
492
  type SearchProviderConfigView = {
492
493
  enabled: boolean;
493
494
  apiKeySet: boolean;
@@ -496,6 +497,8 @@ type SearchProviderConfigView = {
496
497
  docsUrl?: string;
497
498
  summary?: boolean;
498
499
  freshness?: BochaFreshnessValue;
500
+ searchDepth?: TavilySearchDepthValue;
501
+ includeAnswer?: boolean;
499
502
  };
500
503
  type SearchConfigView = {
501
504
  provider: SearchProviderName;
@@ -505,6 +508,7 @@ type SearchConfigView = {
505
508
  };
506
509
  providers: {
507
510
  bocha: SearchProviderConfigView;
511
+ tavily: SearchProviderConfigView;
508
512
  brave: SearchProviderConfigView;
509
513
  };
510
514
  };
@@ -522,6 +526,12 @@ type SearchConfigUpdate = {
522
526
  summary?: boolean;
523
527
  freshness?: BochaFreshnessValue | null;
524
528
  };
529
+ tavily?: {
530
+ apiKey?: string | null;
531
+ baseUrl?: string | null;
532
+ searchDepth?: TavilySearchDepthValue | null;
533
+ includeAnswer?: boolean;
534
+ };
525
535
  brave?: {
526
536
  apiKey?: string | null;
527
537
  baseUrl?: string | null;
@@ -700,6 +710,8 @@ type AgentProfileView = {
700
710
  avatarUrl?: string;
701
711
  workspace?: string;
702
712
  model?: string;
713
+ runtime?: string;
714
+ runtimeConfig?: Record<string, unknown>;
703
715
  engine?: string;
704
716
  engineConfig?: Record<string, unknown>;
705
717
  contextTokens?: number;
@@ -712,6 +724,17 @@ type AgentCreateRequest = {
712
724
  description?: string;
713
725
  avatar?: string;
714
726
  home?: string;
727
+ model?: string;
728
+ runtime?: string;
729
+ runtimeConfig?: Record<string, unknown>;
730
+ };
731
+ type AgentUpdateRequest = {
732
+ displayName?: string;
733
+ description?: string;
734
+ avatar?: string;
735
+ model?: string;
736
+ runtime?: string;
737
+ runtimeConfig?: Record<string, unknown>;
715
738
  };
716
739
  type AgentDeleteResult = {
717
740
  deleted: boolean;
@@ -854,6 +877,20 @@ type CronListView = {
854
877
  jobs: CronJobView[];
855
878
  total: number;
856
879
  };
880
+ type CronCreateRequest = {
881
+ name: string;
882
+ message: string;
883
+ schedule: CronScheduleView;
884
+ agentId?: string | null;
885
+ deliver?: boolean;
886
+ channel?: string | null;
887
+ to?: string | null;
888
+ accountId?: string | null;
889
+ deleteAfterRun?: boolean;
890
+ };
891
+ type CronCreateResult = {
892
+ job: CronJobView;
893
+ };
857
894
  type CronEnableRequest = {
858
895
  enabled: boolean;
859
896
  };
@@ -1192,6 +1229,9 @@ type PluginConfigProjectionOptions = {
1192
1229
  pluginUiMetadata?: PluginUiMetadata[];
1193
1230
  };
1194
1231
  //#endregion
1232
+ //#region src/ui/search-config.d.ts
1233
+ declare function updateSearch(configPath: string, patch: SearchConfigUpdate): ConfigView["search"];
1234
+ //#endregion
1195
1235
  //#region src/ui/config.d.ts
1196
1236
  type ExecuteActionResult = {
1197
1237
  ok: true;
@@ -1211,7 +1251,6 @@ declare function updateModel(configPath: string, patch: {
1211
1251
  model?: string;
1212
1252
  workspace?: string;
1213
1253
  }): ConfigView;
1214
- declare function updateSearch(configPath: string, patch: SearchConfigUpdate): ConfigView["search"];
1215
1254
  declare function updateProvider(configPath: string, providerName: string, patch: ProviderConfigUpdate): ProviderConfigView | null;
1216
1255
  declare function createCustomProvider(configPath: string, patch?: ProviderConfigUpdate): {
1217
1256
  name: string;
@@ -1243,4 +1282,4 @@ declare function getUiBridgeSecretPath(): string;
1243
1282
  declare function readUiBridgeSecret(): string | null;
1244
1283
  declare function ensureUiBridgeSecret(): string;
1245
1284
  //#endregion
1246
- export { AgentBindingView, AgentCreateRequest, AgentDeleteResult, AgentProfileView, ApiError, ApiResponse, AppMetaView, AuthEnabledUpdateRequest, AuthLoginRequest, AuthPasswordUpdateRequest, AuthSetupRequest, AuthStatusView, BindingPeerView, BochaFreshnessValue, BootstrapPhase, BootstrapRemoteState, BootstrapStageState, BootstrapStatusView, ChannelAuthPollRequest, ChannelAuthPollResult, ChannelAuthStartRequest, ChannelAuthStartResult, ChannelSpecView, type ChatSessionTypeCtaView, type ChatSessionTypeOptionView, type ChatSessionTypesView, ConfigActionExecuteRequest, ConfigActionExecuteResult, ConfigActionManifest, ConfigActionType, ConfigMetaView, ConfigSchemaResponse, ConfigUiHint, ConfigUiHints, ConfigView, CronActionResult, CronEnableRequest, CronJobStateView, CronJobView, CronListView, CronPayloadView, CronRunRequest, CronScheduleView, DEFAULT_SESSION_TYPE, MarketplaceApiConfig, MarketplaceInstallKind, MarketplaceInstallSkillParams, MarketplaceInstallSpec, MarketplaceInstalledRecord, MarketplaceInstalledView, MarketplaceInstaller, MarketplaceItemSummary, MarketplaceItemType, MarketplaceItemView, MarketplaceListView, MarketplaceLocalizedTextMap, MarketplaceMcpContentView, MarketplaceMcpDoctorResult, MarketplaceMcpInstallKind, MarketplaceMcpInstallRequest, MarketplaceMcpInstallResult, MarketplaceMcpInstallSpec, MarketplaceMcpManageAction, MarketplaceMcpManageRequest, MarketplaceMcpManageResult, MarketplaceMcpTemplateInput, MarketplacePluginContentView, MarketplacePluginInstallKind, MarketplacePluginInstallRequest, MarketplacePluginInstallResult, MarketplacePluginManageAction, MarketplacePluginManageRequest, MarketplacePluginManageResult, MarketplaceRecommendationView, MarketplaceSkillContentView, MarketplaceSkillInstallKind, MarketplaceSkillInstallRequest, MarketplaceSkillInstallResult, MarketplaceSkillManageAction, MarketplaceSkillManageRequest, MarketplaceSkillManageResult, MarketplaceSort, NcpSessionSkillsView, ProviderAuthImportResult, ProviderAuthPollRequest, ProviderAuthPollResult, ProviderAuthStartRequest, ProviderAuthStartResult, ProviderConfigUpdate, ProviderConfigView, ProviderConnectionTestRequest, ProviderConnectionTestResult, ProviderCreateRequest, ProviderCreateResult, ProviderDeleteResult, ProviderSpecView, RemoteAccessView, RemoteAccountView, RemoteBrowserAuthPollRequest, RemoteBrowserAuthPollResult, RemoteBrowserAuthStartRequest, RemoteBrowserAuthStartResult, RemoteDoctorCheckView, RemoteDoctorView, RemoteLoginRequest, RemoteRuntimeView, RemoteServiceAction, RemoteServiceActionResult, RemoteServiceView, RemoteSettingsUpdateRequest, RemoteSettingsView, RuntimeConfigUpdate, SearchConfigUpdate, SearchConfigView, SearchProviderConfigView, SearchProviderName, SearchProviderSpecView, SecretProviderEnvView, SecretProviderExecView, SecretProviderFileView, SecretProviderView, SecretRefView, SecretSourceView, SecretsConfigUpdate, SecretsView, ServerPathBreadcrumbView, ServerPathBrowseView, ServerPathEntryView, SessionConfigView, SessionEntryView, SessionEventView, SessionHistoryView, SessionMessageView, SessionPatchUpdate, SessionPatchValidationError, SessionSkillEntryView, SessionTypeDescribeParams, SessionsListView, UiNcpAgent, UiNcpAssetPutView, UiNcpAssetView, UiNcpSessionListView, UiNcpSessionMessagesView, UiNcpSessionService, UiNcpStoredAssetRecord, type UiRemoteAccessHost, UiServerEvent, UiServerHandle, UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, ensureUiBridgeSecret, executeConfigAction, getSessionHistory, getUiBridgeSecretPath, listSessions, loadConfigOrDefault, patchSession, readUiBridgeSecret, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSearch, updateSecrets };
1285
+ export { AgentBindingView, AgentCreateRequest, AgentDeleteResult, AgentProfileView, AgentUpdateRequest, ApiError, ApiResponse, AppMetaView, AuthEnabledUpdateRequest, AuthLoginRequest, AuthPasswordUpdateRequest, AuthSetupRequest, AuthStatusView, BindingPeerView, BochaFreshnessValue, BootstrapPhase, BootstrapRemoteState, BootstrapStageState, BootstrapStatusView, ChannelAuthPollRequest, ChannelAuthPollResult, ChannelAuthStartRequest, ChannelAuthStartResult, ChannelSpecView, type ChatSessionTypeCtaView, type ChatSessionTypeOptionView, type ChatSessionTypesView, ConfigActionExecuteRequest, ConfigActionExecuteResult, ConfigActionManifest, ConfigActionType, ConfigMetaView, ConfigSchemaResponse, ConfigUiHint, ConfigUiHints, ConfigView, CronActionResult, CronCreateRequest, CronCreateResult, CronEnableRequest, CronJobStateView, CronJobView, CronListView, CronPayloadView, CronRunRequest, CronScheduleView, DEFAULT_SESSION_TYPE, MarketplaceApiConfig, MarketplaceInstallKind, MarketplaceInstallSkillParams, MarketplaceInstallSpec, MarketplaceInstalledRecord, MarketplaceInstalledView, MarketplaceInstaller, MarketplaceItemSummary, MarketplaceItemType, MarketplaceItemView, MarketplaceListView, MarketplaceLocalizedTextMap, MarketplaceMcpContentView, MarketplaceMcpDoctorResult, MarketplaceMcpInstallKind, MarketplaceMcpInstallRequest, MarketplaceMcpInstallResult, MarketplaceMcpInstallSpec, MarketplaceMcpManageAction, MarketplaceMcpManageRequest, MarketplaceMcpManageResult, MarketplaceMcpTemplateInput, MarketplacePluginContentView, MarketplacePluginInstallKind, MarketplacePluginInstallRequest, MarketplacePluginInstallResult, MarketplacePluginManageAction, MarketplacePluginManageRequest, MarketplacePluginManageResult, MarketplaceRecommendationView, MarketplaceSkillContentView, MarketplaceSkillInstallKind, MarketplaceSkillInstallRequest, MarketplaceSkillInstallResult, MarketplaceSkillManageAction, MarketplaceSkillManageRequest, MarketplaceSkillManageResult, MarketplaceSort, NcpSessionSkillsView, ProviderAuthImportResult, ProviderAuthPollRequest, ProviderAuthPollResult, ProviderAuthStartRequest, ProviderAuthStartResult, ProviderConfigUpdate, ProviderConfigView, ProviderConnectionTestRequest, ProviderConnectionTestResult, ProviderCreateRequest, ProviderCreateResult, ProviderDeleteResult, ProviderSpecView, RemoteAccessView, RemoteAccountView, RemoteBrowserAuthPollRequest, RemoteBrowserAuthPollResult, RemoteBrowserAuthStartRequest, RemoteBrowserAuthStartResult, RemoteDoctorCheckView, RemoteDoctorView, RemoteLoginRequest, RemoteRuntimeView, RemoteServiceAction, RemoteServiceActionResult, RemoteServiceView, RemoteSettingsUpdateRequest, RemoteSettingsView, RuntimeConfigUpdate, SearchConfigUpdate, SearchConfigView, SearchProviderConfigView, SearchProviderName, SearchProviderSpecView, SecretProviderEnvView, SecretProviderExecView, SecretProviderFileView, SecretProviderView, SecretRefView, SecretSourceView, SecretsConfigUpdate, SecretsView, ServerPathBreadcrumbView, ServerPathBrowseView, ServerPathEntryView, SessionConfigView, SessionEntryView, SessionEventView, SessionHistoryView, SessionMessageView, SessionPatchUpdate, SessionPatchValidationError, SessionSkillEntryView, SessionTypeDescribeParams, SessionsListView, TavilySearchDepthValue, UiNcpAgent, UiNcpAssetPutView, UiNcpAssetView, UiNcpSessionListView, UiNcpSessionMessagesView, UiNcpSessionService, UiNcpStoredAssetRecord, type UiRemoteAccessHost, UiServerEvent, UiServerHandle, UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, ensureUiBridgeSecret, executeConfigAction, getSessionHistory, getUiBridgeSecretPath, listSessions, loadConfigOrDefault, patchSession, readUiBridgeSecret, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSearch, updateSecrets };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import { readFile, readdir, realpath, stat } from "node:fs/promises";
7
7
  import { dirname, isAbsolute, join, parse, resolve } from "node:path";
8
8
  import * as NextclawCore from "@nextclaw/core";
9
- import { ConfigSchema, DEFAULT_WORKSPACE_PATH, LiteLLMProvider, SessionManager, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, getWorkspacePathFromConfig, hasSecretRef, isSensitiveConfigPath, loadConfig, normalizeThinkingLevels, parseThinkingLevel, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig } from "@nextclaw/core";
9
+ import { ConfigSchema, DEFAULT_WORKSPACE_PATH, LiteLLMProvider, SessionManager, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, getWorkspacePathFromConfig, hasSecretRef, isSensitiveConfigPath, loadConfig, normalizeThinkingLevels, parseThinkingLevel, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig, updateAgentProfile } from "@nextclaw/core";
10
10
  import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
11
11
  import { mountNcpHttpAgentRoutes } from "@nextclaw/ncp-http-agent-server";
12
12
  import { discoverPluginStatusReport, enablePluginInConfig, mergePluginConfigView, toPluginConfigView } from "@nextclaw/openclaw-compat";
@@ -291,7 +291,10 @@ function createAgent(configPath, input, options = {}) {
291
291
  displayName: input.displayName,
292
292
  description: input.description,
293
293
  avatar: input.avatar,
294
- home: input.home
294
+ home: input.home,
295
+ model: input.model,
296
+ runtime: input.runtime,
297
+ runtimeConfig: input.runtimeConfig
295
298
  }, {
296
299
  configPath,
297
300
  initializeHomeDirectory: options.initializeAgentHomeDirectory
@@ -305,6 +308,18 @@ function deleteAgent(configPath, agentId) {
305
308
  agentId: agentId.trim().toLowerCase()
306
309
  };
307
310
  }
311
+ function updateAgent(configPath, agentId, input) {
312
+ const updated = updateAgentProfile({
313
+ id: agentId,
314
+ displayName: input.displayName,
315
+ description: input.description,
316
+ avatar: input.avatar,
317
+ model: input.model,
318
+ runtime: input.runtime,
319
+ runtimeConfig: input.runtimeConfig
320
+ }, { configPath });
321
+ return toAgentProfileView(loadConfig(configPath), updated.id);
322
+ }
308
323
  function readAgentAvatar(configPath, agentId) {
309
324
  return readAgentAvatarContent({
310
325
  config: loadConfig(configPath),
@@ -323,6 +338,8 @@ function toAgentProfileView(config, agentId) {
323
338
  avatarUrl: buildAgentAvatarUrl(profile.id, profile.avatar),
324
339
  workspace: profile.workspace,
325
340
  model: profile.model,
341
+ runtime: profile.runtime,
342
+ runtimeConfig: profile.runtimeConfig,
326
343
  engine: profile.engine,
327
344
  engineConfig: profile.engineConfig,
328
345
  contextTokens: profile.contextTokens,
@@ -410,6 +427,20 @@ var AgentsRoutesController = class {
410
427
  return c.json(err("AGENT_CREATE_FAILED", error instanceof Error ? error.message : String(error)), 400);
411
428
  }
412
429
  };
430
+ updateAgent = async (c) => {
431
+ const agentId = c.req.param("agentId");
432
+ const body = await readJson(c.req.raw);
433
+ if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
434
+ try {
435
+ const agent = updateAgent(this.options.configPath, agentId, body.data);
436
+ await this.publishAgentUpdates(["agents.list"]);
437
+ return c.json(ok(agent));
438
+ } catch (error) {
439
+ const message = error instanceof Error ? error.message : String(error);
440
+ const status = message.includes("not found") ? 404 : 400;
441
+ return c.json(err("AGENT_UPDATE_FAILED", message), status);
442
+ }
443
+ };
413
444
  deleteAgent = async (c) => {
414
445
  const agentId = c.req.param("agentId");
415
446
  try {
@@ -766,6 +797,230 @@ function findServerBuiltinProviderByName(name) {
766
797
  return SERVER_BUILTIN_PROVIDER_OVERRIDE_MAP.get(name) ?? findBuiltinProviderByName(name);
767
798
  }
768
799
  //#endregion
800
+ //#region src/ui/search-config.ts
801
+ const MASK_MIN_LENGTH$1 = 8;
802
+ const BOCHA_OPEN_URL = "https://open.bocha.cn";
803
+ const TAVILY_DOCS_URL = "https://docs.tavily.com/documentation/api-reference/endpoint/search";
804
+ const SEARCH_PROVIDER_NAMES = [
805
+ "bocha",
806
+ "tavily",
807
+ "brave"
808
+ ];
809
+ function normalizeOptionalString$1(value) {
810
+ if (typeof value !== "string") return null;
811
+ const trimmed = value.trim();
812
+ return trimmed.length > 0 ? trimmed : null;
813
+ }
814
+ function maskApiKey$1(value) {
815
+ if (!value) return { apiKeySet: false };
816
+ if (value.length < MASK_MIN_LENGTH$1) return {
817
+ apiKeySet: true,
818
+ apiKeyMasked: "****"
819
+ };
820
+ return {
821
+ apiKeySet: true,
822
+ apiKeyMasked: `${value.slice(0, 2)}****${value.slice(-4)}`
823
+ };
824
+ }
825
+ function clearSecretRef$1(refs, path) {
826
+ if (!refs[path]) return refs;
827
+ const nextRefs = { ...refs };
828
+ delete nextRefs[path];
829
+ return nextRefs;
830
+ }
831
+ function isSearchProviderName(value) {
832
+ return typeof value === "string" && SEARCH_PROVIDER_NAMES.some((providerName) => providerName === value);
833
+ }
834
+ const SEARCH_PROVIDER_META = [
835
+ {
836
+ name: "bocha",
837
+ displayName: "Bocha Search",
838
+ description: "China-friendly web search with AI-ready summaries.",
839
+ docsUrl: BOCHA_OPEN_URL,
840
+ isDefault: true,
841
+ supportsSummary: true
842
+ },
843
+ {
844
+ name: "tavily",
845
+ displayName: "Tavily Search",
846
+ description: "Research-focused web search with optional synthesized answers.",
847
+ docsUrl: TAVILY_DOCS_URL,
848
+ supportsSummary: true
849
+ },
850
+ {
851
+ name: "brave",
852
+ displayName: "Brave Search",
853
+ description: "Brave web search API kept as an optional provider.",
854
+ supportsSummary: false
855
+ }
856
+ ];
857
+ function toSearchProviderView(config, providerName, provider) {
858
+ const apiKeyRefSet = hasSecretRef(config, `search.providers.${providerName}.apiKey`);
859
+ const masked = maskApiKey$1(provider.apiKey);
860
+ const view = {
861
+ enabled: config.search.enabledProviders.includes(providerName),
862
+ apiKeySet: masked.apiKeySet || apiKeyRefSet,
863
+ apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
864
+ baseUrl: provider.baseUrl
865
+ };
866
+ if ("docsUrl" in provider) view.docsUrl = provider.docsUrl;
867
+ if ("summary" in provider) view.summary = provider.summary;
868
+ if ("freshness" in provider) view.freshness = provider.freshness;
869
+ if ("searchDepth" in provider) view.searchDepth = provider.searchDepth;
870
+ if ("includeAnswer" in provider) view.includeAnswer = Boolean(provider.includeAnswer);
871
+ return view;
872
+ }
873
+ function buildSearchView(config) {
874
+ return {
875
+ provider: config.search.provider,
876
+ enabledProviders: [...config.search.enabledProviders],
877
+ defaults: { maxResults: config.search.defaults.maxResults },
878
+ providers: {
879
+ bocha: toSearchProviderView(config, "bocha", config.search.providers.bocha),
880
+ tavily: toSearchProviderView(config, "tavily", config.search.providers.tavily),
881
+ brave: toSearchProviderView(config, "brave", config.search.providers.brave)
882
+ }
883
+ };
884
+ }
885
+ function replaceSearchConfig(config, search, refs = config.secrets.refs) {
886
+ return {
887
+ ...config,
888
+ search,
889
+ secrets: refs === config.secrets.refs ? config.secrets : {
890
+ ...config.secrets,
891
+ refs
892
+ }
893
+ };
894
+ }
895
+ function applyActiveSearchProviderPatch(config, provider) {
896
+ if (!isSearchProviderName(provider)) return config;
897
+ return replaceSearchConfig(config, {
898
+ ...config.search,
899
+ provider
900
+ });
901
+ }
902
+ function applyEnabledSearchProvidersPatch(config, enabledProviders) {
903
+ if (!Array.isArray(enabledProviders)) return config;
904
+ const nextEnabledProviders = Array.from(new Set(enabledProviders.filter((value) => isSearchProviderName(value))));
905
+ return replaceSearchConfig(config, {
906
+ ...config.search,
907
+ enabledProviders: nextEnabledProviders
908
+ });
909
+ }
910
+ function applySearchDefaultsPatch(config, defaults) {
911
+ if (!defaults || !Object.prototype.hasOwnProperty.call(defaults, "maxResults")) return config;
912
+ const nextMaxResults = defaults.maxResults;
913
+ if (typeof nextMaxResults === "number" && Number.isFinite(nextMaxResults)) return replaceSearchConfig(config, {
914
+ ...config.search,
915
+ defaults: {
916
+ ...config.search.defaults,
917
+ maxResults: Math.max(1, Math.min(50, Math.trunc(nextMaxResults)))
918
+ }
919
+ });
920
+ return config;
921
+ }
922
+ function applyBochaSearchPatch(config, patch) {
923
+ if (!patch) return config;
924
+ let nextRefs = config.secrets.refs;
925
+ let nextProvider = config.search.providers.bocha;
926
+ if (Object.prototype.hasOwnProperty.call(patch, "apiKey")) {
927
+ nextProvider = {
928
+ ...nextProvider,
929
+ apiKey: patch.apiKey ?? ""
930
+ };
931
+ nextRefs = clearSecretRef$1(nextRefs, "search.providers.bocha.apiKey");
932
+ }
933
+ if (Object.prototype.hasOwnProperty.call(patch, "baseUrl")) nextProvider = {
934
+ ...nextProvider,
935
+ baseUrl: normalizeOptionalString$1(patch.baseUrl) ?? "https://api.bocha.cn/v1/web-search"
936
+ };
937
+ if (Object.prototype.hasOwnProperty.call(patch, "docsUrl")) nextProvider = {
938
+ ...nextProvider,
939
+ docsUrl: normalizeOptionalString$1(patch.docsUrl) ?? BOCHA_OPEN_URL
940
+ };
941
+ if (Object.prototype.hasOwnProperty.call(patch, "summary")) nextProvider = {
942
+ ...nextProvider,
943
+ summary: Boolean(patch.summary)
944
+ };
945
+ if (Object.prototype.hasOwnProperty.call(patch, "freshness")) {
946
+ const freshness = normalizeOptionalString$1(patch.freshness);
947
+ nextProvider = {
948
+ ...nextProvider,
949
+ freshness: freshness === "noLimit" || freshness === "oneDay" || freshness === "oneWeek" || freshness === "oneMonth" || freshness === "oneYear" ? freshness : "noLimit"
950
+ };
951
+ }
952
+ return replaceSearchConfig(config, {
953
+ ...config.search,
954
+ providers: {
955
+ ...config.search.providers,
956
+ bocha: nextProvider
957
+ }
958
+ }, nextRefs);
959
+ }
960
+ function applyTavilySearchPatch(config, patch) {
961
+ if (!patch) return config;
962
+ let nextRefs = config.secrets.refs;
963
+ let nextProvider = config.search.providers.tavily;
964
+ if (Object.prototype.hasOwnProperty.call(patch, "apiKey")) {
965
+ nextProvider = {
966
+ ...nextProvider,
967
+ apiKey: patch.apiKey ?? ""
968
+ };
969
+ nextRefs = clearSecretRef$1(nextRefs, "search.providers.tavily.apiKey");
970
+ }
971
+ if (Object.prototype.hasOwnProperty.call(patch, "baseUrl")) nextProvider = {
972
+ ...nextProvider,
973
+ baseUrl: normalizeOptionalString$1(patch.baseUrl) ?? "https://api.tavily.com/search"
974
+ };
975
+ if (Object.prototype.hasOwnProperty.call(patch, "searchDepth")) {
976
+ const searchDepth = normalizeOptionalString$1(patch.searchDepth);
977
+ nextProvider = {
978
+ ...nextProvider,
979
+ searchDepth: searchDepth === "advanced" ? "advanced" : "basic"
980
+ };
981
+ }
982
+ if (Object.prototype.hasOwnProperty.call(patch, "includeAnswer")) nextProvider = {
983
+ ...nextProvider,
984
+ includeAnswer: Boolean(patch.includeAnswer)
985
+ };
986
+ return replaceSearchConfig(config, {
987
+ ...config.search,
988
+ providers: {
989
+ ...config.search.providers,
990
+ tavily: nextProvider
991
+ }
992
+ }, nextRefs);
993
+ }
994
+ function applyBraveSearchPatch(config, patch) {
995
+ if (!patch) return config;
996
+ let nextRefs = config.secrets.refs;
997
+ let nextProvider = config.search.providers.brave;
998
+ if (Object.prototype.hasOwnProperty.call(patch, "apiKey")) {
999
+ nextProvider = {
1000
+ ...nextProvider,
1001
+ apiKey: patch.apiKey ?? ""
1002
+ };
1003
+ nextRefs = clearSecretRef$1(nextRefs, "search.providers.brave.apiKey");
1004
+ }
1005
+ if (Object.prototype.hasOwnProperty.call(patch, "baseUrl")) nextProvider = {
1006
+ ...nextProvider,
1007
+ baseUrl: normalizeOptionalString$1(patch.baseUrl) ?? "https://api.search.brave.com/res/v1/web/search"
1008
+ };
1009
+ return replaceSearchConfig(config, {
1010
+ ...config.search,
1011
+ providers: {
1012
+ ...config.search.providers,
1013
+ brave: nextProvider
1014
+ }
1015
+ }, nextRefs);
1016
+ }
1017
+ function updateSearch(configPath, patch) {
1018
+ const nextConfig = applyBraveSearchPatch(applyTavilySearchPatch(applyBochaSearchPatch(applySearchDefaultsPatch(applyEnabledSearchProvidersPatch(applyActiveSearchProviderPatch(loadConfig(configPath), patch.provider), patch.enabledProviders), patch.defaults), patch.providers?.bocha), patch.providers?.tavily), patch.providers?.brave);
1019
+ const next = ConfigSchema.parse(nextConfig);
1020
+ saveConfig(next, configPath);
1021
+ return buildSearchView(next);
1022
+ }
1023
+ //#endregion
769
1024
  //#region src/ui/session-preference-patch.ts
770
1025
  function applySessionPreferencePatch(params) {
771
1026
  const nextMetadata = params.metadata;
@@ -876,20 +1131,6 @@ function ensureProviderConfig(config, providerName) {
876
1131
  function clearSecretRefsByPrefix(config, pathPrefix) {
877
1132
  for (const key of Object.keys(config.secrets.refs)) if (key === pathPrefix || key.startsWith(`${pathPrefix}.`)) delete config.secrets.refs[key];
878
1133
  }
879
- const BOCHA_OPEN_URL = "https://open.bocha.cn";
880
- const SEARCH_PROVIDER_META = [{
881
- name: "bocha",
882
- displayName: "Bocha Search",
883
- description: "China-friendly web search with AI-ready summaries.",
884
- docsUrl: BOCHA_OPEN_URL,
885
- isDefault: true,
886
- supportsSummary: true
887
- }, {
888
- name: "brave",
889
- displayName: "Brave Search",
890
- description: "Brave web search API kept as an optional provider.",
891
- supportsSummary: false
892
- }];
893
1134
  function matchesExtraSensitivePath(path) {
894
1135
  if (path === "session" || path.startsWith("session.")) return false;
895
1136
  return EXTRA_SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(path));
@@ -1106,31 +1347,6 @@ function buildConfigView(config, options) {
1106
1347
  }
1107
1348
  };
1108
1349
  }
1109
- function toSearchProviderView(config, providerName, provider) {
1110
- const apiKeyRefSet = hasSecretRef(config, `search.providers.${providerName}.apiKey`);
1111
- const masked = maskApiKey(provider.apiKey);
1112
- const base = {
1113
- enabled: config.search.enabledProviders.includes(providerName),
1114
- apiKeySet: masked.apiKeySet || apiKeyRefSet,
1115
- apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
1116
- baseUrl: provider.baseUrl
1117
- };
1118
- if ("docsUrl" in provider) base.docsUrl = provider.docsUrl;
1119
- if ("summary" in provider) base.summary = provider.summary;
1120
- if ("freshness" in provider) base.freshness = provider.freshness;
1121
- return base;
1122
- }
1123
- function buildSearchView(config) {
1124
- return {
1125
- provider: config.search.provider,
1126
- enabledProviders: [...config.search.enabledProviders],
1127
- defaults: { maxResults: config.search.defaults.maxResults },
1128
- providers: {
1129
- bocha: toSearchProviderView(config, "bocha", config.search.providers.bocha),
1130
- brave: toSearchProviderView(config, "brave", config.search.providers.brave)
1131
- }
1132
- };
1133
- }
1134
1350
  function clearSecretRef(config, path) {
1135
1351
  if (config.secrets.refs[path]) delete config.secrets.refs[path];
1136
1352
  }
@@ -1266,41 +1482,6 @@ function updateModel(configPath, patch) {
1266
1482
  saveConfig(next, configPath);
1267
1483
  return buildConfigView(next);
1268
1484
  }
1269
- function updateSearch(configPath, patch) {
1270
- const config = loadConfigOrDefault(configPath);
1271
- if (patch.provider === "bocha" || patch.provider === "brave") config.search.provider = patch.provider;
1272
- if (Array.isArray(patch.enabledProviders)) config.search.enabledProviders = Array.from(new Set(patch.enabledProviders.filter((value) => value === "bocha" || value === "brave")));
1273
- if (patch.defaults && Object.prototype.hasOwnProperty.call(patch.defaults, "maxResults")) {
1274
- const nextMaxResults = patch.defaults.maxResults;
1275
- if (typeof nextMaxResults === "number" && Number.isFinite(nextMaxResults)) config.search.defaults.maxResults = Math.max(1, Math.min(50, Math.trunc(nextMaxResults)));
1276
- }
1277
- const bochaPatch = patch.providers?.bocha;
1278
- if (bochaPatch) {
1279
- if (Object.prototype.hasOwnProperty.call(bochaPatch, "apiKey")) {
1280
- config.search.providers.bocha.apiKey = bochaPatch.apiKey ?? "";
1281
- clearSecretRef(config, "search.providers.bocha.apiKey");
1282
- }
1283
- if (Object.prototype.hasOwnProperty.call(bochaPatch, "baseUrl")) config.search.providers.bocha.baseUrl = normalizeOptionalString(bochaPatch.baseUrl) ?? "https://api.bocha.cn/v1/web-search";
1284
- if (Object.prototype.hasOwnProperty.call(bochaPatch, "docsUrl")) config.search.providers.bocha.docsUrl = normalizeOptionalString(bochaPatch.docsUrl) ?? BOCHA_OPEN_URL;
1285
- if (Object.prototype.hasOwnProperty.call(bochaPatch, "summary")) config.search.providers.bocha.summary = Boolean(bochaPatch.summary);
1286
- if (Object.prototype.hasOwnProperty.call(bochaPatch, "freshness")) {
1287
- const freshness = normalizeOptionalString(bochaPatch.freshness);
1288
- if (freshness === "noLimit" || freshness === "oneDay" || freshness === "oneWeek" || freshness === "oneMonth" || freshness === "oneYear") config.search.providers.bocha.freshness = freshness;
1289
- else config.search.providers.bocha.freshness = "noLimit";
1290
- }
1291
- }
1292
- const bravePatch = patch.providers?.brave;
1293
- if (bravePatch) {
1294
- if (Object.prototype.hasOwnProperty.call(bravePatch, "apiKey")) {
1295
- config.search.providers.brave.apiKey = bravePatch.apiKey ?? "";
1296
- clearSecretRef(config, "search.providers.brave.apiKey");
1297
- }
1298
- if (Object.prototype.hasOwnProperty.call(bravePatch, "baseUrl")) config.search.providers.brave.baseUrl = normalizeOptionalString(bravePatch.baseUrl) ?? "https://api.search.brave.com/res/v1/web/search";
1299
- }
1300
- const next = ConfigSchema.parse(config);
1301
- saveConfig(next, configPath);
1302
- return buildSearchView(next);
1303
- }
1304
1485
  function updateProvider(configPath, providerName, patch) {
1305
1486
  const config = loadConfigOrDefault(configPath);
1306
1487
  const provider = ensureProviderConfig(config, providerName);
@@ -1495,13 +1676,14 @@ function createSessionManager(config) {
1495
1676
  }
1496
1677
  const DEFAULT_SESSION_TYPE = "native";
1497
1678
  const SESSION_TYPE_METADATA_KEY = "session_type";
1679
+ const RUNTIME_METADATA_KEY = "runtime";
1498
1680
  function normalizeSessionType(value) {
1499
1681
  if (typeof value !== "string") return null;
1500
1682
  const normalized = value.trim().toLowerCase();
1501
1683
  return normalized.length > 0 ? normalized : null;
1502
1684
  }
1503
1685
  function readSessionType(session) {
1504
- return normalizeSessionType(session.metadata[SESSION_TYPE_METADATA_KEY]) ?? "native";
1686
+ return normalizeSessionType(session.metadata[RUNTIME_METADATA_KEY]) ?? normalizeSessionType(session.metadata[SESSION_TYPE_METADATA_KEY]) ?? "native";
1505
1687
  }
1506
1688
  function countUserMessages(session) {
1507
1689
  return session.messages.reduce((total, message) => {
@@ -1642,6 +1824,7 @@ function patchSession(configPath, key, patch, options) {
1642
1824
  availableSessionTypes.add(DEFAULT_SESSION_TYPE);
1643
1825
  if (!availableSessionTypes.has(normalizedSessionType)) throw new SessionPatchValidationError("SESSION_TYPE_UNAVAILABLE", `sessionType is unavailable: ${normalizedSessionType}`);
1644
1826
  session.metadata[SESSION_TYPE_METADATA_KEY] = normalizedSessionType;
1827
+ session.metadata[RUNTIME_METADATA_KEY] = normalizedSessionType;
1645
1828
  }
1646
1829
  session.updatedAt = /* @__PURE__ */ new Date();
1647
1830
  sessionManager.save(session);
@@ -2449,6 +2632,56 @@ function buildCronJobView(job) {
2449
2632
  function findCronJob(service, id) {
2450
2633
  return service.listJobs(true).find((job) => job.id === id) ?? null;
2451
2634
  }
2635
+ function normalizeCronSchedule(schedule) {
2636
+ if (!schedule) return null;
2637
+ if (schedule.kind === "every") {
2638
+ if (typeof schedule.everyMs !== "number" || !Number.isFinite(schedule.everyMs) || schedule.everyMs <= 0) return null;
2639
+ return {
2640
+ kind: "every",
2641
+ everyMs: schedule.everyMs
2642
+ };
2643
+ }
2644
+ if (schedule.kind === "at") {
2645
+ if (typeof schedule.atMs !== "number" || !Number.isFinite(schedule.atMs)) return null;
2646
+ return {
2647
+ kind: "at",
2648
+ atMs: schedule.atMs
2649
+ };
2650
+ }
2651
+ if (schedule.kind === "cron") {
2652
+ const expr = readNonEmptyString(schedule.expr);
2653
+ if (!expr) return null;
2654
+ const tz = readNonEmptyString(schedule.tz);
2655
+ return tz ? {
2656
+ kind: "cron",
2657
+ expr,
2658
+ tz
2659
+ } : {
2660
+ kind: "cron",
2661
+ expr
2662
+ };
2663
+ }
2664
+ return null;
2665
+ }
2666
+ function readCronCreateParams(input) {
2667
+ const name = readNonEmptyString(input.name);
2668
+ if (!name) return { error: "name must be a non-empty string" };
2669
+ const message = readNonEmptyString(input.message);
2670
+ if (!message) return { error: "message must be a non-empty string" };
2671
+ const schedule = normalizeCronSchedule(input.schedule);
2672
+ if (!schedule) return { error: "schedule must be a valid at/every/cron definition" };
2673
+ return { params: {
2674
+ name,
2675
+ message,
2676
+ schedule,
2677
+ agentId: readNonEmptyString(input.agentId),
2678
+ deliver: input.deliver === true,
2679
+ channel: readNonEmptyString(input.channel),
2680
+ to: readNonEmptyString(input.to),
2681
+ accountId: readNonEmptyString(input.accountId),
2682
+ deleteAfterRun: input.deleteAfterRun === true
2683
+ } };
2684
+ }
2452
2685
  var CronRoutesController = class {
2453
2686
  constructor(options) {
2454
2687
  this.options = options;
@@ -2463,6 +2696,15 @@ var CronRoutesController = class {
2463
2696
  total: jobs.length
2464
2697
  }));
2465
2698
  };
2699
+ createJob = async (c) => {
2700
+ if (!this.options.cronService) return c.json(err("NOT_AVAILABLE", "cron service unavailable"), 503);
2701
+ const body = await readJson(c.req.raw);
2702
+ if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
2703
+ const normalized = readCronCreateParams(body.data);
2704
+ if ("error" in normalized) return c.json(err("INVALID_BODY", normalized.error), 400);
2705
+ const data = { job: buildCronJobView(this.options.cronService.addJob(normalized.params)) };
2706
+ return c.json(ok(data), 201);
2707
+ };
2466
2708
  deleteJob = (c) => {
2467
2709
  if (!this.options.cronService) return c.json(err("NOT_AVAILABLE", "cron service unavailable"), 503);
2468
2710
  const id = decodeURIComponent(c.req.param("id"));
@@ -2738,9 +2980,11 @@ function applySessionTypePatch(metadata, patch) {
2738
2980
  const sessionType = typeof patch.sessionType === "string" ? patch.sessionType.trim() : "";
2739
2981
  if (sessionType) {
2740
2982
  metadata.session_type = sessionType;
2983
+ metadata.runtime = sessionType;
2741
2984
  delete metadata.sessionType;
2742
2985
  return;
2743
2986
  }
2987
+ delete metadata.runtime;
2744
2988
  delete metadata.session_type;
2745
2989
  delete metadata.sessionType;
2746
2990
  }
@@ -4190,6 +4434,7 @@ function registerAuthRoutes(app, authController) {
4190
4434
  function registerAgentRoutes(app, agentsController) {
4191
4435
  app.get("/api/agents", agentsController.listAgents);
4192
4436
  app.post("/api/agents", agentsController.createAgent);
4437
+ app.put("/api/agents/:agentId", agentsController.updateAgent);
4193
4438
  app.delete("/api/agents/:agentId", agentsController.deleteAgent);
4194
4439
  app.get("/api/agents/:agentId/avatar", agentsController.getAgentAvatar);
4195
4440
  }
@@ -4237,6 +4482,7 @@ function registerNcpRuntimeRoutes(app, options, ncpAssetController) {
4237
4482
  }
4238
4483
  function registerCronRoutes(app, cronController) {
4239
4484
  app.get("/api/cron", cronController.listJobs);
4485
+ app.post("/api/cron", cronController.createJob);
4240
4486
  app.delete("/api/cron/:id", cronController.deleteJob);
4241
4487
  app.put("/api/cron/:id/enable", cronController.enableJob);
4242
4488
  app.post("/api/cron/:id/run", cronController.runJob);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
@@ -18,12 +18,12 @@
18
18
  "@hono/node-server": "^1.13.3",
19
19
  "hono": "^4.6.2",
20
20
  "ws": "^8.18.0",
21
- "@nextclaw/mcp": "0.1.66",
22
- "@nextclaw/core": "0.12.1",
23
- "@nextclaw/openclaw-compat": "1.0.1",
24
- "@nextclaw/runtime": "0.2.33",
21
+ "@nextclaw/core": "0.12.2",
22
+ "@nextclaw/mcp": "0.1.67",
25
23
  "@nextclaw/ncp": "0.5.0",
26
- "@nextclaw/ncp-http-agent-server": "0.3.12"
24
+ "@nextclaw/ncp-http-agent-server": "0.3.12",
25
+ "@nextclaw/openclaw-compat": "1.0.2",
26
+ "@nextclaw/runtime": "0.2.34"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^20.17.6",