@nextclaw/server 0.6.7 → 0.6.9

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
@@ -54,6 +54,48 @@ type ProviderConnectionTestResult = {
54
54
  latencyMs: number;
55
55
  message: string;
56
56
  };
57
+ type SearchProviderName = "bocha" | "brave";
58
+ type BochaFreshnessValue = "noLimit" | "oneDay" | "oneWeek" | "oneMonth" | "oneYear" | string;
59
+ type SearchProviderConfigView = {
60
+ enabled: boolean;
61
+ apiKeySet: boolean;
62
+ apiKeyMasked?: string;
63
+ baseUrl: string;
64
+ docsUrl?: string;
65
+ summary?: boolean;
66
+ freshness?: BochaFreshnessValue;
67
+ };
68
+ type SearchConfigView = {
69
+ provider: SearchProviderName;
70
+ enabledProviders: SearchProviderName[];
71
+ defaults: {
72
+ maxResults: number;
73
+ };
74
+ providers: {
75
+ bocha: SearchProviderConfigView;
76
+ brave: SearchProviderConfigView;
77
+ };
78
+ };
79
+ type SearchConfigUpdate = {
80
+ provider?: SearchProviderName;
81
+ enabledProviders?: SearchProviderName[];
82
+ defaults?: {
83
+ maxResults?: number;
84
+ };
85
+ providers?: {
86
+ bocha?: {
87
+ apiKey?: string | null;
88
+ baseUrl?: string | null;
89
+ docsUrl?: string | null;
90
+ summary?: boolean;
91
+ freshness?: BochaFreshnessValue | null;
92
+ };
93
+ brave?: {
94
+ apiKey?: string | null;
95
+ baseUrl?: string | null;
96
+ };
97
+ };
98
+ };
57
99
  type ProviderAuthStartResult = {
58
100
  provider: string;
59
101
  kind: "device_code";
@@ -117,6 +159,8 @@ type SessionEntryView = {
117
159
  updatedAt: string;
118
160
  label?: string;
119
161
  preferredModel?: string;
162
+ sessionType: string;
163
+ sessionTypeMutable: boolean;
120
164
  messageCount: number;
121
165
  lastRole?: string;
122
166
  lastTimestamp?: string;
@@ -144,6 +188,8 @@ type SessionHistoryView = {
144
188
  key: string;
145
189
  totalMessages: number;
146
190
  totalEvents: number;
191
+ sessionType: string;
192
+ sessionTypeMutable: boolean;
147
193
  metadata: Record<string, unknown>;
148
194
  messages: SessionMessageView[];
149
195
  events: SessionEventView[];
@@ -151,6 +197,7 @@ type SessionHistoryView = {
151
197
  type SessionPatchUpdate = {
152
198
  label?: string | null;
153
199
  preferredModel?: string | null;
200
+ sessionType?: string | null;
154
201
  clearHistory?: boolean;
155
202
  };
156
203
  type CronScheduleView = {
@@ -300,6 +347,14 @@ type ChatCapabilitiesView = {
300
347
  stopSupported: boolean;
301
348
  stopReason?: string;
302
349
  };
350
+ type ChatSessionTypeOptionView = {
351
+ value: string;
352
+ label: string;
353
+ };
354
+ type ChatSessionTypesView = {
355
+ defaultType: string;
356
+ options: ChatSessionTypeOptionView[];
357
+ };
303
358
  type ChatCommandOptionView = {
304
359
  name: string;
305
360
  description: string;
@@ -363,6 +418,7 @@ type UiChatRuntime = {
363
418
  limit?: number;
364
419
  }) => Promise<ChatRunListView> | ChatRunListView;
365
420
  getCapabilities?: (params: Pick<ChatTurnRequest, "sessionKey" | "agentId">) => Promise<ChatCapabilitiesView> | ChatCapabilitiesView;
421
+ listSessionTypes?: () => Promise<ChatSessionTypesView> | ChatSessionTypesView;
366
422
  stopTurn?: (params: ChatTurnStopRequest) => Promise<ChatTurnStopResult> | ChatTurnStopResult;
367
423
  };
368
424
  type ConfigView = {
@@ -391,6 +447,7 @@ type ConfigView = {
391
447
  };
392
448
  };
393
449
  providers: Record<string, ProviderConfigView>;
450
+ search: SearchConfigView;
394
451
  channels: Record<string, Record<string, unknown>>;
395
452
  bindings?: AgentBindingView[];
396
453
  session?: SessionConfigView;
@@ -451,8 +508,17 @@ type ChannelSpecView = {
451
508
  zh?: string;
452
509
  };
453
510
  };
511
+ type SearchProviderSpecView = {
512
+ name: SearchProviderName;
513
+ displayName: string;
514
+ description: string;
515
+ docsUrl?: string;
516
+ isDefault?: boolean;
517
+ supportsSummary?: boolean;
518
+ };
454
519
  type ConfigMetaView = {
455
520
  providers: ProviderSpecView[];
521
+ search: SearchProviderSpecView[];
456
522
  channels: ChannelSpecView[];
457
523
  };
458
524
  type ConfigUiHint = {
@@ -773,6 +839,7 @@ declare function loadConfigOrDefault(configPath: string): Config;
773
839
  declare function updateModel(configPath: string, patch: {
774
840
  model?: string;
775
841
  }): ConfigView;
842
+ declare function updateSearch(configPath: string, patch: SearchConfigUpdate): ConfigView["search"];
776
843
  declare function updateProvider(configPath: string, providerName: string, patch: ProviderConfigUpdate): ProviderConfigView | null;
777
844
  declare function createCustomProvider(configPath: string, patch?: ProviderConfigUpdate): {
778
845
  name: string;
@@ -781,15 +848,22 @@ declare function createCustomProvider(configPath: string, patch?: ProviderConfig
781
848
  declare function deleteCustomProvider(configPath: string, providerName: string): boolean | null;
782
849
  declare function testProviderConnection(configPath: string, providerName: string, patch: ProviderConnectionTestRequest): Promise<ProviderConnectionTestResult | null>;
783
850
  declare function updateChannel(configPath: string, channelName: string, patch: Record<string, unknown>): Record<string, unknown> | null;
851
+ declare const DEFAULT_SESSION_TYPE = "native";
852
+ declare class SessionPatchValidationError extends Error {
853
+ readonly code: "SESSION_TYPE_INVALID" | "SESSION_TYPE_IMMUTABLE" | "SESSION_TYPE_UNAVAILABLE";
854
+ constructor(code: "SESSION_TYPE_INVALID" | "SESSION_TYPE_IMMUTABLE" | "SESSION_TYPE_UNAVAILABLE", message: string);
855
+ }
784
856
  declare function listSessions(configPath: string, query?: {
785
857
  q?: string;
786
858
  limit?: number;
787
859
  activeMinutes?: number;
788
860
  }): SessionsListView;
789
861
  declare function getSessionHistory(configPath: string, key: string, limit?: number): SessionHistoryView | null;
790
- declare function patchSession(configPath: string, key: string, patch: SessionPatchUpdate): SessionHistoryView | null;
862
+ declare function patchSession(configPath: string, key: string, patch: SessionPatchUpdate, options?: {
863
+ availableSessionTypes?: string[];
864
+ }): SessionHistoryView | null;
791
865
  declare function deleteSession(configPath: string, key: string): boolean;
792
866
  declare function updateRuntime(configPath: string, patch: RuntimeConfigUpdate): Pick<ConfigView, "agents" | "bindings" | "session">;
793
867
  declare function updateSecrets(configPath: string, patch: SecretsConfigUpdate): SecretsView;
794
868
 
795
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type BindingPeerView, type ChannelSpecView, type ChatCapabilitiesView, type ChatCommandOptionView, type ChatCommandView, type ChatCommandsView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallKind, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallKind, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderAuthImportResult, type ProviderAuthPollRequest, type ProviderAuthPollResult, type ProviderAuthStartRequest, type ProviderAuthStartResult, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, type ProviderSpecView, type RuntimeConfigUpdate, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSecrets };
869
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type BindingPeerView, type BochaFreshnessValue, type ChannelSpecView, type ChatCapabilitiesView, type ChatCommandOptionView, type ChatCommandView, type ChatCommandsView, type ChatRunListView, type ChatRunState, type ChatRunView, type ChatSessionTypeOptionView, type ChatSessionTypesView, type ChatTurnRequest, type ChatTurnResult, type ChatTurnStopRequest, type ChatTurnStopResult, type ChatTurnStreamEvent, type ChatTurnView, type ConfigActionExecuteRequest, type ConfigActionExecuteResult, type ConfigActionManifest, type ConfigActionType, type ConfigMetaView, type ConfigSchemaResponse, type ConfigUiHint, type ConfigUiHints, type ConfigView, type CronActionResult, type CronEnableRequest, type CronJobStateView, type CronJobView, type CronListView, type CronPayloadView, type CronRunRequest, type CronScheduleView, DEFAULT_SESSION_TYPE, type MarketplaceApiConfig, type MarketplaceInstallKind, type MarketplaceInstallSkillParams, type MarketplaceInstallSpec, type MarketplaceInstalledRecord, type MarketplaceInstalledView, type MarketplaceInstaller, type MarketplaceItemSummary, type MarketplaceItemType, type MarketplaceItemView, type MarketplaceListView, type MarketplaceLocalizedTextMap, type MarketplacePluginContentView, type MarketplacePluginInstallKind, type MarketplacePluginInstallRequest, type MarketplacePluginInstallResult, type MarketplacePluginManageAction, type MarketplacePluginManageRequest, type MarketplacePluginManageResult, type MarketplaceRecommendationView, type MarketplaceSkillContentView, type MarketplaceSkillInstallKind, type MarketplaceSkillInstallRequest, type MarketplaceSkillInstallResult, type MarketplaceSkillManageAction, type MarketplaceSkillManageRequest, type MarketplaceSkillManageResult, type MarketplaceSort, type ProviderAuthImportResult, type ProviderAuthPollRequest, type ProviderAuthPollResult, type ProviderAuthStartRequest, type ProviderAuthStartResult, type ProviderConfigUpdate, type ProviderConfigView, type ProviderConnectionTestRequest, type ProviderConnectionTestResult, type ProviderCreateRequest, type ProviderCreateResult, type ProviderDeleteResult, type ProviderSpecView, type RuntimeConfigUpdate, type SearchConfigUpdate, type SearchConfigView, type SearchProviderConfigView, type SearchProviderName, type SearchProviderSpecView, type SecretProviderEnvView, type SecretProviderExecView, type SecretProviderFileView, type SecretProviderView, type SecretRefView, type SecretSourceView, type SecretsConfigUpdate, type SecretsView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, SessionPatchValidationError, type SessionsListView, type UiChatRuntime, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, executeConfigAction, getSessionHistory, listSessions, loadConfigOrDefault, patchSession, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSearch, updateSecrets };
package/dist/index.js CHANGED
@@ -212,6 +212,7 @@ function clearSecretRefsByPrefix(config, pathPrefix) {
212
212
  }
213
213
  }
214
214
  var DOCS_BASE_URL = "https://docs.nextclaw.io";
215
+ var BOCHA_OPEN_URL = "https://open.bocha.cn";
215
216
  var CHANNEL_TUTORIAL_URLS = {
216
217
  feishu: {
217
218
  default: `${DOCS_BASE_URL}/guide/tutorials/feishu`,
@@ -219,6 +220,22 @@ var CHANNEL_TUTORIAL_URLS = {
219
220
  zh: `${DOCS_BASE_URL}/zh/guide/tutorials/feishu`
220
221
  }
221
222
  };
223
+ var SEARCH_PROVIDER_META = [
224
+ {
225
+ name: "bocha",
226
+ displayName: "Bocha Search",
227
+ description: "China-friendly web search with AI-ready summaries.",
228
+ docsUrl: BOCHA_OPEN_URL,
229
+ isDefault: true,
230
+ supportsSummary: true
231
+ },
232
+ {
233
+ name: "brave",
234
+ displayName: "Brave Search",
235
+ description: "Brave web search API kept as an optional provider.",
236
+ supportsSummary: false
237
+ }
238
+ ];
222
239
  function matchesExtraSensitivePath(path) {
223
240
  if (path === "session" || path.startsWith("session.")) {
224
241
  return false;
@@ -470,6 +487,7 @@ function buildConfigView(config) {
470
487
  return {
471
488
  agents: config.agents,
472
489
  providers,
490
+ search: buildSearchView(config),
473
491
  channels: sanitizePublicConfigValue(
474
492
  config.channels,
475
493
  "channels",
@@ -488,6 +506,40 @@ function buildConfigView(config) {
488
506
  }
489
507
  };
490
508
  }
509
+ function toSearchProviderView(config, providerName, provider) {
510
+ const apiKeyPath = `search.providers.${providerName}.apiKey`;
511
+ const apiKeyRefSet = hasSecretRef(config, apiKeyPath);
512
+ const masked = maskApiKey(provider.apiKey);
513
+ const base = {
514
+ enabled: config.search.enabledProviders.includes(providerName),
515
+ apiKeySet: masked.apiKeySet || apiKeyRefSet,
516
+ apiKeyMasked: masked.apiKeyMasked ?? (apiKeyRefSet ? "****" : void 0),
517
+ baseUrl: provider.baseUrl
518
+ };
519
+ if ("docsUrl" in provider) {
520
+ base.docsUrl = provider.docsUrl;
521
+ }
522
+ if ("summary" in provider) {
523
+ base.summary = provider.summary;
524
+ }
525
+ if ("freshness" in provider) {
526
+ base.freshness = provider.freshness;
527
+ }
528
+ return base;
529
+ }
530
+ function buildSearchView(config) {
531
+ return {
532
+ provider: config.search.provider,
533
+ enabledProviders: [...config.search.enabledProviders],
534
+ defaults: {
535
+ maxResults: config.search.defaults.maxResults
536
+ },
537
+ providers: {
538
+ bocha: toSearchProviderView(config, "bocha", config.search.providers.bocha),
539
+ brave: toSearchProviderView(config, "brave", config.search.providers.brave)
540
+ }
541
+ };
542
+ }
491
543
  function clearSecretRef(config, path) {
492
544
  if (config.secrets.refs[path]) {
493
545
  delete config.secrets.refs[path];
@@ -574,7 +626,7 @@ function buildConfigMeta(config) {
574
626
  tutorialUrls
575
627
  };
576
628
  });
577
- return { providers, channels };
629
+ return { providers, search: SEARCH_PROVIDER_META, channels };
578
630
  }
579
631
  function buildConfigSchemaView(_config) {
580
632
  return buildConfigSchema({ version: getPackageVersion() });
@@ -643,6 +695,60 @@ function updateModel(configPath, patch) {
643
695
  saveConfig(next, configPath);
644
696
  return buildConfigView(next);
645
697
  }
698
+ function updateSearch(configPath, patch) {
699
+ const config = loadConfigOrDefault(configPath);
700
+ if (patch.provider === "bocha" || patch.provider === "brave") {
701
+ config.search.provider = patch.provider;
702
+ }
703
+ if (Array.isArray(patch.enabledProviders)) {
704
+ config.search.enabledProviders = Array.from(new Set(
705
+ patch.enabledProviders.filter((value) => value === "bocha" || value === "brave")
706
+ ));
707
+ }
708
+ if (patch.defaults && Object.prototype.hasOwnProperty.call(patch.defaults, "maxResults")) {
709
+ const nextMaxResults = patch.defaults.maxResults;
710
+ if (typeof nextMaxResults === "number" && Number.isFinite(nextMaxResults)) {
711
+ config.search.defaults.maxResults = Math.max(1, Math.min(50, Math.trunc(nextMaxResults)));
712
+ }
713
+ }
714
+ const bochaPatch = patch.providers?.bocha;
715
+ if (bochaPatch) {
716
+ if (Object.prototype.hasOwnProperty.call(bochaPatch, "apiKey")) {
717
+ config.search.providers.bocha.apiKey = bochaPatch.apiKey ?? "";
718
+ clearSecretRef(config, "search.providers.bocha.apiKey");
719
+ }
720
+ if (Object.prototype.hasOwnProperty.call(bochaPatch, "baseUrl")) {
721
+ config.search.providers.bocha.baseUrl = normalizeOptionalString(bochaPatch.baseUrl) ?? "https://api.bocha.cn/v1/web-search";
722
+ }
723
+ if (Object.prototype.hasOwnProperty.call(bochaPatch, "docsUrl")) {
724
+ config.search.providers.bocha.docsUrl = normalizeOptionalString(bochaPatch.docsUrl) ?? BOCHA_OPEN_URL;
725
+ }
726
+ if (Object.prototype.hasOwnProperty.call(bochaPatch, "summary")) {
727
+ config.search.providers.bocha.summary = Boolean(bochaPatch.summary);
728
+ }
729
+ if (Object.prototype.hasOwnProperty.call(bochaPatch, "freshness")) {
730
+ const freshness = normalizeOptionalString(bochaPatch.freshness);
731
+ if (freshness === "noLimit" || freshness === "oneDay" || freshness === "oneWeek" || freshness === "oneMonth" || freshness === "oneYear") {
732
+ config.search.providers.bocha.freshness = freshness;
733
+ } else {
734
+ config.search.providers.bocha.freshness = "noLimit";
735
+ }
736
+ }
737
+ }
738
+ const bravePatch = patch.providers?.brave;
739
+ if (bravePatch) {
740
+ if (Object.prototype.hasOwnProperty.call(bravePatch, "apiKey")) {
741
+ config.search.providers.brave.apiKey = bravePatch.apiKey ?? "";
742
+ clearSecretRef(config, "search.providers.brave.apiKey");
743
+ }
744
+ if (Object.prototype.hasOwnProperty.call(bravePatch, "baseUrl")) {
745
+ config.search.providers.brave.baseUrl = normalizeOptionalString(bravePatch.baseUrl) ?? "https://api.search.brave.com/res/v1/web/search";
746
+ }
747
+ }
748
+ const next = ConfigSchema.parse(config);
749
+ saveConfig(next, configPath);
750
+ return buildSearchView(next);
751
+ }
646
752
  function updateProvider(configPath, providerName, patch) {
647
753
  const config = loadConfigOrDefault(configPath);
648
754
  const provider = ensureProviderConfig(config, providerName);
@@ -875,6 +981,36 @@ function normalizeSessionKey(value) {
875
981
  function createSessionManager(config) {
876
982
  return new SessionManager(getWorkspacePathFromConfig(config));
877
983
  }
984
+ var DEFAULT_SESSION_TYPE = "native";
985
+ var SESSION_TYPE_METADATA_KEY = "session_type";
986
+ function normalizeSessionType(value) {
987
+ if (typeof value !== "string") {
988
+ return null;
989
+ }
990
+ const normalized = value.trim().toLowerCase();
991
+ return normalized.length > 0 ? normalized : null;
992
+ }
993
+ function readSessionType(session) {
994
+ const normalized = normalizeSessionType(session.metadata[SESSION_TYPE_METADATA_KEY]);
995
+ return normalized ?? DEFAULT_SESSION_TYPE;
996
+ }
997
+ function countUserMessages(session) {
998
+ return session.messages.reduce((total, message) => {
999
+ const role = typeof message.role === "string" ? message.role.trim().toLowerCase() : "";
1000
+ return role === "user" ? total + 1 : total;
1001
+ }, 0);
1002
+ }
1003
+ function isSessionTypeMutable(session) {
1004
+ const activeUiRunId = typeof session.metadata.ui_active_run_id === "string" ? session.metadata.ui_active_run_id.trim() : "";
1005
+ return countUserMessages(session) === 0 && activeUiRunId.length === 0;
1006
+ }
1007
+ var SessionPatchValidationError = class extends Error {
1008
+ constructor(code, message) {
1009
+ super(message);
1010
+ this.code = code;
1011
+ this.name = "SessionPatchValidationError";
1012
+ }
1013
+ };
878
1014
  function listSessions(configPath, query) {
879
1015
  const config = loadConfigOrDefault(configPath);
880
1016
  const sessionManager = createSessionManager(config);
@@ -895,12 +1031,22 @@ function listSessions(configPath, query) {
895
1031
  const preferredModel = typeof metadata.preferred_model === "string" ? metadata.preferred_model.trim() : "";
896
1032
  const createdAt = typeof item.created_at === "string" ? item.created_at : (/* @__PURE__ */ new Date(0)).toISOString();
897
1033
  const updatedAt = typeof item.updated_at === "string" ? item.updated_at : createdAt;
1034
+ const sessionType = readSessionType({
1035
+ metadata,
1036
+ messages
1037
+ });
1038
+ const sessionTypeMutable = isSessionTypeMutable({
1039
+ metadata,
1040
+ messages
1041
+ });
898
1042
  return {
899
1043
  key,
900
1044
  createdAt,
901
1045
  updatedAt,
902
1046
  label: label || void 0,
903
1047
  preferredModel: preferredModel || void 0,
1048
+ sessionType,
1049
+ sessionTypeMutable,
904
1050
  messageCount: messages.length,
905
1051
  lastRole: typeof lastMessage?.role === "string" ? lastMessage.role : void 0,
906
1052
  lastTimestamp: typeof lastMessage?.timestamp === "string" ? lastMessage.timestamp : void 0
@@ -940,10 +1086,14 @@ function getSessionHistory(configPath, key, limit) {
940
1086
  const safeEventLimit = Math.min(2e3, Math.max(50, safeLimit * 4));
941
1087
  const allEvents = session.events ?? [];
942
1088
  const events = allEvents.length > safeEventLimit ? allEvents.slice(-safeEventLimit) : allEvents;
1089
+ const sessionType = readSessionType(session);
1090
+ const sessionTypeMutable = isSessionTypeMutable(session);
943
1091
  return {
944
1092
  key: normalizedKey,
945
1093
  totalMessages: allMessages.length,
946
1094
  totalEvents: allEvents.length,
1095
+ sessionType,
1096
+ sessionTypeMutable,
947
1097
  metadata: session.metadata,
948
1098
  messages: messages.map((message) => {
949
1099
  const entry = {
@@ -990,7 +1140,7 @@ function getSessionHistory(configPath, key, limit) {
990
1140
  })
991
1141
  };
992
1142
  }
993
- function patchSession(configPath, key, patch) {
1143
+ function patchSession(configPath, key, patch, options) {
994
1144
  const normalizedKey = normalizeSessionKey(key);
995
1145
  if (!normalizedKey) {
996
1146
  return null;
@@ -1020,6 +1170,32 @@ function patchSession(configPath, key, patch) {
1020
1170
  delete session.metadata.preferred_model;
1021
1171
  }
1022
1172
  }
1173
+ if (Object.prototype.hasOwnProperty.call(patch, "sessionType")) {
1174
+ const normalizedSessionType = normalizeSessionType(patch.sessionType);
1175
+ if (!normalizedSessionType) {
1176
+ throw new SessionPatchValidationError(
1177
+ "SESSION_TYPE_INVALID",
1178
+ "sessionType must be a non-empty string"
1179
+ );
1180
+ }
1181
+ if (!isSessionTypeMutable(session)) {
1182
+ throw new SessionPatchValidationError(
1183
+ "SESSION_TYPE_IMMUTABLE",
1184
+ "sessionType cannot be changed after the first user message"
1185
+ );
1186
+ }
1187
+ const availableSessionTypes = new Set(
1188
+ (options?.availableSessionTypes ?? [DEFAULT_SESSION_TYPE]).map((item) => normalizeSessionType(item)).filter((item) => Boolean(item))
1189
+ );
1190
+ availableSessionTypes.add(DEFAULT_SESSION_TYPE);
1191
+ if (!availableSessionTypes.has(normalizedSessionType)) {
1192
+ throw new SessionPatchValidationError(
1193
+ "SESSION_TYPE_UNAVAILABLE",
1194
+ `sessionType is unavailable: ${normalizedSessionType}`
1195
+ );
1196
+ }
1197
+ session.metadata[SESSION_TYPE_METADATA_KEY] = normalizedSessionType;
1198
+ }
1023
1199
  session.updatedAt = /* @__PURE__ */ new Date();
1024
1200
  sessionManager.save(session);
1025
1201
  return getSessionHistory(configPath, normalizedKey, 200);
@@ -1851,6 +2027,67 @@ function readNonEmptyString(value) {
1851
2027
  const trimmed = value.trim();
1852
2028
  return trimmed || void 0;
1853
2029
  }
2030
+ function normalizeSessionType2(value) {
2031
+ return readNonEmptyString(value)?.toLowerCase();
2032
+ }
2033
+ function resolveSessionTypeLabel(sessionType) {
2034
+ if (sessionType === "native") {
2035
+ return "Native";
2036
+ }
2037
+ if (sessionType === "codex-sdk") {
2038
+ return "Codex";
2039
+ }
2040
+ if (sessionType === "claude-agent-sdk") {
2041
+ return "Claude Code";
2042
+ }
2043
+ return sessionType;
2044
+ }
2045
+ async function buildChatSessionTypesView(chatRuntime) {
2046
+ if (!chatRuntime?.listSessionTypes) {
2047
+ return {
2048
+ defaultType: DEFAULT_SESSION_TYPE,
2049
+ options: [{ value: DEFAULT_SESSION_TYPE, label: resolveSessionTypeLabel(DEFAULT_SESSION_TYPE) }]
2050
+ };
2051
+ }
2052
+ const payload = await chatRuntime.listSessionTypes();
2053
+ const deduped = /* @__PURE__ */ new Map();
2054
+ for (const rawOption of payload.options ?? []) {
2055
+ const normalized = normalizeSessionType2(rawOption.value);
2056
+ if (!normalized) {
2057
+ continue;
2058
+ }
2059
+ deduped.set(normalized, {
2060
+ value: normalized,
2061
+ label: readNonEmptyString(rawOption.label) ?? resolveSessionTypeLabel(normalized)
2062
+ });
2063
+ }
2064
+ if (!deduped.has(DEFAULT_SESSION_TYPE)) {
2065
+ deduped.set(DEFAULT_SESSION_TYPE, {
2066
+ value: DEFAULT_SESSION_TYPE,
2067
+ label: resolveSessionTypeLabel(DEFAULT_SESSION_TYPE)
2068
+ });
2069
+ }
2070
+ const defaultType = normalizeSessionType2(payload.defaultType) ?? DEFAULT_SESSION_TYPE;
2071
+ if (!deduped.has(defaultType)) {
2072
+ deduped.set(defaultType, {
2073
+ value: defaultType,
2074
+ label: resolveSessionTypeLabel(defaultType)
2075
+ });
2076
+ }
2077
+ const options = Array.from(deduped.values()).sort((left, right) => {
2078
+ if (left.value === DEFAULT_SESSION_TYPE) {
2079
+ return -1;
2080
+ }
2081
+ if (right.value === DEFAULT_SESSION_TYPE) {
2082
+ return 1;
2083
+ }
2084
+ return left.value.localeCompare(right.value);
2085
+ });
2086
+ return {
2087
+ defaultType,
2088
+ options
2089
+ };
2090
+ }
1854
2091
  function resolveAgentIdFromSessionKey(sessionKey) {
1855
2092
  const parsed = NextclawCore.parseAgentScopedSessionKey(sessionKey);
1856
2093
  const agentId = readNonEmptyString(parsed?.agentId);
@@ -2895,6 +3132,15 @@ function createUiRouter(options) {
2895
3132
  model: view.agents.defaults.model
2896
3133
  }));
2897
3134
  });
3135
+ app.put("/api/config/search", async (c) => {
3136
+ const body = await readJson(c.req.raw);
3137
+ if (!body.ok) {
3138
+ return c.json(err("INVALID_BODY", "invalid json body"), 400);
3139
+ }
3140
+ const result = updateSearch(options.configPath, body.data);
3141
+ options.publish({ type: "config.updated", payload: { path: "search" } });
3142
+ return c.json(ok(result));
3143
+ });
2898
3144
  app.put("/api/config/providers/:provider", async (c) => {
2899
3145
  const provider = c.req.param("provider");
2900
3146
  const body = await readJson(c.req.raw);
@@ -3052,6 +3298,14 @@ function createUiRouter(options) {
3052
3298
  return c.json(err("CHAT_RUNTIME_FAILED", String(error)), 500);
3053
3299
  }
3054
3300
  });
3301
+ app.get("/api/chat/session-types", async (c) => {
3302
+ try {
3303
+ const payload = await buildChatSessionTypesView(options.chatRuntime);
3304
+ return c.json(ok(payload));
3305
+ } catch (error) {
3306
+ return c.json(err("CHAT_SESSION_TYPES_FAILED", String(error)), 500);
3307
+ }
3308
+ });
3055
3309
  app.get("/api/chat/commands", async (c) => {
3056
3310
  try {
3057
3311
  const config = loadConfigOrDefault(options.configPath);
@@ -3524,7 +3778,25 @@ function createUiRouter(options) {
3524
3778
  if (!body.ok || !body.data || typeof body.data !== "object") {
3525
3779
  return c.json(err("INVALID_BODY", "invalid json body"), 400);
3526
3780
  }
3527
- const data = patchSession(options.configPath, key, body.data);
3781
+ let availableSessionTypes;
3782
+ if (Object.prototype.hasOwnProperty.call(body.data, "sessionType")) {
3783
+ const sessionTypes = await buildChatSessionTypesView(options.chatRuntime);
3784
+ availableSessionTypes = sessionTypes.options.map((item) => item.value);
3785
+ }
3786
+ let data;
3787
+ try {
3788
+ data = patchSession(options.configPath, key, body.data, {
3789
+ ...availableSessionTypes ? { availableSessionTypes } : {}
3790
+ });
3791
+ } catch (error) {
3792
+ if (error instanceof SessionPatchValidationError) {
3793
+ if (error.code === "SESSION_TYPE_IMMUTABLE") {
3794
+ return c.json(err(error.code, error.message), 409);
3795
+ }
3796
+ return c.json(err(error.code, error.message), 400);
3797
+ }
3798
+ throw error;
3799
+ }
3528
3800
  if (!data) {
3529
3801
  return c.json(err("NOT_FOUND", `session not found: ${key}`), 404);
3530
3802
  }
@@ -3729,6 +4001,8 @@ function startUiServer(options) {
3729
4001
  };
3730
4002
  }
3731
4003
  export {
4004
+ DEFAULT_SESSION_TYPE,
4005
+ SessionPatchValidationError,
3732
4006
  buildConfigMeta,
3733
4007
  buildConfigSchemaView,
3734
4008
  buildConfigView,
@@ -3747,5 +4021,6 @@ export {
3747
4021
  updateModel,
3748
4022
  updateProvider,
3749
4023
  updateRuntime,
4024
+ updateSearch,
3750
4025
  updateSecrets
3751
4026
  };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "private": false,
5
5
  "description": "Nextclaw UI/API server.",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": {
9
+ "development": "./src/index.ts",
9
10
  "types": "./dist/index.d.ts",
10
11
  "default": "./dist/index.js"
11
12
  }
@@ -17,9 +18,9 @@
17
18
  "@hono/node-server": "^1.13.3",
18
19
  "hono": "^4.6.2",
19
20
  "ws": "^8.18.0",
20
- "@nextclaw/openclaw-compat": "0.2.2",
21
- "@nextclaw/runtime": "0.1.2",
22
- "@nextclaw/core": "0.7.3"
21
+ "@nextclaw/openclaw-compat": "0.2.4",
22
+ "@nextclaw/core": "0.7.5",
23
+ "@nextclaw/runtime": "0.1.4"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@types/node": "^20.17.6",