nextclaw 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/index.js +2661 -753
  3. package/package.json +9 -5
  4. package/templates/USAGE.md +57 -0
  5. package/ui-dist/assets/ChannelsList-C7F_As4r.js +1 -0
  6. package/ui-dist/assets/ChatPage-Oo7-OUsx.js +37 -0
  7. package/ui-dist/assets/{DocBrowser-B9ws5JL7.js → DocBrowser-Dsd8Dlq8.js} +1 -1
  8. package/ui-dist/assets/{LogoBadge-DvGAzkZ3.js → LogoBadge-2ChEc_oz.js} +1 -1
  9. package/ui-dist/assets/MarketplacePage-BXck6-X3.js +49 -0
  10. package/ui-dist/assets/{ModelConfig-BL_HsOsm.js → ModelConfig-CgHRSD0b.js} +1 -1
  11. package/ui-dist/assets/ProvidersList-PPfZucvS.js +1 -0
  12. package/ui-dist/assets/RuntimeConfig-ClLEKNTN.js +1 -0
  13. package/ui-dist/assets/{SearchConfig-BhaI0fUf.js → SearchConfig-CuXVCbrf.js} +1 -1
  14. package/ui-dist/assets/{SecretsConfig-CFoimOh9.js → SecretsConfig-udJz6Ake.js} +2 -2
  15. package/ui-dist/assets/SessionsConfig-C1XnFfiC.js +2 -0
  16. package/ui-dist/assets/{session-run-status-TkIuGbVw.js → chat-message-BETwXLD4.js} +3 -3
  17. package/ui-dist/assets/{index-uMsNsQX6.js → index-COJdlL0e.js} +1 -1
  18. package/ui-dist/assets/index-CsvP4CER.js +8 -0
  19. package/ui-dist/assets/index-D-bXl7qL.css +1 -0
  20. package/ui-dist/assets/{label-D8ly4a2P.js → label-BGL-ztxh.js} +1 -1
  21. package/ui-dist/assets/{page-layout-BSYfvwbp.js → page-layout-aw88k7tG.js} +1 -1
  22. package/ui-dist/assets/popover-DyEvzhmV.js +1 -0
  23. package/ui-dist/assets/security-config-BuPAQn82.js +1 -0
  24. package/ui-dist/assets/skeleton-drzO_tdU.js +1 -0
  25. package/ui-dist/assets/{switch-Ce_g9lpN.js → switch-BK8jIzto.js} +1 -1
  26. package/ui-dist/assets/{tabs-custom-Cf5azvT5.js → tabs-custom-Da3cEOji.js} +1 -1
  27. package/ui-dist/assets/{useConfirmDialog-A8Ek8Wu7.js → useConfirmDialog-z0CE92iS.js} +2 -2
  28. package/ui-dist/assets/{vendor-B7ozqnFC.js → vendor-CkJHmX1g.js} +65 -70
  29. package/ui-dist/index.html +3 -3
  30. package/ui-dist/assets/ChannelsList-DF2U-LY1.js +0 -1
  31. package/ui-dist/assets/ChatPage-BX39y0U5.js +0 -36
  32. package/ui-dist/assets/MarketplacePage-DG5mHWJ8.js +0 -49
  33. package/ui-dist/assets/ProvidersList-CH5z00YT.js +0 -1
  34. package/ui-dist/assets/RuntimeConfig-BplBgkwo.js +0 -1
  35. package/ui-dist/assets/SessionsConfig-BHTAYn9T.js +0 -2
  36. package/ui-dist/assets/index-BLeJkJ0o.css +0 -1
  37. package/ui-dist/assets/index-DK4TS5ev.js +0 -8
  38. package/ui-dist/assets/index-X5J6Mm--.js +0 -1
  39. package/ui-dist/assets/security-config-DlKEYHNN.js +0 -1
  40. package/ui-dist/assets/skeleton-CWbsNx2h.js +0 -1
package/dist/cli/index.js CHANGED
@@ -6,12 +6,12 @@ import { APP_NAME as APP_NAME5, APP_TAGLINE } from "@nextclaw/core";
6
6
 
7
7
  // src/cli/runtime.ts
8
8
  import {
9
- loadConfig as loadConfig7,
9
+ loadConfig as loadConfig8,
10
10
  saveConfig as saveConfig6,
11
11
  getConfigPath as getConfigPath4,
12
12
  getDataDir as getDataDir8,
13
13
  ConfigSchema as ConfigSchema2,
14
- getWorkspacePath as getWorkspacePath6,
14
+ getWorkspacePath as getWorkspacePath9,
15
15
  expandHome as expandHome2,
16
16
  MessageBus as MessageBus2,
17
17
  AgentLoop,
@@ -22,12 +22,12 @@ import {
22
22
  DEFAULT_WORKSPACE_PATH
23
23
  } from "@nextclaw/core";
24
24
  import {
25
- getPluginChannelBindings as getPluginChannelBindings3,
25
+ getPluginChannelBindings as getPluginChannelBindings4,
26
26
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
27
27
  setPluginRuntimeBridge as setPluginRuntimeBridge2
28
28
  } from "@nextclaw/openclaw-compat";
29
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
30
- import { join as join7, resolve as resolve9 } from "path";
29
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
30
+ import { join as join7, resolve as resolve12 } from "path";
31
31
  import { createInterface as createInterface2 } from "readline";
32
32
  import { fileURLToPath as fileURLToPath4 } from "url";
33
33
  import { spawn as spawn3 } from "child_process";
@@ -115,32 +115,32 @@ function resolveRestartSentinelPath() {
115
115
  return resolve(getDataDir(), "run", RESTART_SENTINEL_FILENAME);
116
116
  }
117
117
  async function writeRestartSentinel(payload) {
118
- const path = resolveRestartSentinelPath();
119
- mkdirSync(resolve(path, ".."), { recursive: true });
118
+ const path2 = resolveRestartSentinelPath();
119
+ mkdirSync(resolve(path2, ".."), { recursive: true });
120
120
  const file = {
121
121
  version: 1,
122
122
  payload
123
123
  };
124
- writeFileSync(path, `${JSON.stringify(file, null, 2)}
124
+ writeFileSync(path2, `${JSON.stringify(file, null, 2)}
125
125
  `, "utf-8");
126
- return path;
126
+ return path2;
127
127
  }
128
128
  async function consumeRestartSentinel() {
129
- const path = resolveRestartSentinelPath();
130
- if (!existsSync(path)) {
129
+ const path2 = resolveRestartSentinelPath();
130
+ if (!existsSync(path2)) {
131
131
  return null;
132
132
  }
133
133
  try {
134
- const raw = readFileSync(path, "utf-8");
134
+ const raw = readFileSync(path2, "utf-8");
135
135
  const parsed = JSON.parse(raw);
136
136
  if (!parsed || parsed.version !== 1 || !parsed.payload) {
137
- rmSync(path, { force: true });
137
+ rmSync(path2, { force: true });
138
138
  return null;
139
139
  }
140
- rmSync(path, { force: true });
140
+ rmSync(path2, { force: true });
141
141
  return parsed;
142
142
  } catch {
143
- rmSync(path, { force: true });
143
+ rmSync(path2, { force: true });
144
144
  return null;
145
145
  }
146
146
  }
@@ -186,24 +186,236 @@ function parseSessionKey(sessionKey) {
186
186
  }
187
187
 
188
188
  // src/cli/skills/marketplace.ts
189
- import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
190
- import { basename, dirname, isAbsolute, join, relative, resolve as resolve2 } from "path";
189
+ import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
190
+ import { basename, dirname, isAbsolute, join, relative, resolve as resolve3 } from "path";
191
191
  import { SkillsLoader } from "@nextclaw/core";
192
+
193
+ // src/cli/skills/marketplace.metadata.ts
194
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
195
+ import { resolve as resolve2 } from "path";
196
+ import { parse as parseYaml } from "yaml";
197
+ var DEFAULT_MARKETPLACE_META_FILENAME = "marketplace.json";
198
+ function parseSkillFrontmatter(raw) {
199
+ const normalized = raw.replace(/\r\n/g, "\n");
200
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/);
201
+ if (!match || !match[1]) {
202
+ return {};
203
+ }
204
+ let parsed;
205
+ try {
206
+ parsed = parseYaml(match[1]);
207
+ } catch (error) {
208
+ const message = error instanceof Error ? error.message : String(error);
209
+ throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
210
+ }
211
+ if (!isRecord(parsed)) {
212
+ return {};
213
+ }
214
+ const summaryI18n = readLocalizedTextMapField(parsed, [["summaryi18n"], ["summary_i18n"]]);
215
+ const descriptionI18n = readLocalizedTextMapField(parsed, [["descriptioni18n"], ["description_i18n"]]);
216
+ const summaryZh = readFrontmatterStringField(parsed, [["summaryzh"], ["summary_zh"]]);
217
+ const descriptionZh = readFrontmatterStringField(parsed, [["descriptionzh"], ["description_zh"]]);
218
+ return {
219
+ name: readFrontmatterStringField(parsed, [["name"]]),
220
+ summary: readFrontmatterStringField(parsed, [["summary"]]),
221
+ summaryI18n: mergeLocalizedTextMap(summaryI18n, { zh: summaryZh }),
222
+ description: readFrontmatterStringField(parsed, [["description"]]),
223
+ descriptionI18n: mergeLocalizedTextMap(descriptionI18n, { zh: descriptionZh }),
224
+ author: readFrontmatterStringField(parsed, [["author"]]),
225
+ tags: readFrontmatterTags(parsed)
226
+ };
227
+ }
228
+ function buildLocalizedTextMap(englishText, ...maps) {
229
+ const normalized = mergeLocalizedTextMap(...maps);
230
+ return {
231
+ ...normalized ?? {},
232
+ en: englishText
233
+ };
234
+ }
235
+ function readMarketplaceMetadataFile(skillDir, explicitMetaFile) {
236
+ const metadataPath = resolveMarketplaceMetadataPath(skillDir, explicitMetaFile);
237
+ if (!metadataPath) {
238
+ return {};
239
+ }
240
+ let parsed;
241
+ try {
242
+ parsed = JSON.parse(readFileSync2(metadataPath, "utf8"));
243
+ } catch (error) {
244
+ const message = error instanceof Error ? error.message : String(error);
245
+ throw new Error(`Invalid marketplace metadata file: ${metadataPath} (${message})`);
246
+ }
247
+ if (!isRecord(parsed)) {
248
+ throw new Error(`Invalid marketplace metadata file: ${metadataPath} (root must be an object)`);
249
+ }
250
+ return {
251
+ slug: readMetadataString(parsed, "slug"),
252
+ name: readMetadataString(parsed, "name"),
253
+ summary: readMetadataString(parsed, "summary"),
254
+ summaryI18n: readMetadataLocalizedTextMap(parsed, "summaryI18n"),
255
+ description: readMetadataString(parsed, "description"),
256
+ descriptionI18n: readMetadataLocalizedTextMap(parsed, "descriptionI18n"),
257
+ author: readMetadataString(parsed, "author"),
258
+ tags: readMetadataStringArray(parsed, "tags"),
259
+ sourceRepo: readMetadataString(parsed, "sourceRepo"),
260
+ homepage: readMetadataString(parsed, "homepage"),
261
+ publishedAt: readMetadataString(parsed, "publishedAt"),
262
+ updatedAt: readMetadataString(parsed, "updatedAt")
263
+ };
264
+ }
265
+ function resolveMarketplaceMetadataPath(skillDir, explicitMetaFile) {
266
+ const resolved = explicitMetaFile?.trim() ? resolve2(explicitMetaFile) : resolve2(skillDir, DEFAULT_MARKETPLACE_META_FILENAME);
267
+ return existsSync2(resolved) ? resolved : void 0;
268
+ }
269
+ function mergeLocalizedTextMap(...maps) {
270
+ const localized = {};
271
+ for (const map of maps) {
272
+ for (const [locale, text] of Object.entries(map ?? {})) {
273
+ const normalizedText = typeof text === "string" ? text.trim() : "";
274
+ if (!normalizedText) {
275
+ continue;
276
+ }
277
+ localized[normalizeLocaleTag(locale)] = normalizedText;
278
+ }
279
+ }
280
+ return Object.keys(localized).length > 0 ? localized : void 0;
281
+ }
282
+ function readMetadataString(record, fieldName) {
283
+ const value = record[fieldName];
284
+ if (value == null) {
285
+ return void 0;
286
+ }
287
+ if (typeof value !== "string") {
288
+ throw new Error(`Invalid marketplace metadata field: ${fieldName} must be a string`);
289
+ }
290
+ const normalized = value.trim();
291
+ return normalized || void 0;
292
+ }
293
+ function readMetadataStringArray(record, fieldName) {
294
+ const value = record[fieldName];
295
+ if (value == null) {
296
+ return void 0;
297
+ }
298
+ if (!Array.isArray(value)) {
299
+ throw new Error(`Invalid marketplace metadata field: ${fieldName} must be an array`);
300
+ }
301
+ const tags = value.map((entry, index) => {
302
+ if (typeof entry !== "string") {
303
+ throw new Error(`Invalid marketplace metadata field: ${fieldName}[${index}] must be a string`);
304
+ }
305
+ return entry.trim();
306
+ }).filter(Boolean);
307
+ return tags.length > 0 ? tags : void 0;
308
+ }
309
+ function readMetadataLocalizedTextMap(record, fieldName) {
310
+ const value = record[fieldName];
311
+ if (value == null) {
312
+ return void 0;
313
+ }
314
+ if (!isRecord(value)) {
315
+ throw new Error(`Invalid marketplace metadata field: ${fieldName} must be an object`);
316
+ }
317
+ const localized = {};
318
+ for (const [locale, text] of Object.entries(value)) {
319
+ if (typeof text !== "string") {
320
+ throw new Error(`Invalid marketplace metadata field: ${fieldName}.${locale} must be a string`);
321
+ }
322
+ const normalized = text.trim();
323
+ if (!normalized) {
324
+ continue;
325
+ }
326
+ localized[normalizeLocaleTag(locale)] = normalized;
327
+ }
328
+ return Object.keys(localized).length > 0 ? localized : void 0;
329
+ }
330
+ function readFrontmatterStringField(record, keyPaths) {
331
+ for (const keyPath of keyPaths) {
332
+ const value = readNestedFrontmatterValue(record, keyPath);
333
+ if (typeof value !== "string") {
334
+ continue;
335
+ }
336
+ const normalized = value.trim();
337
+ if (normalized) {
338
+ return normalized;
339
+ }
340
+ }
341
+ return void 0;
342
+ }
343
+ function readLocalizedTextMapField(record, keyPaths) {
344
+ for (const keyPath of keyPaths) {
345
+ const value = readNestedFrontmatterValue(record, keyPath);
346
+ if (!isRecord(value)) {
347
+ continue;
348
+ }
349
+ const normalized = {};
350
+ for (const [locale, text] of Object.entries(value)) {
351
+ if (typeof text !== "string") {
352
+ continue;
353
+ }
354
+ const trimmed = text.trim();
355
+ if (!trimmed) {
356
+ continue;
357
+ }
358
+ normalized[normalizeLocaleTag(locale)] = trimmed;
359
+ }
360
+ if (Object.keys(normalized).length > 0) {
361
+ return normalized;
362
+ }
363
+ }
364
+ return void 0;
365
+ }
366
+ function readFrontmatterTags(record) {
367
+ const rawTags = readNestedFrontmatterValue(record, ["tags"]);
368
+ if (Array.isArray(rawTags)) {
369
+ const tags2 = rawTags.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
370
+ return tags2.length > 0 ? tags2 : void 0;
371
+ }
372
+ if (typeof rawTags !== "string") {
373
+ return void 0;
374
+ }
375
+ const tags = rawTags.split(",").map((entry) => entry.trim()).filter(Boolean);
376
+ return tags.length > 0 ? tags : void 0;
377
+ }
378
+ function readNestedFrontmatterValue(record, keyPath) {
379
+ let current = record;
380
+ for (const rawKey of keyPath) {
381
+ if (!isRecord(current)) {
382
+ return void 0;
383
+ }
384
+ const normalizedKey = normalizeFrontmatterKey(rawKey);
385
+ const matchingKey = Object.keys(current).find((candidate) => normalizeFrontmatterKey(candidate) === normalizedKey);
386
+ if (!matchingKey) {
387
+ return void 0;
388
+ }
389
+ current = current[matchingKey];
390
+ }
391
+ return current;
392
+ }
393
+ function normalizeFrontmatterKey(raw) {
394
+ return raw.replace(/[-_]/g, "").toLowerCase();
395
+ }
396
+ function normalizeLocaleTag(raw) {
397
+ return raw.trim().toLowerCase();
398
+ }
399
+ function isRecord(value) {
400
+ return typeof value === "object" && value !== null && !Array.isArray(value);
401
+ }
402
+
403
+ // src/cli/skills/marketplace.ts
192
404
  var DEFAULT_MARKETPLACE_API_BASE = "https://marketplace-api.nextclaw.io";
193
405
  async function installMarketplaceSkill(options) {
194
406
  const slug = validateSkillSlug(options.slug.trim(), "slug");
195
- const workdir = resolve2(options.workdir);
196
- if (!existsSync2(workdir)) {
407
+ const workdir = resolve3(options.workdir);
408
+ if (!existsSync3(workdir)) {
197
409
  throw new Error(`Workdir does not exist: ${workdir}`);
198
410
  }
199
411
  const dirName = options.dir?.trim() || "skills";
200
- const destinationDir = isAbsolute(dirName) ? resolve2(dirName, slug) : resolve2(workdir, dirName, slug);
412
+ const destinationDir = isAbsolute(dirName) ? resolve3(dirName, slug) : resolve3(workdir, dirName, slug);
201
413
  const skillFile = join(destinationDir, "SKILL.md");
202
414
  const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
203
415
  const item = await fetchMarketplaceSkillItem(apiBase, slug);
204
416
  if (item.install.kind === "builtin") {
205
- if (!options.force && existsSync2(destinationDir)) {
206
- if (existsSync2(skillFile)) {
417
+ if (!options.force && existsSync3(destinationDir)) {
418
+ if (existsSync3(skillFile)) {
207
419
  return {
208
420
  slug,
209
421
  destinationDir,
@@ -213,7 +425,7 @@ async function installMarketplaceSkill(options) {
213
425
  }
214
426
  throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
215
427
  }
216
- if (existsSync2(destinationDir) && options.force) {
428
+ if (existsSync3(destinationDir) && options.force) {
217
429
  rmSync2(destinationDir, { recursive: true, force: true });
218
430
  }
219
431
  installBuiltinSkill(workdir, destinationDir, slug);
@@ -224,7 +436,7 @@ async function installMarketplaceSkill(options) {
224
436
  };
225
437
  }
226
438
  const filesPayload = await fetchMarketplaceSkillFiles(apiBase, slug);
227
- if (!options.force && existsSync2(destinationDir)) {
439
+ if (!options.force && existsSync3(destinationDir)) {
228
440
  const existingDirState = inspectMarketplaceSkillDirectory(destinationDir, filesPayload.files);
229
441
  if (existingDirState === "installed") {
230
442
  return {
@@ -240,12 +452,12 @@ async function installMarketplaceSkill(options) {
240
452
  throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
241
453
  }
242
454
  }
243
- if (existsSync2(destinationDir) && options.force) {
455
+ if (existsSync3(destinationDir) && options.force) {
244
456
  rmSync2(destinationDir, { recursive: true, force: true });
245
457
  }
246
458
  mkdirSync2(destinationDir, { recursive: true });
247
459
  for (const file of filesPayload.files) {
248
- const targetPath = resolve2(destinationDir, ...file.path.split("/"));
460
+ const targetPath = resolve3(destinationDir, ...file.path.split("/"));
249
461
  const rel = relative(destinationDir, targetPath);
250
462
  if (rel.startsWith("..") || isAbsolute(rel)) {
251
463
  throw new Error(`Invalid marketplace file path: ${file.path}`);
@@ -254,7 +466,7 @@ async function installMarketplaceSkill(options) {
254
466
  const bytes = file.contentBase64 ? decodeMarketplaceFileContent(file.path, file.contentBase64) : await fetchMarketplaceSkillFileBlob(apiBase, slug, file);
255
467
  writeFileSync2(targetPath, bytes);
256
468
  }
257
- if (!existsSync2(join(destinationDir, "SKILL.md"))) {
469
+ if (!existsSync3(join(destinationDir, "SKILL.md"))) {
258
470
  throw new Error(`Marketplace skill ${slug} does not include SKILL.md`);
259
471
  }
260
472
  return {
@@ -264,7 +476,7 @@ async function installMarketplaceSkill(options) {
264
476
  };
265
477
  }
266
478
  function inspectMarketplaceSkillDirectory(destinationDir, files) {
267
- if (existsSync2(join(destinationDir, "SKILL.md"))) {
479
+ if (existsSync3(join(destinationDir, "SKILL.md"))) {
268
480
  return "installed";
269
481
  }
270
482
  const discoveredFiles = collectRelativeFiles(destinationDir);
@@ -300,28 +512,31 @@ function collectRelativeFiles(rootDir) {
300
512
  };
301
513
  return walk(rootDir) ? output : null;
302
514
  }
303
- function normalizeMarketplaceRelativePath(path) {
304
- return path.replace(/\\/g, "/");
515
+ function normalizeMarketplaceRelativePath(path2) {
516
+ return path2.replace(/\\/g, "/");
305
517
  }
306
- function isIgnorableMarketplaceResidue(path) {
307
- return path === ".DS_Store";
518
+ function isIgnorableMarketplaceResidue(path2) {
519
+ return path2 === ".DS_Store";
308
520
  }
309
521
  async function publishMarketplaceSkill(options) {
310
- const skillDir = resolve2(options.skillDir);
311
- if (!existsSync2(skillDir)) {
522
+ const skillDir = resolve3(options.skillDir);
523
+ if (!existsSync3(skillDir)) {
312
524
  throw new Error(`Skill directory not found: ${skillDir}`);
313
525
  }
314
526
  const files = collectFiles(skillDir);
315
527
  if (!files.some((file) => file.path === "SKILL.md")) {
316
528
  throw new Error(`Skill directory must include SKILL.md: ${skillDir}`);
317
529
  }
318
- const parsedFrontmatter = parseSkillFrontmatter(readFileSync2(join(skillDir, "SKILL.md"), "utf8"));
319
- const slug = validateSkillSlug(options.slug?.trim() || basename(skillDir), "slug");
320
- const name = options.name?.trim() || parsedFrontmatter.name || slug;
321
- const description = options.description?.trim() || parsedFrontmatter.description;
322
- const summary = options.summary?.trim() || parsedFrontmatter.summary || description || `${slug} skill`;
323
- const author = options.author?.trim() || parsedFrontmatter.author || "nextclaw";
324
- const tags = normalizeTags(options.tags && options.tags.length > 0 ? options.tags : parsedFrontmatter.tags);
530
+ const parsedFrontmatter = parseSkillFrontmatter(readFileSync3(join(skillDir, "SKILL.md"), "utf8"));
531
+ const metadata = readMarketplaceMetadataFile(skillDir, options.metaFile);
532
+ const slug = validateSkillSlug(options.slug?.trim() || metadata.slug || basename(skillDir), "slug");
533
+ const name = options.name?.trim() || metadata.name || parsedFrontmatter.name || slug;
534
+ const description = options.description?.trim() || metadata.description || metadata.descriptionI18n?.en || parsedFrontmatter.description;
535
+ const summary = options.summary?.trim() || metadata.summary || metadata.summaryI18n?.en || parsedFrontmatter.summary || description || `${slug} skill`;
536
+ const summaryI18n = buildLocalizedTextMap(summary, parsedFrontmatter.summaryI18n, metadata.summaryI18n, options.summaryI18n);
537
+ const descriptionI18n = description ? buildLocalizedTextMap(description, parsedFrontmatter.descriptionI18n, metadata.descriptionI18n, options.descriptionI18n) : void 0;
538
+ const author = options.author?.trim() || metadata.author || parsedFrontmatter.author || "nextclaw";
539
+ const tags = normalizeTags(options.tags && options.tags.length > 0 ? options.tags : metadata.tags ?? parsedFrontmatter.tags);
325
540
  const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
326
541
  const token = resolveMarketplaceAdminToken(options.token);
327
542
  if (options.requireExisting) {
@@ -337,13 +552,15 @@ async function publishMarketplaceSkill(options) {
337
552
  slug,
338
553
  name,
339
554
  summary,
555
+ summaryI18n,
340
556
  description,
557
+ descriptionI18n,
341
558
  author,
342
559
  tags,
343
- sourceRepo: options.sourceRepo?.trim() || void 0,
344
- homepage: options.homepage?.trim() || void 0,
345
- publishedAt: options.publishedAt?.trim() || void 0,
346
- updatedAt: options.updatedAt?.trim() || void 0,
560
+ sourceRepo: options.sourceRepo?.trim() || metadata.sourceRepo,
561
+ homepage: options.homepage?.trim() || metadata.homepage,
562
+ publishedAt: options.publishedAt?.trim() || metadata.publishedAt,
563
+ updatedAt: options.updatedAt?.trim() || metadata.updatedAt,
347
564
  files
348
565
  })
349
566
  });
@@ -372,7 +589,7 @@ function collectFiles(rootDir) {
372
589
  if (!entry.isFile()) {
373
590
  continue;
374
591
  }
375
- const content = readFileSync2(absolute);
592
+ const content = readFileSync3(absolute);
376
593
  output.push({
377
594
  path: relativePath,
378
595
  contentBase64: content.toString("base64")
@@ -422,11 +639,11 @@ async function fetchMarketplaceSkillFiles(apiBase, slug) {
422
639
  const message = payload.error?.message || `marketplace skill file fetch failed: ${response.status}`;
423
640
  throw new Error(message);
424
641
  }
425
- if (!isRecord(payload.data) || !Array.isArray(payload.data.files)) {
642
+ if (!isRecord2(payload.data) || !Array.isArray(payload.data.files)) {
426
643
  throw new Error("Invalid marketplace skill file manifest response");
427
644
  }
428
645
  const files = payload.data.files.map((entry, index) => {
429
- if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
646
+ if (!isRecord2(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
430
647
  throw new Error(`Invalid marketplace skill file manifest at index ${index}`);
431
648
  }
432
649
  const normalized = {
@@ -456,10 +673,10 @@ async function fetchMarketplaceSkillFileBlob(apiBase, slug, file) {
456
673
  const arrayBuffer = await response.arrayBuffer();
457
674
  return Buffer.from(arrayBuffer);
458
675
  }
459
- function decodeMarketplaceFileContent(path, contentBase64) {
676
+ function decodeMarketplaceFileContent(path2, contentBase64) {
460
677
  const normalized = contentBase64.replace(/\s+/g, "");
461
678
  if (!normalized || normalized.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
462
- throw new Error(`Invalid marketplace file contentBase64 for path: ${path}`);
679
+ throw new Error(`Invalid marketplace file contentBase64 for path: ${path2}`);
463
680
  }
464
681
  return Buffer.from(normalized, "base64");
465
682
  }
@@ -494,7 +711,7 @@ async function readMarketplaceEnvelope(response) {
494
711
  } catch {
495
712
  throw new Error(`Invalid marketplace response: ${response.status}`);
496
713
  }
497
- if (!isRecord(payload) || typeof payload.ok !== "boolean") {
714
+ if (!isRecord2(payload) || typeof payload.ok !== "boolean") {
498
715
  throw new Error(`Invalid marketplace response shape: ${response.status}`);
499
716
  }
500
717
  return payload;
@@ -526,56 +743,17 @@ function normalizeTags(rawTags) {
526
743
  }
527
744
  return output.length > 0 ? output : ["skill"];
528
745
  }
529
- function parseSkillFrontmatter(raw) {
530
- const normalized = raw.replace(/\r\n/g, "\n");
531
- const match = normalized.match(/^---\n([\s\S]*?)\n---/);
532
- if (!match || !match[1]) {
533
- return {};
534
- }
535
- const metadata = /* @__PURE__ */ new Map();
536
- for (const line of match[1].split("\n")) {
537
- const parsed = line.match(/^([A-Za-z0-9_-]+):\s*(.+)$/);
538
- if (!parsed) {
539
- continue;
540
- }
541
- const key = parsed[1]?.trim().toLowerCase();
542
- const value = parsed[2]?.trim();
543
- if (!key || !value) {
544
- continue;
545
- }
546
- metadata.set(key, trimYamlString(value));
547
- }
548
- const rawTags = metadata.get("tags");
549
- let tags;
550
- if (rawTags) {
551
- if (rawTags.startsWith("[") && rawTags.endsWith("]")) {
552
- tags = rawTags.slice(1, -1).split(",").map((entry) => trimYamlString(entry)).filter(Boolean);
553
- } else {
554
- tags = rawTags.split(",").map((entry) => entry.trim()).filter(Boolean);
555
- }
556
- }
557
- return {
558
- name: metadata.get("name"),
559
- summary: metadata.get("summary"),
560
- description: metadata.get("description"),
561
- author: metadata.get("author"),
562
- tags
563
- };
564
- }
565
- function trimYamlString(raw) {
566
- return raw.replace(/^['"]/, "").replace(/['"]$/, "").trim();
567
- }
568
- function isRecord(value) {
746
+ function isRecord2(value) {
569
747
  return typeof value === "object" && value !== null && !Array.isArray(value);
570
748
  }
571
749
 
572
750
  // src/cli/update/runner.ts
573
751
  import { spawnSync } from "child_process";
574
- import { resolve as resolve4 } from "path";
752
+ import { resolve as resolve5 } from "path";
575
753
 
576
754
  // src/cli/utils.ts
577
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, rmSync as rmSync3 } from "fs";
578
- import { join as join2, resolve as resolve3 } from "path";
755
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync as rmSync3 } from "fs";
756
+ import { join as join2, resolve as resolve4 } from "path";
579
757
  import { spawn } from "child_process";
580
758
  import { isIP } from "net";
581
759
  import { fileURLToPath } from "url";
@@ -628,33 +806,33 @@ function buildServeArgs(options) {
628
806
  return [cliPath, "serve", "--ui-port", String(options.uiPort)];
629
807
  }
630
808
  function readServiceState() {
631
- const path = resolveServiceStatePath();
632
- if (!existsSync3(path)) {
809
+ const path2 = resolveServiceStatePath();
810
+ if (!existsSync4(path2)) {
633
811
  return null;
634
812
  }
635
813
  try {
636
- const raw = readFileSync3(path, "utf-8");
814
+ const raw = readFileSync4(path2, "utf-8");
637
815
  return JSON.parse(raw);
638
816
  } catch {
639
817
  return null;
640
818
  }
641
819
  }
642
820
  function writeServiceState(state) {
643
- const path = resolveServiceStatePath();
644
- mkdirSync3(resolve3(path, ".."), { recursive: true });
645
- writeFileSync3(path, JSON.stringify(state, null, 2));
821
+ const path2 = resolveServiceStatePath();
822
+ mkdirSync3(resolve4(path2, ".."), { recursive: true });
823
+ writeFileSync3(path2, JSON.stringify(state, null, 2));
646
824
  }
647
825
  function clearServiceState() {
648
- const path = resolveServiceStatePath();
649
- if (existsSync3(path)) {
650
- rmSync3(path, { force: true });
826
+ const path2 = resolveServiceStatePath();
827
+ if (existsSync4(path2)) {
828
+ rmSync3(path2, { force: true });
651
829
  }
652
830
  }
653
831
  function resolveServiceStatePath() {
654
- return resolve3(getDataDir2(), "run", "service.json");
832
+ return resolve4(getDataDir2(), "run", "service.json");
655
833
  }
656
834
  function resolveServiceLogPath() {
657
- return resolve3(getDataDir2(), "logs", "service.log");
835
+ return resolve4(getDataDir2(), "logs", "service.log");
658
836
  }
659
837
  function isProcessRunning(pid) {
660
838
  try {
@@ -670,7 +848,7 @@ async function waitForExit(pid, timeoutMs) {
670
848
  if (!isProcessRunning(pid)) {
671
849
  return true;
672
850
  }
673
- await new Promise((resolve10) => setTimeout(resolve10, 200));
851
+ await new Promise((resolve13) => setTimeout(resolve13, 200));
674
852
  }
675
853
  return !isProcessRunning(pid);
676
854
  }
@@ -680,8 +858,8 @@ function resolveUiStaticDir() {
680
858
  if (envDir) {
681
859
  candidates.push(envDir);
682
860
  }
683
- const cliDir = resolve3(fileURLToPath(new URL(".", import.meta.url)));
684
- const pkgRoot = resolve3(cliDir, "..", "..");
861
+ const cliDir = resolve4(fileURLToPath(new URL(".", import.meta.url)));
862
+ const pkgRoot = resolve4(cliDir, "..", "..");
685
863
  candidates.push(join2(pkgRoot, "ui-dist"));
686
864
  candidates.push(join2(pkgRoot, "ui"));
687
865
  candidates.push(join2(pkgRoot, "..", "ui-dist"));
@@ -693,7 +871,7 @@ function resolveUiStaticDir() {
693
871
  candidates.push(join2(pkgRoot, "..", "..", "packages", "nextclaw-ui", "dist"));
694
872
  candidates.push(join2(pkgRoot, "..", "..", "nextclaw-ui", "dist"));
695
873
  for (const dir of candidates) {
696
- if (existsSync3(join2(dir, "index.html"))) {
874
+ if (existsSync4(join2(dir, "index.html"))) {
697
875
  return dir;
698
876
  }
699
877
  }
@@ -744,7 +922,7 @@ function findExecutableOnPath(binary, env = process.env, platform = process.plat
744
922
  return null;
745
923
  }
746
924
  if (target.includes("/") || target.includes("\\")) {
747
- return existsSync3(target) ? target : null;
925
+ return existsSync4(target) ? target : null;
748
926
  }
749
927
  const rawPath = env.PATH ?? env.Path ?? env.path ?? "";
750
928
  if (!rawPath.trim()) {
@@ -754,7 +932,7 @@ function findExecutableOnPath(binary, env = process.env, platform = process.plat
754
932
  if (entries.length === 0) {
755
933
  return null;
756
934
  }
757
- const checkCandidates = (candidate) => existsSync3(candidate) ? candidate : null;
935
+ const checkCandidates = (candidate) => existsSync4(candidate) ? candidate : null;
758
936
  for (const dir of entries) {
759
937
  const direct = checkCandidates(join2(dir, target));
760
938
  if (direct) {
@@ -776,12 +954,12 @@ function which(binary) {
776
954
  return findExecutableOnPath(binary) !== null;
777
955
  }
778
956
  function resolveVersionFromPackageTree(startDir, expectedName) {
779
- let current = resolve3(startDir);
957
+ let current = resolve4(startDir);
780
958
  while (current.length > 0) {
781
959
  const pkgPath = join2(current, "package.json");
782
- if (existsSync3(pkgPath)) {
960
+ if (existsSync4(pkgPath)) {
783
961
  try {
784
- const raw = readFileSync3(pkgPath, "utf-8");
962
+ const raw = readFileSync4(pkgPath, "utf-8");
785
963
  const parsed = JSON.parse(raw);
786
964
  if (typeof parsed.version === "string") {
787
965
  if (!expectedName || parsed.name === expectedName) {
@@ -791,7 +969,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
791
969
  } catch {
792
970
  }
793
971
  }
794
- const parent = resolve3(current, "..");
972
+ const parent = resolve4(current, "..");
795
973
  if (parent === current) {
796
974
  break;
797
975
  }
@@ -800,7 +978,7 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
800
978
  return null;
801
979
  }
802
980
  function getPackageVersion() {
803
- const cliDir = resolve3(fileURLToPath(new URL(".", import.meta.url)));
981
+ const cliDir = resolve4(fileURLToPath(new URL(".", import.meta.url)));
804
982
  return resolveVersionFromPackageTree(cliDir, "nextclaw") ?? resolveVersionFromPackageTree(cliDir) ?? getCorePackageVersion();
805
983
  }
806
984
  function printAgentResponse(response) {
@@ -809,8 +987,8 @@ function printAgentResponse(response) {
809
987
  async function prompt(rl, question) {
810
988
  rl.setPrompt(question);
811
989
  rl.prompt();
812
- return new Promise((resolve10) => {
813
- rl.once("line", (line) => resolve10(line));
990
+ return new Promise((resolve13) => {
991
+ rl.once("line", (line) => resolve13(line));
814
992
  });
815
993
  }
816
994
 
@@ -845,7 +1023,7 @@ function runSelfUpdate(options = {}) {
845
1023
  return { ok: result.status === 0, code: result.status };
846
1024
  };
847
1025
  if (updateCommand) {
848
- const cwd = options.cwd ? resolve4(options.cwd) : process.cwd();
1026
+ const cwd = options.cwd ? resolve5(options.cwd) : process.cwd();
849
1027
  const shellCommand = resolveShellCommand(updateCommand);
850
1028
  const ok = runStep(shellCommand.cmd, shellCommand.args, cwd);
851
1029
  if (!ok.ok) {
@@ -866,168 +1044,546 @@ function runSelfUpdate(options = {}) {
866
1044
 
867
1045
  // src/cli/commands/plugins.ts
868
1046
  import {
869
- addPluginLoadPath,
870
- buildPluginStatusReport,
871
- disablePluginInConfig,
872
- enablePluginInConfig,
873
- installPluginFromNpmSpec,
874
- installPluginFromPath,
1047
+ buildPluginStatusReport as buildPluginStatusReport2,
875
1048
  loadOpenClawPlugins,
876
- recordPluginInstall,
877
- resolveUninstallDirectoryTarget,
878
- uninstallPlugin
1049
+ resolveUninstallDirectoryTargets
879
1050
  } from "@nextclaw/openclaw-compat";
880
- import {
881
- loadConfig,
882
- saveConfig,
883
- getWorkspacePath,
884
- expandHome
885
- } from "@nextclaw/core";
1051
+
1052
+ // src/cli/commands/plugin-command-utils.ts
886
1053
  import { builtinProviderIds } from "@nextclaw/runtime";
887
- import { createInterface } from "readline";
888
- import { existsSync as existsSync4 } from "fs";
889
- import { resolve as resolve5 } from "path";
890
1054
  var RESERVED_PROVIDER_IDS = builtinProviderIds();
891
- function loadPluginRegistry(config2, workspaceDir) {
892
- return loadOpenClawPlugins({
893
- config: config2,
894
- workspaceDir,
895
- reservedToolNames: [
896
- "read_file",
897
- "write_file",
898
- "edit_file",
899
- "list_dir",
900
- "exec",
901
- "web_search",
902
- "web_fetch",
903
- "message",
904
- "spawn",
905
- "sessions_list",
906
- "sessions_history",
907
- "sessions_send",
908
- "memory_search",
909
- "memory_get",
910
- "subagents",
911
- "gateway",
912
- "cron"
913
- ],
1055
+ var RESERVED_TOOL_NAMES = [
1056
+ "read_file",
1057
+ "write_file",
1058
+ "edit_file",
1059
+ "list_dir",
1060
+ "exec",
1061
+ "web_search",
1062
+ "web_fetch",
1063
+ "message",
1064
+ "spawn",
1065
+ "sessions_list",
1066
+ "sessions_history",
1067
+ "sessions_send",
1068
+ "memory_search",
1069
+ "memory_get",
1070
+ "subagents",
1071
+ "gateway",
1072
+ "cron"
1073
+ ];
1074
+ function buildReservedPluginLoadOptions() {
1075
+ return {
1076
+ reservedToolNames: [...RESERVED_TOOL_NAMES],
914
1077
  reservedChannelIds: [],
915
1078
  reservedProviderIds: RESERVED_PROVIDER_IDS,
916
1079
  reservedEngineKinds: ["native"],
917
- logger: {
918
- info: (message) => console.log(message),
919
- warn: (message) => console.warn(message),
920
- error: (message) => console.error(message),
921
- debug: (message) => console.debug(message)
922
- }
923
- });
924
- }
925
- function toExtensionRegistry(pluginRegistry) {
926
- return {
927
- tools: pluginRegistry.tools.map((tool) => ({
928
- extensionId: tool.pluginId,
929
- factory: tool.factory,
930
- names: tool.names,
931
- optional: tool.optional,
932
- source: tool.source
933
- })),
934
- channels: pluginRegistry.channels.map((channel) => ({
935
- extensionId: channel.pluginId,
936
- channel: channel.channel,
937
- source: channel.source
938
- })),
939
- engines: pluginRegistry.engines.map((engine) => ({
940
- extensionId: engine.pluginId,
941
- kind: engine.kind,
942
- factory: engine.factory,
943
- source: engine.source
944
- })),
945
- diagnostics: pluginRegistry.diagnostics.map((diag) => ({
946
- level: diag.level,
947
- message: diag.message,
948
- extensionId: diag.pluginId,
949
- source: diag.source
950
- }))
1080
+ reservedNcpAgentRuntimeKinds: ["native"]
951
1081
  };
952
1082
  }
953
- function logPluginDiagnostics(registry) {
954
- for (const diag of registry.diagnostics) {
955
- const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
956
- const text = `${prefix}${diag.message}`;
957
- if (diag.level === "error") {
958
- console.error(`[plugins] ${text}`);
959
- } else {
960
- console.warn(`[plugins] ${text}`);
961
- }
1083
+ function appendPluginCapabilityLines(lines, plugin) {
1084
+ if (plugin.toolNames.length > 0) {
1085
+ lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
1086
+ }
1087
+ if (plugin.channelIds.length > 0) {
1088
+ lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
1089
+ }
1090
+ if (plugin.providerIds.length > 0) {
1091
+ lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
1092
+ }
1093
+ if (plugin.engineKinds.length > 0) {
1094
+ lines.push(`Engines: ${plugin.engineKinds.join(", ")}`);
1095
+ }
1096
+ if (plugin.ncpAgentRuntimeKinds.length > 0) {
1097
+ lines.push(`NCP runtimes: ${plugin.ncpAgentRuntimeKinds.join(", ")}`);
962
1098
  }
963
1099
  }
964
- function toPluginConfigView(config2, bindings) {
965
- const view = JSON.parse(JSON.stringify(config2));
966
- const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
967
- for (const binding of bindings) {
968
- const pluginConfig = config2.plugins.entries?.[binding.pluginId]?.config;
969
- if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
970
- continue;
1100
+
1101
+ // src/cli/commands/dev-first-party-plugin-load-paths.ts
1102
+ import fs from "fs";
1103
+ import path from "path";
1104
+ var readJsonFile = (filePath) => {
1105
+ try {
1106
+ const raw = fs.readFileSync(filePath, "utf-8");
1107
+ const parsed = JSON.parse(raw);
1108
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
1109
+ } catch {
1110
+ return null;
1111
+ }
1112
+ };
1113
+ var readString = (value) => {
1114
+ if (typeof value !== "string") {
1115
+ return void 0;
1116
+ }
1117
+ const trimmed = value.trim();
1118
+ return trimmed || void 0;
1119
+ };
1120
+ var hasOpenClawExtensions = (pkg) => {
1121
+ const openclaw = pkg.openclaw;
1122
+ if (!openclaw || typeof openclaw !== "object" || Array.isArray(openclaw)) {
1123
+ return false;
1124
+ }
1125
+ const extensions = openclaw.extensions;
1126
+ return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim().length > 0);
1127
+ };
1128
+ var normalizePackageSpec = (spec) => {
1129
+ const trimmed = spec.trim();
1130
+ if (!trimmed) {
1131
+ return void 0;
1132
+ }
1133
+ if (trimmed.startsWith("@")) {
1134
+ const slashIndex = trimmed.indexOf("/");
1135
+ if (slashIndex < 0) {
1136
+ return void 0;
971
1137
  }
972
- channels2[binding.channelId] = JSON.parse(JSON.stringify(pluginConfig));
1138
+ const secondAtIndex = trimmed.indexOf("@", slashIndex + 1);
1139
+ return secondAtIndex < 0 ? trimmed : trimmed.slice(0, secondAtIndex);
973
1140
  }
974
- view.channels = channels2;
975
- return view;
976
- }
977
- function mergePluginConfigView(baseConfig, pluginViewConfig, bindings) {
978
- const next = JSON.parse(JSON.stringify(baseConfig));
979
- const pluginChannels = pluginViewConfig.channels && typeof pluginViewConfig.channels === "object" && !Array.isArray(pluginViewConfig.channels) ? pluginViewConfig.channels : {};
980
- const entries = { ...next.plugins.entries ?? {} };
981
- for (const binding of bindings) {
982
- if (!Object.prototype.hasOwnProperty.call(pluginChannels, binding.channelId)) {
1141
+ const versionIndex = trimmed.indexOf("@");
1142
+ return versionIndex < 0 ? trimmed : trimmed.slice(0, versionIndex);
1143
+ };
1144
+ var readWorkspacePluginPackages = (workspaceExtensionsDir) => {
1145
+ if (!workspaceExtensionsDir.trim() || !fs.existsSync(workspaceExtensionsDir)) {
1146
+ return [];
1147
+ }
1148
+ const entries = fs.readdirSync(workspaceExtensionsDir, { withFileTypes: true });
1149
+ const packages = [];
1150
+ for (const entry of entries) {
1151
+ if (!entry.isDirectory()) {
983
1152
  continue;
984
1153
  }
985
- const channelConfig = pluginChannels[binding.channelId];
986
- if (!channelConfig || typeof channelConfig !== "object" || Array.isArray(channelConfig)) {
1154
+ const packageDir = path.join(workspaceExtensionsDir, entry.name);
1155
+ const pkg = readJsonFile(path.join(packageDir, "package.json"));
1156
+ if (!pkg || !hasOpenClawExtensions(pkg)) {
987
1157
  continue;
988
1158
  }
989
- entries[binding.pluginId] = {
990
- ...entries[binding.pluginId] ?? {},
991
- config: channelConfig
992
- };
1159
+ const packageName = readString(pkg.name);
1160
+ if (!packageName?.startsWith("@nextclaw/")) {
1161
+ continue;
1162
+ }
1163
+ packages.push({
1164
+ packageName,
1165
+ dir: packageDir
1166
+ });
993
1167
  }
994
- next.plugins = {
995
- ...next.plugins,
996
- entries
997
- };
998
- return next;
999
- }
1000
- var PluginCommands = class {
1001
- constructor() {
1168
+ return packages;
1169
+ };
1170
+ var resolveDevFirstPartyPluginLoadPaths = (config2, workspaceExtensionsDir) => {
1171
+ const rootDir = workspaceExtensionsDir?.trim();
1172
+ if (!rootDir) {
1173
+ return [];
1002
1174
  }
1003
- pluginsList(opts = {}) {
1004
- const config2 = loadConfig();
1005
- const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1006
- const report = buildPluginStatusReport({
1007
- config: config2,
1008
- workspaceDir,
1009
- reservedChannelIds: [],
1010
- reservedProviderIds: RESERVED_PROVIDER_IDS,
1011
- reservedEngineKinds: ["native"]
1012
- });
1013
- const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
1014
- if (opts.json) {
1015
- console.log(
1016
- JSON.stringify(
1017
- {
1018
- workspaceDir,
1019
- plugins: list,
1020
- diagnostics: report.diagnostics
1021
- },
1022
- null,
1023
- 2
1024
- )
1025
- );
1026
- return;
1175
+ const workspacePackages = readWorkspacePluginPackages(rootDir);
1176
+ if (workspacePackages.length === 0) {
1177
+ return [];
1178
+ }
1179
+ const packageDirByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry.dir]));
1180
+ const loadPaths = [];
1181
+ const installs = config2.plugins.installs ?? {};
1182
+ for (const installRecord of Object.values(installs)) {
1183
+ const packageName = normalizePackageSpec(installRecord.spec ?? "");
1184
+ if (!packageName) {
1185
+ continue;
1027
1186
  }
1028
- if (list.length === 0) {
1029
- console.log("No plugins discovered.");
1030
- return;
1187
+ const packageDir = packageDirByName.get(packageName);
1188
+ if (!packageDir || loadPaths.includes(packageDir)) {
1189
+ continue;
1190
+ }
1191
+ loadPaths.push(packageDir);
1192
+ }
1193
+ return loadPaths;
1194
+ };
1195
+ var resolveDevFirstPartyPluginInstallRoots = (config2, workspaceExtensionsDir) => {
1196
+ const rootDir = workspaceExtensionsDir?.trim();
1197
+ if (!rootDir) {
1198
+ return [];
1199
+ }
1200
+ const workspacePackages = readWorkspacePluginPackages(rootDir);
1201
+ if (workspacePackages.length === 0) {
1202
+ return [];
1203
+ }
1204
+ const packageNames = new Set(workspacePackages.map((entry) => entry.packageName));
1205
+ const installRoots = [];
1206
+ for (const installRecord of Object.values(config2.plugins.installs ?? {})) {
1207
+ const packageName = normalizePackageSpec(installRecord.spec ?? "");
1208
+ if (!packageName || !packageNames.has(packageName)) {
1209
+ continue;
1210
+ }
1211
+ const installPath = readString(installRecord.installPath);
1212
+ if (!installPath || installRoots.includes(installPath)) {
1213
+ continue;
1214
+ }
1215
+ installRoots.push(installPath);
1216
+ }
1217
+ return installRoots;
1218
+ };
1219
+ var applyDevFirstPartyPluginLoadPaths = (config2, workspaceExtensionsDir) => {
1220
+ const devLoadPaths = resolveDevFirstPartyPluginLoadPaths(config2, workspaceExtensionsDir);
1221
+ if (devLoadPaths.length === 0) {
1222
+ return config2;
1223
+ }
1224
+ const existingLoadPaths = Array.isArray(config2.plugins.load?.paths) ? config2.plugins.load.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
1225
+ const mergedLoadPaths = [...devLoadPaths];
1226
+ for (const entry of existingLoadPaths) {
1227
+ if (!mergedLoadPaths.includes(entry)) {
1228
+ mergedLoadPaths.push(entry);
1229
+ }
1230
+ }
1231
+ return {
1232
+ ...config2,
1233
+ plugins: {
1234
+ ...config2.plugins,
1235
+ load: {
1236
+ ...config2.plugins.load,
1237
+ paths: mergedLoadPaths
1238
+ }
1239
+ }
1240
+ };
1241
+ };
1242
+
1243
+ // src/cli/commands/plugins.ts
1244
+ import {
1245
+ loadConfig as loadConfig2,
1246
+ getWorkspacePath as getWorkspacePath2
1247
+ } from "@nextclaw/core";
1248
+ import { createInterface } from "readline";
1249
+ import { resolve as resolve7 } from "path";
1250
+
1251
+ // src/cli/commands/plugin-mutation-actions.ts
1252
+ import {
1253
+ addPluginLoadPath,
1254
+ buildPluginStatusReport,
1255
+ disablePluginInConfig,
1256
+ enablePluginInConfig,
1257
+ installPluginFromNpmSpec,
1258
+ installPluginFromPath,
1259
+ recordPluginInstall,
1260
+ uninstallPlugin
1261
+ } from "@nextclaw/openclaw-compat";
1262
+ import { existsSync as existsSync5 } from "fs";
1263
+ import { resolve as resolve6 } from "path";
1264
+ import { expandHome, getWorkspacePath, loadConfig, saveConfig } from "@nextclaw/core";
1265
+ function resolveFileNpmSpecToLocalPath(raw) {
1266
+ const trimmed = raw.trim();
1267
+ if (!trimmed.toLowerCase().startsWith("file:")) {
1268
+ return null;
1269
+ }
1270
+ const rest = trimmed.slice("file:".length);
1271
+ if (!rest) {
1272
+ return { ok: false, error: "unsupported file: spec: missing path" };
1273
+ }
1274
+ if (rest.startsWith("///")) {
1275
+ return { ok: true, path: rest.slice(2) };
1276
+ }
1277
+ if (rest.startsWith("//localhost/")) {
1278
+ return { ok: true, path: rest.slice("//localhost".length) };
1279
+ }
1280
+ if (rest.startsWith("//")) {
1281
+ return {
1282
+ ok: false,
1283
+ error: 'unsupported file: URL host (expected "file:<path>" or "file:///abs/path")'
1284
+ };
1285
+ }
1286
+ return { ok: true, path: rest };
1287
+ }
1288
+ function looksLikePath(raw) {
1289
+ return raw.startsWith(".") || raw.startsWith("~") || raw.startsWith("/") || raw.endsWith(".ts") || raw.endsWith(".js") || raw.endsWith(".mjs") || raw.endsWith(".cjs") || raw.endsWith(".tgz") || raw.endsWith(".tar.gz") || raw.endsWith(".tar") || raw.endsWith(".zip");
1290
+ }
1291
+ function isArchivePath(filePath) {
1292
+ const lower = filePath.toLowerCase();
1293
+ return lower.endsWith(".zip") || lower.endsWith(".tgz") || lower.endsWith(".tar.gz") || lower.endsWith(".tar");
1294
+ }
1295
+ async function enablePluginMutation(id) {
1296
+ const config2 = loadConfig();
1297
+ const next = enablePluginInConfig(config2, id);
1298
+ saveConfig(next);
1299
+ return {
1300
+ message: `Enabled plugin "${id}".`
1301
+ };
1302
+ }
1303
+ async function disablePluginMutation(id) {
1304
+ const config2 = loadConfig();
1305
+ const next = disablePluginInConfig(config2, id);
1306
+ saveConfig(next);
1307
+ return {
1308
+ message: `Disabled plugin "${id}".`
1309
+ };
1310
+ }
1311
+ async function uninstallPluginMutation(id, opts = {}) {
1312
+ const config2 = loadConfig();
1313
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1314
+ const report = buildPluginStatusReport({
1315
+ config: config2,
1316
+ workspaceDir,
1317
+ ...buildReservedPluginLoadOptions()
1318
+ });
1319
+ const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
1320
+ const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
1321
+ const pluginId = plugin?.id ?? id;
1322
+ const hasEntry = pluginId in (config2.plugins.entries ?? {});
1323
+ const hasInstall = pluginId in (config2.plugins.installs ?? {});
1324
+ if (!hasEntry && !hasInstall) {
1325
+ if (plugin) {
1326
+ throw new Error(
1327
+ `Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`
1328
+ );
1329
+ }
1330
+ throw new Error(`Plugin not found: ${id}`);
1331
+ }
1332
+ const result = await uninstallPlugin({
1333
+ config: config2,
1334
+ pluginId,
1335
+ deleteFiles: !keepFiles
1336
+ });
1337
+ if (!result.ok) {
1338
+ throw new Error(result.error);
1339
+ }
1340
+ saveConfig(result.config);
1341
+ const removed = [];
1342
+ if (result.actions.entry) {
1343
+ removed.push("config entry");
1344
+ }
1345
+ if (result.actions.install) {
1346
+ removed.push("install record");
1347
+ }
1348
+ if (result.actions.allowlist) {
1349
+ removed.push("allowlist");
1350
+ }
1351
+ if (result.actions.loadPath) {
1352
+ removed.push("load path");
1353
+ }
1354
+ if (result.actions.directory) {
1355
+ removed.push("directory");
1356
+ }
1357
+ return {
1358
+ message: `Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`,
1359
+ warnings: result.warnings
1360
+ };
1361
+ }
1362
+ async function installPluginMutation(pathOrSpec, opts = {}) {
1363
+ const fileSpec = resolveFileNpmSpecToLocalPath(pathOrSpec);
1364
+ if (fileSpec && !fileSpec.ok) {
1365
+ throw new Error(fileSpec.error);
1366
+ }
1367
+ const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
1368
+ const resolved = resolve6(expandHome(normalized));
1369
+ const config2 = loadConfig();
1370
+ if (existsSync5(resolved)) {
1371
+ if (opts.link) {
1372
+ const probe = await installPluginFromPath({ path: resolved, dryRun: true });
1373
+ if (!probe.ok) {
1374
+ throw new Error(probe.error);
1375
+ }
1376
+ let next3 = addPluginLoadPath(config2, resolved);
1377
+ next3 = enablePluginInConfig(next3, probe.pluginId);
1378
+ next3 = recordPluginInstall(next3, {
1379
+ pluginId: probe.pluginId,
1380
+ source: "path",
1381
+ sourcePath: resolved,
1382
+ installPath: resolved,
1383
+ version: probe.version
1384
+ });
1385
+ saveConfig(next3);
1386
+ return {
1387
+ message: `Linked plugin path: ${resolved}`
1388
+ };
1389
+ }
1390
+ const result2 = await installPluginFromPath({
1391
+ path: resolved,
1392
+ logger: {
1393
+ info: (message) => console.log(message),
1394
+ warn: (message) => console.warn(message)
1395
+ }
1396
+ });
1397
+ if (!result2.ok) {
1398
+ throw new Error(result2.error);
1399
+ }
1400
+ let next2 = enablePluginInConfig(config2, result2.pluginId);
1401
+ next2 = recordPluginInstall(next2, {
1402
+ pluginId: result2.pluginId,
1403
+ source: isArchivePath(resolved) ? "archive" : "path",
1404
+ sourcePath: resolved,
1405
+ installPath: result2.targetDir,
1406
+ version: result2.version
1407
+ });
1408
+ saveConfig(next2);
1409
+ return {
1410
+ message: `Installed plugin: ${result2.pluginId}`
1411
+ };
1412
+ }
1413
+ if (opts.link) {
1414
+ throw new Error("`--link` requires a local path.");
1415
+ }
1416
+ if (looksLikePath(pathOrSpec)) {
1417
+ throw new Error(`Path not found: ${resolved}`);
1418
+ }
1419
+ const result = await installPluginFromNpmSpec({
1420
+ spec: pathOrSpec,
1421
+ logger: {
1422
+ info: (message) => console.log(message),
1423
+ warn: (message) => console.warn(message)
1424
+ }
1425
+ });
1426
+ if (!result.ok) {
1427
+ throw new Error(result.error);
1428
+ }
1429
+ let next = enablePluginInConfig(config2, result.pluginId);
1430
+ next = recordPluginInstall(next, {
1431
+ pluginId: result.pluginId,
1432
+ source: "npm",
1433
+ spec: pathOrSpec,
1434
+ installPath: result.targetDir,
1435
+ version: result.version
1436
+ });
1437
+ saveConfig(next);
1438
+ return {
1439
+ message: `Installed plugin: ${result.pluginId}`
1440
+ };
1441
+ }
1442
+
1443
+ // src/cli/commands/plugins.ts
1444
+ function loadPluginRegistry(config2, workspaceDir) {
1445
+ const workspaceExtensionsDir = process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR;
1446
+ const configWithDevPluginPaths = applyDevFirstPartyPluginLoadPaths(
1447
+ config2,
1448
+ workspaceExtensionsDir
1449
+ );
1450
+ const excludedRoots = resolveDevFirstPartyPluginInstallRoots(config2, workspaceExtensionsDir);
1451
+ return loadOpenClawPlugins({
1452
+ config: configWithDevPluginPaths,
1453
+ workspaceDir,
1454
+ excludeRoots: excludedRoots,
1455
+ ...buildReservedPluginLoadOptions(),
1456
+ logger: {
1457
+ info: (message) => console.log(message),
1458
+ warn: (message) => console.warn(message),
1459
+ error: (message) => console.error(message),
1460
+ debug: (message) => console.debug(message)
1461
+ }
1462
+ });
1463
+ }
1464
+ function toExtensionRegistry(pluginRegistry) {
1465
+ return {
1466
+ tools: pluginRegistry.tools.map((tool) => ({
1467
+ extensionId: tool.pluginId,
1468
+ factory: tool.factory,
1469
+ names: tool.names,
1470
+ optional: tool.optional,
1471
+ source: tool.source
1472
+ })),
1473
+ channels: pluginRegistry.channels.map((channel) => ({
1474
+ extensionId: channel.pluginId,
1475
+ channel: channel.channel,
1476
+ source: channel.source
1477
+ })),
1478
+ engines: pluginRegistry.engines.map((engine) => ({
1479
+ extensionId: engine.pluginId,
1480
+ kind: engine.kind,
1481
+ factory: engine.factory,
1482
+ source: engine.source
1483
+ })),
1484
+ ncpAgentRuntimes: pluginRegistry.ncpAgentRuntimes.map((runtime2) => ({
1485
+ pluginId: runtime2.pluginId,
1486
+ kind: runtime2.kind,
1487
+ label: runtime2.label,
1488
+ createRuntime: runtime2.createRuntime,
1489
+ source: runtime2.source
1490
+ })),
1491
+ diagnostics: pluginRegistry.diagnostics.map((diag) => ({
1492
+ level: diag.level,
1493
+ message: diag.message,
1494
+ extensionId: diag.pluginId,
1495
+ source: diag.source
1496
+ }))
1497
+ };
1498
+ }
1499
+ function logPluginDiagnostics(registry) {
1500
+ for (const diag of registry.diagnostics) {
1501
+ const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
1502
+ const text = `${prefix}${diag.message}`;
1503
+ if (diag.level === "error") {
1504
+ console.error(`[plugins] ${text}`);
1505
+ } else {
1506
+ console.warn(`[plugins] ${text}`);
1507
+ }
1508
+ }
1509
+ }
1510
+ function toPluginConfigView(config2, bindings) {
1511
+ const view = JSON.parse(JSON.stringify(config2));
1512
+ const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
1513
+ for (const binding of bindings) {
1514
+ const pluginConfig = config2.plugins.entries?.[binding.pluginId]?.config;
1515
+ if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
1516
+ continue;
1517
+ }
1518
+ channels2[binding.channelId] = JSON.parse(JSON.stringify(pluginConfig));
1519
+ }
1520
+ view.channels = channels2;
1521
+ return view;
1522
+ }
1523
+ function mergePluginConfigView(baseConfig, pluginViewConfig, bindings) {
1524
+ const next = JSON.parse(JSON.stringify(baseConfig));
1525
+ const pluginChannels = pluginViewConfig.channels && typeof pluginViewConfig.channels === "object" && !Array.isArray(pluginViewConfig.channels) ? pluginViewConfig.channels : {};
1526
+ const entries = { ...next.plugins.entries ?? {} };
1527
+ for (const binding of bindings) {
1528
+ if (!Object.prototype.hasOwnProperty.call(pluginChannels, binding.channelId)) {
1529
+ continue;
1530
+ }
1531
+ const channelConfig = pluginChannels[binding.channelId];
1532
+ if (!channelConfig || typeof channelConfig !== "object" || Array.isArray(channelConfig)) {
1533
+ continue;
1534
+ }
1535
+ entries[binding.pluginId] = {
1536
+ ...entries[binding.pluginId] ?? {},
1537
+ config: channelConfig
1538
+ };
1539
+ }
1540
+ next.plugins = {
1541
+ ...next.plugins,
1542
+ entries
1543
+ };
1544
+ return next;
1545
+ }
1546
+ var PluginCommands = class {
1547
+ constructor() {
1548
+ }
1549
+ async enablePlugin(id) {
1550
+ return await enablePluginMutation(id);
1551
+ }
1552
+ async disablePlugin(id) {
1553
+ return await disablePluginMutation(id);
1554
+ }
1555
+ async uninstallPlugin(id, opts = {}) {
1556
+ return await uninstallPluginMutation(id, opts);
1557
+ }
1558
+ async installPlugin(pathOrSpec, opts = {}) {
1559
+ return await installPluginMutation(pathOrSpec, opts);
1560
+ }
1561
+ pluginsList(opts = {}) {
1562
+ const config2 = loadConfig2();
1563
+ const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1564
+ const report = buildPluginStatusReport2({
1565
+ config: config2,
1566
+ workspaceDir,
1567
+ ...buildReservedPluginLoadOptions()
1568
+ });
1569
+ const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
1570
+ if (opts.json) {
1571
+ console.log(
1572
+ JSON.stringify(
1573
+ {
1574
+ workspaceDir,
1575
+ plugins: list,
1576
+ diagnostics: report.diagnostics
1577
+ },
1578
+ null,
1579
+ 2
1580
+ )
1581
+ );
1582
+ return;
1583
+ }
1584
+ if (list.length === 0) {
1585
+ console.log("No plugins discovered.");
1586
+ return;
1031
1587
  }
1032
1588
  for (const plugin of list) {
1033
1589
  const status = plugin.status === "loaded" ? "loaded" : plugin.status === "disabled" ? "disabled" : "error";
@@ -1043,17 +1599,10 @@ var PluginCommands = class {
1043
1599
  if (plugin.version) {
1044
1600
  console.log(` version: ${plugin.version}`);
1045
1601
  }
1046
- if (plugin.toolNames.length > 0) {
1047
- console.log(` tools: ${plugin.toolNames.join(", ")}`);
1048
- }
1049
- if (plugin.channelIds.length > 0) {
1050
- console.log(` channels: ${plugin.channelIds.join(", ")}`);
1051
- }
1052
- if (plugin.providerIds.length > 0) {
1053
- console.log(` providers: ${plugin.providerIds.join(", ")}`);
1054
- }
1055
- if (plugin.engineKinds.length > 0) {
1056
- console.log(` engines: ${plugin.engineKinds.join(", ")}`);
1602
+ const capabilityLines = [];
1603
+ appendPluginCapabilityLines(capabilityLines, plugin);
1604
+ for (const line of capabilityLines) {
1605
+ console.log(` ${line.toLowerCase()}`);
1057
1606
  }
1058
1607
  if (plugin.error) {
1059
1608
  console.log(` error: ${plugin.error}`);
@@ -1062,14 +1611,12 @@ var PluginCommands = class {
1062
1611
  }
1063
1612
  }
1064
1613
  pluginsInfo(id, opts = {}) {
1065
- const config2 = loadConfig();
1066
- const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1067
- const report = buildPluginStatusReport({
1614
+ const config2 = loadConfig2();
1615
+ const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1616
+ const report = buildPluginStatusReport2({
1068
1617
  config: config2,
1069
1618
  workspaceDir,
1070
- reservedChannelIds: [],
1071
- reservedProviderIds: RESERVED_PROVIDER_IDS,
1072
- reservedEngineKinds: ["native"]
1619
+ ...buildReservedPluginLoadOptions()
1073
1620
  });
1074
1621
  const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
1075
1622
  if (!plugin) {
@@ -1096,18 +1643,7 @@ var PluginCommands = class {
1096
1643
  if (plugin.version) {
1097
1644
  lines.push(`Version: ${plugin.version}`);
1098
1645
  }
1099
- if (plugin.toolNames.length > 0) {
1100
- lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
1101
- }
1102
- if (plugin.channelIds.length > 0) {
1103
- lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
1104
- }
1105
- if (plugin.providerIds.length > 0) {
1106
- lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
1107
- }
1108
- if (plugin.engineKinds.length > 0) {
1109
- lines.push(`Engines: ${plugin.engineKinds.join(", ")}`);
1110
- }
1646
+ appendPluginCapabilityLines(lines, plugin);
1111
1647
  if (plugin.error) {
1112
1648
  lines.push(`Error: ${plugin.error}`);
1113
1649
  }
@@ -1133,33 +1669,37 @@ var PluginCommands = class {
1133
1669
  console.log(lines.join("\n"));
1134
1670
  }
1135
1671
  async pluginsEnable(id) {
1136
- const config2 = loadConfig();
1137
- const next = enablePluginInConfig(config2, id);
1138
- saveConfig(next);
1139
- console.log(`Enabled plugin "${id}".`);
1672
+ try {
1673
+ const result = await this.enablePlugin(id);
1674
+ console.log(result.message);
1675
+ } catch (error) {
1676
+ console.error(error instanceof Error ? error.message : String(error));
1677
+ process.exit(1);
1678
+ }
1140
1679
  console.log("If gateway is running, plugin changes are hot-applied automatically.");
1141
1680
  }
1142
1681
  async pluginsDisable(id) {
1143
- const config2 = loadConfig();
1144
- const next = disablePluginInConfig(config2, id);
1145
- saveConfig(next);
1146
- console.log(`Disabled plugin "${id}".`);
1682
+ try {
1683
+ const result = await this.disablePlugin(id);
1684
+ console.log(result.message);
1685
+ } catch (error) {
1686
+ console.error(error instanceof Error ? error.message : String(error));
1687
+ process.exit(1);
1688
+ }
1147
1689
  console.log("If gateway is running, plugin changes are hot-applied automatically.");
1148
1690
  }
1149
1691
  async pluginsUninstall(id, opts = {}) {
1150
- const config2 = loadConfig();
1151
- const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1152
- const report = buildPluginStatusReport({
1692
+ if (opts.keepConfig) {
1693
+ console.log("`--keep-config` is deprecated, use `--keep-files`.");
1694
+ }
1695
+ const config2 = loadConfig2();
1696
+ const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1697
+ const report = buildPluginStatusReport2({
1153
1698
  config: config2,
1154
1699
  workspaceDir,
1155
- reservedChannelIds: [],
1156
- reservedProviderIds: RESERVED_PROVIDER_IDS,
1157
- reservedEngineKinds: ["native"]
1700
+ ...buildReservedPluginLoadOptions()
1158
1701
  });
1159
1702
  const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
1160
- if (opts.keepConfig) {
1161
- console.log("`--keep-config` is deprecated, use `--keep-files`.");
1162
- }
1163
1703
  const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
1164
1704
  const pluginId = plugin?.id ?? id;
1165
1705
  const hasEntry = pluginId in (config2.plugins.entries ?? {});
@@ -1175,7 +1715,7 @@ var PluginCommands = class {
1175
1715
  process.exit(1);
1176
1716
  }
1177
1717
  const install = config2.plugins.installs?.[pluginId];
1178
- const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve5(install.installPath) === resolve5(install.sourcePath));
1718
+ const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve7(install.installPath) === resolve7(install.sourcePath));
1179
1719
  const preview = [];
1180
1720
  if (hasEntry) {
1181
1721
  preview.push("config entry");
@@ -1189,12 +1729,13 @@ var PluginCommands = class {
1189
1729
  if (isLinked && install?.sourcePath && config2.plugins.load?.paths?.includes(install.sourcePath)) {
1190
1730
  preview.push("load path");
1191
1731
  }
1192
- const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
1732
+ const deleteTargets = !keepFiles ? resolveUninstallDirectoryTargets({
1733
+ config: config2,
1193
1734
  pluginId,
1194
1735
  hasInstall,
1195
1736
  installRecord: install
1196
- }) : null;
1197
- if (deleteTarget) {
1737
+ }) : [];
1738
+ for (const deleteTarget of deleteTargets) {
1198
1739
  preview.push(`directory: ${deleteTarget}`);
1199
1740
  }
1200
1741
  const pluginName = plugin?.name || pluginId;
@@ -1212,132 +1753,35 @@ var PluginCommands = class {
1212
1753
  return;
1213
1754
  }
1214
1755
  }
1215
- const result = await uninstallPlugin({
1216
- config: config2,
1217
- pluginId,
1218
- deleteFiles: !keepFiles
1219
- });
1220
- if (!result.ok) {
1221
- console.error(result.error);
1756
+ try {
1757
+ const result = await this.uninstallPlugin(id, opts);
1758
+ for (const warning of result.warnings) {
1759
+ console.warn(warning);
1760
+ }
1761
+ console.log(result.message);
1762
+ } catch (error) {
1763
+ console.error(error instanceof Error ? error.message : String(error));
1222
1764
  process.exit(1);
1223
1765
  }
1224
- for (const warning of result.warnings) {
1225
- console.warn(warning);
1226
- }
1227
- saveConfig(result.config);
1228
- const removed = [];
1229
- if (result.actions.entry) {
1230
- removed.push("config entry");
1231
- }
1232
- if (result.actions.install) {
1233
- removed.push("install record");
1234
- }
1235
- if (result.actions.allowlist) {
1236
- removed.push("allowlist");
1237
- }
1238
- if (result.actions.loadPath) {
1239
- removed.push("load path");
1240
- }
1241
- if (result.actions.directory) {
1242
- removed.push("directory");
1243
- }
1244
- console.log(`Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`);
1245
1766
  console.log("If gateway is running, plugin changes are hot-applied automatically.");
1246
1767
  }
1247
1768
  async pluginsInstall(pathOrSpec, opts = {}) {
1248
- const fileSpec = this.resolveFileNpmSpecToLocalPath(pathOrSpec);
1249
- if (fileSpec && !fileSpec.ok) {
1250
- console.error(fileSpec.error);
1251
- process.exit(1);
1252
- }
1253
- const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
1254
- const resolved = resolve5(expandHome(normalized));
1255
- const config2 = loadConfig();
1256
- if (existsSync4(resolved)) {
1257
- if (opts.link) {
1258
- const probe = await installPluginFromPath({ path: resolved, dryRun: true });
1259
- if (!probe.ok) {
1260
- console.error(probe.error);
1261
- process.exit(1);
1262
- }
1263
- let next3 = addPluginLoadPath(config2, resolved);
1264
- next3 = enablePluginInConfig(next3, probe.pluginId);
1265
- next3 = recordPluginInstall(next3, {
1266
- pluginId: probe.pluginId,
1267
- source: "path",
1268
- sourcePath: resolved,
1269
- installPath: resolved,
1270
- version: probe.version
1271
- });
1272
- saveConfig(next3);
1273
- console.log(`Linked plugin path: ${resolved}`);
1274
- console.log("If gateway is running, plugin changes are hot-applied automatically.");
1275
- return;
1276
- }
1277
- const result2 = await installPluginFromPath({
1278
- path: resolved,
1279
- logger: {
1280
- info: (message) => console.log(message),
1281
- warn: (message) => console.warn(message)
1282
- }
1283
- });
1284
- if (!result2.ok) {
1285
- console.error(result2.error);
1286
- process.exit(1);
1287
- }
1288
- let next2 = enablePluginInConfig(config2, result2.pluginId);
1289
- next2 = recordPluginInstall(next2, {
1290
- pluginId: result2.pluginId,
1291
- source: this.isArchivePath(resolved) ? "archive" : "path",
1292
- sourcePath: resolved,
1293
- installPath: result2.targetDir,
1294
- version: result2.version
1295
- });
1296
- saveConfig(next2);
1297
- console.log(`Installed plugin: ${result2.pluginId}`);
1298
- console.log("If gateway is running, plugin changes are hot-applied automatically.");
1299
- return;
1300
- }
1301
- if (opts.link) {
1302
- console.error("`--link` requires a local path.");
1303
- process.exit(1);
1304
- }
1305
- if (this.looksLikePath(pathOrSpec)) {
1306
- console.error(`Path not found: ${resolved}`);
1307
- process.exit(1);
1308
- }
1309
- const result = await installPluginFromNpmSpec({
1310
- spec: pathOrSpec,
1311
- logger: {
1312
- info: (message) => console.log(message),
1313
- warn: (message) => console.warn(message)
1314
- }
1315
- });
1316
- if (!result.ok) {
1317
- console.error(result.error);
1769
+ try {
1770
+ const result = await this.installPlugin(pathOrSpec, opts);
1771
+ console.log(result.message);
1772
+ } catch (error) {
1773
+ console.error(error instanceof Error ? error.message : String(error));
1318
1774
  process.exit(1);
1319
1775
  }
1320
- let next = enablePluginInConfig(config2, result.pluginId);
1321
- next = recordPluginInstall(next, {
1322
- pluginId: result.pluginId,
1323
- source: "npm",
1324
- spec: pathOrSpec,
1325
- installPath: result.targetDir,
1326
- version: result.version
1327
- });
1328
- saveConfig(next);
1329
- console.log(`Installed plugin: ${result.pluginId}`);
1330
1776
  console.log("If gateway is running, plugin changes are hot-applied automatically.");
1331
1777
  }
1332
1778
  pluginsDoctor() {
1333
- const config2 = loadConfig();
1334
- const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1335
- const report = buildPluginStatusReport({
1779
+ const config2 = loadConfig2();
1780
+ const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1781
+ const report = buildPluginStatusReport2({
1336
1782
  config: config2,
1337
1783
  workspaceDir,
1338
- reservedChannelIds: [],
1339
- reservedProviderIds: RESERVED_PROVIDER_IDS,
1340
- reservedEngineKinds: ["native"]
1784
+ ...buildReservedPluginLoadOptions()
1341
1785
  });
1342
1786
  const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
1343
1787
  const diagnostics = report.diagnostics.filter((diag) => diag.level === "error");
@@ -1367,47 +1811,17 @@ var PluginCommands = class {
1367
1811
  input: process.stdin,
1368
1812
  output: process.stdout
1369
1813
  });
1370
- const answer = await new Promise((resolve10) => {
1371
- rl.question(`${question} [y/N] `, (line) => resolve10(line));
1814
+ const answer = await new Promise((resolve13) => {
1815
+ rl.question(`${question} [y/N] `, (line) => resolve13(line));
1372
1816
  });
1373
1817
  rl.close();
1374
1818
  const normalized = answer.trim().toLowerCase();
1375
1819
  return normalized === "y" || normalized === "yes";
1376
1820
  }
1377
- resolveFileNpmSpecToLocalPath(raw) {
1378
- const trimmed = raw.trim();
1379
- if (!trimmed.toLowerCase().startsWith("file:")) {
1380
- return null;
1381
- }
1382
- const rest = trimmed.slice("file:".length);
1383
- if (!rest) {
1384
- return { ok: false, error: "unsupported file: spec: missing path" };
1385
- }
1386
- if (rest.startsWith("///")) {
1387
- return { ok: true, path: rest.slice(2) };
1388
- }
1389
- if (rest.startsWith("//localhost/")) {
1390
- return { ok: true, path: rest.slice("//localhost".length) };
1391
- }
1392
- if (rest.startsWith("//")) {
1393
- return {
1394
- ok: false,
1395
- error: 'unsupported file: URL host (expected "file:<path>" or "file:///abs/path")'
1396
- };
1397
- }
1398
- return { ok: true, path: rest };
1399
- }
1400
- looksLikePath(raw) {
1401
- return raw.startsWith(".") || raw.startsWith("~") || raw.startsWith("/") || raw.endsWith(".ts") || raw.endsWith(".js") || raw.endsWith(".mjs") || raw.endsWith(".cjs") || raw.endsWith(".tgz") || raw.endsWith(".tar.gz") || raw.endsWith(".tar") || raw.endsWith(".zip");
1402
- }
1403
- isArchivePath(filePath) {
1404
- const lower = filePath.toLowerCase();
1405
- return lower.endsWith(".zip") || lower.endsWith(".tgz") || lower.endsWith(".tar.gz") || lower.endsWith(".tar");
1406
- }
1407
1821
  };
1408
1822
 
1409
1823
  // src/cli/commands/config.ts
1410
- import { buildReloadPlan, diffConfigPaths, loadConfig as loadConfig2, saveConfig as saveConfig2 } from "@nextclaw/core";
1824
+ import { buildReloadPlan, diffConfigPaths, loadConfig as loadConfig3, saveConfig as saveConfig2 } from "@nextclaw/core";
1411
1825
 
1412
1826
  // src/cli/config-path.ts
1413
1827
  function isIndexSegment(raw) {
@@ -1602,7 +2016,7 @@ var ConfigCommands = class {
1602
2016
  this.deps = deps;
1603
2017
  }
1604
2018
  configGet(pathExpr, opts = {}) {
1605
- const config2 = loadConfig2();
2019
+ const config2 = loadConfig3();
1606
2020
  let parsedPath;
1607
2021
  try {
1608
2022
  parsedPath = parseRequiredConfigPath(pathExpr);
@@ -1644,7 +2058,7 @@ var ConfigCommands = class {
1644
2058
  process.exit(1);
1645
2059
  return;
1646
2060
  }
1647
- const prevConfig = loadConfig2();
2061
+ const prevConfig = loadConfig3();
1648
2062
  const nextConfig = structuredClone(prevConfig);
1649
2063
  try {
1650
2064
  setAtConfigPath(nextConfig, parsedPath, parsedValue);
@@ -1670,7 +2084,7 @@ var ConfigCommands = class {
1670
2084
  process.exit(1);
1671
2085
  return;
1672
2086
  }
1673
- const prevConfig = loadConfig2();
2087
+ const prevConfig = loadConfig3();
1674
2088
  const nextConfig = structuredClone(prevConfig);
1675
2089
  const removed = unsetAtConfigPath(nextConfig, parsedPath);
1676
2090
  if (!removed) {
@@ -1703,12 +2117,12 @@ var ConfigCommands = class {
1703
2117
  };
1704
2118
 
1705
2119
  // src/cli/commands/secrets.ts
1706
- import { readFileSync as readFileSync4 } from "fs";
2120
+ import { readFileSync as readFileSync5 } from "fs";
1707
2121
  import {
1708
2122
  buildReloadPlan as buildReloadPlan2,
1709
2123
  diffConfigPaths as diffConfigPaths2,
1710
2124
  getConfigPath,
1711
- loadConfig as loadConfig3,
2125
+ loadConfig as loadConfig4,
1712
2126
  resolveConfigSecrets,
1713
2127
  saveConfig as saveConfig3
1714
2128
  } from "@nextclaw/core";
@@ -1761,17 +2175,17 @@ function parseRefsPatch(raw) {
1761
2175
  throw new Error("refs patch must be an object");
1762
2176
  }
1763
2177
  const output = {};
1764
- for (const [path, value] of Object.entries(raw)) {
2178
+ for (const [path2, value] of Object.entries(raw)) {
1765
2179
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1766
- throw new Error(`invalid ref for ${path}`);
2180
+ throw new Error(`invalid ref for ${path2}`);
1767
2181
  }
1768
2182
  const source = normalizeSecretSource(value.source);
1769
2183
  const id = normalizeOptionalString(value.id);
1770
2184
  const provider = normalizeOptionalString(value.provider);
1771
2185
  if (!source || !id) {
1772
- throw new Error(`invalid ref for ${path}: source/id is required`);
2186
+ throw new Error(`invalid ref for ${path2}: source/id is required`);
1773
2187
  }
1774
- output[path] = { source, ...provider ? { provider } : {}, id };
2188
+ output[path2] = { source, ...provider ? { provider } : {}, id };
1775
2189
  }
1776
2190
  return output;
1777
2191
  }
@@ -1804,21 +2218,21 @@ var SecretsCommands = class {
1804
2218
  this.deps = deps;
1805
2219
  }
1806
2220
  secretsAudit(opts = {}) {
1807
- const config2 = loadConfig3();
2221
+ const config2 = loadConfig4();
1808
2222
  const configPath = getConfigPath();
1809
2223
  const refs = config2.secrets.refs;
1810
2224
  const items = [];
1811
- for (const [path, ref] of Object.entries(refs)) {
2225
+ for (const [path2, ref] of Object.entries(refs)) {
1812
2226
  const provider = inferProviderAlias(config2, ref);
1813
2227
  const scopedConfig = structuredClone(config2);
1814
- scopedConfig.secrets.refs = { [path]: ref };
2228
+ scopedConfig.secrets.refs = { [path2]: ref };
1815
2229
  try {
1816
2230
  const resolved = resolveConfigSecrets(scopedConfig, { configPath });
1817
- const parsedPath = parseRequiredConfigPath(path);
2231
+ const parsedPath = parseRequiredConfigPath(path2);
1818
2232
  const target = getAtConfigPath(resolved, parsedPath);
1819
2233
  if (!target.found) {
1820
2234
  items.push({
1821
- path,
2235
+ path: path2,
1822
2236
  source: ref.source,
1823
2237
  provider,
1824
2238
  id: ref.id,
@@ -1830,7 +2244,7 @@ var SecretsCommands = class {
1830
2244
  const resolvedValue = target.value;
1831
2245
  const detail = typeof resolvedValue === "string" ? `resolved (length=${resolvedValue.length})` : `resolved (${typeof resolvedValue})`;
1832
2246
  items.push({
1833
- path,
2247
+ path: path2,
1834
2248
  source: ref.source,
1835
2249
  provider,
1836
2250
  id: ref.id,
@@ -1839,7 +2253,7 @@ var SecretsCommands = class {
1839
2253
  });
1840
2254
  } catch (error) {
1841
2255
  items.push({
1842
- path,
2256
+ path: path2,
1843
2257
  source: ref.source,
1844
2258
  provider,
1845
2259
  id: ref.id,
@@ -1872,7 +2286,7 @@ var SecretsCommands = class {
1872
2286
  if (!alias) {
1873
2287
  throw new Error("provider alias is required");
1874
2288
  }
1875
- const prevConfig = loadConfig3();
2289
+ const prevConfig = loadConfig4();
1876
2290
  const nextConfig = structuredClone(prevConfig);
1877
2291
  const remove = Boolean(opts.remove);
1878
2292
  if (remove) {
@@ -1893,13 +2307,13 @@ var SecretsCommands = class {
1893
2307
  ...normalizeOptionalString(opts.prefix) ? { prefix: normalizeOptionalString(opts.prefix) } : {}
1894
2308
  };
1895
2309
  } else if (source === "file") {
1896
- const path = normalizeOptionalString(opts.path);
1897
- if (!path) {
2310
+ const path2 = normalizeOptionalString(opts.path);
2311
+ if (!path2) {
1898
2312
  throw new Error("file source requires --path");
1899
2313
  }
1900
2314
  nextConfig.secrets.providers[alias] = {
1901
2315
  source,
1902
- path,
2316
+ path: path2,
1903
2317
  format: "json"
1904
2318
  };
1905
2319
  } else {
@@ -1934,7 +2348,7 @@ var SecretsCommands = class {
1934
2348
  console.log(`Secrets provider ${remove ? "removed" : "configured"}: ${alias}`);
1935
2349
  }
1936
2350
  async secretsApply(opts) {
1937
- const prevConfig = loadConfig3();
2351
+ const prevConfig = loadConfig4();
1938
2352
  const nextConfig = structuredClone(prevConfig);
1939
2353
  if (opts.enable && opts.disable) {
1940
2354
  throw new Error("cannot set --enable and --disable at the same time");
@@ -1946,7 +2360,7 @@ var SecretsCommands = class {
1946
2360
  nextConfig.secrets.enabled = false;
1947
2361
  }
1948
2362
  if (opts.file) {
1949
- const raw = readFileSync4(opts.file, "utf-8");
2363
+ const raw = readFileSync5(opts.file, "utf-8");
1950
2364
  const patch = parseApplyFile(raw);
1951
2365
  if (patch.defaults) {
1952
2366
  nextConfig.secrets.defaults = patch.defaults;
@@ -1962,12 +2376,12 @@ var SecretsCommands = class {
1962
2376
  }
1963
2377
  }
1964
2378
  if (opts.path) {
1965
- const path = opts.path.trim();
1966
- if (!path) {
2379
+ const path2 = opts.path.trim();
2380
+ if (!path2) {
1967
2381
  throw new Error("path is empty");
1968
2382
  }
1969
2383
  if (opts.remove) {
1970
- delete nextConfig.secrets.refs[path];
2384
+ delete nextConfig.secrets.refs[path2];
1971
2385
  } else {
1972
2386
  const source = normalizeSecretSource(opts.source);
1973
2387
  const id = normalizeOptionalString(opts.id);
@@ -1975,7 +2389,7 @@ var SecretsCommands = class {
1975
2389
  throw new Error("apply single ref requires --source and --id");
1976
2390
  }
1977
2391
  const provider = normalizeOptionalString(opts.provider);
1978
- nextConfig.secrets.refs[path] = {
2392
+ nextConfig.secrets.refs[path2] = {
1979
2393
  source,
1980
2394
  id,
1981
2395
  ...provider ? { provider } : {}
@@ -1999,7 +2413,7 @@ var SecretsCommands = class {
1999
2413
  console.log("Secrets applied.");
2000
2414
  }
2001
2415
  async secretsReload(opts = {}) {
2002
- const config2 = loadConfig3();
2416
+ const config2 = loadConfig4();
2003
2417
  const configPath = getConfigPath();
2004
2418
  resolveConfigSecrets(config2, { configPath });
2005
2419
  saveConfig3(config2);
@@ -2027,9 +2441,9 @@ var SecretsCommands = class {
2027
2441
 
2028
2442
  // src/cli/commands/channels.ts
2029
2443
  import { spawnSync as spawnSync2 } from "child_process";
2030
- import { getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig4, saveConfig as saveConfig4 } from "@nextclaw/core";
2444
+ import { getWorkspacePath as getWorkspacePath3, loadConfig as loadConfig5, saveConfig as saveConfig4 } from "@nextclaw/core";
2031
2445
  import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds as builtinProviderIds2 } from "@nextclaw/runtime";
2032
- import { buildPluginStatusReport as buildPluginStatusReport2, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
2446
+ import { buildPluginStatusReport as buildPluginStatusReport3, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
2033
2447
  var CHANNEL_LABELS = {
2034
2448
  telegram: "Telegram",
2035
2449
  whatsapp: "WhatsApp",
@@ -2048,7 +2462,7 @@ var ChannelCommands = class {
2048
2462
  this.deps = deps;
2049
2463
  }
2050
2464
  channelsStatus() {
2051
- const config2 = loadConfig4();
2465
+ const config2 = loadConfig5();
2052
2466
  console.log("Channel Status");
2053
2467
  const channelConfig = config2.channels;
2054
2468
  for (const channelId of BUILTIN_CHANNEL_PLUGIN_IDS) {
@@ -2056,8 +2470,8 @@ var ChannelCommands = class {
2056
2470
  const enabled = channelConfig[channelId]?.enabled === true;
2057
2471
  console.log(`${label}: ${enabled ? "\u2713" : "\u2717"}`);
2058
2472
  }
2059
- const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
2060
- const report = buildPluginStatusReport2({
2473
+ const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
2474
+ const report = buildPluginStatusReport3({
2061
2475
  config: config2,
2062
2476
  workspaceDir,
2063
2477
  reservedChannelIds: [],
@@ -2087,8 +2501,8 @@ var ChannelCommands = class {
2087
2501
  console.error("--channel is required");
2088
2502
  process.exit(1);
2089
2503
  }
2090
- const config2 = loadConfig4();
2091
- const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
2504
+ const config2 = loadConfig5();
2505
+ const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
2092
2506
  const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
2093
2507
  const bindings = getPluginChannelBindings(pluginRegistry);
2094
2508
  const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
@@ -2217,15 +2631,15 @@ var CronCommands = class {
2217
2631
 
2218
2632
  // src/cli/commands/diagnostics.ts
2219
2633
  import { createServer as createNetServer } from "net";
2220
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2221
- import { resolve as resolve6 } from "path";
2634
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
2635
+ import { resolve as resolve8 } from "path";
2222
2636
  import {
2223
2637
  APP_NAME,
2224
2638
  getConfigPath as getConfigPath2,
2225
2639
  getDataDir as getDataDir4,
2226
- getWorkspacePath as getWorkspacePath3,
2640
+ getWorkspacePath as getWorkspacePath4,
2227
2641
  hasSecretRef,
2228
- loadConfig as loadConfig5
2642
+ loadConfig as loadConfig6
2229
2643
  } from "@nextclaw/core";
2230
2644
  import { listBuiltinProviders } from "@nextclaw/runtime";
2231
2645
  var DiagnosticsCommands = class {
@@ -2387,9 +2801,9 @@ var DiagnosticsCommands = class {
2387
2801
  }
2388
2802
  async collectRuntimeStatus(params) {
2389
2803
  const configPath = getConfigPath2();
2390
- const config2 = loadConfig5();
2391
- const workspacePath = getWorkspacePath3(config2.agents.defaults.workspace);
2392
- const serviceStatePath = resolve6(getDataDir4(), "run", "service.json");
2804
+ const config2 = loadConfig6();
2805
+ const workspacePath = getWorkspacePath4(config2.agents.defaults.workspace);
2806
+ const serviceStatePath = resolve8(getDataDir4(), "run", "service.json");
2393
2807
  const fixActions = [];
2394
2808
  let serviceState = readServiceState();
2395
2809
  if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
@@ -2429,11 +2843,11 @@ var DiagnosticsCommands = class {
2429
2843
  });
2430
2844
  const issues = [];
2431
2845
  const recommendations = [];
2432
- if (!existsSync5(configPath)) {
2846
+ if (!existsSync6(configPath)) {
2433
2847
  issues.push("Config file is missing.");
2434
2848
  recommendations.push(`Run ${APP_NAME} init to create config files.`);
2435
2849
  }
2436
- if (!existsSync5(workspacePath)) {
2850
+ if (!existsSync6(workspacePath)) {
2437
2851
  issues.push("Workspace directory does not exist.");
2438
2852
  recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
2439
2853
  }
@@ -2466,13 +2880,13 @@ var DiagnosticsCommands = class {
2466
2880
  return {
2467
2881
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2468
2882
  configPath,
2469
- configExists: existsSync5(configPath),
2883
+ configExists: existsSync6(configPath),
2470
2884
  workspacePath,
2471
- workspaceExists: existsSync5(workspacePath),
2885
+ workspaceExists: existsSync6(workspacePath),
2472
2886
  model: config2.agents.defaults.model,
2473
2887
  providers,
2474
2888
  serviceStatePath,
2475
- serviceStateExists: existsSync5(serviceStatePath),
2889
+ serviceStateExists: existsSync6(serviceStatePath),
2476
2890
  fixActions,
2477
2891
  process: {
2478
2892
  managedByState,
@@ -2521,12 +2935,12 @@ var DiagnosticsCommands = class {
2521
2935
  clearTimeout(timer);
2522
2936
  }
2523
2937
  }
2524
- readLogTail(path, maxLines = 25) {
2525
- if (!existsSync5(path)) {
2938
+ readLogTail(path2, maxLines = 25) {
2939
+ if (!existsSync6(path2)) {
2526
2940
  return [];
2527
2941
  }
2528
2942
  try {
2529
- const lines = readFileSync5(path, "utf-8").split(/\r?\n/).filter(Boolean);
2943
+ const lines = readFileSync6(path2, "utf-8").split(/\r?\n/).filter(Boolean);
2530
2944
  if (lines.length <= maxLines) {
2531
2945
  return lines;
2532
2946
  }
@@ -2536,17 +2950,17 @@ var DiagnosticsCommands = class {
2536
2950
  }
2537
2951
  }
2538
2952
  async checkPortAvailability(params) {
2539
- return await new Promise((resolve10) => {
2953
+ return await new Promise((resolve13) => {
2540
2954
  const server = createNetServer();
2541
2955
  server.once("error", (error) => {
2542
- resolve10({
2956
+ resolve13({
2543
2957
  available: false,
2544
2958
  detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
2545
2959
  });
2546
2960
  });
2547
2961
  server.listen(params.port, params.host, () => {
2548
2962
  server.close(() => {
2549
- resolve10({
2963
+ resolve13({
2550
2964
  available: true,
2551
2965
  detail: `bind ok on ${params.host}:${params.port}`
2552
2966
  });
@@ -2559,24 +2973,24 @@ var DiagnosticsCommands = class {
2559
2973
  // src/cli/commands/service.ts
2560
2974
  import * as NextclawCore from "@nextclaw/core";
2561
2975
  import {
2562
- getPluginChannelBindings as getPluginChannelBindings2,
2976
+ getPluginChannelBindings as getPluginChannelBindings3,
2563
2977
  resolvePluginChannelMessageToolHints,
2564
2978
  setPluginRuntimeBridge,
2565
- startPluginChannelGateways,
2566
- stopPluginChannelGateways
2979
+ startPluginChannelGateways as startPluginChannelGateways2,
2980
+ stopPluginChannelGateways as stopPluginChannelGateways2
2567
2981
  } from "@nextclaw/openclaw-compat";
2568
2982
  import { startUiServer } from "@nextclaw/server";
2569
- import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync, rmSync as rmSync4 } from "fs";
2570
- import { dirname as dirname2, join as join5, resolve as resolve7 } from "path";
2983
+ import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, openSync, rmSync as rmSync4 } from "fs";
2984
+ import { dirname as dirname2, join as join5, resolve as resolve10 } from "path";
2571
2985
  import { spawn as spawn2 } from "child_process";
2572
2986
  import { request as httpRequest } from "http";
2573
2987
  import { request as httpsRequest } from "https";
2574
- import { fileURLToPath as fileURLToPath2 } from "url";
2988
+ import { createServer as createNetServer2 } from "net";
2575
2989
  import chokidar from "chokidar";
2576
2990
 
2577
2991
  // src/cli/gateway/controller.ts
2578
2992
  import { createHash } from "crypto";
2579
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
2993
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
2580
2994
  import {
2581
2995
  buildConfigSchema,
2582
2996
  ConfigSchema,
@@ -2585,11 +2999,11 @@ import {
2585
2999
  } from "@nextclaw/core";
2586
3000
  var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
2587
3001
  var readConfigSnapshot = (getConfigPath5) => {
2588
- const path = getConfigPath5();
3002
+ const path2 = getConfigPath5();
2589
3003
  let raw = "";
2590
3004
  let parsed = {};
2591
- if (existsSync6(path)) {
2592
- raw = readFileSync6(path, "utf-8");
3005
+ if (existsSync7(path2)) {
3006
+ raw = readFileSync7(path2, "utf-8");
2593
3007
  try {
2594
3008
  parsed = JSON.parse(raw);
2595
3009
  } catch {
@@ -2900,11 +3314,15 @@ var ConfigReloader = class {
2900
3314
  this.currentConfig = nextConfig;
2901
3315
  this.options.providerManager?.setConfig(nextConfig);
2902
3316
  const plan = buildReloadPlan3(changedPaths);
3317
+ let reloadPluginsResult = void 0;
2903
3318
  if (plan.reloadPlugins) {
2904
- await this.reloadPlugins(nextConfig);
3319
+ reloadPluginsResult = await this.reloadPlugins({
3320
+ config: nextConfig,
3321
+ changedPaths
3322
+ });
2905
3323
  console.log("Config reload: plugins reloaded.");
2906
3324
  }
2907
- if (plan.restartChannels) {
3325
+ if (plan.restartChannels || reloadPluginsResult?.restartChannels) {
2908
3326
  await this.reloadChannels(nextConfig);
2909
3327
  console.log("Config reload: channels restarted.");
2910
3328
  }
@@ -2999,11 +3417,11 @@ var ConfigReloader = class {
2999
3417
  this.providerReloadTask = null;
3000
3418
  }
3001
3419
  }
3002
- async reloadPlugins(nextConfig) {
3420
+ async reloadPlugins(params) {
3003
3421
  if (!this.options.reloadPlugins) {
3004
3422
  return;
3005
3423
  }
3006
- await this.options.reloadPlugins(nextConfig);
3424
+ return await this.options.reloadPlugins(params);
3007
3425
  }
3008
3426
  };
3009
3427
 
@@ -3025,6 +3443,104 @@ var MissingProvider = class extends LLMProvider {
3025
3443
  }
3026
3444
  };
3027
3445
 
3446
+ // src/cli/commands/service-plugin-reload.ts
3447
+ import { getWorkspacePath as getWorkspacePath5 } from "@nextclaw/core";
3448
+ import {
3449
+ getPluginChannelBindings as getPluginChannelBindings2,
3450
+ startPluginChannelGateways,
3451
+ stopPluginChannelGateways
3452
+ } from "@nextclaw/openclaw-compat";
3453
+
3454
+ // src/cli/commands/plugin-reload.ts
3455
+ function buildPluginChannelBindingSignature(binding) {
3456
+ return `${binding.pluginId}:${binding.channelId}`;
3457
+ }
3458
+ function buildSortedBindingSignatures(bindings) {
3459
+ return bindings.map(buildPluginChannelBindingSignature).sort();
3460
+ }
3461
+ function buildSortedExtensionChannelIds(channels2) {
3462
+ return channels2.map((registration) => registration.channel.id).filter((id) => typeof id === "string" && id.trim().length > 0).sort();
3463
+ }
3464
+ function areSortedStringListsEqual(left, right) {
3465
+ if (left.length !== right.length) {
3466
+ return false;
3467
+ }
3468
+ for (let index = 0; index < left.length; index += 1) {
3469
+ if (left[index] !== right[index]) {
3470
+ return false;
3471
+ }
3472
+ }
3473
+ return true;
3474
+ }
3475
+ function readPluginIdFromPluginsEntryPath(path2) {
3476
+ const prefix = "plugins.entries.";
3477
+ if (!path2.startsWith(prefix)) {
3478
+ return null;
3479
+ }
3480
+ const suffix = path2.slice(prefix.length);
3481
+ const [pluginId] = suffix.split(".");
3482
+ return pluginId?.trim() ? pluginId.trim() : null;
3483
+ }
3484
+ function shouldRestartChannelsForPluginReload(params) {
3485
+ const currentBindingSignatures = buildSortedBindingSignatures(params.currentPluginChannelBindings);
3486
+ const nextBindingSignatures = buildSortedBindingSignatures(params.nextPluginChannelBindings);
3487
+ if (!areSortedStringListsEqual(currentBindingSignatures, nextBindingSignatures)) {
3488
+ return true;
3489
+ }
3490
+ const currentExtensionChannelIds = buildSortedExtensionChannelIds(params.currentExtensionChannels);
3491
+ const nextExtensionChannelIds = buildSortedExtensionChannelIds(params.nextExtensionChannels);
3492
+ if (!areSortedStringListsEqual(currentExtensionChannelIds, nextExtensionChannelIds)) {
3493
+ return true;
3494
+ }
3495
+ const channelPluginIds = /* @__PURE__ */ new Set();
3496
+ for (const binding of params.currentPluginChannelBindings) {
3497
+ channelPluginIds.add(binding.pluginId);
3498
+ }
3499
+ for (const binding of params.nextPluginChannelBindings) {
3500
+ channelPluginIds.add(binding.pluginId);
3501
+ }
3502
+ for (const path2 of params.changedPaths) {
3503
+ const pluginId = readPluginIdFromPluginsEntryPath(path2);
3504
+ if (pluginId && channelPluginIds.has(pluginId)) {
3505
+ return true;
3506
+ }
3507
+ }
3508
+ return false;
3509
+ }
3510
+
3511
+ // src/cli/commands/service-plugin-reload.ts
3512
+ async function reloadServicePlugins(params) {
3513
+ const nextWorkspace = getWorkspacePath5(params.nextConfig.agents.defaults.workspace);
3514
+ const nextPluginRegistry = loadPluginRegistry(params.nextConfig, nextWorkspace);
3515
+ const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
3516
+ const nextPluginChannelBindings = getPluginChannelBindings2(nextPluginRegistry);
3517
+ const shouldRestartChannels = shouldRestartChannelsForPluginReload({
3518
+ changedPaths: params.changedPaths,
3519
+ currentPluginChannelBindings: params.pluginChannelBindings,
3520
+ nextPluginChannelBindings,
3521
+ currentExtensionChannels: params.extensionRegistry.channels,
3522
+ nextExtensionChannels: nextExtensionRegistry.channels
3523
+ });
3524
+ logPluginDiagnostics(nextPluginRegistry);
3525
+ let pluginGatewayHandles = params.pluginGatewayHandles;
3526
+ if (shouldRestartChannels) {
3527
+ await stopPluginChannelGateways(pluginGatewayHandles);
3528
+ const startedPluginGateways = await startPluginChannelGateways({
3529
+ registry: nextPluginRegistry,
3530
+ logger: params.pluginGatewayLogger
3531
+ });
3532
+ pluginGatewayHandles = startedPluginGateways.handles;
3533
+ params.logPluginGatewayDiagnostics(startedPluginGateways.diagnostics);
3534
+ }
3535
+ return {
3536
+ pluginRegistry: nextPluginRegistry,
3537
+ extensionRegistry: nextExtensionRegistry,
3538
+ pluginChannelBindings: nextPluginChannelBindings,
3539
+ pluginGatewayHandles,
3540
+ restartChannels: shouldRestartChannels
3541
+ };
3542
+ }
3543
+
3028
3544
  // src/cli/commands/agent-runtime-pool.ts
3029
3545
  import {
3030
3546
  NativeAgentEngine,
@@ -3032,7 +3548,7 @@ import {
3032
3548
  createAssistantStreamDeltaControlMessage,
3033
3549
  createAssistantStreamResetControlMessage,
3034
3550
  AgentRouteResolver,
3035
- getWorkspacePath as getWorkspacePath4,
3551
+ getWorkspacePath as getWorkspacePath6,
3036
3552
  parseAgentScopedSessionKey
3037
3553
  } from "@nextclaw/core";
3038
3554
  function normalizeAgentId(value) {
@@ -3077,7 +3593,7 @@ function resolveAgentProfiles(config2) {
3077
3593
  }
3078
3594
  return Array.from(unique.values()).map((entry) => ({
3079
3595
  id: entry.id,
3080
- workspace: getWorkspacePath4(entry.workspace ?? defaults.workspace),
3596
+ workspace: getWorkspacePath6(entry.workspace ?? defaults.workspace),
3081
3597
  model: entry.model ?? defaults.model,
3082
3598
  engine: normalizeEngineKind(entry.engine ?? defaults.engine),
3083
3599
  engineConfig: entry.engineConfig ?? toRecord(defaults.engineConfig),
@@ -3342,7 +3858,7 @@ var GatewayAgentRuntimePool = class {
3342
3858
  const normalizedAgentId = normalizeAgentId(agentId);
3343
3859
  return this.resolvedProfiles.find((profile) => profile.id === normalizedAgentId) ?? this.resolvedProfiles.find((profile) => profile.id === this.defaultAgentId) ?? this.resolvedProfiles[0] ?? {
3344
3860
  id: this.defaultAgentId,
3345
- workspace: getWorkspacePath4(this.options.config.agents.defaults.workspace),
3861
+ workspace: getWorkspacePath6(this.options.config.agents.defaults.workspace),
3346
3862
  model: this.options.config.agents.defaults.model,
3347
3863
  maxIterations: this.options.config.agents.defaults.maxToolIterations,
3348
3864
  contextTokens: this.options.config.agents.defaults.contextTokens,
@@ -3455,72 +3971,1400 @@ var GatewayAgentRuntimePool = class {
3455
3971
  engine
3456
3972
  });
3457
3973
  }
3458
- this.runtimes = nextRuntimes;
3459
- this.dynamicEngineRuntimes.clear();
3974
+ this.runtimes = nextRuntimes;
3975
+ this.dynamicEngineRuntimes.clear();
3976
+ }
3977
+ };
3978
+ function parseCommandArgsFromText(commandName, rawTail, specs) {
3979
+ if (!rawTail) {
3980
+ return {};
3981
+ }
3982
+ const command = specs.find((item) => item.name.trim().toLowerCase() === commandName);
3983
+ const options = command?.options;
3984
+ if (!options || options.length === 0) {
3985
+ return {};
3986
+ }
3987
+ const tokens = rawTail.split(/\s+/).filter(Boolean);
3988
+ const args = {};
3989
+ let cursor = 0;
3990
+ for (let i = 0; i < options.length; i += 1) {
3991
+ if (cursor >= tokens.length) {
3992
+ break;
3993
+ }
3994
+ const option = options[i];
3995
+ const isLastOption = i === options.length - 1;
3996
+ const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
3997
+ cursor += isLastOption ? tokens.length - cursor : 1;
3998
+ const parsedValue = parseCommandOptionValue(option.type, rawValue);
3999
+ if (parsedValue !== void 0) {
4000
+ args[option.name] = parsedValue;
4001
+ }
4002
+ }
4003
+ return args;
4004
+ }
4005
+ function parseCommandOptionValue(type, rawValue) {
4006
+ const value = rawValue.trim();
4007
+ if (!value) {
4008
+ return void 0;
4009
+ }
4010
+ if (type === "number") {
4011
+ const parsed = Number(value);
4012
+ return Number.isFinite(parsed) ? parsed : void 0;
4013
+ }
4014
+ if (type === "boolean") {
4015
+ const lowered = value.toLowerCase();
4016
+ if (["1", "true", "yes", "on"].includes(lowered)) {
4017
+ return true;
4018
+ }
4019
+ if (["0", "false", "no", "off"].includes(lowered)) {
4020
+ return false;
4021
+ }
4022
+ return void 0;
4023
+ }
4024
+ return value;
4025
+ }
4026
+ function formatUserFacingError(error, maxChars = 320) {
4027
+ const raw = error instanceof Error ? error.message || error.name || "Unknown error" : String(error ?? "Unknown error");
4028
+ const normalized = raw.replace(/\s+/g, " ").trim();
4029
+ if (!normalized) {
4030
+ return "Unknown error";
4031
+ }
4032
+ if (normalized.length <= maxChars) {
4033
+ return normalized;
4034
+ }
4035
+ return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
4036
+ }
4037
+
4038
+ // src/cli/commands/cli-subcommand-launch.ts
4039
+ import { createRequire } from "module";
4040
+ import { extname, resolve as resolve9 } from "path";
4041
+ import { fileURLToPath as fileURLToPath2 } from "url";
4042
+ var require2 = createRequire(import.meta.url);
4043
+ var resolveCliSubcommandEntry = (params) => {
4044
+ const argvEntry = params.argvEntry?.trim();
4045
+ if (argvEntry) {
4046
+ return resolve9(argvEntry);
4047
+ }
4048
+ return fileURLToPath2(new URL("../index.js", params.importMetaUrl));
4049
+ };
4050
+
4051
+ // src/cli/commands/ncp/create-ui-ncp-agent.ts
4052
+ import {
4053
+ DisposableStore
4054
+ } from "@nextclaw/core";
4055
+ import { DefaultNcpAgentRuntime } from "@nextclaw/ncp-agent-runtime";
4056
+ import {
4057
+ readAssistantReasoningNormalizationMode,
4058
+ readAssistantReasoningNormalizationModeFromMetadata,
4059
+ writeAssistantReasoningNormalizationModeToMetadata
4060
+ } from "@nextclaw/ncp";
4061
+ import { createAgentClientFromServer, DefaultNcpAgentBackend } from "@nextclaw/ncp-toolkit";
4062
+
4063
+ // src/cli/commands/ncp/nextclaw-ncp-context-builder.ts
4064
+ import {
4065
+ ContextBuilder,
4066
+ InputBudgetPruner,
4067
+ getWorkspacePath as getWorkspacePath7,
4068
+ parseThinkingLevel,
4069
+ resolveThinkingLevel
4070
+ } from "@nextclaw/core";
4071
+
4072
+ // src/cli/commands/ncp/nextclaw-ncp-message-bridge.ts
4073
+ import {
4074
+ sanitizeAssistantReplyTags
4075
+ } from "@nextclaw/ncp";
4076
+ function normalizeString(value) {
4077
+ if (typeof value !== "string") {
4078
+ return null;
4079
+ }
4080
+ const trimmed = value.trim();
4081
+ return trimmed.length > 0 ? trimmed : null;
4082
+ }
4083
+ function isRecord3(value) {
4084
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4085
+ }
4086
+ function cloneMetadata(value) {
4087
+ return isRecord3(value) ? structuredClone(value) : void 0;
4088
+ }
4089
+ function readStringArray(value) {
4090
+ if (!Array.isArray(value)) {
4091
+ return null;
4092
+ }
4093
+ const deduped = /* @__PURE__ */ new Set();
4094
+ for (const item of value) {
4095
+ const normalized = normalizeString(item);
4096
+ if (normalized) {
4097
+ deduped.add(normalized);
4098
+ }
4099
+ }
4100
+ return [...deduped];
4101
+ }
4102
+ function mergeSessionMetadata(currentMetadata, inputMetadata) {
4103
+ if (!inputMetadata) {
4104
+ return currentMetadata;
4105
+ }
4106
+ const nextMetadata = {
4107
+ ...currentMetadata,
4108
+ ...structuredClone(inputMetadata)
4109
+ };
4110
+ const model = normalizeString(inputMetadata.model) ?? normalizeString(inputMetadata.preferred_model) ?? normalizeString(inputMetadata.preferredModel);
4111
+ if (model) {
4112
+ nextMetadata.model = model;
4113
+ nextMetadata.preferred_model = model;
4114
+ }
4115
+ const thinking = normalizeString(inputMetadata.thinking) ?? normalizeString(inputMetadata.preferred_thinking) ?? normalizeString(inputMetadata.thinking_level) ?? normalizeString(inputMetadata.thinkingLevel);
4116
+ if (thinking) {
4117
+ nextMetadata.thinking = thinking;
4118
+ nextMetadata.preferred_thinking = thinking;
4119
+ }
4120
+ const sessionType = normalizeString(inputMetadata.session_type) ?? normalizeString(inputMetadata.sessionType);
4121
+ if (sessionType) {
4122
+ nextMetadata.session_type = sessionType;
4123
+ }
4124
+ const label = normalizeString(inputMetadata.label) ?? normalizeString(inputMetadata.session_label);
4125
+ if (label) {
4126
+ nextMetadata.label = label;
4127
+ }
4128
+ const requestedSkills = readStringArray(inputMetadata.requested_skills) ?? readStringArray(inputMetadata.requestedSkills);
4129
+ if (requestedSkills) {
4130
+ nextMetadata.requested_skills = requestedSkills;
4131
+ }
4132
+ return nextMetadata;
4133
+ }
4134
+ function extractMessageMetadata(messages) {
4135
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
4136
+ const message = messages[index];
4137
+ if (message?.role !== "user") {
4138
+ continue;
4139
+ }
4140
+ const metadata = cloneMetadata(message.metadata);
4141
+ if (metadata) {
4142
+ return metadata;
4143
+ }
4144
+ }
4145
+ return void 0;
4146
+ }
4147
+ function ensureIsoTimestamp(value, fallback) {
4148
+ if (typeof value !== "string") {
4149
+ return fallback;
4150
+ }
4151
+ const timestamp = Date.parse(value);
4152
+ if (!Number.isFinite(timestamp)) {
4153
+ return fallback;
4154
+ }
4155
+ return new Date(timestamp).toISOString();
4156
+ }
4157
+ function serializeToolArgs(args) {
4158
+ if (typeof args === "string") {
4159
+ return args;
4160
+ }
4161
+ return JSON.stringify(args ?? {});
4162
+ }
4163
+ function serializeLegacyContent(parts) {
4164
+ const text = parts.filter((part) => part.type === "text").map((part) => part.text).join("");
4165
+ if (text.length > 0) {
4166
+ return text;
4167
+ }
4168
+ if (parts.length === 0) {
4169
+ return "";
4170
+ }
4171
+ return structuredClone(parts);
4172
+ }
4173
+ function extractTextFromNcpMessage(message) {
4174
+ if (!message) {
4175
+ return "";
4176
+ }
4177
+ const normalizedMessage = message.role === "assistant" ? sanitizeAssistantReplyTags(message) : message;
4178
+ return normalizedMessage.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
4179
+ }
4180
+ function toLegacyMessages(messages) {
4181
+ const legacyMessages = [];
4182
+ for (const rawMessage of messages) {
4183
+ const message = rawMessage.role === "assistant" ? sanitizeAssistantReplyTags(rawMessage) : rawMessage;
4184
+ const timestamp = ensureIsoTimestamp(message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
4185
+ if (message.role === "assistant") {
4186
+ const textContent = extractTextFromNcpMessage(message);
4187
+ const reasoningContent = message.parts.filter((part) => part.type === "reasoning").map((part) => part.text).join("");
4188
+ const toolInvocations = message.parts.filter(
4189
+ (part) => part.type === "tool-invocation"
4190
+ );
4191
+ const assistantMessage = {
4192
+ role: "assistant",
4193
+ content: textContent,
4194
+ timestamp,
4195
+ ncp_message_id: message.id,
4196
+ ncp_parts: structuredClone(message.parts)
4197
+ };
4198
+ if (typeof message.metadata?.reply_to === "string" && message.metadata.reply_to.trim().length > 0) {
4199
+ assistantMessage.reply_to = message.metadata.reply_to.trim();
4200
+ }
4201
+ if (reasoningContent.length > 0) {
4202
+ assistantMessage.reasoning_content = reasoningContent;
4203
+ }
4204
+ if (toolInvocations.length > 0) {
4205
+ assistantMessage.tool_calls = toolInvocations.map((toolInvocation, index) => ({
4206
+ id: toolInvocation.toolCallId ?? `${message.id}:tool:${index}`,
4207
+ type: "function",
4208
+ function: {
4209
+ name: toolInvocation.toolName,
4210
+ arguments: serializeToolArgs(toolInvocation.args)
4211
+ }
4212
+ }));
4213
+ }
4214
+ legacyMessages.push(assistantMessage);
4215
+ for (const toolInvocation of toolInvocations) {
4216
+ if (toolInvocation.state !== "result") {
4217
+ continue;
4218
+ }
4219
+ legacyMessages.push({
4220
+ role: "tool",
4221
+ name: toolInvocation.toolName,
4222
+ tool_call_id: toolInvocation.toolCallId,
4223
+ content: typeof toolInvocation.result === "string" ? toolInvocation.result : JSON.stringify(toolInvocation.result ?? null),
4224
+ timestamp,
4225
+ ncp_message_id: message.id
4226
+ });
4227
+ }
4228
+ continue;
4229
+ }
4230
+ legacyMessages.push({
4231
+ role: message.role,
4232
+ content: serializeLegacyContent(message.parts),
4233
+ timestamp,
4234
+ ncp_message_id: message.id
4235
+ });
4236
+ }
4237
+ return legacyMessages;
4238
+ }
4239
+
4240
+ // src/cli/commands/ncp/nextclaw-ncp-tool-registry.ts
4241
+ import {
4242
+ CronTool,
4243
+ EditFileTool,
4244
+ ExecTool,
4245
+ ExtensionToolAdapter,
4246
+ GatewayTool,
4247
+ ListDirTool,
4248
+ MemoryGetTool,
4249
+ MemorySearchTool,
4250
+ MessageTool,
4251
+ ReadFileTool,
4252
+ SessionsHistoryTool,
4253
+ SessionsListTool,
4254
+ SessionsSendTool,
4255
+ SpawnTool,
4256
+ SubagentManager,
4257
+ SubagentsTool,
4258
+ ToolRegistry,
4259
+ WebFetchTool,
4260
+ WebSearchTool,
4261
+ WriteFileTool
4262
+ } from "@nextclaw/core";
4263
+ function toToolParams(args) {
4264
+ if (isRecord3(args)) {
4265
+ return args;
4266
+ }
4267
+ if (typeof args === "string") {
4268
+ try {
4269
+ const parsed = JSON.parse(args);
4270
+ return isRecord3(parsed) ? parsed : {};
4271
+ } catch {
4272
+ return {};
4273
+ }
4274
+ }
4275
+ return {};
4276
+ }
4277
+ function readMetadataAccountId(metadata, sessionMetadata) {
4278
+ const candidates = [
4279
+ metadata.accountId,
4280
+ metadata.account_id,
4281
+ sessionMetadata.last_account_id
4282
+ ];
4283
+ for (const candidate of candidates) {
4284
+ const normalized = normalizeString(candidate);
4285
+ if (normalized) {
4286
+ return normalized;
4287
+ }
4288
+ }
4289
+ return void 0;
4290
+ }
4291
+ var CoreToolNcpAdapter = class {
4292
+ constructor(tool, executeTool) {
4293
+ this.tool = tool;
4294
+ this.executeTool = executeTool;
4295
+ }
4296
+ get name() {
4297
+ return this.tool.name;
4298
+ }
4299
+ get description() {
4300
+ return this.tool.description;
4301
+ }
4302
+ get parameters() {
4303
+ return this.tool.parameters;
4304
+ }
4305
+ async execute(args) {
4306
+ return this.executeTool(this.tool.name, args);
4307
+ }
4308
+ };
4309
+ var NextclawNcpToolRegistry = class {
4310
+ constructor(options) {
4311
+ this.options = options;
4312
+ const initialConfig = this.options.getConfig();
4313
+ this.subagents = new SubagentManager({
4314
+ providerManager: this.options.providerManager,
4315
+ workspace: initialConfig.agents.defaults.workspace,
4316
+ bus: this.options.bus,
4317
+ model: initialConfig.agents.defaults.model,
4318
+ contextTokens: initialConfig.agents.defaults.contextTokens,
4319
+ searchConfig: initialConfig.search,
4320
+ execConfig: initialConfig.tools.exec,
4321
+ restrictToWorkspace: initialConfig.tools.restrictToWorkspace
4322
+ });
4323
+ }
4324
+ subagents;
4325
+ registry = new ToolRegistry();
4326
+ tools = /* @__PURE__ */ new Map();
4327
+ currentExtensionToolContext = {};
4328
+ prepareForRun(context) {
4329
+ this.subagents.updateRuntimeOptions({
4330
+ model: context.model,
4331
+ maxTokens: context.maxTokens,
4332
+ contextTokens: context.contextTokens,
4333
+ searchConfig: context.searchConfig,
4334
+ execConfig: { timeout: context.execTimeoutSeconds },
4335
+ restrictToWorkspace: context.restrictToWorkspace
4336
+ });
4337
+ this.currentExtensionToolContext = {
4338
+ config: context.config,
4339
+ workspaceDir: context.workspace,
4340
+ sessionKey: context.sessionId,
4341
+ channel: context.channel,
4342
+ chatId: context.chatId,
4343
+ sandboxed: context.restrictToWorkspace
4344
+ };
4345
+ this.registry = new ToolRegistry();
4346
+ this.tools.clear();
4347
+ this.registerDefaultTools(context);
4348
+ this.registerExtensionTools(context);
4349
+ }
4350
+ listTools() {
4351
+ return [...this.tools.values()];
4352
+ }
4353
+ getTool(name) {
4354
+ return this.tools.get(name);
4355
+ }
4356
+ getToolDefinitions() {
4357
+ return this.listTools().map((tool) => ({
4358
+ name: tool.name,
4359
+ description: tool.description,
4360
+ parameters: tool.parameters
4361
+ }));
4362
+ }
4363
+ async execute(toolCallId, toolName, args) {
4364
+ return this.registry.execute(toolName, toToolParams(args), toolCallId);
4365
+ }
4366
+ registerDefaultTools(context) {
4367
+ const allowedDir = context.restrictToWorkspace ? context.workspace : void 0;
4368
+ this.registerTool(new ReadFileTool(allowedDir));
4369
+ this.registerTool(new WriteFileTool(allowedDir));
4370
+ this.registerTool(new EditFileTool(allowedDir));
4371
+ this.registerTool(new ListDirTool(allowedDir));
4372
+ const execTool = new ExecTool({
4373
+ workingDir: context.workspace,
4374
+ timeout: context.execTimeoutSeconds,
4375
+ restrictToWorkspace: context.restrictToWorkspace
4376
+ });
4377
+ execTool.setContext({
4378
+ sessionKey: context.sessionId,
4379
+ channel: context.channel,
4380
+ chatId: context.chatId
4381
+ });
4382
+ this.registerTool(execTool);
4383
+ this.registerTool(new WebSearchTool(context.searchConfig));
4384
+ this.registerTool(new WebFetchTool());
4385
+ const messageTool = new MessageTool((message) => this.options.bus.publishOutbound(message));
4386
+ messageTool.setContext(context.channel, context.chatId);
4387
+ this.registerTool(messageTool);
4388
+ const spawnTool = new SpawnTool(this.subagents);
4389
+ spawnTool.setContext(
4390
+ context.channel,
4391
+ context.chatId,
4392
+ context.model,
4393
+ context.sessionId,
4394
+ context.agentId
4395
+ );
4396
+ this.registerTool(spawnTool);
4397
+ this.registerTool(new SessionsListTool(this.options.sessionManager));
4398
+ this.registerTool(new SessionsHistoryTool(this.options.sessionManager));
4399
+ const sessionsSendTool = new SessionsSendTool(this.options.sessionManager, this.options.bus);
4400
+ sessionsSendTool.setContext({
4401
+ currentSessionKey: context.sessionId,
4402
+ currentAgentId: context.agentId,
4403
+ channel: context.channel,
4404
+ chatId: context.chatId,
4405
+ maxPingPongTurns: context.config.session?.agentToAgent?.maxPingPongTurns ?? 0,
4406
+ currentHandoffDepth: context.handoffDepth
4407
+ });
4408
+ this.registerTool(sessionsSendTool);
4409
+ this.registerTool(new MemorySearchTool(context.workspace));
4410
+ this.registerTool(new MemoryGetTool(context.workspace));
4411
+ this.registerTool(new SubagentsTool(this.subagents));
4412
+ const gatewayTool = new GatewayTool(this.options.gatewayController);
4413
+ gatewayTool.setContext({ sessionKey: context.sessionId });
4414
+ this.registerTool(gatewayTool);
4415
+ if (this.options.cronService) {
4416
+ const cronTool = new CronTool(this.options.cronService);
4417
+ cronTool.setContext(context.channel, context.chatId);
4418
+ this.registerTool(cronTool);
4419
+ }
4420
+ }
4421
+ registerExtensionTools(context) {
4422
+ const extensionRegistry = this.options.getExtensionRegistry?.();
4423
+ if (!extensionRegistry || extensionRegistry.tools.length === 0) {
4424
+ return;
4425
+ }
4426
+ const seen = new Set(this.registry.toolNames);
4427
+ for (const registration of extensionRegistry.tools) {
4428
+ for (const alias of registration.names) {
4429
+ if (seen.has(alias)) {
4430
+ continue;
4431
+ }
4432
+ seen.add(alias);
4433
+ this.registerTool(
4434
+ new ExtensionToolAdapter({
4435
+ registration,
4436
+ alias,
4437
+ config: context.config,
4438
+ workspaceDir: context.workspace,
4439
+ contextProvider: () => this.currentExtensionToolContext,
4440
+ diagnostics: extensionRegistry.diagnostics
4441
+ })
4442
+ );
4443
+ }
4444
+ }
4445
+ }
4446
+ registerTool(tool) {
4447
+ this.registry.register(tool);
4448
+ this.tools.set(
4449
+ tool.name,
4450
+ new CoreToolNcpAdapter(tool, async (toolName, args) => this.registry.execute(toolName, toToolParams(args)))
4451
+ );
4452
+ }
4453
+ };
4454
+ function resolveAgentHandoffDepth(metadata) {
4455
+ const rawDepth = Number(metadata.agent_handoff_depth ?? 0);
4456
+ if (!Number.isFinite(rawDepth) || rawDepth < 0) {
4457
+ return 0;
4458
+ }
4459
+ return Math.trunc(rawDepth);
4460
+ }
4461
+ function readAccountIdForHints(metadata, sessionMetadata) {
4462
+ return readMetadataAccountId(metadata, sessionMetadata);
4463
+ }
4464
+
4465
+ // src/cli/commands/ncp/nextclaw-ncp-context-builder.ts
4466
+ var TIME_HINT_TRIGGER_PATTERNS = [
4467
+ /\b(now|right now|current time|what time|today|tonight|tomorrow|yesterday|this morning|this afternoon|this evening|date)\b/i,
4468
+ /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/
4469
+ ];
4470
+ function isRecord4(value) {
4471
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4472
+ }
4473
+ function mergeInputMetadata(input) {
4474
+ const messageMetadata = input.messages.slice().reverse().find((message) => isRecord4(message.metadata))?.metadata;
4475
+ return {
4476
+ ...isRecord4(messageMetadata) ? structuredClone(messageMetadata) : {},
4477
+ ...isRecord4(input.metadata) ? structuredClone(input.metadata) : {}
4478
+ };
4479
+ }
4480
+ function resolveRequestedSkillNames(metadata) {
4481
+ const rawValue = metadata.requested_skills ?? metadata.requestedSkills;
4482
+ const values = [];
4483
+ if (Array.isArray(rawValue)) {
4484
+ for (const item of rawValue) {
4485
+ const normalized = normalizeString(item);
4486
+ if (normalized) {
4487
+ values.push(normalized);
4488
+ }
4489
+ }
4490
+ } else if (typeof rawValue === "string") {
4491
+ values.push(
4492
+ ...rawValue.split(/[,\s]+/g).map((item) => item.trim()).filter(Boolean)
4493
+ );
4494
+ }
4495
+ return Array.from(new Set(values)).slice(0, 8);
4496
+ }
4497
+ function resolveRequestedToolNames(metadata) {
4498
+ const rawValue = metadata.requested_tools ?? metadata.requestedTools;
4499
+ if (!Array.isArray(rawValue)) {
4500
+ return [];
4501
+ }
4502
+ return Array.from(
4503
+ new Set(
4504
+ rawValue.map((item) => normalizeString(item)).filter((item) => Boolean(item))
4505
+ )
4506
+ );
4507
+ }
4508
+ function normalizeOptionalString2(value) {
4509
+ return normalizeString(value) ?? void 0;
4510
+ }
4511
+ function readMetadataModel(metadata) {
4512
+ const candidates = [metadata.model, metadata.llm_model, metadata.agent_model, metadata.session_model];
4513
+ for (const candidate of candidates) {
4514
+ const normalized = normalizeString(candidate);
4515
+ if (normalized) {
4516
+ return normalized;
4517
+ }
4518
+ }
4519
+ return null;
4520
+ }
4521
+ function readMetadataThinking(metadata) {
4522
+ const candidates = [
4523
+ metadata.thinking,
4524
+ metadata.thinking_level,
4525
+ metadata.thinkingLevel,
4526
+ metadata.thinking_effort,
4527
+ metadata.thinkingEffort
4528
+ ];
4529
+ for (const candidate of candidates) {
4530
+ if (typeof candidate !== "string") {
4531
+ continue;
4532
+ }
4533
+ const normalized = candidate.trim().toLowerCase();
4534
+ if (!normalized) {
4535
+ continue;
4536
+ }
4537
+ if (normalized === "clear" || normalized === "reset" || normalized === "off!") {
4538
+ return "__clear__";
4539
+ }
4540
+ const level = parseThinkingLevel(normalized);
4541
+ if (level) {
4542
+ return level;
4543
+ }
4544
+ }
4545
+ return null;
4546
+ }
4547
+ function resolvePrimaryAgentProfile(config2) {
4548
+ const configuredDefaultAgentId = config2.agents.list.find((entry) => entry.default)?.id?.trim() || config2.agents.list[0]?.id?.trim() || "main";
4549
+ const profile = config2.agents.list.find((entry) => entry.id.trim() === configuredDefaultAgentId);
4550
+ return {
4551
+ agentId: configuredDefaultAgentId,
4552
+ workspace: getWorkspacePath7(profile?.workspace ?? config2.agents.defaults.workspace),
4553
+ model: profile?.model ?? config2.agents.defaults.model,
4554
+ maxIterations: profile?.maxToolIterations ?? config2.agents.defaults.maxToolIterations,
4555
+ contextTokens: profile?.contextTokens ?? config2.agents.defaults.contextTokens,
4556
+ restrictToWorkspace: config2.tools.restrictToWorkspace,
4557
+ searchConfig: config2.search,
4558
+ execTimeoutSeconds: config2.tools.exec.timeout
4559
+ };
4560
+ }
4561
+ function shouldAppendTimeHint(content) {
4562
+ const normalized = content.trim();
4563
+ if (!normalized) {
4564
+ return false;
4565
+ }
4566
+ return TIME_HINT_TRIGGER_PATTERNS.some((pattern) => pattern.test(normalized));
4567
+ }
4568
+ function buildMinutePrecisionTimeHint(date) {
4569
+ const year = date.getFullYear();
4570
+ const month = String(date.getMonth() + 1).padStart(2, "0");
4571
+ const day = String(date.getDate()).padStart(2, "0");
4572
+ const hour = String(date.getHours()).padStart(2, "0");
4573
+ const minute = String(date.getMinutes()).padStart(2, "0");
4574
+ const offsetMinutes = -date.getTimezoneOffset();
4575
+ const sign = offsetMinutes >= 0 ? "+" : "-";
4576
+ const absMinutes = Math.abs(offsetMinutes);
4577
+ const offsetHour = String(Math.floor(absMinutes / 60)).padStart(2, "0");
4578
+ const offsetMinute = String(absMinutes % 60).padStart(2, "0");
4579
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
4580
+ return `${year}-${month}-${day} ${hour}:${minute} ${sign}${offsetHour}:${offsetMinute} (${timezone})`;
4581
+ }
4582
+ function appendTimeHintForPrompt(content, timestamp) {
4583
+ if (!shouldAppendTimeHint(content)) {
4584
+ return content;
4585
+ }
4586
+ const date = Number.isNaN(timestamp.getTime()) ? /* @__PURE__ */ new Date() : timestamp;
4587
+ return `${content}
4588
+
4589
+ [time_hint_local_minute] ${buildMinutePrecisionTimeHint(date)}`;
4590
+ }
4591
+ function prependRequestedSkills(content, requestedSkillNames) {
4592
+ if (requestedSkillNames.length === 0) {
4593
+ return content;
4594
+ }
4595
+ return `[Requested skills for this turn: ${requestedSkillNames.join(", ")}]
4596
+
4597
+ ${content}`;
4598
+ }
4599
+ function filterTools(toolDefinitions, requestedToolNames) {
4600
+ if (toolDefinitions.length === 0) {
4601
+ return void 0;
4602
+ }
4603
+ if (requestedToolNames.length === 0) {
4604
+ return [...toolDefinitions];
4605
+ }
4606
+ const requested = new Set(requestedToolNames);
4607
+ const filtered = toolDefinitions.filter((tool) => requested.has(tool.function.name));
4608
+ return filtered.length > 0 ? filtered : void 0;
4609
+ }
4610
+ var NextclawNcpContextBuilder = class {
4611
+ constructor(options) {
4612
+ this.options = options;
4613
+ }
4614
+ inputBudgetPruner = new InputBudgetPruner();
4615
+ prepare(input, _options) {
4616
+ const config2 = this.options.getConfig();
4617
+ const profile = resolvePrimaryAgentProfile(config2);
4618
+ const requestMetadata = mergeInputMetadata(input);
4619
+ const session = this.options.sessionManager.getOrCreate(input.sessionId);
4620
+ const clearModel = requestMetadata.clear_model === true || requestMetadata.reset_model === true;
4621
+ if (clearModel) {
4622
+ delete session.metadata.preferred_model;
4623
+ }
4624
+ const inboundModel = readMetadataModel(requestMetadata);
4625
+ if (inboundModel) {
4626
+ session.metadata.preferred_model = inboundModel;
4627
+ }
4628
+ const effectiveModel = normalizeOptionalString2(session.metadata.preferred_model) ?? profile.model;
4629
+ const clearThinking = requestMetadata.clear_thinking === true || requestMetadata.reset_thinking === true;
4630
+ if (clearThinking) {
4631
+ delete session.metadata.preferred_thinking;
4632
+ }
4633
+ const inboundThinking = readMetadataThinking(requestMetadata);
4634
+ if (inboundThinking === "__clear__") {
4635
+ delete session.metadata.preferred_thinking;
4636
+ } else if (inboundThinking) {
4637
+ session.metadata.preferred_thinking = inboundThinking;
4638
+ }
4639
+ const runtimeThinking = resolveThinkingLevel({
4640
+ config: config2,
4641
+ agentId: profile.agentId,
4642
+ model: effectiveModel,
4643
+ sessionThinkingLevel: parseThinkingLevel(session.metadata.preferred_thinking) ?? null
4644
+ });
4645
+ const channel = normalizeOptionalString2(requestMetadata.channel) ?? normalizeOptionalString2(session.metadata.last_channel) ?? "ui";
4646
+ const chatId = normalizeOptionalString2(requestMetadata.chatId) ?? normalizeOptionalString2(requestMetadata.chat_id) ?? normalizeOptionalString2(session.metadata.last_to) ?? "web-ui";
4647
+ session.metadata.last_channel = channel;
4648
+ session.metadata.last_to = chatId;
4649
+ const requestedSkillNames = resolveRequestedSkillNames(requestMetadata);
4650
+ const requestedToolNames = resolveRequestedToolNames(requestMetadata);
4651
+ const currentUserText = extractTextFromNcpMessage(input.messages[input.messages.length - 1]);
4652
+ const currentMessage = appendTimeHintForPrompt(
4653
+ prependRequestedSkills(currentUserText, requestedSkillNames),
4654
+ new Date(
4655
+ ensureIsoTimestamp(
4656
+ input.messages[input.messages.length - 1]?.timestamp,
4657
+ (/* @__PURE__ */ new Date()).toISOString()
4658
+ )
4659
+ )
4660
+ );
4661
+ this.options.toolRegistry.prepareForRun({
4662
+ sessionId: input.sessionId,
4663
+ channel,
4664
+ chatId,
4665
+ agentId: profile.agentId,
4666
+ config: config2,
4667
+ contextTokens: profile.contextTokens,
4668
+ execTimeoutSeconds: profile.execTimeoutSeconds,
4669
+ handoffDepth: resolveAgentHandoffDepth(requestMetadata),
4670
+ maxTokens: void 0,
4671
+ metadata: requestMetadata,
4672
+ model: effectiveModel,
4673
+ restrictToWorkspace: profile.restrictToWorkspace,
4674
+ searchConfig: profile.searchConfig,
4675
+ workspace: profile.workspace
4676
+ });
4677
+ const accountId = readAccountIdForHints(requestMetadata, session.metadata);
4678
+ const messageToolHints = this.options.resolveMessageToolHints?.({
4679
+ sessionKey: input.sessionId,
4680
+ channel,
4681
+ chatId,
4682
+ accountId: accountId ?? null
4683
+ });
4684
+ const contextBuilder = new ContextBuilder(profile.workspace, config2.agents.context);
4685
+ const sessionMessages = _options?.sessionMessages ?? [];
4686
+ const messages = contextBuilder.buildMessages({
4687
+ history: toLegacyMessages([...sessionMessages]),
4688
+ currentMessage,
4689
+ channel,
4690
+ chatId,
4691
+ sessionKey: input.sessionId,
4692
+ thinkingLevel: runtimeThinking,
4693
+ skillNames: requestedSkillNames,
4694
+ messageToolHints
4695
+ });
4696
+ const pruned = this.inputBudgetPruner.prune({
4697
+ messages,
4698
+ contextTokens: profile.contextTokens
4699
+ });
4700
+ const toolDefinitions = this.options.toolRegistry.getToolDefinitions().map((tool) => ({
4701
+ type: "function",
4702
+ function: {
4703
+ name: tool.name,
4704
+ description: tool.description,
4705
+ parameters: tool.parameters
4706
+ }
4707
+ }));
4708
+ return {
4709
+ messages: pruned.messages,
4710
+ tools: filterTools(toolDefinitions, requestedToolNames),
4711
+ model: effectiveModel,
4712
+ thinkingLevel: runtimeThinking
4713
+ };
4714
+ }
4715
+ };
4716
+
4717
+ // src/cli/commands/ncp/nextclaw-agent-session-store.ts
4718
+ import { sanitizeAssistantReplyTags as sanitizeAssistantReplyTags2 } from "@nextclaw/ncp";
4719
+ function tryParseJson(value) {
4720
+ try {
4721
+ return JSON.parse(value);
4722
+ } catch {
4723
+ return value;
4724
+ }
4725
+ }
4726
+ function toTextPart(text) {
4727
+ return text.length > 0 ? { type: "text", text } : null;
4728
+ }
4729
+ function contentToParts(content) {
4730
+ if (typeof content === "string") {
4731
+ const textPart = toTextPart(content);
4732
+ return textPart ? [textPart] : [];
4733
+ }
4734
+ if (Array.isArray(content)) {
4735
+ return content.length > 0 ? [
4736
+ {
4737
+ type: "extension",
4738
+ extensionType: "nextclaw.legacy.content-array",
4739
+ data: structuredClone(content)
4740
+ }
4741
+ ] : [];
4742
+ }
4743
+ if (content && typeof content === "object") {
4744
+ return [
4745
+ {
4746
+ type: "extension",
4747
+ extensionType: "nextclaw.legacy.content-object",
4748
+ data: structuredClone(content)
4749
+ }
4750
+ ];
4751
+ }
4752
+ return [];
4753
+ }
4754
+ function parseLegacyToolCalls(value) {
4755
+ if (!Array.isArray(value)) {
4756
+ return [];
4757
+ }
4758
+ return value.map((entry) => {
4759
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
4760
+ return null;
4761
+ }
4762
+ const toolCall = entry;
4763
+ const id = normalizeString(toolCall.id);
4764
+ const rawFunction = toolCall.function;
4765
+ if (!id || !rawFunction || typeof rawFunction !== "object" || Array.isArray(rawFunction)) {
4766
+ return null;
4767
+ }
4768
+ const fn = rawFunction;
4769
+ const name = normalizeString(fn.name);
4770
+ const args = typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {});
4771
+ if (!name) {
4772
+ return null;
4773
+ }
4774
+ return {
4775
+ id,
4776
+ type: "function",
4777
+ function: {
4778
+ name,
4779
+ arguments: args
4780
+ }
4781
+ };
4782
+ }).filter((entry) => entry !== null);
4783
+ }
4784
+ function createMessageId(sessionId, index, role, timestamp) {
4785
+ const safeRole = role.trim().toLowerCase() || "message";
4786
+ return `${sessionId}:${safeRole}:${index}:${timestamp}`;
4787
+ }
4788
+ function isNcpMessagePart(value) {
4789
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.type === "string";
4790
+ }
4791
+ function readStoredNcpParts(message) {
4792
+ const rawParts = message.ncp_parts;
4793
+ if (!Array.isArray(rawParts)) {
4794
+ return null;
4795
+ }
4796
+ const parts = rawParts.filter((part) => isNcpMessagePart(part));
4797
+ if (parts.length !== rawParts.length) {
4798
+ return null;
4799
+ }
4800
+ return structuredClone(parts);
4801
+ }
4802
+ function buildAssistantMessage(params) {
4803
+ const timestamp = ensureIsoTimestamp(params.message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
4804
+ const replyTo = typeof params.message.reply_to === "string" && params.message.reply_to.trim().length > 0 ? params.message.reply_to.trim() : void 0;
4805
+ const storedParts = readStoredNcpParts(params.message);
4806
+ if (storedParts) {
4807
+ return sanitizeAssistantReplyTags2({
4808
+ id: createMessageId(params.sessionId, params.index, "assistant", timestamp),
4809
+ sessionId: params.sessionId,
4810
+ role: "assistant",
4811
+ status: "final",
4812
+ timestamp,
4813
+ parts: storedParts,
4814
+ metadata: replyTo ? { reply_to: replyTo } : void 0
4815
+ });
4816
+ }
4817
+ const toolCalls = parseLegacyToolCalls(params.message.tool_calls);
4818
+ const parts = [...contentToParts(params.message.content)];
4819
+ const reasoning = normalizeString(params.message.reasoning_content);
4820
+ if (reasoning) {
4821
+ parts.push({
4822
+ type: "reasoning",
4823
+ text: reasoning
4824
+ });
4825
+ }
4826
+ for (const toolCall of toolCalls) {
4827
+ parts.push({
4828
+ type: "tool-invocation",
4829
+ toolCallId: toolCall.id,
4830
+ toolName: toolCall.function.name,
4831
+ state: "call",
4832
+ args: tryParseJson(toolCall.function.arguments)
4833
+ });
4834
+ }
4835
+ return sanitizeAssistantReplyTags2({
4836
+ id: createMessageId(params.sessionId, params.index, "assistant", timestamp),
4837
+ sessionId: params.sessionId,
4838
+ role: "assistant",
4839
+ status: "final",
4840
+ timestamp,
4841
+ parts,
4842
+ metadata: replyTo ? { reply_to: replyTo } : void 0
4843
+ });
4844
+ }
4845
+ function buildGenericMessage(params) {
4846
+ const timestamp = ensureIsoTimestamp(params.message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
4847
+ return {
4848
+ id: createMessageId(params.sessionId, params.index, params.role, timestamp),
4849
+ sessionId: params.sessionId,
4850
+ role: params.role,
4851
+ status: "final",
4852
+ timestamp,
4853
+ parts: contentToParts(params.message.content)
4854
+ };
4855
+ }
4856
+ function attachToolResult(target, toolCallId, result, toolName) {
4857
+ target.parts = target.parts.map((part) => {
4858
+ if (part.type !== "tool-invocation" || part.toolCallId !== toolCallId) {
4859
+ return part;
4860
+ }
4861
+ return {
4862
+ ...part,
4863
+ toolName: toolName ?? part.toolName,
4864
+ state: "result",
4865
+ result
4866
+ };
4867
+ });
4868
+ }
4869
+ function toNcpMessages(sessionId, messages) {
4870
+ const ncpMessages = [];
4871
+ const assistantIndexByToolCallId = /* @__PURE__ */ new Map();
4872
+ messages.forEach((message, index) => {
4873
+ const role = normalizeString(message.role)?.toLowerCase() ?? "assistant";
4874
+ if (role === "tool") {
4875
+ const toolCallId = normalizeString(message.tool_call_id);
4876
+ if (toolCallId) {
4877
+ const assistantIndex = assistantIndexByToolCallId.get(toolCallId);
4878
+ if (assistantIndex !== void 0) {
4879
+ attachToolResult(
4880
+ ncpMessages[assistantIndex],
4881
+ toolCallId,
4882
+ structuredClone(message.content),
4883
+ normalizeString(message.name) ?? void 0
4884
+ );
4885
+ return;
4886
+ }
4887
+ }
4888
+ ncpMessages.push(
4889
+ buildGenericMessage({
4890
+ sessionId,
4891
+ index,
4892
+ role: "tool",
4893
+ message
4894
+ })
4895
+ );
4896
+ return;
4897
+ }
4898
+ if (role === "assistant") {
4899
+ const assistant = buildAssistantMessage({ sessionId, index, message });
4900
+ const assistantPosition = ncpMessages.push(assistant) - 1;
4901
+ for (const part of assistant.parts) {
4902
+ if (part.type === "tool-invocation" && part.toolCallId) {
4903
+ assistantIndexByToolCallId.set(part.toolCallId, assistantPosition);
4904
+ }
4905
+ }
4906
+ return;
4907
+ }
4908
+ const normalizedRole = role === "system" || role === "user" || role === "service" ? role : "user";
4909
+ ncpMessages.push(
4910
+ buildGenericMessage({
4911
+ sessionId,
4912
+ index,
4913
+ role: normalizedRole,
4914
+ message
4915
+ })
4916
+ );
4917
+ });
4918
+ return ncpMessages;
4919
+ }
4920
+ function resolveLegacyEventType(message) {
4921
+ const role = normalizeString(message.role)?.toLowerCase() ?? "";
4922
+ if (role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
4923
+ return "assistant.tool_call";
4924
+ }
4925
+ if (role === "tool") {
4926
+ return "tool.result";
4927
+ }
4928
+ if (role === "assistant") {
4929
+ return "message.assistant";
4930
+ }
4931
+ if (role === "user") {
4932
+ return "message.user";
4933
+ }
4934
+ if (role === "system") {
4935
+ return "message.system";
4936
+ }
4937
+ return `message.${role || "other"}`;
4938
+ }
4939
+ var NextclawAgentSessionStore = class {
4940
+ constructor(sessionManager, options = {}) {
4941
+ this.sessionManager = sessionManager;
4942
+ this.options = options;
4943
+ }
4944
+ async getSession(sessionId) {
4945
+ const session = this.sessionManager.getIfExists(sessionId);
4946
+ if (!session) {
4947
+ return null;
4948
+ }
4949
+ return {
4950
+ sessionId,
4951
+ messages: toNcpMessages(sessionId, session.messages),
4952
+ updatedAt: session.updatedAt.toISOString(),
4953
+ metadata: structuredClone(session.metadata)
4954
+ };
4955
+ }
4956
+ async listSessions() {
4957
+ const records = this.sessionManager.listSessions();
4958
+ const sessions = [];
4959
+ for (const record of records) {
4960
+ const sessionId = normalizeString(record.key);
4961
+ if (!sessionId) {
4962
+ continue;
4963
+ }
4964
+ const session = this.sessionManager.getIfExists(sessionId);
4965
+ if (!session) {
4966
+ continue;
4967
+ }
4968
+ sessions.push({
4969
+ sessionId,
4970
+ messages: toNcpMessages(sessionId, session.messages),
4971
+ updatedAt: session.updatedAt.toISOString(),
4972
+ metadata: structuredClone(session.metadata)
4973
+ });
4974
+ }
4975
+ sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
4976
+ return sessions;
4977
+ }
4978
+ async saveSession(sessionRecord) {
4979
+ if (this.options.writeMode === "runtime-owned") {
4980
+ return;
4981
+ }
4982
+ const session = this.sessionManager.getIfExists(sessionRecord.sessionId) ?? this.sessionManager.getOrCreate(sessionRecord.sessionId);
4983
+ const legacyMessages = toLegacyMessages(sessionRecord.messages);
4984
+ const nextMetadata = mergeSessionMetadata(
4985
+ session.metadata,
4986
+ extractMessageMetadata(sessionRecord.messages)
4987
+ );
4988
+ session.metadata = mergeSessionMetadata(nextMetadata, cloneMetadata(sessionRecord.metadata));
4989
+ this.sessionManager.clear(session);
4990
+ for (const message of legacyMessages) {
4991
+ this.sessionManager.appendEvent(session, {
4992
+ type: resolveLegacyEventType(message),
4993
+ timestamp: ensureIsoTimestamp(message.timestamp, (/* @__PURE__ */ new Date()).toISOString()),
4994
+ data: {
4995
+ message
4996
+ }
4997
+ });
4998
+ }
4999
+ if (legacyMessages.length === 0) {
5000
+ session.updatedAt = new Date(ensureIsoTimestamp(sessionRecord.updatedAt, (/* @__PURE__ */ new Date()).toISOString()));
5001
+ }
5002
+ this.sessionManager.save(session);
5003
+ }
5004
+ async deleteSession(sessionId) {
5005
+ const existing = await this.getSession(sessionId);
5006
+ if (!existing) {
5007
+ return null;
5008
+ }
5009
+ this.sessionManager.delete(sessionId);
5010
+ return existing;
5011
+ }
5012
+ };
5013
+
5014
+ // src/cli/commands/ncp/provider-manager-ncp-llm-api.ts
5015
+ import { parseThinkingLevel as parseThinkingLevel2 } from "@nextclaw/core";
5016
+ function normalizeModel(value) {
5017
+ if (typeof value !== "string") {
5018
+ return null;
5019
+ }
5020
+ const trimmed = value.trim();
5021
+ return trimmed.length > 0 ? trimmed : null;
5022
+ }
5023
+ function normalizeThinkingLevel(value) {
5024
+ return parseThinkingLevel2(value);
5025
+ }
5026
+ function normalizeFinishReason(value) {
5027
+ if (typeof value !== "string") {
5028
+ return "stop";
5029
+ }
5030
+ const trimmed = value.trim();
5031
+ return trimmed.length > 0 ? trimmed : "stop";
5032
+ }
5033
+ function toToolCallDelta(toolCall, index) {
5034
+ return {
5035
+ index,
5036
+ id: toolCall.id,
5037
+ type: "function",
5038
+ function: {
5039
+ name: toolCall.name,
5040
+ arguments: JSON.stringify(toolCall.arguments ?? {})
5041
+ }
5042
+ };
5043
+ }
5044
+ function toFinalChunk(response, options) {
5045
+ const delta = {};
5046
+ if (options.includeText && typeof response.content === "string" && response.content.length > 0) {
5047
+ delta.content = response.content;
5048
+ }
5049
+ if (options.includeReasoning && typeof response.reasoningContent === "string" && response.reasoningContent.length > 0) {
5050
+ delta.reasoning_content = response.reasoningContent;
5051
+ }
5052
+ if (options.includeToolCalls && response.toolCalls.length > 0) {
5053
+ delta.tool_calls = response.toolCalls.map((toolCall, index) => toToolCallDelta(toolCall, index));
5054
+ }
5055
+ return {
5056
+ choices: [
5057
+ {
5058
+ delta,
5059
+ finish_reason: normalizeFinishReason(response.finishReason)
5060
+ }
5061
+ ],
5062
+ usage: response.usage
5063
+ };
5064
+ }
5065
+ var ProviderManagerNcpLLMApi = class {
5066
+ constructor(providerManager) {
5067
+ this.providerManager = providerManager;
5068
+ }
5069
+ async *generate(input, options) {
5070
+ const model = normalizeModel(input.model) ?? this.providerManager.get(null).getDefaultModel();
5071
+ const thinkingLevel = normalizeThinkingLevel(input.thinkingLevel);
5072
+ let sawTextDelta = false;
5073
+ let sawReasoningDelta = false;
5074
+ let sawToolCallDelta = false;
5075
+ for await (const event of this.providerManager.chatStream({
5076
+ messages: input.messages,
5077
+ tools: input.tools,
5078
+ model,
5079
+ ...thinkingLevel ? { thinkingLevel } : {},
5080
+ maxTokens: input.max_tokens,
5081
+ signal: options?.signal
5082
+ })) {
5083
+ if (event.type === "delta") {
5084
+ sawTextDelta = true;
5085
+ yield {
5086
+ choices: [
5087
+ {
5088
+ delta: {
5089
+ content: event.delta
5090
+ }
5091
+ }
5092
+ ]
5093
+ };
5094
+ continue;
5095
+ }
5096
+ if (event.type === "reasoning_delta") {
5097
+ sawReasoningDelta = true;
5098
+ yield {
5099
+ choices: [
5100
+ {
5101
+ delta: {
5102
+ reasoning_content: event.delta
5103
+ }
5104
+ }
5105
+ ]
5106
+ };
5107
+ continue;
5108
+ }
5109
+ if (event.type === "tool_call_delta") {
5110
+ sawToolCallDelta = true;
5111
+ yield {
5112
+ choices: [
5113
+ {
5114
+ delta: {
5115
+ tool_calls: event.toolCalls
5116
+ }
5117
+ }
5118
+ ]
5119
+ };
5120
+ continue;
5121
+ }
5122
+ yield toFinalChunk(event.response, {
5123
+ includeText: !sawTextDelta,
5124
+ includeReasoning: !sawReasoningDelta,
5125
+ includeToolCalls: !sawToolCallDelta
5126
+ });
5127
+ }
3460
5128
  }
3461
5129
  };
3462
- function parseCommandArgsFromText(commandName, rawTail, specs) {
3463
- if (!rawTail) {
3464
- return {};
5130
+
5131
+ // src/cli/commands/ncp/ui-ncp-runtime-registry.ts
5132
+ import { toDisposable } from "@nextclaw/core";
5133
+ var DEFAULT_UI_NCP_RUNTIME_KIND = "native";
5134
+ function normalizeRuntimeKind(value) {
5135
+ if (typeof value !== "string") {
5136
+ return null;
3465
5137
  }
3466
- const command = specs.find((item) => item.name.trim().toLowerCase() === commandName);
3467
- const options = command?.options;
3468
- if (!options || options.length === 0) {
3469
- return {};
5138
+ const normalized = value.trim().toLowerCase();
5139
+ return normalized.length > 0 ? normalized : null;
5140
+ }
5141
+ function readRequestedRuntimeKind(sessionMetadata) {
5142
+ return normalizeRuntimeKind(sessionMetadata.session_type) ?? normalizeRuntimeKind(sessionMetadata.sessionType) ?? null;
5143
+ }
5144
+ var UiNcpRuntimeRegistry = class {
5145
+ constructor(defaultKind = DEFAULT_UI_NCP_RUNTIME_KIND) {
5146
+ this.defaultKind = defaultKind;
5147
+ }
5148
+ registrations = /* @__PURE__ */ new Map();
5149
+ register(registration) {
5150
+ const normalizedKind = normalizeRuntimeKind(registration.kind);
5151
+ if (!normalizedKind) {
5152
+ throw new Error("ui ncp runtime kind must be a non-empty string");
5153
+ }
5154
+ const token = Symbol(normalizedKind);
5155
+ this.registrations.set(normalizedKind, {
5156
+ ...registration,
5157
+ kind: normalizedKind,
5158
+ token
5159
+ });
5160
+ return toDisposable(() => {
5161
+ const current = this.registrations.get(normalizedKind);
5162
+ if (!current || current.token !== token) {
5163
+ return;
5164
+ }
5165
+ this.registrations.delete(normalizedKind);
5166
+ });
3470
5167
  }
3471
- const tokens = rawTail.split(/\s+/).filter(Boolean);
3472
- const args = {};
3473
- let cursor = 0;
3474
- for (let i = 0; i < options.length; i += 1) {
3475
- if (cursor >= tokens.length) {
3476
- break;
3477
- }
3478
- const option = options[i];
3479
- const isLastOption = i === options.length - 1;
3480
- const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
3481
- cursor += isLastOption ? tokens.length - cursor : 1;
3482
- const parsedValue = parseCommandOptionValue(option.type, rawValue);
3483
- if (parsedValue !== void 0) {
3484
- args[option.name] = parsedValue;
5168
+ createRuntime(params) {
5169
+ const requestedKind = readRequestedRuntimeKind(params.sessionMetadata) ?? this.defaultKind;
5170
+ const registration = this.registrations.get(requestedKind);
5171
+ if (!registration) {
5172
+ throw new Error(`ncp runtime unavailable: ${requestedKind}`);
3485
5173
  }
5174
+ const nextSessionMetadata = {
5175
+ ...params.sessionMetadata,
5176
+ session_type: registration.kind
5177
+ };
5178
+ params.setSessionMetadata(nextSessionMetadata);
5179
+ return registration.createRuntime({
5180
+ ...params,
5181
+ sessionMetadata: nextSessionMetadata
5182
+ });
3486
5183
  }
3487
- return args;
5184
+ listSessionTypes() {
5185
+ const options = [...this.registrations.values()].map((registration) => ({
5186
+ value: registration.kind,
5187
+ label: registration.label
5188
+ })).sort((left, right) => {
5189
+ if (left.value === this.defaultKind) {
5190
+ return -1;
5191
+ }
5192
+ if (right.value === this.defaultKind) {
5193
+ return 1;
5194
+ }
5195
+ return left.value.localeCompare(right.value);
5196
+ });
5197
+ return {
5198
+ defaultType: this.defaultKind,
5199
+ options
5200
+ };
5201
+ }
5202
+ };
5203
+
5204
+ // src/cli/commands/ncp/create-ui-ncp-agent.ts
5205
+ function isRecord5(value) {
5206
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3488
5207
  }
3489
- function parseCommandOptionValue(type, rawValue) {
3490
- const value = rawValue.trim();
3491
- if (!value) {
3492
- return void 0;
5208
+ function resolveNativeReasoningNormalizationMode(params) {
5209
+ const runtimeEntry = params.config.ui.ncp.runtimes.native;
5210
+ const runtimeMetadata = isRecord5(runtimeEntry) ? runtimeEntry : {};
5211
+ return readAssistantReasoningNormalizationModeFromMetadata(params.sessionMetadata) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalizationMode) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization_mode) ?? "think-tags";
5212
+ }
5213
+ function buildPluginRuntimeSnapshotKey(extensionRegistry) {
5214
+ const registrations = extensionRegistry?.ncpAgentRuntimes ?? [];
5215
+ return registrations.map((registration) => [
5216
+ registration.pluginId,
5217
+ registration.kind,
5218
+ registration.label,
5219
+ registration.source
5220
+ ].join(":")).join("|");
5221
+ }
5222
+ async function createUiNcpAgent(params) {
5223
+ const sessionStore = new NextclawAgentSessionStore(params.sessionManager);
5224
+ const runtimeRegistry = new UiNcpRuntimeRegistry();
5225
+ runtimeRegistry.register({
5226
+ kind: "native",
5227
+ label: "Native",
5228
+ createRuntime: ({ stateManager, sessionMetadata, setSessionMetadata }) => {
5229
+ const reasoningNormalizationMode = resolveNativeReasoningNormalizationMode({
5230
+ config: params.getConfig(),
5231
+ sessionMetadata
5232
+ });
5233
+ if (reasoningNormalizationMode !== "off" && readAssistantReasoningNormalizationModeFromMetadata(sessionMetadata) !== reasoningNormalizationMode) {
5234
+ setSessionMetadata(
5235
+ writeAssistantReasoningNormalizationModeToMetadata(
5236
+ sessionMetadata,
5237
+ reasoningNormalizationMode
5238
+ )
5239
+ );
5240
+ }
5241
+ const toolRegistry = new NextclawNcpToolRegistry({
5242
+ bus: params.bus,
5243
+ providerManager: params.providerManager,
5244
+ sessionManager: params.sessionManager,
5245
+ cronService: params.cronService,
5246
+ gatewayController: params.gatewayController,
5247
+ getConfig: params.getConfig,
5248
+ getExtensionRegistry: params.getExtensionRegistry
5249
+ });
5250
+ return new DefaultNcpAgentRuntime({
5251
+ contextBuilder: new NextclawNcpContextBuilder({
5252
+ sessionManager: params.sessionManager,
5253
+ toolRegistry,
5254
+ getConfig: params.getConfig,
5255
+ resolveMessageToolHints: params.resolveMessageToolHints
5256
+ }),
5257
+ llmApi: new ProviderManagerNcpLLMApi(params.providerManager),
5258
+ toolRegistry,
5259
+ stateManager,
5260
+ reasoningNormalizationMode
5261
+ });
5262
+ }
5263
+ });
5264
+ const pluginRuntimeScopes = /* @__PURE__ */ new Map();
5265
+ let pluginRuntimeSnapshotKey = "";
5266
+ let activeExtensionRegistry;
5267
+ const syncPluginRuntimeRegistrations = (extensionRegistry) => {
5268
+ const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
5269
+ if (nextSnapshotKey === pluginRuntimeSnapshotKey) {
5270
+ return;
5271
+ }
5272
+ pluginRuntimeSnapshotKey = nextSnapshotKey;
5273
+ for (const scope of pluginRuntimeScopes.values()) {
5274
+ scope.dispose();
5275
+ }
5276
+ pluginRuntimeScopes.clear();
5277
+ for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
5278
+ const pluginId = registration.pluginId.trim() || registration.kind;
5279
+ let scope = pluginRuntimeScopes.get(pluginId);
5280
+ if (!scope) {
5281
+ scope = new DisposableStore();
5282
+ pluginRuntimeScopes.set(pluginId, scope);
5283
+ }
5284
+ scope.add(runtimeRegistry.register({
5285
+ kind: registration.kind,
5286
+ label: registration.label,
5287
+ createRuntime: registration.createRuntime
5288
+ }));
5289
+ }
5290
+ };
5291
+ const resolveActiveExtensionRegistry = () => activeExtensionRegistry ?? params.getExtensionRegistry?.();
5292
+ const refreshPluginRuntimeRegistrations = () => {
5293
+ syncPluginRuntimeRegistrations(resolveActiveExtensionRegistry());
5294
+ };
5295
+ refreshPluginRuntimeRegistrations();
5296
+ const backend = new DefaultNcpAgentBackend({
5297
+ endpointId: "nextclaw-ui-agent",
5298
+ sessionStore,
5299
+ createRuntime: (runtimeParams) => {
5300
+ refreshPluginRuntimeRegistrations();
5301
+ return runtimeRegistry.createRuntime(runtimeParams);
5302
+ }
5303
+ });
5304
+ await backend.start();
5305
+ return {
5306
+ basePath: "/api/ncp/agent",
5307
+ agentClientEndpoint: createAgentClientFromServer(backend),
5308
+ streamProvider: backend,
5309
+ sessionApi: backend,
5310
+ listSessionTypes: () => {
5311
+ refreshPluginRuntimeRegistrations();
5312
+ return runtimeRegistry.listSessionTypes();
5313
+ },
5314
+ applyExtensionRegistry: (extensionRegistry) => {
5315
+ activeExtensionRegistry = extensionRegistry;
5316
+ syncPluginRuntimeRegistrations(extensionRegistry);
5317
+ }
5318
+ };
5319
+ }
5320
+
5321
+ // src/cli/commands/service-marketplace-helpers.ts
5322
+ var containsAbsoluteFsPath = (line) => {
5323
+ const normalized = line.trim();
5324
+ if (!normalized) {
5325
+ return false;
3493
5326
  }
3494
- if (type === "number") {
3495
- const parsed = Number(value);
3496
- return Number.isFinite(parsed) ? parsed : void 0;
5327
+ const lowered = normalized.toLowerCase();
5328
+ if (lowered.includes("http://") || lowered.includes("https://")) {
5329
+ return false;
3497
5330
  }
3498
- if (type === "boolean") {
3499
- const lowered = value.toLowerCase();
3500
- if (["1", "true", "yes", "on"].includes(lowered)) {
3501
- return true;
5331
+ if (/^[A-Za-z]:\\/.test(normalized)) {
5332
+ return true;
5333
+ }
5334
+ return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
5335
+ };
5336
+ var pickUserFacingCommandSummary = (output, fallback) => {
5337
+ const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
5338
+ if (lines.length === 0) {
5339
+ return fallback;
5340
+ }
5341
+ const visibleLines = lines.filter((line) => {
5342
+ if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
5343
+ return false;
3502
5344
  }
3503
- if (["0", "false", "no", "off"].includes(lowered)) {
5345
+ if (containsAbsoluteFsPath(line)) {
3504
5346
  return false;
3505
5347
  }
3506
- return void 0;
3507
- }
3508
- return value;
3509
- }
3510
- function formatUserFacingError(error, maxChars = 320) {
3511
- const raw = error instanceof Error ? error.message || error.name || "Unknown error" : String(error ?? "Unknown error");
3512
- const normalized = raw.replace(/\s+/g, " ").trim();
3513
- if (!normalized) {
3514
- return "Unknown error";
5348
+ return true;
5349
+ });
5350
+ if (visibleLines.length === 0) {
5351
+ return fallback;
3515
5352
  }
3516
- if (normalized.length <= maxChars) {
3517
- return normalized;
5353
+ const preferred = [...visibleLines].reverse().find(
5354
+ (line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
5355
+ );
5356
+ return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
5357
+ };
5358
+ var buildMarketplaceSkillInstallArgs = (params) => {
5359
+ const args = ["skills", "install", params.slug, "--workdir", params.workspace];
5360
+ if (params.force) {
5361
+ args.push("--force");
3518
5362
  }
3519
- return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
3520
- }
5363
+ return args;
5364
+ };
3521
5365
 
3522
5366
  // src/cli/commands/ui-chat-run-coordinator.ts
3523
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
5367
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
3524
5368
  import { join as join4 } from "path";
3525
5369
  import {
3526
5370
  getDataDir as getDataDir5,
@@ -3536,7 +5380,7 @@ function createRunId() {
3536
5380
  const rand = Math.random().toString(36).slice(2, 10);
3537
5381
  return `run-${now}-${rand}`;
3538
5382
  }
3539
- function isRecord2(value) {
5383
+ function isRecord6(value) {
3540
5384
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3541
5385
  }
3542
5386
  function readOptionalString(value) {
@@ -3726,7 +5570,7 @@ var UiChatRunCoordinator = class {
3726
5570
  const parsedAgentId = parseAgentScopedSessionKey2(sessionKey)?.agentId;
3727
5571
  const agentId = explicitAgentId ?? readOptionalString(parsedAgentId);
3728
5572
  const model = readOptionalString(input.model);
3729
- const metadata = isRecord2(input.metadata) ? { ...input.metadata } : {};
5573
+ const metadata = isRecord6(input.metadata) ? { ...input.metadata } : {};
3730
5574
  if (model) {
3731
5575
  metadata.model = model;
3732
5576
  }
@@ -3947,14 +5791,14 @@ var UiChatRunCoordinator = class {
3947
5791
  this.emitRunUpdated(run);
3948
5792
  }
3949
5793
  waitForRunUpdate(run, signal) {
3950
- return new Promise((resolve10) => {
5794
+ return new Promise((resolve13) => {
3951
5795
  const wake = () => {
3952
5796
  cleanup();
3953
- resolve10();
5797
+ resolve13();
3954
5798
  };
3955
5799
  const onAbort = () => {
3956
5800
  cleanup();
3957
- resolve10();
5801
+ resolve13();
3958
5802
  };
3959
5803
  const cleanup = () => {
3960
5804
  run.waiters.delete(wake);
@@ -4075,16 +5919,16 @@ var UiChatRunCoordinator = class {
4075
5919
  `);
4076
5920
  }
4077
5921
  loadPersistedRuns() {
4078
- if (!existsSync7(RUNS_DIR)) {
5922
+ if (!existsSync8(RUNS_DIR)) {
4079
5923
  return;
4080
5924
  }
4081
5925
  for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
4082
5926
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
4083
5927
  continue;
4084
5928
  }
4085
- const path = join4(RUNS_DIR, entry.name);
5929
+ const path2 = join4(RUNS_DIR, entry.name);
4086
5930
  try {
4087
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
5931
+ const parsed = JSON.parse(readFileSync8(path2, "utf-8"));
4088
5932
  const runId = readOptionalString(parsed.runId);
4089
5933
  const sessionKey = readOptionalString(parsed.sessionKey);
4090
5934
  if (!runId || !sessionKey) {
@@ -4147,10 +5991,10 @@ var {
4147
5991
  getDataDir: getDataDir6,
4148
5992
  getProvider,
4149
5993
  getProviderName,
4150
- getWorkspacePath: getWorkspacePath5,
5994
+ getWorkspacePath: getWorkspacePath8,
4151
5995
  HeartbeatService,
4152
5996
  LiteLLMProvider,
4153
- loadConfig: loadConfig6,
5997
+ loadConfig: loadConfig7,
4154
5998
  MessageBus,
4155
5999
  ProviderManager,
4156
6000
  resolveConfigSecrets: resolveConfigSecrets2,
@@ -4165,64 +6009,18 @@ function createSkillsLoader(workspace) {
4165
6009
  }
4166
6010
  return new ctor(workspace);
4167
6011
  }
4168
- function containsAbsoluteFsPath(line) {
4169
- const normalized = line.trim();
4170
- if (!normalized) {
4171
- return false;
4172
- }
4173
- const lowered = normalized.toLowerCase();
4174
- if (lowered.includes("http://") || lowered.includes("https://")) {
4175
- return false;
4176
- }
4177
- if (/^[A-Za-z]:\\/.test(normalized)) {
4178
- return true;
4179
- }
4180
- return /(?:^|\s)(?:~\/|\/[^\s]+)/.test(normalized);
4181
- }
4182
- function pickUserFacingCommandSummary(output, fallback) {
4183
- const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
4184
- if (lines.length === 0) {
4185
- return fallback;
4186
- }
4187
- const visibleLines = lines.filter((line) => {
4188
- if (/^(path|install path|source path|destination|location)\s*:/i.test(line)) {
4189
- return false;
4190
- }
4191
- if (containsAbsoluteFsPath(line)) {
4192
- return false;
4193
- }
4194
- return true;
4195
- });
4196
- if (visibleLines.length === 0) {
4197
- return fallback;
4198
- }
4199
- const preferred = [...visibleLines].reverse().find(
4200
- (line) => /\b(installed|enabled|disabled|uninstalled|published|updated|already installed|removed)\b/i.test(line)
4201
- );
4202
- return preferred ?? visibleLines[visibleLines.length - 1] ?? fallback;
4203
- }
4204
- function buildMarketplaceSkillInstallArgs(params) {
4205
- const args = ["skills", "install", params.slug, "--workdir", params.workspace];
4206
- if (params.force) {
4207
- args.push("--force");
4208
- }
4209
- return args;
4210
- }
4211
- function resolveCliSubcommandEntry(params) {
4212
- const argvEntry = params.argvEntry?.trim();
4213
- if (argvEntry) {
4214
- return resolve7(argvEntry);
4215
- }
4216
- return fileURLToPath2(new URL("../index.js", params.importMetaUrl));
4217
- }
4218
6012
  var ServiceCommands = class {
4219
6013
  constructor(deps) {
4220
6014
  this.deps = deps;
4221
6015
  }
6016
+ applyLiveConfigReload = null;
6017
+ liveUiNcpAgent = null;
4222
6018
  async startGateway(options = {}) {
6019
+ this.applyLiveConfigReload = null;
6020
+ this.liveUiNcpAgent = null;
4223
6021
  const runtimeConfigPath = getConfigPath3();
4224
- const config2 = resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath });
4225
- const workspace = getWorkspacePath5(config2.agents.defaults.workspace);
6022
+ const config2 = resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath });
6023
+ const workspace = getWorkspacePath8(config2.agents.defaults.workspace);
4226
6024
  let pluginRegistry = loadPluginRegistry(config2, workspace);
4227
6025
  let extensionRegistry = toExtensionRegistry(pluginRegistry);
4228
6026
  logPluginDiagnostics(pluginRegistry);
@@ -4266,7 +6064,7 @@ var ServiceCommands = class {
4266
6064
  sessionManager,
4267
6065
  providerManager,
4268
6066
  makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
4269
- loadConfig: () => resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }),
6067
+ loadConfig: () => resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath }),
4270
6068
  getExtensionChannels: () => extensionRegistry.channels,
4271
6069
  onRestartRequired: (paths) => {
4272
6070
  void this.deps.requestRestart({
@@ -4276,6 +6074,9 @@ var ServiceCommands = class {
4276
6074
  });
4277
6075
  }
4278
6076
  });
6077
+ this.applyLiveConfigReload = async () => {
6078
+ await reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath }));
6079
+ };
4279
6080
  const gatewayController = new GatewayControllerImpl({
4280
6081
  reloader,
4281
6082
  cron: cron2,
@@ -4307,38 +6108,42 @@ var ServiceCommands = class {
4307
6108
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
4308
6109
  registry: pluginRegistry,
4309
6110
  channel,
4310
- cfg: resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }),
6111
+ cfg: resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath }),
4311
6112
  accountId
4312
6113
  })
4313
6114
  });
4314
6115
  reloader.setApplyAgentRuntimeConfig((nextConfig) => runtimePool.applyRuntimeConfig(nextConfig));
4315
- reloader.setReloadPlugins(async (nextConfig) => {
4316
- const nextWorkspace = getWorkspacePath5(nextConfig.agents.defaults.workspace);
4317
- const nextPluginRegistry = loadPluginRegistry(nextConfig, nextWorkspace);
4318
- const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
4319
- logPluginDiagnostics(nextPluginRegistry);
4320
- await stopPluginChannelGateways(pluginGatewayHandles);
4321
- const startedPluginGateways = await startPluginChannelGateways({
4322
- registry: nextPluginRegistry,
4323
- logger: pluginGatewayLogger
6116
+ reloader.setReloadPlugins(async ({ config: nextConfig, changedPaths }) => {
6117
+ const result = await reloadServicePlugins({
6118
+ nextConfig,
6119
+ changedPaths,
6120
+ pluginRegistry,
6121
+ extensionRegistry,
6122
+ pluginChannelBindings,
6123
+ pluginGatewayHandles,
6124
+ pluginGatewayLogger,
6125
+ logPluginGatewayDiagnostics
4324
6126
  });
4325
- pluginGatewayHandles = startedPluginGateways.handles;
4326
- logPluginGatewayDiagnostics(startedPluginGateways.diagnostics);
4327
- pluginRegistry = nextPluginRegistry;
4328
- extensionRegistry = nextExtensionRegistry;
4329
- pluginChannelBindings = getPluginChannelBindings2(nextPluginRegistry);
4330
- runtimePool.applyExtensionRegistry(nextExtensionRegistry);
6127
+ pluginRegistry = result.pluginRegistry;
6128
+ extensionRegistry = result.extensionRegistry;
6129
+ pluginChannelBindings = result.pluginChannelBindings;
6130
+ pluginGatewayHandles = result.pluginGatewayHandles;
6131
+ runtimePool.applyExtensionRegistry(result.extensionRegistry);
6132
+ this.liveUiNcpAgent?.applyExtensionRegistry?.(result.extensionRegistry);
4331
6133
  runtimePool.applyRuntimeConfig(nextConfig);
4332
- console.log("Config reload: plugin channel gateways restarted.");
6134
+ if (result.restartChannels) {
6135
+ console.log("Config reload: plugin channel gateways restarted.");
6136
+ }
6137
+ return { restartChannels: result.restartChannels };
4333
6138
  });
4334
- let pluginChannelBindings = getPluginChannelBindings2(pluginRegistry);
6139
+ let pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
4335
6140
  setPluginRuntimeBridge({
4336
- loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }), pluginChannelBindings),
6141
+ loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath }), pluginChannelBindings),
4337
6142
  writeConfigFile: async (nextConfigView) => {
4338
6143
  if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
4339
6144
  throw new Error("plugin runtime writeConfigFile expects an object config");
4340
6145
  }
4341
- const current = loadConfig6();
6146
+ const current = loadConfig7();
4342
6147
  const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
4343
6148
  saveConfig5(next);
4344
6149
  },
@@ -4405,19 +6210,36 @@ var ServiceCommands = class {
4405
6210
  } else {
4406
6211
  console.log("Warning: No channels enabled");
4407
6212
  }
4408
- this.startUiIfEnabled(uiConfig, uiStaticDir, cron2, runtimePool, sessionManager);
6213
+ await this.startUiIfEnabled(
6214
+ uiConfig,
6215
+ uiStaticDir,
6216
+ cron2,
6217
+ runtimePool,
6218
+ sessionManager,
6219
+ providerManager,
6220
+ bus,
6221
+ gatewayController,
6222
+ () => resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath }),
6223
+ () => extensionRegistry,
6224
+ ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
6225
+ registry: pluginRegistry,
6226
+ channel,
6227
+ cfg: resolveConfigSecrets2(loadConfig7(), { configPath: runtimeConfigPath }),
6228
+ accountId
6229
+ })
6230
+ );
4409
6231
  const cronStatus = cron2.status();
4410
6232
  if (cronStatus.jobs > 0) {
4411
6233
  console.log(`\u2713 Cron: ${cronStatus.jobs} scheduled jobs`);
4412
6234
  }
4413
6235
  console.log("\u2713 Heartbeat: every 30m");
4414
- const configPath = resolve7(getConfigPath3());
6236
+ const configPath = resolve10(getConfigPath3());
4415
6237
  const watcher = chokidar.watch(configPath, {
4416
6238
  ignoreInitial: true,
4417
6239
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
4418
6240
  });
4419
6241
  watcher.on("all", (event, changedPath) => {
4420
- if (resolve7(changedPath) !== configPath) {
6242
+ if (resolve10(changedPath) !== configPath) {
4421
6243
  return;
4422
6244
  }
4423
6245
  if (event === "add") {
@@ -4435,7 +6257,7 @@ var ServiceCommands = class {
4435
6257
  await cron2.start();
4436
6258
  await heartbeat.start();
4437
6259
  try {
4438
- const startedPluginGateways = await startPluginChannelGateways({
6260
+ const startedPluginGateways = await startPluginChannelGateways2({
4439
6261
  registry: pluginRegistry,
4440
6262
  logger: pluginGatewayLogger
4441
6263
  });
@@ -4445,7 +6267,9 @@ var ServiceCommands = class {
4445
6267
  await this.wakeFromRestartSentinel({ bus, sessionManager });
4446
6268
  await runtimePool.run();
4447
6269
  } finally {
4448
- await stopPluginChannelGateways(pluginGatewayHandles);
6270
+ this.applyLiveConfigReload = null;
6271
+ this.liveUiNcpAgent = null;
6272
+ await stopPluginChannelGateways2(pluginGatewayHandles);
4449
6273
  setPluginRuntimeBridge(null);
4450
6274
  }
4451
6275
  }
@@ -4509,7 +6333,7 @@ var ServiceCommands = class {
4509
6333
  if (!sentinel) {
4510
6334
  return;
4511
6335
  }
4512
- await new Promise((resolve10) => setTimeout(resolve10, 750));
6336
+ await new Promise((resolve13) => setTimeout(resolve13, 750));
4513
6337
  const payload = sentinel.payload;
4514
6338
  const summary = formatRestartSentinelMessage(payload);
4515
6339
  const sentinelSessionKey = this.normalizeOptionalString(payload.sessionKey);
@@ -4555,7 +6379,7 @@ var ServiceCommands = class {
4555
6379
  });
4556
6380
  }
4557
6381
  async runForeground(options) {
4558
- const config2 = loadConfig6();
6382
+ const config2 = loadConfig7();
4559
6383
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
4560
6384
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
4561
6385
  if (options.open) {
@@ -4568,7 +6392,7 @@ var ServiceCommands = class {
4568
6392
  });
4569
6393
  }
4570
6394
  async startService(options) {
4571
- const config2 = loadConfig6();
6395
+ const config2 = loadConfig7();
4572
6396
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
4573
6397
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
4574
6398
  const apiUrl = `${uiUrl}/api`;
@@ -4616,8 +6440,19 @@ var ServiceCommands = class {
4616
6440
  if (!staticDir) {
4617
6441
  console.log("Warning: UI frontend not found in package assets.");
4618
6442
  }
6443
+ const healthUrl = `${apiUrl}/health`;
6444
+ const portPreflight = await this.checkUiPortPreflight({
6445
+ host: uiConfig.host,
6446
+ port: uiConfig.port,
6447
+ healthUrl
6448
+ });
6449
+ if (!portPreflight.ok) {
6450
+ console.error(`Error: Cannot start ${APP_NAME2} because UI port ${uiConfig.port} is already occupied.`);
6451
+ console.error(portPreflight.message);
6452
+ return;
6453
+ }
4619
6454
  const logPath = resolveServiceLogPath();
4620
- const logDir = resolve7(logPath, "..");
6455
+ const logDir = resolve10(logPath, "..");
4621
6456
  mkdirSync5(logDir, { recursive: true });
4622
6457
  const logFd = openSync(logPath, "a");
4623
6458
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
@@ -4645,13 +6480,12 @@ var ServiceCommands = class {
4645
6480
  this.printStartupFailureDiagnostics({
4646
6481
  uiUrl,
4647
6482
  apiUrl,
4648
- healthUrl: `${apiUrl}/health`,
6483
+ healthUrl,
4649
6484
  logPath,
4650
6485
  lastProbeError: null
4651
6486
  });
4652
6487
  return;
4653
6488
  }
4654
- const healthUrl = `${apiUrl}/health`;
4655
6489
  this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
4656
6490
  let readiness = await this.waitForBackgroundServiceReady({
4657
6491
  pid: child.pid,
@@ -4766,14 +6600,14 @@ var ServiceCommands = class {
4766
6600
  const probe = await this.probeHealthEndpoint(params.healthUrl);
4767
6601
  if (!probe.healthy) {
4768
6602
  lastProbeError = probe.error;
4769
- await new Promise((resolve10) => setTimeout(resolve10, 200));
6603
+ await new Promise((resolve13) => setTimeout(resolve13, 200));
4770
6604
  continue;
4771
6605
  }
4772
- await new Promise((resolve10) => setTimeout(resolve10, 300));
6606
+ await new Promise((resolve13) => setTimeout(resolve13, 300));
4773
6607
  if (isProcessRunning(params.pid)) {
4774
6608
  return { ready: true, lastProbeError: null };
4775
6609
  }
4776
- await new Promise((resolve10) => setTimeout(resolve10, 200));
6610
+ await new Promise((resolve13) => setTimeout(resolve13, 200));
4777
6611
  }
4778
6612
  return { ready: false, lastProbeError };
4779
6613
  }
@@ -4810,6 +6644,58 @@ var ServiceCommands = class {
4810
6644
  }
4811
6645
  console.error(lines.join("\n"));
4812
6646
  }
6647
+ async checkUiPortPreflight(params) {
6648
+ const availability = await this.checkPortAvailability({
6649
+ host: params.host,
6650
+ port: params.port
6651
+ });
6652
+ if (availability.available) {
6653
+ return { ok: true };
6654
+ }
6655
+ const probe = await this.probeHealthEndpoint(params.healthUrl);
6656
+ const lines = [
6657
+ `Port probe: ${availability.detail}`
6658
+ ];
6659
+ if (probe.healthy) {
6660
+ lines.push(
6661
+ `Health probe: ${params.healthUrl} is already healthy. Another process is already serving this UI/API port.`
6662
+ );
6663
+ } else if (probe.error) {
6664
+ lines.push(`Health probe: ${probe.error}`);
6665
+ lines.push(
6666
+ "The port is occupied by a process that does not answer as a healthy NextClaw HTTP server."
6667
+ );
6668
+ }
6669
+ lines.push(
6670
+ `Fix: free port ${params.port} or start NextClaw with another port via --ui-port <port>.`
6671
+ );
6672
+ lines.push(
6673
+ `Inspect locally with: ss -ltnp | grep ${params.port} || lsof -iTCP:${params.port} -sTCP:LISTEN -n -P`
6674
+ );
6675
+ return {
6676
+ ok: false,
6677
+ message: lines.join("\n")
6678
+ };
6679
+ }
6680
+ async checkPortAvailability(params) {
6681
+ return await new Promise((resolve13) => {
6682
+ const server = createNetServer2();
6683
+ server.once("error", (error) => {
6684
+ resolve13({
6685
+ available: false,
6686
+ detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
6687
+ });
6688
+ });
6689
+ server.listen(params.port, params.host, () => {
6690
+ server.close(() => {
6691
+ resolve13({
6692
+ available: true,
6693
+ detail: `bind ok on ${params.host}:${params.port}`
6694
+ });
6695
+ });
6696
+ });
6697
+ });
6698
+ }
4813
6699
  getHeaderValue(headers, key) {
4814
6700
  const value = headers[key];
4815
6701
  if (typeof value === "string") {
@@ -4838,7 +6724,7 @@ var ServiceCommands = class {
4838
6724
  return { healthy: false, error: "invalid health URL" };
4839
6725
  }
4840
6726
  const requestImpl = parsed.protocol === "https:" ? httpsRequest : httpRequest;
4841
- return new Promise((resolve10) => {
6727
+ return new Promise((resolve13) => {
4842
6728
  const req = requestImpl(
4843
6729
  {
4844
6730
  protocol: parsed.protocol,
@@ -4874,19 +6760,19 @@ var ServiceCommands = class {
4874
6760
  if (bodySnippet) {
4875
6761
  details.push(`body=${bodySnippet}`);
4876
6762
  }
4877
- resolve10({ healthy: false, error: details.join("; ") });
6763
+ resolve13({ healthy: false, error: details.join("; ") });
4878
6764
  return;
4879
6765
  }
4880
6766
  try {
4881
6767
  const payload = JSON.parse(responseText);
4882
6768
  const healthy = payload?.ok === true && payload?.data?.status === "ok";
4883
6769
  if (!healthy) {
4884
- resolve10({ healthy: false, error: "health payload not ok" });
6770
+ resolve13({ healthy: false, error: "health payload not ok" });
4885
6771
  return;
4886
6772
  }
4887
- resolve10({ healthy: true, error: null });
6773
+ resolve13({ healthy: true, error: null });
4888
6774
  } catch {
4889
- resolve10({ healthy: false, error: "invalid health JSON response" });
6775
+ resolve13({ healthy: false, error: "invalid health JSON response" });
4890
6776
  }
4891
6777
  });
4892
6778
  }
@@ -4895,7 +6781,7 @@ var ServiceCommands = class {
4895
6781
  req.destroy(new Error("probe timeout"));
4896
6782
  });
4897
6783
  req.on("error", (error) => {
4898
- resolve10({ healthy: false, error: error.message || String(error) });
6784
+ resolve13({ healthy: false, error: error.message || String(error) });
4899
6785
  });
4900
6786
  req.end();
4901
6787
  });
@@ -4945,13 +6831,22 @@ var ServiceCommands = class {
4945
6831
  const publicBase = `http://${publicIp}:${port}`;
4946
6832
  console.log(`Public UI (if firewall/NAT allows): ${publicBase}`);
4947
6833
  console.log(`Public API (if firewall/NAT allows): ${publicBase}/api`);
6834
+ console.log(
6835
+ `Public deploy note: NextClaw serves plain HTTP on ${port}.`
6836
+ );
6837
+ console.log(
6838
+ `For https:// or standard 80/443 access, terminate TLS in Nginx/Caddy and proxy to http://127.0.0.1:${port}.`
6839
+ );
6840
+ console.log(
6841
+ `If a reverse proxy returns 502, verify its upstream is http://127.0.0.1:${port} (not https://, not a stale port, and not a stopped process).`
6842
+ );
4948
6843
  }
4949
6844
  printServiceControlHints() {
4950
6845
  console.log("Service controls:");
4951
6846
  console.log(` - Check status: ${APP_NAME2} status`);
4952
6847
  console.log(` - If you need to stop the service, run: ${APP_NAME2} stop`);
4953
6848
  }
4954
- startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager) {
6849
+ async startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager, providerManager, bus, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints) {
4955
6850
  if (!uiConfig.enabled) {
4956
6851
  return;
4957
6852
  }
@@ -4991,13 +6886,7 @@ var ServiceCommands = class {
4991
6886
  if (sessionType === "native") {
4992
6887
  return "Native";
4993
6888
  }
4994
- if (sessionType === "codex-sdk") {
4995
- return "Codex";
4996
- }
4997
- if (sessionType === "claude-agent-sdk") {
4998
- return "Claude Code";
4999
- }
5000
- return sessionType;
6889
+ return sessionType.trim().split(/[-_]+/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || sessionType;
5001
6890
  };
5002
6891
  let publishUiEvent = null;
5003
6892
  runtimePool.setSystemSessionUpdatedHandler(({ sessionKey, message }) => {
@@ -5022,6 +6911,20 @@ var ServiceCommands = class {
5022
6911
  publishUiEvent?.({ type: "run.updated", payload: { run } });
5023
6912
  }
5024
6913
  });
6914
+ const ncpAgent = await createUiNcpAgent({
6915
+ bus,
6916
+ providerManager,
6917
+ sessionManager,
6918
+ cronService,
6919
+ gatewayController,
6920
+ getConfig,
6921
+ getExtensionRegistry,
6922
+ resolveMessageToolHints: ({ channel, accountId }) => resolveMessageToolHints({
6923
+ channel,
6924
+ accountId
6925
+ })
6926
+ });
6927
+ this.liveUiNcpAgent = ncpAgent;
5025
6928
  const uiServer = startUiServer({
5026
6929
  host: uiConfig.host,
5027
6930
  port: uiConfig.port,
@@ -5040,6 +6943,7 @@ var ServiceCommands = class {
5040
6943
  uninstallSkill: (slug) => this.uninstallMarketplaceSkill(slug)
5041
6944
  }
5042
6945
  },
6946
+ ncpAgent,
5043
6947
  chatRuntime: {
5044
6948
  listSessionTypes: async () => {
5045
6949
  const options = runtimePool.listAvailableEngineKinds().map((value) => ({
@@ -5119,9 +7023,9 @@ var ServiceCommands = class {
5119
7023
  }
5120
7024
  }
5121
7025
  async installMarketplacePlugin(spec) {
5122
- const output = await this.runCliSubcommand(["plugins", "install", spec]);
5123
- const summary = pickUserFacingCommandSummary(output, `Installed plugin: ${spec}`);
5124
- return { message: summary };
7026
+ const result = await installPluginMutation(spec);
7027
+ await this.applyLiveConfigReload?.();
7028
+ return { message: result.message };
5125
7029
  }
5126
7030
  async installMarketplaceSkill(params) {
5127
7031
  if (params.kind === "builtin") {
@@ -5134,7 +7038,7 @@ var ServiceCommands = class {
5134
7038
  if (params.kind && params.kind !== "marketplace") {
5135
7039
  throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
5136
7040
  }
5137
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
7041
+ const workspace = getWorkspacePath8(loadConfig7().agents.defaults.workspace);
5138
7042
  const args = buildMarketplaceSkillInstallArgs({
5139
7043
  slug: params.slug,
5140
7044
  workspace,
@@ -5153,24 +7057,26 @@ var ServiceCommands = class {
5153
7057
  }
5154
7058
  }
5155
7059
  async enableMarketplacePlugin(id) {
5156
- const output = await this.runCliSubcommand(["plugins", "enable", id]);
5157
- const summary = pickUserFacingCommandSummary(output, `Enabled plugin: ${id}`);
5158
- return { message: summary };
7060
+ const result = await enablePluginMutation(id);
7061
+ await this.applyLiveConfigReload?.();
7062
+ return { message: result.message };
5159
7063
  }
5160
7064
  async disableMarketplacePlugin(id) {
5161
- const output = await this.runCliSubcommand(["plugins", "disable", id]);
5162
- const summary = pickUserFacingCommandSummary(output, `Disabled plugin: ${id}`);
5163
- return { message: summary };
7065
+ const result = await disablePluginMutation(id);
7066
+ await this.applyLiveConfigReload?.();
7067
+ return { message: result.message };
5164
7068
  }
5165
7069
  async uninstallMarketplacePlugin(id) {
5166
- const output = await this.runCliSubcommand(["plugins", "uninstall", id, "--force"]);
5167
- const summary = pickUserFacingCommandSummary(output, `Uninstalled plugin: ${id}`);
5168
- return { message: summary };
7070
+ await disablePluginMutation(id);
7071
+ await this.applyLiveConfigReload?.();
7072
+ const result = await uninstallPluginMutation(id, { force: true });
7073
+ await this.applyLiveConfigReload?.();
7074
+ return { message: result.message };
5169
7075
  }
5170
7076
  async uninstallMarketplaceSkill(slug) {
5171
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
7077
+ const workspace = getWorkspacePath8(loadConfig7().agents.defaults.workspace);
5172
7078
  const targetDir = join5(workspace, "skills", slug);
5173
- if (!existsSync8(targetDir)) {
7079
+ if (!existsSync9(targetDir)) {
5174
7080
  throw new Error(`Skill not installed in workspace: ${slug}`);
5175
7081
  }
5176
7082
  rmSync4(targetDir, { recursive: true, force: true });
@@ -5179,10 +7085,10 @@ var ServiceCommands = class {
5179
7085
  };
5180
7086
  }
5181
7087
  installBuiltinMarketplaceSkill(slug, force) {
5182
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
7088
+ const workspace = getWorkspacePath8(loadConfig7().agents.defaults.workspace);
5183
7089
  const destination = join5(workspace, "skills", slug);
5184
7090
  const destinationSkillFile = join5(destination, "SKILL.md");
5185
- if (existsSync8(destinationSkillFile) && !force) {
7091
+ if (existsSync9(destinationSkillFile) && !force) {
5186
7092
  return {
5187
7093
  message: `${slug} is already installed`
5188
7094
  };
@@ -5190,7 +7096,7 @@ var ServiceCommands = class {
5190
7096
  const loader = createSkillsLoader(workspace);
5191
7097
  const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
5192
7098
  if (!builtin) {
5193
- if (existsSync8(destinationSkillFile)) {
7099
+ if (existsSync9(destinationSkillFile)) {
5194
7100
  return {
5195
7101
  message: `${slug} is already installed`
5196
7102
  };
@@ -5257,9 +7163,9 @@ ${stderr}`.trim();
5257
7163
  };
5258
7164
 
5259
7165
  // src/cli/workspace.ts
5260
- import { cpSync as cpSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
5261
- import { createRequire } from "module";
5262
- import { dirname as dirname3, join as join6, resolve as resolve8 } from "path";
7166
+ import { cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
7167
+ import { createRequire as createRequire2 } from "module";
7168
+ import { dirname as dirname3, join as join6, resolve as resolve11 } from "path";
5263
7169
  import { fileURLToPath as fileURLToPath3 } from "url";
5264
7170
  import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
5265
7171
  import { spawnSync as spawnSync3 } from "child_process";
@@ -5290,27 +7196,27 @@ var WorkspaceManager = class {
5290
7196
  ];
5291
7197
  for (const entry of templateFiles) {
5292
7198
  const filePath = join6(workspace, entry.target);
5293
- if (!force && existsSync9(filePath)) {
7199
+ if (!force && existsSync10(filePath)) {
5294
7200
  continue;
5295
7201
  }
5296
7202
  const templatePath = join6(templateDir, entry.source);
5297
- if (!existsSync9(templatePath)) {
7203
+ if (!existsSync10(templatePath)) {
5298
7204
  console.warn(`Warning: Template file missing: ${templatePath}`);
5299
7205
  continue;
5300
7206
  }
5301
- const raw = readFileSync8(templatePath, "utf-8");
7207
+ const raw = readFileSync9(templatePath, "utf-8");
5302
7208
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
5303
7209
  mkdirSync6(dirname3(filePath), { recursive: true });
5304
7210
  writeFileSync5(filePath, content);
5305
7211
  created.push(entry.target);
5306
7212
  }
5307
7213
  const memoryDir = join6(workspace, "memory");
5308
- if (!existsSync9(memoryDir)) {
7214
+ if (!existsSync10(memoryDir)) {
5309
7215
  mkdirSync6(memoryDir, { recursive: true });
5310
7216
  created.push(join6("memory", ""));
5311
7217
  }
5312
7218
  const skillsDir = join6(workspace, "skills");
5313
- if (!existsSync9(skillsDir)) {
7219
+ if (!existsSync10(skillsDir)) {
5314
7220
  mkdirSync6(skillsDir, { recursive: true });
5315
7221
  created.push(join6("skills", ""));
5316
7222
  }
@@ -5332,11 +7238,11 @@ var WorkspaceManager = class {
5332
7238
  continue;
5333
7239
  }
5334
7240
  const src = join6(sourceDir, entry.name);
5335
- if (!existsSync9(join6(src, "SKILL.md"))) {
7241
+ if (!existsSync10(join6(src, "SKILL.md"))) {
5336
7242
  continue;
5337
7243
  }
5338
7244
  const dest = join6(targetDir, entry.name);
5339
- if (!force && existsSync9(dest)) {
7245
+ if (!force && existsSync10(dest)) {
5340
7246
  continue;
5341
7247
  }
5342
7248
  try {
@@ -5351,15 +7257,15 @@ var WorkspaceManager = class {
5351
7257
  }
5352
7258
  resolveBuiltinSkillsDir() {
5353
7259
  try {
5354
- const require2 = createRequire(import.meta.url);
5355
- const entry = require2.resolve("@nextclaw/core");
5356
- const pkgRoot = resolve8(dirname3(entry), "..");
7260
+ const require3 = createRequire2(import.meta.url);
7261
+ const entry = require3.resolve("@nextclaw/core");
7262
+ const pkgRoot = resolve11(dirname3(entry), "..");
5357
7263
  const distSkills = join6(pkgRoot, "dist", "skills");
5358
- if (existsSync9(distSkills)) {
7264
+ if (existsSync10(distSkills)) {
5359
7265
  return distSkills;
5360
7266
  }
5361
7267
  const srcSkills = join6(pkgRoot, "src", "agent", "skills");
5362
- if (existsSync9(srcSkills)) {
7268
+ if (existsSync10(srcSkills)) {
5363
7269
  return srcSkills;
5364
7270
  }
5365
7271
  return null;
@@ -5372,11 +7278,11 @@ var WorkspaceManager = class {
5372
7278
  if (override) {
5373
7279
  return override;
5374
7280
  }
5375
- const cliDir = resolve8(fileURLToPath3(new URL(".", import.meta.url)));
5376
- const pkgRoot = resolve8(cliDir, "..", "..");
7281
+ const cliDir = resolve11(fileURLToPath3(new URL(".", import.meta.url)));
7282
+ const pkgRoot = resolve11(cliDir, "..", "..");
5377
7283
  const candidates = [join6(pkgRoot, "templates")];
5378
7284
  for (const candidate of candidates) {
5379
- if (existsSync9(candidate)) {
7285
+ if (existsSync10(candidate)) {
5380
7286
  return candidate;
5381
7287
  }
5382
7288
  }
@@ -5384,21 +7290,21 @@ var WorkspaceManager = class {
5384
7290
  }
5385
7291
  getBridgeDir() {
5386
7292
  const userBridge = join6(getDataDir7(), "bridge");
5387
- if (existsSync9(join6(userBridge, "dist", "index.js"))) {
7293
+ if (existsSync10(join6(userBridge, "dist", "index.js"))) {
5388
7294
  return userBridge;
5389
7295
  }
5390
7296
  if (!which("npm")) {
5391
7297
  console.error("npm not found. Please install Node.js >= 18.");
5392
7298
  process.exit(1);
5393
7299
  }
5394
- const cliDir = resolve8(fileURLToPath3(new URL(".", import.meta.url)));
5395
- const pkgRoot = resolve8(cliDir, "..", "..");
7300
+ const cliDir = resolve11(fileURLToPath3(new URL(".", import.meta.url)));
7301
+ const pkgRoot = resolve11(cliDir, "..", "..");
5396
7302
  const pkgBridge = join6(pkgRoot, "bridge");
5397
7303
  const srcBridge = join6(pkgRoot, "..", "..", "bridge");
5398
7304
  let source = null;
5399
- if (existsSync9(join6(pkgBridge, "package.json"))) {
7305
+ if (existsSync10(join6(pkgBridge, "package.json"))) {
5400
7306
  source = pkgBridge;
5401
- } else if (existsSync9(join6(srcBridge, "package.json"))) {
7307
+ } else if (existsSync10(join6(srcBridge, "package.json"))) {
5402
7308
  source = srcBridge;
5403
7309
  }
5404
7310
  if (!source) {
@@ -5406,8 +7312,8 @@ var WorkspaceManager = class {
5406
7312
  process.exit(1);
5407
7313
  }
5408
7314
  console.log(`${this.logo} Setting up bridge...`);
5409
- mkdirSync6(resolve8(userBridge, ".."), { recursive: true });
5410
- if (existsSync9(userBridge)) {
7315
+ mkdirSync6(resolve11(userBridge, ".."), { recursive: true });
7316
+ if (existsSync10(userBridge)) {
5411
7317
  rmSync5(userBridge, { recursive: true, force: true });
5412
7318
  }
5413
7319
  cpSync3(source, userBridge, {
@@ -5443,7 +7349,7 @@ function resolveSkillsInstallWorkdir(params) {
5443
7349
  if (params.explicitWorkdir) {
5444
7350
  return expandHome2(params.explicitWorkdir);
5445
7351
  }
5446
- return getWorkspacePath6(params.configuredWorkspace);
7352
+ return getWorkspacePath9(params.configuredWorkspace);
5447
7353
  }
5448
7354
  var CliRuntime = class {
5449
7355
  logo;
@@ -5542,7 +7448,7 @@ var CliRuntime = class {
5542
7448
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
5543
7449
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath4(new URL("./index.js", import.meta.url));
5544
7450
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
5545
- const serviceStatePath = resolve9(getDataDir8(), "run", "service.json");
7451
+ const serviceStatePath = resolve12(getDataDir8(), "run", "service.json");
5546
7452
  const helperScript = [
5547
7453
  'const { spawnSync } = require("node:child_process");',
5548
7454
  'const { readFileSync } = require("node:fs");',
@@ -5672,15 +7578,15 @@ var CliRuntime = class {
5672
7578
  const force = Boolean(options.force);
5673
7579
  const configPath = getConfigPath4();
5674
7580
  let createdConfig = false;
5675
- if (!existsSync10(configPath)) {
7581
+ if (!existsSync11(configPath)) {
5676
7582
  const config3 = ConfigSchema2.parse({});
5677
7583
  saveConfig6(config3);
5678
7584
  createdConfig = true;
5679
7585
  }
5680
- const config2 = loadConfig7();
7586
+ const config2 = loadConfig8();
5681
7587
  const workspaceSetting = config2.agents.defaults.workspace;
5682
7588
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
5683
- const workspaceExisted = existsSync10(workspacePath);
7589
+ const workspaceExisted = existsSync11(workspacePath);
5684
7590
  mkdirSync7(workspacePath, { recursive: true });
5685
7591
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
5686
7592
  workspacePath,
@@ -5713,7 +7619,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5713
7619
  async login(opts = {}) {
5714
7620
  await this.init({ source: "login", auto: true });
5715
7621
  const configPath = getConfigPath4();
5716
- const config2 = loadConfig7(configPath);
7622
+ const config2 = loadConfig8(configPath);
5717
7623
  const providers = config2.providers;
5718
7624
  const nextclawProvider = providers.nextclaw ?? {
5719
7625
  displayName: "",
@@ -5870,15 +7776,15 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5870
7776
  }
5871
7777
  async agent(opts) {
5872
7778
  const configPath = getConfigPath4();
5873
- const config2 = resolveConfigSecrets3(loadConfig7(), { configPath });
5874
- const workspace = getWorkspacePath6(config2.agents.defaults.workspace);
7779
+ const config2 = resolveConfigSecrets3(loadConfig8(), { configPath });
7780
+ const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
5875
7781
  const pluginRegistry = loadPluginRegistry(config2, workspace);
5876
7782
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
5877
7783
  logPluginDiagnostics(pluginRegistry);
5878
- const pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
7784
+ const pluginChannelBindings = getPluginChannelBindings4(pluginRegistry);
5879
7785
  setPluginRuntimeBridge2({
5880
7786
  loadConfig: () => toPluginConfigView(
5881
- resolveConfigSecrets3(loadConfig7(), { configPath }),
7787
+ resolveConfigSecrets3(loadConfig8(), { configPath }),
5882
7788
  pluginChannelBindings
5883
7789
  ),
5884
7790
  writeConfigFile: async (nextConfigView) => {
@@ -5887,7 +7793,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5887
7793
  "plugin runtime writeConfigFile expects an object config"
5888
7794
  );
5889
7795
  }
5890
- const current = loadConfig7();
7796
+ const current = loadConfig8();
5891
7797
  const next = mergePluginConfigView(
5892
7798
  current,
5893
7799
  nextConfigView,
@@ -5919,7 +7825,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5919
7825
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
5920
7826
  registry: pluginRegistry,
5921
7827
  channel,
5922
- cfg: resolveConfigSecrets3(loadConfig7(), { configPath }),
7828
+ cfg: resolveConfigSecrets3(loadConfig8(), { configPath }),
5923
7829
  accountId
5924
7830
  })
5925
7831
  });
@@ -5939,9 +7845,9 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5939
7845
  `
5940
7846
  );
5941
7847
  const historyFile = join7(getDataDir8(), "history", "cli_history");
5942
- const historyDir = resolve9(historyFile, "..");
7848
+ const historyDir = resolve12(historyFile, "..");
5943
7849
  mkdirSync7(historyDir, { recursive: true });
5944
- const history = existsSync10(historyFile) ? readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean) : [];
7850
+ const history = existsSync11(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
5945
7851
  const rl = createInterface2({
5946
7852
  input: process.stdin,
5947
7853
  output: process.stdout
@@ -6096,7 +8002,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
6096
8002
  await this.diagnosticsCommands.doctor(opts);
6097
8003
  }
6098
8004
  async skillsInstall(options) {
6099
- const config2 = loadConfig7();
8005
+ const config2 = loadConfig8();
6100
8006
  const workdir = resolveSkillsInstallWorkdir({
6101
8007
  explicitWorkdir: options.workdir,
6102
8008
  configuredWorkspace: config2.agents.defaults.workspace
@@ -6118,6 +8024,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
6118
8024
  async skillsPublish(options) {
6119
8025
  const result = await publishMarketplaceSkill({
6120
8026
  skillDir: expandHome2(options.dir),
8027
+ metaFile: options.meta ? expandHome2(options.meta) : void 0,
6121
8028
  slug: options.slug,
6122
8029
  name: options.name,
6123
8030
  summary: options.summary,
@@ -6131,12 +8038,13 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
6131
8038
  apiBaseUrl: options.apiBaseUrl,
6132
8039
  token: options.token
6133
8040
  });
6134
- console.log(result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`);
6135
- console.log(` Files: ${result.fileCount}`);
8041
+ console.log(`${result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`}
8042
+ Files: ${result.fileCount}`);
6136
8043
  }
6137
8044
  async skillsUpdate(options) {
6138
8045
  const result = await publishMarketplaceSkill({
6139
8046
  skillDir: expandHome2(options.dir),
8047
+ metaFile: options.meta ? expandHome2(options.meta) : void 0,
6140
8048
  slug: options.slug,
6141
8049
  name: options.name,
6142
8050
  summary: options.summary,
@@ -6173,8 +8081,8 @@ program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <
6173
8081
  var skills = program.command("skills").description("Manage skills");
6174
8082
  skills.command("install <slug>").description("Install a skill from NextClaw marketplace").option("--api-base <url>", "Marketplace API base URL").option("--workdir <dir>", "Workspace directory to install into").option("--dir <dir>", "Skills directory name (default: skills)").option("-f, --force", "Overwrite existing skill files", false).action(async (slug, opts) => runtime.skillsInstall({ slug, ...opts, apiBaseUrl: opts.apiBase }));
6175
8083
  var withRepeatableTag = (value, previous = []) => [...previous, value];
6176
- skills.command("publish <dir>").description("Upload or create a skill in marketplace").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--published-at <datetime>", "Published time (ISO datetime)").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsPublish({ dir, ...opts, apiBaseUrl: opts.apiBase }));
6177
- skills.command("update <dir>").description("Update an existing skill in marketplace").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsUpdate({ dir, ...opts, apiBaseUrl: opts.apiBase }));
8084
+ skills.command("publish <dir>").description("Upload or create a skill in marketplace").option("--meta <path>", "Marketplace metadata file (default: <dir>/marketplace.json)").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--published-at <datetime>", "Published time (ISO datetime)").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsPublish({ dir, ...opts, apiBaseUrl: opts.apiBase }));
8085
+ skills.command("update <dir>").description("Update an existing skill in marketplace").option("--meta <path>", "Marketplace metadata file (default: <dir>/marketplace.json)").option("--slug <slug>", "Skill slug (default: directory name)").option("--name <name>", "Skill display name").option("--summary <summary>", "Skill summary").option("--description <description>", "Skill description").option("--author <author>", "Skill author").option("--tag <tag>", "Skill tag (repeatable)", withRepeatableTag, []).option("--source-repo <url>", "Source repository URL").option("--homepage <url>", "Homepage URL").option("--updated-at <datetime>", "Updated time (ISO datetime)").option("--api-base <url>", "Marketplace API base URL").option("--token <token>", "Marketplace admin token").action(async (dir, opts) => runtime.skillsUpdate({ dir, ...opts, apiBaseUrl: opts.apiBase }));
6178
8086
  var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
6179
8087
  plugins.command("list").description("List discovered plugins").option("--json", "Print JSON").option("--enabled", "Only show enabled plugins", false).option("--verbose", "Show detailed entries", false).action((opts) => runtime.pluginsList(opts));
6180
8088
  plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));
@@ -6184,9 +8092,9 @@ plugins.command("uninstall <id>").description("Uninstall a plugin").option("--ke
6184
8092
  plugins.command("install <path-or-spec>").description("Install a plugin (path, archive, or npm spec)").option("-l, --link", "Link a local path instead of copying", false).action(async (pathOrSpec, opts) => runtime.pluginsInstall(pathOrSpec, opts));
6185
8093
  plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
6186
8094
  var config = program.command("config").description("Manage config values");
6187
- config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
6188
- config.command("set <path> <value>").description("Set a config value by dot path").option("--json", "Parse value as JSON", false).action((path, value, opts) => runtime.configSet(path, value, opts));
6189
- config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
8095
+ config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path2, opts) => runtime.configGet(path2, opts));
8096
+ config.command("set <path> <value>").description("Set a config value by dot path").option("--json", "Parse value as JSON", false).action((path2, value, opts) => runtime.configSet(path2, value, opts));
8097
+ config.command("unset <path>").description("Remove a config value by dot path").action((path2) => runtime.configUnset(path2));
6190
8098
  var secrets = program.command("secrets").description("Manage secrets refs/providers");
6191
8099
  secrets.command("audit").description("Audit secret refs resolution status").option("--json", "Output JSON", false).option("--strict", "Exit non-zero when unresolved refs exist", false).action((opts) => runtime.secretsAudit(opts));
6192
8100
  secrets.command("configure").description("Configure a secret provider alias").requiredOption("--provider <alias>", "Provider alias").option("--source <source>", "Provider source (env|file|exec)").option("--prefix <prefix>", "Env key prefix (env source)").option("--path <path>", "Secret JSON file path (file source)").option("--command <command>", "Command for exec source").option(