@nextclaw/server 0.11.21 → 0.11.23

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
@@ -760,8 +760,42 @@ type SessionPatchUpdate = {
760
760
  preferredModel?: string | null;
761
761
  preferredThinking?: ThinkingLevel | null;
762
762
  sessionType?: string | null;
763
+ projectRoot?: string | null;
763
764
  clearHistory?: boolean;
764
765
  };
766
+ type SessionSkillEntryView = {
767
+ ref: string;
768
+ name: string;
769
+ path: string;
770
+ scope: "project" | "workspace";
771
+ source: "project" | "workspace";
772
+ available: boolean;
773
+ description?: string;
774
+ descriptionZh?: string;
775
+ };
776
+ type NcpSessionSkillsView = {
777
+ sessionId: string;
778
+ total: number;
779
+ refs: string[];
780
+ records: SessionSkillEntryView[];
781
+ };
782
+ type ServerPathEntryView = {
783
+ name: string;
784
+ path: string;
785
+ kind: "directory" | "file";
786
+ hidden: boolean;
787
+ };
788
+ type ServerPathBreadcrumbView = {
789
+ label: string;
790
+ path: string;
791
+ };
792
+ type ServerPathBrowseView = {
793
+ currentPath: string;
794
+ parentPath: string | null;
795
+ homePath: string;
796
+ breadcrumbs: ServerPathBreadcrumbView[];
797
+ entries: ServerPathEntryView[];
798
+ };
765
799
  type CronScheduleView = {
766
800
  kind: "at";
767
801
  atMs?: number | null;
@@ -1183,4 +1217,4 @@ declare function getUiBridgeSecretPath(): string;
1183
1217
  declare function readUiBridgeSecret(): string | null;
1184
1218
  declare function ensureUiBridgeSecret(): string;
1185
1219
 
1186
- export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type AuthEnabledUpdateRequest, type AuthLoginRequest, type AuthPasswordUpdateRequest, type AuthSetupRequest, type AuthStatusView, type BindingPeerView, type BochaFreshnessValue, type BootstrapPhase, type BootstrapRemoteState, type BootstrapStageState, type BootstrapStatusView, type ChannelAuthPollRequest, type ChannelAuthPollResult, type ChannelAuthStartRequest, type ChannelAuthStartResult, type ChannelSpecView, type ChatSessionTypeCtaView, type ChatSessionTypeOptionView, type ChatSessionTypesView, 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 MarketplaceMcpContentView, type MarketplaceMcpDoctorResult, type MarketplaceMcpInstallKind, type MarketplaceMcpInstallRequest, type MarketplaceMcpInstallResult, type MarketplaceMcpInstallSpec, type MarketplaceMcpManageAction, type MarketplaceMcpManageRequest, type MarketplaceMcpManageResult, type MarketplaceMcpTemplateInput, 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 RemoteAccessView, type RemoteAccountView, type RemoteBrowserAuthPollRequest, type RemoteBrowserAuthPollResult, type RemoteBrowserAuthStartRequest, type RemoteBrowserAuthStartResult, type RemoteDoctorCheckView, type RemoteDoctorView, type RemoteLoginRequest, type RemoteRuntimeView, type RemoteServiceAction, type RemoteServiceActionResult, type RemoteServiceView, type RemoteSettingsUpdateRequest, type RemoteSettingsView, 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 SessionTypeDescribeParams, type SessionsListView, type UiNcpAgent, type UiNcpAssetPutView, type UiNcpAssetView, type UiNcpSessionListView, type UiNcpSessionMessagesView, type UiNcpSessionService, type UiNcpStoredAssetRecord, type UiRemoteAccessHost, type UiServerEvent, type UiServerHandle, type UiServerOptions, buildConfigMeta, buildConfigSchemaView, buildConfigView, createCustomProvider, createUiRouter, deleteCustomProvider, deleteSession, ensureUiBridgeSecret, executeConfigAction, getSessionHistory, getUiBridgeSecretPath, listSessions, loadConfigOrDefault, patchSession, readUiBridgeSecret, startUiServer, testProviderConnection, updateChannel, updateModel, updateProvider, updateRuntime, updateSearch, updateSecrets };
1220
+ export { type AgentBindingView, type AgentProfileView, type ApiError, type ApiResponse, type AppMetaView, type AuthEnabledUpdateRequest, type AuthLoginRequest, type AuthPasswordUpdateRequest, type AuthSetupRequest, type AuthStatusView, type BindingPeerView, type BochaFreshnessValue, type BootstrapPhase, type BootstrapRemoteState, type BootstrapStageState, type BootstrapStatusView, type ChannelAuthPollRequest, type ChannelAuthPollResult, type ChannelAuthStartRequest, type ChannelAuthStartResult, type ChannelSpecView, type ChatSessionTypeCtaView, type ChatSessionTypeOptionView, type ChatSessionTypesView, 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 MarketplaceMcpContentView, type MarketplaceMcpDoctorResult, type MarketplaceMcpInstallKind, type MarketplaceMcpInstallRequest, type MarketplaceMcpInstallResult, type MarketplaceMcpInstallSpec, type MarketplaceMcpManageAction, type MarketplaceMcpManageRequest, type MarketplaceMcpManageResult, type MarketplaceMcpTemplateInput, 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 NcpSessionSkillsView, 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 RemoteAccessView, type RemoteAccountView, type RemoteBrowserAuthPollRequest, type RemoteBrowserAuthPollResult, type RemoteBrowserAuthStartRequest, type RemoteBrowserAuthStartResult, type RemoteDoctorCheckView, type RemoteDoctorView, type RemoteLoginRequest, type RemoteRuntimeView, type RemoteServiceAction, type RemoteServiceActionResult, type RemoteServiceView, type RemoteSettingsUpdateRequest, type RemoteSettingsView, 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 ServerPathBreadcrumbView, type ServerPathBrowseView, type ServerPathEntryView, type SessionConfigView, type SessionEntryView, type SessionEventView, type SessionHistoryView, type SessionMessageView, type SessionPatchUpdate, SessionPatchValidationError, type SessionSkillEntryView, type SessionTypeDescribeParams, type SessionsListView, type UiNcpAgent, type UiNcpAssetPutView, type UiNcpAssetView, type UiNcpSessionListView, type UiNcpSessionMessagesView, type UiNcpSessionService, type UiNcpStoredAssetRecord, type UiRemoteAccessHost, type UiServerEvent, type UiServerHandle, type 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
@@ -4,7 +4,7 @@ import { compress } from "hono/compress";
4
4
  import { serve } from "@hono/node-server";
5
5
  import { WebSocketServer, WebSocket } from "ws";
6
6
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
7
- import { readFile as readFile3, stat } from "fs/promises";
7
+ import { readFile as readFile3, stat as stat3 } from "fs/promises";
8
8
  import { join as join2 } from "path";
9
9
 
10
10
  // src/ui/auth.service.ts
@@ -3128,6 +3128,199 @@ var NcpAssetRoutesController = class {
3128
3128
  };
3129
3129
  };
3130
3130
 
3131
+ // src/ui/session-project/session-project-root.ts
3132
+ import { realpath, stat } from "fs/promises";
3133
+ import { resolve as resolve2 } from "path";
3134
+ import { expandHome } from "@nextclaw/core";
3135
+ var SessionProjectRootValidationError = class extends Error {
3136
+ constructor(code, message) {
3137
+ super(message);
3138
+ this.code = code;
3139
+ this.name = "SessionProjectRootValidationError";
3140
+ }
3141
+ };
3142
+ function resolveCandidateProjectRoot(value) {
3143
+ return resolve2(expandHome(value));
3144
+ }
3145
+ async function normalizeSessionProjectRoot(value) {
3146
+ if (value == null) {
3147
+ return null;
3148
+ }
3149
+ if (typeof value !== "string") {
3150
+ throw new SessionProjectRootValidationError(
3151
+ "PROJECT_ROOT_INVALID_TYPE",
3152
+ "projectRoot must be a string or null"
3153
+ );
3154
+ }
3155
+ const trimmed = value.trim();
3156
+ if (!trimmed) {
3157
+ return null;
3158
+ }
3159
+ const candidate = resolveCandidateProjectRoot(trimmed);
3160
+ let canonicalPath;
3161
+ try {
3162
+ canonicalPath = await realpath(candidate);
3163
+ } catch {
3164
+ throw new SessionProjectRootValidationError(
3165
+ "PROJECT_ROOT_NOT_FOUND",
3166
+ "projectRoot directory does not exist"
3167
+ );
3168
+ }
3169
+ const projectRootStats = await stat(canonicalPath);
3170
+ if (!projectRootStats.isDirectory()) {
3171
+ throw new SessionProjectRootValidationError(
3172
+ "PROJECT_ROOT_NOT_DIRECTORY",
3173
+ "projectRoot must point to a directory"
3174
+ );
3175
+ }
3176
+ return canonicalPath;
3177
+ }
3178
+ function isSessionProjectRootValidationError(error) {
3179
+ return error instanceof SessionProjectRootValidationError;
3180
+ }
3181
+
3182
+ // src/ui/session-project/session-skills.ts
3183
+ import * as NextclawCore from "@nextclaw/core";
3184
+
3185
+ // src/ui/router/marketplace/constants.ts
3186
+ var DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
3187
+ var NEXTCLAW_PLUGIN_NPM_PREFIX = "@nextclaw/channel-plugin-";
3188
+ var BUILTIN_CHANNEL_PLUGIN_ID_PREFIX = "builtin-channel-";
3189
+ var MARKETPLACE_REMOTE_PAGE_SIZE = 100;
3190
+ var MARKETPLACE_REMOTE_MAX_PAGES = 20;
3191
+ var MARKETPLACE_ZH_COPY_BY_SLUG = {
3192
+ weather: {
3193
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002",
3194
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u5FEB\u901F\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002"
3195
+ },
3196
+ summarize: {
3197
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7ED3\u6784\u5316\u6458\u8981\u3002",
3198
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u6587\u4EF6\u4E0E\u957F\u6587\u672C\u7684\u6458\u8981\u5DE5\u4F5C\u6D41\u3002"
3199
+ },
3200
+ github: {
3201
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E GitHub \u5DE5\u4F5C\u6D41\u3002",
3202
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B Issue\u3001PR \u4E0E\u4ED3\u5E93\u76F8\u5173\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
3203
+ },
3204
+ tmux: {
3205
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7EC8\u7AEF/Tmux \u534F\u4F5C\u5DE5\u4F5C\u6D41\u3002",
3206
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u57FA\u4E8E Tmux \u7684\u4EFB\u52A1\u6267\u884C\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
3207
+ },
3208
+ gog: {
3209
+ summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u56FE\u8C31\u5BFC\u5411\u751F\u6210\u5DE5\u4F5C\u6D41\u3002",
3210
+ description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u56FE\u8C31\u4E0E\u89C4\u5212\u5BFC\u5411\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
3211
+ },
3212
+ pdf: {
3213
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E PDF \u8BFB\u53D6/\u5408\u5E76/\u62C6\u5206/OCR \u5DE5\u4F5C\u6D41\u3002",
3214
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u8BFB\u53D6\u3001\u63D0\u53D6\u3001\u5408\u5E76\u3001\u62C6\u5206\u3001\u65CB\u8F6C\u5E76\u5BF9 PDF \u6267\u884C OCR \u5904\u7406\u3002"
3215
+ },
3216
+ docx: {
3217
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u521B\u5EFA\u548C\u7F16\u8F91 Word \u6587\u6863\u3002",
3218
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u7F16\u8F91\u5E76\u91CD\u6784 .docx \u6587\u6863\u3002"
3219
+ },
3220
+ pptx: {
3221
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u6F14\u793A\u6587\u7A3F\u64CD\u4F5C\u3002",
3222
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u89E3\u6790\u3001\u7F16\u8F91\u5E76\u91CD\u7EC4 .pptx \u6F14\u793A\u6587\u7A3F\u3002"
3223
+ },
3224
+ xlsx: {
3225
+ summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u8868\u683C\u6587\u6863\u5DE5\u4F5C\u6D41\u3002",
3226
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u6253\u5F00\u3001\u7F16\u8F91\u3001\u6E05\u6D17\u5E76\u8F6C\u6362 .xlsx \u4E0E .csv \u7B49\u8868\u683C\u6587\u4EF6\u3002"
3227
+ },
3228
+ bird: {
3229
+ summary: "OpenClaw \u793E\u533A\u6280\u80FD\uFF0C\u7528\u4E8E X/Twitter \u8BFB\u53D6/\u641C\u7D22/\u53D1\u5E03\u5DE5\u4F5C\u6D41\u3002",
3230
+ description: "\u4F7F\u7528 bird CLI \u5728\u4EE3\u7406\u5DE5\u4F5C\u6D41\u4E2D\u8BFB\u53D6\u7EBF\u7A0B\u3001\u641C\u7D22\u5E16\u5B50\u5E76\u8D77\u8349\u63A8\u6587/\u56DE\u590D\u3002"
3231
+ },
3232
+ "cloudflare-deploy": {
3233
+ summary: "OpenAI \u7CBE\u9009\u6280\u80FD\uFF0C\u7528\u4E8E\u5728 Cloudflare \u4E0A\u90E8\u7F72\u5E94\u7528\u4E0E\u57FA\u7840\u8BBE\u65BD\u3002",
3234
+ description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u9009\u62E9 Cloudflare \u4EA7\u54C1\u5E76\u90E8\u7F72 Workers\u3001Pages \u53CA\u76F8\u5173\u670D\u52A1\u3002"
3235
+ },
3236
+ "channel-plugin-discord": {
3237
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Discord \u6E20\u9053\u96C6\u6210\u3002",
3238
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Discord \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3239
+ },
3240
+ "channel-plugin-telegram": {
3241
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Telegram \u6E20\u9053\u96C6\u6210\u3002",
3242
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Telegram \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3243
+ },
3244
+ "channel-plugin-slack": {
3245
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Slack \u6E20\u9053\u96C6\u6210\u3002",
3246
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Slack \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3247
+ },
3248
+ "channel-plugin-wecom": {
3249
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u96C6\u6210\u3002",
3250
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3251
+ },
3252
+ "channel-plugin-email": {
3253
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Email \u6E20\u9053\u96C6\u6210\u3002",
3254
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Email \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3255
+ },
3256
+ "channel-plugin-whatsapp": {
3257
+ summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E WhatsApp \u6E20\u9053\u96C6\u6210\u3002",
3258
+ description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B WhatsApp \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3259
+ },
3260
+ "channel-plugin-clawbay": {
3261
+ summary: "Clawbay \u5B98\u65B9\u6E20\u9053\u63D2\u4EF6\uFF0C\u7528\u4E8E NextClaw \u96C6\u6210\u3002",
3262
+ description: "\u901A\u8FC7\u63D2\u4EF6\u8FD0\u884C\u65F6\u4E3A NextClaw \u63D0\u4F9B Clawbay \u6E20\u9053\u80FD\u529B\u3002"
3263
+ }
3264
+ };
3265
+
3266
+ // src/ui/session-project/session-skills.ts
3267
+ var SCOPE_SORT_ORDER = {
3268
+ project: 0,
3269
+ workspace: 1
3270
+ };
3271
+ var SessionSkillsViewBuilder = class {
3272
+ constructor(options) {
3273
+ this.options = options;
3274
+ }
3275
+ build = (params) => {
3276
+ const config = loadConfigOrDefault(this.options.configPath);
3277
+ const projectContext = NextclawCore.resolveSessionProjectContext({
3278
+ sessionMetadata: params.sessionMetadata,
3279
+ workspace: NextclawCore.getWorkspacePathFromConfig(config)
3280
+ });
3281
+ const skillsLoader = new NextclawCore.SkillsLoader({
3282
+ workspace: projectContext.hostWorkspace,
3283
+ projectRoot: projectContext.projectRoot
3284
+ });
3285
+ const availableRefs = new Set(
3286
+ skillsLoader.listSkills(true).map((skill) => skill.ref)
3287
+ );
3288
+ const records = skillsLoader.listSkills(false).map((skill) => this.buildRecord(skill, skillsLoader, availableRefs)).sort((left, right) => this.compareRecords(left, right));
3289
+ return {
3290
+ sessionId: params.sessionId,
3291
+ total: records.length,
3292
+ refs: records.map((record) => record.ref),
3293
+ records
3294
+ };
3295
+ };
3296
+ buildRecord = (skill, skillsLoader, availableRefs) => {
3297
+ const metadata = skillsLoader.getSkillMetadata(skill) ?? {};
3298
+ const description = readNonEmptyString(metadata.description);
3299
+ const descriptionZh = readNonEmptyString(metadata.description_zh) ?? readNonEmptyString(metadata.descriptionZh) ?? readNonEmptyString(MARKETPLACE_ZH_COPY_BY_SLUG[skill.name]?.description);
3300
+ return {
3301
+ ref: skill.ref,
3302
+ name: skill.name,
3303
+ path: skill.path,
3304
+ scope: skill.scope,
3305
+ source: skill.source,
3306
+ available: availableRefs.has(skill.ref),
3307
+ ...description ? { description } : {},
3308
+ ...descriptionZh ? { descriptionZh } : {}
3309
+ };
3310
+ };
3311
+ compareRecords = (left, right) => {
3312
+ const scopeCompare = SCOPE_SORT_ORDER[left.scope] - SCOPE_SORT_ORDER[right.scope];
3313
+ if (scopeCompare !== 0) {
3314
+ return scopeCompare;
3315
+ }
3316
+ const nameCompare = left.name.localeCompare(right.name);
3317
+ if (nameCompare !== 0) {
3318
+ return nameCompare;
3319
+ }
3320
+ return left.ref.localeCompare(right.ref);
3321
+ };
3322
+ };
3323
+
3131
3324
  // src/ui/router/ncp-session.controller.ts
3132
3325
  function readPositiveInt(value) {
3133
3326
  if (typeof value !== "string") {
@@ -3139,10 +3332,54 @@ function readPositiveInt(value) {
3139
3332
  }
3140
3333
  return parsed;
3141
3334
  }
3335
+ function readSessionMetadata(metadata) {
3336
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
3337
+ return {};
3338
+ }
3339
+ return metadata;
3340
+ }
3341
+ function applySessionTypePatch(metadata, patch) {
3342
+ if (!Object.prototype.hasOwnProperty.call(patch, "sessionType")) {
3343
+ return;
3344
+ }
3345
+ const sessionType = typeof patch.sessionType === "string" ? patch.sessionType.trim() : "";
3346
+ if (sessionType) {
3347
+ metadata.session_type = sessionType;
3348
+ delete metadata.sessionType;
3349
+ return;
3350
+ }
3351
+ delete metadata.session_type;
3352
+ delete metadata.sessionType;
3353
+ }
3354
+ async function applyProjectRootPatch(metadata, patch) {
3355
+ if (!Object.prototype.hasOwnProperty.call(patch, "projectRoot")) {
3356
+ return;
3357
+ }
3358
+ const projectRoot = await normalizeSessionProjectRoot(patch.projectRoot);
3359
+ if (projectRoot) {
3360
+ metadata.project_root = projectRoot;
3361
+ delete metadata.projectRoot;
3362
+ return;
3363
+ }
3364
+ delete metadata.project_root;
3365
+ delete metadata.projectRoot;
3366
+ }
3367
+ async function buildPatchedSessionMetadata(params) {
3368
+ const nextMetadata = applySessionPreferencePatch({
3369
+ metadata: structuredClone(params.metadata),
3370
+ patch: params.patch,
3371
+ createInvalidThinkingError: () => new Error("PREFERRED_THINKING_INVALID")
3372
+ });
3373
+ applySessionTypePatch(nextMetadata, params.patch);
3374
+ await applyProjectRootPatch(nextMetadata, params.patch);
3375
+ return nextMetadata;
3376
+ }
3142
3377
  var NcpSessionRoutesController = class {
3143
3378
  constructor(options) {
3144
3379
  this.options = options;
3380
+ this.sessionSkillsViewBuilder = new SessionSkillsViewBuilder(options);
3145
3381
  }
3382
+ sessionSkillsViewBuilder;
3146
3383
  getSessionTypes = async (c) => {
3147
3384
  const listSessionTypes = this.options.ncpAgent?.listSessionTypes;
3148
3385
  const payload = listSessionTypes ? await listSessionTypes({ describeMode: "observation" }) : {
@@ -3197,6 +3434,37 @@ var NcpSessionRoutesController = class {
3197
3434
  };
3198
3435
  return c.json(ok(payload));
3199
3436
  };
3437
+ getSessionSkills = async (c) => {
3438
+ const sessionApi = this.options.ncpSessionService;
3439
+ if (!sessionApi) {
3440
+ return c.json(err("NOT_AVAILABLE", "ncp session api unavailable"), 503);
3441
+ }
3442
+ const sessionId = decodeURIComponent(c.req.param("sessionId"));
3443
+ const query = c.req.query();
3444
+ const hasProjectRootOverride = Object.prototype.hasOwnProperty.call(query, "projectRoot");
3445
+ const existing = await sessionApi.getSession(sessionId);
3446
+ const metadata = readSessionMetadata(existing?.metadata);
3447
+ if (hasProjectRootOverride) {
3448
+ try {
3449
+ const projectRoot = await normalizeSessionProjectRoot(query.projectRoot);
3450
+ if (projectRoot) {
3451
+ metadata.project_root = projectRoot;
3452
+ } else {
3453
+ delete metadata.project_root;
3454
+ delete metadata.projectRoot;
3455
+ }
3456
+ } catch (error) {
3457
+ if (isSessionProjectRootValidationError(error)) {
3458
+ return c.json(err(error.code, error.message), 400);
3459
+ }
3460
+ throw error;
3461
+ }
3462
+ }
3463
+ return c.json(ok(this.sessionSkillsViewBuilder.build({
3464
+ sessionId,
3465
+ sessionMetadata: metadata
3466
+ })));
3467
+ };
3200
3468
  patchSession = async (c) => {
3201
3469
  const sessionApi = this.options.ncpSessionService;
3202
3470
  if (!sessionApi) {
@@ -3212,25 +3480,13 @@ var NcpSessionRoutesController = class {
3212
3480
  return c.json(err("UNSUPPORTED_PATCH", "clearHistory is not supported for ncp sessions"), 400);
3213
3481
  }
3214
3482
  const existing = await sessionApi.getSession(sessionId);
3215
- if (!existing) {
3216
- return c.json(err("NOT_FOUND", `ncp session not found: ${sessionId}`), 404);
3217
- }
3218
- const metadata = existing.metadata && typeof existing.metadata === "object" && !Array.isArray(existing.metadata) ? existing.metadata : {};
3483
+ const metadata = readSessionMetadata(existing?.metadata);
3219
3484
  let updated;
3220
3485
  try {
3221
- const nextMetadata = applySessionPreferencePatch({
3222
- metadata: structuredClone(metadata),
3223
- patch,
3224
- createInvalidThinkingError: () => new Error("PREFERRED_THINKING_INVALID")
3486
+ const nextMetadata = await buildPatchedSessionMetadata({
3487
+ metadata,
3488
+ patch
3225
3489
  });
3226
- if (Object.prototype.hasOwnProperty.call(patch, "sessionType")) {
3227
- const sessionType = typeof patch.sessionType === "string" ? patch.sessionType.trim() : "";
3228
- if (sessionType) {
3229
- nextMetadata.session_type = sessionType;
3230
- } else {
3231
- delete nextMetadata.session_type;
3232
- }
3233
- }
3234
3490
  updated = await sessionApi.updateSession(sessionId, {
3235
3491
  metadata: nextMetadata
3236
3492
  });
@@ -3238,6 +3494,9 @@ var NcpSessionRoutesController = class {
3238
3494
  if (error instanceof Error && error.message === "PREFERRED_THINKING_INVALID") {
3239
3495
  return c.json(err("PREFERRED_THINKING_INVALID", "preferredThinking must be a supported thinking level"), 400);
3240
3496
  }
3497
+ if (isSessionProjectRootValidationError(error)) {
3498
+ return c.json(err(error.code, error.message), 400);
3499
+ }
3241
3500
  throw error;
3242
3501
  }
3243
3502
  if (!updated) {
@@ -3260,93 +3519,12 @@ var NcpSessionRoutesController = class {
3260
3519
  };
3261
3520
  };
3262
3521
 
3263
- // src/ui/router/marketplace/constants.ts
3264
- var DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
3265
- var NEXTCLAW_PLUGIN_NPM_PREFIX = "@nextclaw/channel-plugin-";
3266
- var BUILTIN_CHANNEL_PLUGIN_ID_PREFIX = "builtin-channel-";
3267
- var MARKETPLACE_REMOTE_PAGE_SIZE = 100;
3268
- var MARKETPLACE_REMOTE_MAX_PAGES = 20;
3269
- var MARKETPLACE_ZH_COPY_BY_SLUG = {
3270
- weather: {
3271
- summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002",
3272
- description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u5FEB\u901F\u5929\u6C14\u67E5\u8BE2\u5DE5\u4F5C\u6D41\u3002"
3273
- },
3274
- summarize: {
3275
- summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7ED3\u6784\u5316\u6458\u8981\u3002",
3276
- description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u6587\u4EF6\u4E0E\u957F\u6587\u672C\u7684\u6458\u8981\u5DE5\u4F5C\u6D41\u3002"
3277
- },
3278
- github: {
3279
- summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E GitHub \u5DE5\u4F5C\u6D41\u3002",
3280
- description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B Issue\u3001PR \u4E0E\u4ED3\u5E93\u76F8\u5173\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
3281
- },
3282
- tmux: {
3283
- summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u7EC8\u7AEF/Tmux \u534F\u4F5C\u5DE5\u4F5C\u6D41\u3002",
3284
- description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u57FA\u4E8E Tmux \u7684\u4EFB\u52A1\u6267\u884C\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
3285
- },
3286
- gog: {
3287
- summary: "NextClaw \u5185\u7F6E\u6280\u80FD\uFF0C\u7528\u4E8E\u56FE\u8C31\u5BFC\u5411\u751F\u6210\u5DE5\u4F5C\u6D41\u3002",
3288
- description: "\u5728 NextClaw \u4E2D\u63D0\u4F9B\u56FE\u8C31\u4E0E\u89C4\u5212\u5BFC\u5411\u5DE5\u4F5C\u6D41\u6307\u5F15\u3002"
3289
- },
3290
- pdf: {
3291
- summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E PDF \u8BFB\u53D6/\u5408\u5E76/\u62C6\u5206/OCR \u5DE5\u4F5C\u6D41\u3002",
3292
- description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u8BFB\u53D6\u3001\u63D0\u53D6\u3001\u5408\u5E76\u3001\u62C6\u5206\u3001\u65CB\u8F6C\u5E76\u5BF9 PDF \u6267\u884C OCR \u5904\u7406\u3002"
3293
- },
3294
- docx: {
3295
- summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u521B\u5EFA\u548C\u7F16\u8F91 Word \u6587\u6863\u3002",
3296
- description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u7F16\u8F91\u5E76\u91CD\u6784 .docx \u6587\u6863\u3002"
3297
- },
3298
- pptx: {
3299
- summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u6F14\u793A\u6587\u7A3F\u64CD\u4F5C\u3002",
3300
- description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u521B\u5EFA\u3001\u89E3\u6790\u3001\u7F16\u8F91\u5E76\u91CD\u7EC4 .pptx \u6F14\u793A\u6587\u7A3F\u3002"
3301
- },
3302
- xlsx: {
3303
- summary: "Anthropic \u6280\u80FD\uFF0C\u7528\u4E8E\u8868\u683C\u6587\u6863\u5DE5\u4F5C\u6D41\u3002",
3304
- description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u6253\u5F00\u3001\u7F16\u8F91\u3001\u6E05\u6D17\u5E76\u8F6C\u6362 .xlsx \u4E0E .csv \u7B49\u8868\u683C\u6587\u4EF6\u3002"
3305
- },
3306
- bird: {
3307
- summary: "OpenClaw \u793E\u533A\u6280\u80FD\uFF0C\u7528\u4E8E X/Twitter \u8BFB\u53D6/\u641C\u7D22/\u53D1\u5E03\u5DE5\u4F5C\u6D41\u3002",
3308
- description: "\u4F7F\u7528 bird CLI \u5728\u4EE3\u7406\u5DE5\u4F5C\u6D41\u4E2D\u8BFB\u53D6\u7EBF\u7A0B\u3001\u641C\u7D22\u5E16\u5B50\u5E76\u8D77\u8349\u63A8\u6587/\u56DE\u590D\u3002"
3309
- },
3310
- "cloudflare-deploy": {
3311
- summary: "OpenAI \u7CBE\u9009\u6280\u80FD\uFF0C\u7528\u4E8E\u5728 Cloudflare \u4E0A\u90E8\u7F72\u5E94\u7528\u4E0E\u57FA\u7840\u8BBE\u65BD\u3002",
3312
- description: "\u4F7F\u7528\u8BE5\u6280\u80FD\u53EF\u9009\u62E9 Cloudflare \u4EA7\u54C1\u5E76\u90E8\u7F72 Workers\u3001Pages \u53CA\u76F8\u5173\u670D\u52A1\u3002"
3313
- },
3314
- "channel-plugin-discord": {
3315
- summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Discord \u6E20\u9053\u96C6\u6210\u3002",
3316
- description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Discord \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3317
- },
3318
- "channel-plugin-telegram": {
3319
- summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Telegram \u6E20\u9053\u96C6\u6210\u3002",
3320
- description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Telegram \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3321
- },
3322
- "channel-plugin-slack": {
3323
- summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Slack \u6E20\u9053\u96C6\u6210\u3002",
3324
- description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Slack \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3325
- },
3326
- "channel-plugin-wecom": {
3327
- summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u96C6\u6210\u3002",
3328
- description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B\u4F01\u4E1A\u5FAE\u4FE1\u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3329
- },
3330
- "channel-plugin-email": {
3331
- summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E Email \u6E20\u9053\u96C6\u6210\u3002",
3332
- description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B Email \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3333
- },
3334
- "channel-plugin-whatsapp": {
3335
- summary: "NextClaw \u5B98\u65B9\u63D2\u4EF6\uFF0C\u7528\u4E8E WhatsApp \u6E20\u9053\u96C6\u6210\u3002",
3336
- description: "\u901A\u8FC7 NextClaw \u63D2\u4EF6\u8FD0\u884C\u65F6\u63D0\u4F9B WhatsApp \u6E20\u9053\u7684\u5165\u7AD9/\u51FA\u7AD9\u652F\u6301\u3002"
3337
- },
3338
- "channel-plugin-clawbay": {
3339
- summary: "Clawbay \u5B98\u65B9\u6E20\u9053\u63D2\u4EF6\uFF0C\u7528\u4E8E NextClaw \u96C6\u6210\u3002",
3340
- description: "\u901A\u8FC7\u63D2\u4EF6\u8FD0\u884C\u65F6\u4E3A NextClaw \u63D0\u4F9B Clawbay \u6E20\u9053\u80FD\u529B\u3002"
3341
- }
3342
- };
3343
-
3344
3522
  // src/ui/router/marketplace/marketplace-network-retry.ts
3345
3523
  var MARKETPLACE_NETWORK_RETRY_ATTEMPTS = 5;
3346
3524
  var MARKETPLACE_NETWORK_RETRY_BASE_MS = 350;
3347
3525
  function sleepMs(ms) {
3348
- return new Promise((resolve2) => {
3349
- setTimeout(resolve2, ms);
3526
+ return new Promise((resolve4) => {
3527
+ setTimeout(resolve4, ms);
3350
3528
  });
3351
3529
  }
3352
3530
  function isRetryableMarketplaceNetworkError(error) {
@@ -3810,7 +3988,7 @@ var McpMarketplaceController = class {
3810
3988
  };
3811
3989
 
3812
3990
  // src/ui/router/marketplace/installed.ts
3813
- import * as NextclawCore from "@nextclaw/core";
3991
+ import * as NextclawCore2 from "@nextclaw/core";
3814
3992
  import { discoverPluginStatusReport } from "@nextclaw/openclaw-compat";
3815
3993
 
3816
3994
  // src/ui/router/marketplace/spec.ts
@@ -3917,9 +4095,9 @@ function dedupeInstalledPluginRecordsByCanonicalSpec(records) {
3917
4095
  }
3918
4096
 
3919
4097
  // src/ui/router/marketplace/installed.ts
3920
- var getWorkspacePathFromConfig3 = NextclawCore.getWorkspacePathFromConfig;
4098
+ var getWorkspacePathFromConfig4 = NextclawCore2.getWorkspacePathFromConfig;
3921
4099
  function createSkillsLoader(workspace) {
3922
- const ctor = NextclawCore.SkillsLoader;
4100
+ const ctor = NextclawCore2.SkillsLoader;
3923
4101
  if (!ctor) {
3924
4102
  return null;
3925
4103
  }
@@ -4076,7 +4254,7 @@ function collectInstalledPluginRecords(options) {
4076
4254
  try {
4077
4255
  const pluginReport = discoverPluginStatusReport({
4078
4256
  config,
4079
- workspaceDir: getWorkspacePathFromConfig3(config)
4257
+ workspaceDir: getWorkspacePathFromConfig4(config)
4080
4258
  });
4081
4259
  discoveredPlugins = pluginReport.plugins;
4082
4260
  } catch {
@@ -4112,7 +4290,7 @@ function collectInstalledPluginRecords(options) {
4112
4290
  }
4113
4291
  function collectInstalledSkillRecords(options) {
4114
4292
  const config = loadConfigOrDefault(options.configPath);
4115
- const workspacePath = getWorkspacePathFromConfig3(config);
4293
+ const workspacePath = getWorkspacePathFromConfig4(config);
4116
4294
  const skillsLoader = createSkillsLoader(workspacePath);
4117
4295
  const availableSkillSet = new Set((skillsLoader?.listSkills(true) ?? []).map((skill) => skill.name));
4118
4296
  const listedSkills = skillsLoader?.listSkills(false) ?? [];
@@ -4181,7 +4359,7 @@ function resolvePluginManageTargetId(options, rawTargetId, rawSpec) {
4181
4359
  }
4182
4360
  function collectKnownSkillNames(options) {
4183
4361
  const config = loadConfigOrDefault(options.configPath);
4184
- const loader = createSkillsLoader(getWorkspacePathFromConfig3(config));
4362
+ const loader = createSkillsLoader(getWorkspacePathFromConfig4(config));
4185
4363
  return new Set((loader?.listSkills(false) ?? []).map((skill) => skill.name));
4186
4364
  }
4187
4365
  function isSupportedMarketplacePluginItem(item) {
@@ -4831,6 +5009,139 @@ var RemoteRoutesController = class {
4831
5009
  };
4832
5010
  };
4833
5011
 
5012
+ // src/ui/server-path/server-path-browse.utils.ts
5013
+ import { readdir, stat as stat2 } from "fs/promises";
5014
+ import { homedir as homedir2 } from "os";
5015
+ import { dirname, parse, resolve as resolve3 } from "path";
5016
+ import { expandHome as expandHome2 } from "@nextclaw/core";
5017
+ var ServerPathBrowseError = class extends Error {
5018
+ constructor(code, message) {
5019
+ super(message);
5020
+ this.code = code;
5021
+ this.name = "ServerPathBrowseError";
5022
+ }
5023
+ };
5024
+ function normalizeBrowsePath(path) {
5025
+ const trimmed = typeof path === "string" ? path.trim() : "";
5026
+ return resolve3(expandHome2(trimmed || homedir2()));
5027
+ }
5028
+ function readRootLabel(rootPath) {
5029
+ const normalized = rootPath.replace(/[\\/]+$/, "");
5030
+ return normalized || rootPath;
5031
+ }
5032
+ function buildBreadcrumbs(currentPath) {
5033
+ const parsedPath = parse(currentPath);
5034
+ const rootPath = parsedPath.root || currentPath;
5035
+ const rootBreadcrumb = {
5036
+ label: readRootLabel(rootPath),
5037
+ path: rootPath
5038
+ };
5039
+ const relativeSegments = currentPath.slice(rootPath.length).split(/[\\/]+/).filter(Boolean);
5040
+ const breadcrumbs = [rootBreadcrumb];
5041
+ let breadcrumbPath = rootPath;
5042
+ for (const segment of relativeSegments) {
5043
+ breadcrumbPath = resolve3(breadcrumbPath, segment);
5044
+ breadcrumbs.push({
5045
+ label: segment,
5046
+ path: breadcrumbPath
5047
+ });
5048
+ }
5049
+ return breadcrumbs;
5050
+ }
5051
+ async function resolveEntryView(params) {
5052
+ const entryPath = resolve3(params.parentPath, params.entryName);
5053
+ let entryStats;
5054
+ try {
5055
+ entryStats = await stat2(entryPath);
5056
+ } catch {
5057
+ return null;
5058
+ }
5059
+ const isDirectory = entryStats.isDirectory();
5060
+ if (!isDirectory && !params.includeFiles) {
5061
+ return null;
5062
+ }
5063
+ return {
5064
+ name: params.entryName,
5065
+ path: entryPath,
5066
+ kind: isDirectory ? "directory" : "file",
5067
+ hidden: params.entryName.startsWith(".")
5068
+ };
5069
+ }
5070
+ function sortEntryViews(left, right) {
5071
+ if (left.kind !== right.kind) {
5072
+ return left.kind === "directory" ? -1 : 1;
5073
+ }
5074
+ return left.name.localeCompare(right.name);
5075
+ }
5076
+ async function browseServerPath(options = {}) {
5077
+ const currentPath = normalizeBrowsePath(options.path);
5078
+ let currentPathStats;
5079
+ try {
5080
+ currentPathStats = await stat2(currentPath);
5081
+ } catch {
5082
+ throw new ServerPathBrowseError(
5083
+ "SERVER_PATH_NOT_FOUND",
5084
+ "server path does not exist"
5085
+ );
5086
+ }
5087
+ if (!currentPathStats.isDirectory()) {
5088
+ throw new ServerPathBrowseError(
5089
+ "SERVER_PATH_NOT_DIRECTORY",
5090
+ "server path must point to a directory"
5091
+ );
5092
+ }
5093
+ let entryNames;
5094
+ try {
5095
+ entryNames = await readdir(currentPath);
5096
+ } catch {
5097
+ throw new ServerPathBrowseError(
5098
+ "SERVER_PATH_NOT_READABLE",
5099
+ "server path is not readable"
5100
+ );
5101
+ }
5102
+ const entries = (await Promise.all(
5103
+ entryNames.map(
5104
+ (entryName) => resolveEntryView({
5105
+ parentPath: currentPath,
5106
+ entryName,
5107
+ includeFiles: options.includeFiles ?? false
5108
+ })
5109
+ )
5110
+ )).filter((entry) => entry !== null).sort(sortEntryViews);
5111
+ const parentPath = dirname(currentPath);
5112
+ return {
5113
+ currentPath,
5114
+ parentPath: parentPath === currentPath ? null : parentPath,
5115
+ homePath: resolve3(homedir2()),
5116
+ breadcrumbs: buildBreadcrumbs(currentPath),
5117
+ entries
5118
+ };
5119
+ }
5120
+ function isServerPathBrowseError(error) {
5121
+ return error instanceof ServerPathBrowseError;
5122
+ }
5123
+
5124
+ // src/ui/router/server-path.controller.ts
5125
+ function readIncludeFilesFlag(value) {
5126
+ return value === "1" || value === "true";
5127
+ }
5128
+ var ServerPathRoutesController = class {
5129
+ browse = async (c) => {
5130
+ try {
5131
+ const payload = await browseServerPath({
5132
+ path: c.req.query("path"),
5133
+ includeFiles: readIncludeFilesFlag(c.req.query("includeFiles"))
5134
+ });
5135
+ return c.json(ok(payload));
5136
+ } catch (error) {
5137
+ if (isServerPathBrowseError(error)) {
5138
+ return c.json(err(error.code, error.message), 400);
5139
+ }
5140
+ throw error;
5141
+ }
5142
+ };
5143
+ };
5144
+
4834
5145
  // src/ui/router.ts
4835
5146
  function registerAuthRoutes(app, authController) {
4836
5147
  app.get("/api/auth/status", authController.getStatus);
@@ -4867,8 +5178,12 @@ function registerNcpSessionRoutes(app, ncpSessionController) {
4867
5178
  app.get("/api/ncp/sessions/:sessionId", ncpSessionController.getSession);
4868
5179
  app.put("/api/ncp/sessions/:sessionId", ncpSessionController.patchSession);
4869
5180
  app.get("/api/ncp/sessions/:sessionId/messages", ncpSessionController.listSessionMessages);
5181
+ app.get("/api/ncp/sessions/:sessionId/skills", ncpSessionController.getSessionSkills);
4870
5182
  app.delete("/api/ncp/sessions/:sessionId", ncpSessionController.deleteSession);
4871
5183
  }
5184
+ function registerServerPathRoutes(app, serverPathController) {
5185
+ app.get("/api/server-paths/browse", serverPathController.browse);
5186
+ }
4872
5187
  function registerNcpRuntimeRoutes(app, options, ncpAssetController) {
4873
5188
  if (!options.ncpAgent) {
4874
5189
  return;
@@ -4910,6 +5225,7 @@ function createUiRouter(options) {
4910
5225
  const cronController = new CronRoutesController(options);
4911
5226
  const ncpSessionController = new NcpSessionRoutesController(options);
4912
5227
  const ncpAssetController = new NcpAssetRoutesController(options);
5228
+ const serverPathController = new ServerPathRoutesController();
4913
5229
  const remoteController = options.remoteAccess ? new RemoteRoutesController(options.remoteAccess) : null;
4914
5230
  const pluginMarketplaceController = new PluginMarketplaceController(options, marketplaceBaseUrl);
4915
5231
  const skillMarketplaceController = new SkillMarketplaceController(options, marketplaceBaseUrl);
@@ -4934,6 +5250,7 @@ function createUiRouter(options) {
4934
5250
  registerAuthRoutes(app, authController);
4935
5251
  registerConfigRoutes(app, configController);
4936
5252
  registerNcpSessionRoutes(app, ncpSessionController);
5253
+ registerServerPathRoutes(app, serverPathController);
4937
5254
  registerNcpRuntimeRoutes(app, options, ncpAssetController);
4938
5255
  registerCronRoutes(app, cronController);
4939
5256
  registerRemoteRoutes(app, remoteController);
@@ -5069,7 +5386,7 @@ function startUiServer(options) {
5069
5386
  },
5070
5387
  isDir: async (path) => {
5071
5388
  try {
5072
- return (await stat(path)).isDirectory();
5389
+ return (await stat3(path)).isDirectory();
5073
5390
  } catch {
5074
5391
  return false;
5075
5392
  }
@@ -5115,10 +5432,10 @@ function startUiServer(options) {
5115
5432
  host: options.host,
5116
5433
  port: options.port,
5117
5434
  publish,
5118
- close: () => new Promise((resolve2) => {
5435
+ close: () => new Promise((resolve4) => {
5119
5436
  wss.close(() => {
5120
5437
  server.close(() => {
5121
- Promise.resolve(options.ncpAgent?.agentClientEndpoint.stop()).catch(() => void 0).finally(() => resolve2());
5438
+ Promise.resolve(options.ncpAgent?.agentClientEndpoint.stop()).catch(() => void 0).finally(() => resolve4());
5122
5439
  });
5123
5440
  });
5124
5441
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/server",
3
- "version": "0.11.21",
3
+ "version": "0.11.23",
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/ncp-http-agent-server": "0.3.9",
22
- "@nextclaw/openclaw-compat": "0.3.55",
23
- "@nextclaw/core": "0.11.14",
24
- "@nextclaw/mcp": "0.1.61",
25
- "@nextclaw/ncp": "0.4.5",
26
- "@nextclaw/runtime": "0.2.28"
21
+ "@nextclaw/ncp": "0.4.6",
22
+ "@nextclaw/core": "0.11.16",
23
+ "@nextclaw/openclaw-compat": "0.3.57",
24
+ "@nextclaw/ncp-http-agent-server": "0.3.10",
25
+ "@nextclaw/mcp": "0.1.63",
26
+ "@nextclaw/runtime": "0.2.30"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^20.17.6",