@nextclaw/server 0.12.1 → 0.12.3

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
@@ -372,6 +372,7 @@ type UiRemoteAccessHost = {
372
372
  startBrowserAuth: (input: RemoteBrowserAuthStartRequest) => Promise<RemoteBrowserAuthStartResult>;
373
373
  pollBrowserAuth: (input: RemoteBrowserAuthPollRequest) => Promise<RemoteBrowserAuthPollResult>;
374
374
  logout: () => Promise<RemoteAccessView> | RemoteAccessView;
375
+ updateProfile: (input: RemoteAccountProfileUpdateRequest) => Promise<RemoteAccessView> | RemoteAccessView;
375
376
  updateSettings: (input: RemoteSettingsUpdateRequest) => Promise<RemoteAccessView> | RemoteAccessView;
376
377
  runDoctor: () => Promise<RemoteDoctorView>;
377
378
  controlService: (action: RemoteServiceAction) => Promise<RemoteServiceActionResult>;
@@ -486,8 +487,9 @@ type ProviderConnectionTestResult = {
486
487
  latencyMs: number;
487
488
  message: string;
488
489
  };
489
- type SearchProviderName = "bocha" | "brave";
490
+ type SearchProviderName = "bocha" | "tavily" | "brave";
490
491
  type BochaFreshnessValue = "noLimit" | "oneDay" | "oneWeek" | "oneMonth" | "oneYear" | string;
492
+ type TavilySearchDepthValue = "basic" | "advanced";
491
493
  type SearchProviderConfigView = {
492
494
  enabled: boolean;
493
495
  apiKeySet: boolean;
@@ -496,6 +498,8 @@ type SearchProviderConfigView = {
496
498
  docsUrl?: string;
497
499
  summary?: boolean;
498
500
  freshness?: BochaFreshnessValue;
501
+ searchDepth?: TavilySearchDepthValue;
502
+ includeAnswer?: boolean;
499
503
  };
500
504
  type SearchConfigView = {
501
505
  provider: SearchProviderName;
@@ -505,6 +509,7 @@ type SearchConfigView = {
505
509
  };
506
510
  providers: {
507
511
  bocha: SearchProviderConfigView;
512
+ tavily: SearchProviderConfigView;
508
513
  brave: SearchProviderConfigView;
509
514
  };
510
515
  };
@@ -522,6 +527,12 @@ type SearchConfigUpdate = {
522
527
  summary?: boolean;
523
528
  freshness?: BochaFreshnessValue | null;
524
529
  };
530
+ tavily?: {
531
+ apiKey?: string | null;
532
+ baseUrl?: string | null;
533
+ searchDepth?: TavilySearchDepthValue | null;
534
+ includeAnswer?: boolean;
535
+ };
525
536
  brave?: {
526
537
  apiKey?: string | null;
527
538
  baseUrl?: string | null;
@@ -605,10 +616,14 @@ type AuthEnabledUpdateRequest = {
605
616
  type RemoteAccountView = {
606
617
  loggedIn: boolean;
607
618
  email?: string;
619
+ username?: string | null;
608
620
  role?: string;
609
621
  platformBase?: string | null;
610
622
  apiBase?: string | null;
611
623
  };
624
+ type RemoteAccountProfileUpdateRequest = {
625
+ username: string;
626
+ };
612
627
  type RemoteRuntimeView = {
613
628
  enabled: boolean;
614
629
  mode: "service" | "foreground";
@@ -700,6 +715,8 @@ type AgentProfileView = {
700
715
  avatarUrl?: string;
701
716
  workspace?: string;
702
717
  model?: string;
718
+ runtime?: string;
719
+ runtimeConfig?: Record<string, unknown>;
703
720
  engine?: string;
704
721
  engineConfig?: Record<string, unknown>;
705
722
  contextTokens?: number;
@@ -712,6 +729,17 @@ type AgentCreateRequest = {
712
729
  description?: string;
713
730
  avatar?: string;
714
731
  home?: string;
732
+ model?: string;
733
+ runtime?: string;
734
+ runtimeConfig?: Record<string, unknown>;
735
+ };
736
+ type AgentUpdateRequest = {
737
+ displayName?: string;
738
+ description?: string;
739
+ avatar?: string;
740
+ model?: string;
741
+ runtime?: string;
742
+ runtimeConfig?: Record<string, unknown>;
715
743
  };
716
744
  type AgentDeleteResult = {
717
745
  deleted: boolean;
@@ -854,6 +882,20 @@ type CronListView = {
854
882
  jobs: CronJobView[];
855
883
  total: number;
856
884
  };
885
+ type CronCreateRequest = {
886
+ name: string;
887
+ message: string;
888
+ schedule: CronScheduleView;
889
+ agentId?: string | null;
890
+ deliver?: boolean;
891
+ channel?: string | null;
892
+ to?: string | null;
893
+ accountId?: string | null;
894
+ deleteAfterRun?: boolean;
895
+ };
896
+ type CronCreateResult = {
897
+ job: CronJobView;
898
+ };
857
899
  type CronEnableRequest = {
858
900
  enabled: boolean;
859
901
  };
@@ -1192,6 +1234,9 @@ type PluginConfigProjectionOptions = {
1192
1234
  pluginUiMetadata?: PluginUiMetadata[];
1193
1235
  };
1194
1236
  //#endregion
1237
+ //#region src/ui/search-config.d.ts
1238
+ declare function updateSearch(configPath: string, patch: SearchConfigUpdate): ConfigView["search"];
1239
+ //#endregion
1195
1240
  //#region src/ui/config.d.ts
1196
1241
  type ExecuteActionResult = {
1197
1242
  ok: true;
@@ -1211,7 +1256,6 @@ declare function updateModel(configPath: string, patch: {
1211
1256
  model?: string;
1212
1257
  workspace?: string;
1213
1258
  }): ConfigView;
1214
- declare function updateSearch(configPath: string, patch: SearchConfigUpdate): ConfigView["search"];
1215
1259
  declare function updateProvider(configPath: string, providerName: string, patch: ProviderConfigUpdate): ProviderConfigView | null;
1216
1260
  declare function createCustomProvider(configPath: string, patch?: ProviderConfigUpdate): {
1217
1261
  name: string;
@@ -1243,4 +1287,4 @@ declare function getUiBridgeSecretPath(): string;
1243
1287
  declare function readUiBridgeSecret(): string | null;
1244
1288
  declare function ensureUiBridgeSecret(): string;
1245
1289
  //#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 };
1290
+ 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, RemoteAccountProfileUpdateRequest, 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"));
@@ -2554,7 +2796,6 @@ var NcpAssetRoutesController = class {
2554
2796
  if (!record || !contentPath) return c.json(err("NOT_FOUND", `asset not found: ${uri}`), 404);
2555
2797
  const body = await readFile(contentPath);
2556
2798
  return new Response(body, { headers: {
2557
- "content-length": String(body.byteLength),
2558
2799
  "content-type": record.mimeType || "application/octet-stream",
2559
2800
  "content-disposition": `inline; filename*=UTF-8''${encodeContentDispositionFileName(record.fileName)}`
2560
2801
  } });
@@ -2738,9 +2979,11 @@ function applySessionTypePatch(metadata, patch) {
2738
2979
  const sessionType = typeof patch.sessionType === "string" ? patch.sessionType.trim() : "";
2739
2980
  if (sessionType) {
2740
2981
  metadata.session_type = sessionType;
2982
+ metadata.runtime = sessionType;
2741
2983
  delete metadata.sessionType;
2742
2984
  return;
2743
2985
  }
2986
+ delete metadata.runtime;
2744
2987
  delete metadata.session_type;
2745
2988
  delete metadata.sessionType;
2746
2989
  }
@@ -4048,6 +4291,17 @@ var RemoteRoutesController = class {
4048
4291
  return c.json(err("REMOTE_LOGOUT_FAILED", formatUserFacingError(error)), 500);
4049
4292
  }
4050
4293
  };
4294
+ updateProfile = async (c) => {
4295
+ const body = await readJson(c.req.raw);
4296
+ if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
4297
+ const username = readNonEmptyString(body.data.username);
4298
+ if (!username) return c.json(err("INVALID_BODY", "username is required"), 400);
4299
+ try {
4300
+ return c.json(ok(await this.host.updateProfile({ username })));
4301
+ } catch (error) {
4302
+ return c.json(err("REMOTE_PROFILE_UPDATE_FAILED", formatUserFacingError(error)), 400);
4303
+ }
4304
+ };
4051
4305
  updateSettings = async (c) => {
4052
4306
  const body = await readJson(c.req.raw);
4053
4307
  if (!body.ok) return c.json(err("INVALID_BODY", "invalid json body"), 400);
@@ -4190,6 +4444,7 @@ function registerAuthRoutes(app, authController) {
4190
4444
  function registerAgentRoutes(app, agentsController) {
4191
4445
  app.get("/api/agents", agentsController.listAgents);
4192
4446
  app.post("/api/agents", agentsController.createAgent);
4447
+ app.put("/api/agents/:agentId", agentsController.updateAgent);
4193
4448
  app.delete("/api/agents/:agentId", agentsController.deleteAgent);
4194
4449
  app.get("/api/agents/:agentId/avatar", agentsController.getAgentAvatar);
4195
4450
  }
@@ -4237,6 +4492,7 @@ function registerNcpRuntimeRoutes(app, options, ncpAssetController) {
4237
4492
  }
4238
4493
  function registerCronRoutes(app, cronController) {
4239
4494
  app.get("/api/cron", cronController.listJobs);
4495
+ app.post("/api/cron", cronController.createJob);
4240
4496
  app.delete("/api/cron/:id", cronController.deleteJob);
4241
4497
  app.put("/api/cron/:id/enable", cronController.enableJob);
4242
4498
  app.post("/api/cron/:id/run", cronController.runJob);
@@ -4249,6 +4505,7 @@ function registerRemoteRoutes(app, remoteController) {
4249
4505
  app.post("/api/remote/auth/start", remoteController.startBrowserAuth);
4250
4506
  app.post("/api/remote/auth/poll", remoteController.pollBrowserAuth);
4251
4507
  app.post("/api/remote/logout", remoteController.logout);
4508
+ app.put("/api/remote/account/profile", remoteController.updateProfile);
4252
4509
  app.put("/api/remote/settings", remoteController.updateSettings);
4253
4510
  app.post("/api/remote/service/:action", remoteController.controlService);
4254
4511
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
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.3",
22
+ "@nextclaw/mcp": "0.1.68",
23
+ "@nextclaw/ncp-http-agent-server": "0.3.12",
24
+ "@nextclaw/openclaw-compat": "1.0.3",
25
25
  "@nextclaw/ncp": "0.5.0",
26
- "@nextclaw/ncp-http-agent-server": "0.3.12"
26
+ "@nextclaw/runtime": "0.2.35"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^20.17.6",