nextclaw 0.10.0 → 0.11.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 (37) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/index.js +1584 -191
  3. package/package.json +9 -5
  4. package/templates/USAGE.md +57 -0
  5. package/ui-dist/assets/{ChannelsList-DF2U-LY1.js → ChannelsList-DBcoVJRW.js} +1 -1
  6. package/ui-dist/assets/ChatPage-CD3cxyyM.js +37 -0
  7. package/ui-dist/assets/{DocBrowser-B9ws5JL7.js → DocBrowser-DDX2HMXW.js} +1 -1
  8. package/ui-dist/assets/{LogoBadge-DvGAzkZ3.js → LogoBadge-J53F_3JA.js} +1 -1
  9. package/ui-dist/assets/{MarketplacePage-DG5mHWJ8.js → MarketplacePage-0BZ4bza0.js} +2 -2
  10. package/ui-dist/assets/{ModelConfig-BL_HsOsm.js → ModelConfig-Wzq9wGHV.js} +1 -1
  11. package/ui-dist/assets/{ProvidersList-CH5z00YT.js → ProvidersList-kwzRS8_M.js} +1 -1
  12. package/ui-dist/assets/RuntimeConfig-N771_AM6.js +1 -0
  13. package/ui-dist/assets/{SearchConfig-BhaI0fUf.js → SearchConfig-DVt5QVa_.js} +1 -1
  14. package/ui-dist/assets/{SecretsConfig-CFoimOh9.js → SecretsConfig-CkwauPa8.js} +2 -2
  15. package/ui-dist/assets/SessionsConfig-C3mnHzkZ.js +2 -0
  16. package/ui-dist/assets/{session-run-status-TkIuGbVw.js → chat-message-pxr79GDs.js} +3 -3
  17. package/ui-dist/assets/{index-X5J6Mm--.js → index-BIvFMkN4.js} +1 -1
  18. package/ui-dist/assets/index-CzkY1reu.js +8 -0
  19. package/ui-dist/assets/{index-uMsNsQX6.js → index-GdpEEKnz.js} +1 -1
  20. package/ui-dist/assets/index-RZ0kHHRI.css +1 -0
  21. package/ui-dist/assets/{label-D8ly4a2P.js → label-CmksBHgc.js} +1 -1
  22. package/ui-dist/assets/{page-layout-BSYfvwbp.js → page-layout-Db0GbnhS.js} +1 -1
  23. package/ui-dist/assets/security-config-CjLFME5Q.js +1 -0
  24. package/ui-dist/assets/skeleton-CkpQeVWN.js +1 -0
  25. package/ui-dist/assets/{switch-Ce_g9lpN.js → switch-C24d-UJU.js} +1 -1
  26. package/ui-dist/assets/tabs-custom-D89bh-fc.js +1 -0
  27. package/ui-dist/assets/{useConfirmDialog-A8Ek8Wu7.js → useConfirmDialog-BeP35LcG.js} +2 -2
  28. package/ui-dist/assets/{vendor-B7ozqnFC.js → vendor-psXJBy9u.js} +65 -70
  29. package/ui-dist/index.html +3 -3
  30. package/ui-dist/assets/ChatPage-BX39y0U5.js +0 -36
  31. package/ui-dist/assets/RuntimeConfig-BplBgkwo.js +0 -1
  32. package/ui-dist/assets/SessionsConfig-BHTAYn9T.js +0 -2
  33. package/ui-dist/assets/index-BLeJkJ0o.css +0 -1
  34. package/ui-dist/assets/index-DK4TS5ev.js +0 -8
  35. package/ui-dist/assets/security-config-DlKEYHNN.js +0 -1
  36. package/ui-dist/assets/skeleton-CWbsNx2h.js +0 -1
  37. package/ui-dist/assets/tabs-custom-Cf5azvT5.js +0 -1
package/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getConfigPath as getConfigPath4,
12
12
  getDataDir as getDataDir8,
13
13
  ConfigSchema as ConfigSchema2,
14
- getWorkspacePath as getWorkspacePath6,
14
+ getWorkspacePath as getWorkspacePath7,
15
15
  expandHome as expandHome2,
16
16
  MessageBus as MessageBus2,
17
17
  AgentLoop,
@@ -26,8 +26,8 @@ import {
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 resolve10 } 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";
@@ -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);
@@ -307,21 +519,24 @@ function isIgnorableMarketplaceResidue(path) {
307
519
  return path === ".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 = {
@@ -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";
@@ -629,11 +807,11 @@ function buildServeArgs(options) {
629
807
  }
630
808
  function readServiceState() {
631
809
  const path = resolveServiceStatePath();
632
- if (!existsSync3(path)) {
810
+ if (!existsSync4(path)) {
633
811
  return null;
634
812
  }
635
813
  try {
636
- const raw = readFileSync3(path, "utf-8");
814
+ const raw = readFileSync4(path, "utf-8");
637
815
  return JSON.parse(raw);
638
816
  } catch {
639
817
  return null;
@@ -641,20 +819,20 @@ function readServiceState() {
641
819
  }
642
820
  function writeServiceState(state) {
643
821
  const path = resolveServiceStatePath();
644
- mkdirSync3(resolve3(path, ".."), { recursive: true });
822
+ mkdirSync3(resolve4(path, ".."), { recursive: true });
645
823
  writeFileSync3(path, JSON.stringify(state, null, 2));
646
824
  }
647
825
  function clearServiceState() {
648
826
  const path = resolveServiceStatePath();
649
- if (existsSync3(path)) {
827
+ if (existsSync4(path)) {
650
828
  rmSync3(path, { 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((resolve11) => setTimeout(resolve11, 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((resolve11) => {
991
+ rl.once("line", (line) => resolve11(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) {
@@ -885,8 +1063,8 @@ import {
885
1063
  } from "@nextclaw/core";
886
1064
  import { builtinProviderIds } from "@nextclaw/runtime";
887
1065
  import { createInterface } from "readline";
888
- import { existsSync as existsSync4 } from "fs";
889
- import { resolve as resolve5 } from "path";
1066
+ import { existsSync as existsSync5 } from "fs";
1067
+ import { resolve as resolve6 } from "path";
890
1068
  var RESERVED_PROVIDER_IDS = builtinProviderIds();
891
1069
  function loadPluginRegistry(config2, workspaceDir) {
892
1070
  return loadOpenClawPlugins({
@@ -1175,7 +1353,7 @@ var PluginCommands = class {
1175
1353
  process.exit(1);
1176
1354
  }
1177
1355
  const install = config2.plugins.installs?.[pluginId];
1178
- const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve5(install.installPath) === resolve5(install.sourcePath));
1356
+ const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve6(install.installPath) === resolve6(install.sourcePath));
1179
1357
  const preview = [];
1180
1358
  if (hasEntry) {
1181
1359
  preview.push("config entry");
@@ -1251,9 +1429,9 @@ var PluginCommands = class {
1251
1429
  process.exit(1);
1252
1430
  }
1253
1431
  const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
1254
- const resolved = resolve5(expandHome(normalized));
1432
+ const resolved = resolve6(expandHome(normalized));
1255
1433
  const config2 = loadConfig();
1256
- if (existsSync4(resolved)) {
1434
+ if (existsSync5(resolved)) {
1257
1435
  if (opts.link) {
1258
1436
  const probe = await installPluginFromPath({ path: resolved, dryRun: true });
1259
1437
  if (!probe.ok) {
@@ -1367,8 +1545,8 @@ var PluginCommands = class {
1367
1545
  input: process.stdin,
1368
1546
  output: process.stdout
1369
1547
  });
1370
- const answer = await new Promise((resolve10) => {
1371
- rl.question(`${question} [y/N] `, (line) => resolve10(line));
1548
+ const answer = await new Promise((resolve11) => {
1549
+ rl.question(`${question} [y/N] `, (line) => resolve11(line));
1372
1550
  });
1373
1551
  rl.close();
1374
1552
  const normalized = answer.trim().toLowerCase();
@@ -1703,7 +1881,7 @@ var ConfigCommands = class {
1703
1881
  };
1704
1882
 
1705
1883
  // src/cli/commands/secrets.ts
1706
- import { readFileSync as readFileSync4 } from "fs";
1884
+ import { readFileSync as readFileSync5 } from "fs";
1707
1885
  import {
1708
1886
  buildReloadPlan as buildReloadPlan2,
1709
1887
  diffConfigPaths as diffConfigPaths2,
@@ -1946,7 +2124,7 @@ var SecretsCommands = class {
1946
2124
  nextConfig.secrets.enabled = false;
1947
2125
  }
1948
2126
  if (opts.file) {
1949
- const raw = readFileSync4(opts.file, "utf-8");
2127
+ const raw = readFileSync5(opts.file, "utf-8");
1950
2128
  const patch = parseApplyFile(raw);
1951
2129
  if (patch.defaults) {
1952
2130
  nextConfig.secrets.defaults = patch.defaults;
@@ -2217,8 +2395,8 @@ var CronCommands = class {
2217
2395
 
2218
2396
  // src/cli/commands/diagnostics.ts
2219
2397
  import { createServer as createNetServer } from "net";
2220
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2221
- import { resolve as resolve6 } from "path";
2398
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
2399
+ import { resolve as resolve7 } from "path";
2222
2400
  import {
2223
2401
  APP_NAME,
2224
2402
  getConfigPath as getConfigPath2,
@@ -2389,7 +2567,7 @@ var DiagnosticsCommands = class {
2389
2567
  const configPath = getConfigPath2();
2390
2568
  const config2 = loadConfig5();
2391
2569
  const workspacePath = getWorkspacePath3(config2.agents.defaults.workspace);
2392
- const serviceStatePath = resolve6(getDataDir4(), "run", "service.json");
2570
+ const serviceStatePath = resolve7(getDataDir4(), "run", "service.json");
2393
2571
  const fixActions = [];
2394
2572
  let serviceState = readServiceState();
2395
2573
  if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
@@ -2429,11 +2607,11 @@ var DiagnosticsCommands = class {
2429
2607
  });
2430
2608
  const issues = [];
2431
2609
  const recommendations = [];
2432
- if (!existsSync5(configPath)) {
2610
+ if (!existsSync6(configPath)) {
2433
2611
  issues.push("Config file is missing.");
2434
2612
  recommendations.push(`Run ${APP_NAME} init to create config files.`);
2435
2613
  }
2436
- if (!existsSync5(workspacePath)) {
2614
+ if (!existsSync6(workspacePath)) {
2437
2615
  issues.push("Workspace directory does not exist.");
2438
2616
  recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
2439
2617
  }
@@ -2466,13 +2644,13 @@ var DiagnosticsCommands = class {
2466
2644
  return {
2467
2645
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2468
2646
  configPath,
2469
- configExists: existsSync5(configPath),
2647
+ configExists: existsSync6(configPath),
2470
2648
  workspacePath,
2471
- workspaceExists: existsSync5(workspacePath),
2649
+ workspaceExists: existsSync6(workspacePath),
2472
2650
  model: config2.agents.defaults.model,
2473
2651
  providers,
2474
2652
  serviceStatePath,
2475
- serviceStateExists: existsSync5(serviceStatePath),
2653
+ serviceStateExists: existsSync6(serviceStatePath),
2476
2654
  fixActions,
2477
2655
  process: {
2478
2656
  managedByState,
@@ -2522,11 +2700,11 @@ var DiagnosticsCommands = class {
2522
2700
  }
2523
2701
  }
2524
2702
  readLogTail(path, maxLines = 25) {
2525
- if (!existsSync5(path)) {
2703
+ if (!existsSync6(path)) {
2526
2704
  return [];
2527
2705
  }
2528
2706
  try {
2529
- const lines = readFileSync5(path, "utf-8").split(/\r?\n/).filter(Boolean);
2707
+ const lines = readFileSync6(path, "utf-8").split(/\r?\n/).filter(Boolean);
2530
2708
  if (lines.length <= maxLines) {
2531
2709
  return lines;
2532
2710
  }
@@ -2536,17 +2714,17 @@ var DiagnosticsCommands = class {
2536
2714
  }
2537
2715
  }
2538
2716
  async checkPortAvailability(params) {
2539
- return await new Promise((resolve10) => {
2717
+ return await new Promise((resolve11) => {
2540
2718
  const server = createNetServer();
2541
2719
  server.once("error", (error) => {
2542
- resolve10({
2720
+ resolve11({
2543
2721
  available: false,
2544
2722
  detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
2545
2723
  });
2546
2724
  });
2547
2725
  server.listen(params.port, params.host, () => {
2548
2726
  server.close(() => {
2549
- resolve10({
2727
+ resolve11({
2550
2728
  available: true,
2551
2729
  detail: `bind ok on ${params.host}:${params.port}`
2552
2730
  });
@@ -2566,17 +2744,18 @@ import {
2566
2744
  stopPluginChannelGateways
2567
2745
  } from "@nextclaw/openclaw-compat";
2568
2746
  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";
2747
+ import { appendFileSync, closeSync, cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, openSync, rmSync as rmSync4 } from "fs";
2748
+ import { dirname as dirname2, join as join5, resolve as resolve8 } from "path";
2571
2749
  import { spawn as spawn2 } from "child_process";
2572
2750
  import { request as httpRequest } from "http";
2573
2751
  import { request as httpsRequest } from "https";
2752
+ import { createServer as createNetServer2 } from "net";
2574
2753
  import { fileURLToPath as fileURLToPath2 } from "url";
2575
2754
  import chokidar from "chokidar";
2576
2755
 
2577
2756
  // src/cli/gateway/controller.ts
2578
2757
  import { createHash } from "crypto";
2579
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
2758
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
2580
2759
  import {
2581
2760
  buildConfigSchema,
2582
2761
  ConfigSchema,
@@ -2588,8 +2767,8 @@ var readConfigSnapshot = (getConfigPath5) => {
2588
2767
  const path = getConfigPath5();
2589
2768
  let raw = "";
2590
2769
  let parsed = {};
2591
- if (existsSync6(path)) {
2592
- raw = readFileSync6(path, "utf-8");
2770
+ if (existsSync7(path)) {
2771
+ raw = readFileSync7(path, "utf-8");
2593
2772
  try {
2594
2773
  parsed = JSON.parse(raw);
2595
2774
  } catch {
@@ -3519,8 +3698,1118 @@ function formatUserFacingError(error, maxChars = 320) {
3519
3698
  return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
3520
3699
  }
3521
3700
 
3701
+ // src/cli/commands/ncp/create-ui-ncp-agent.ts
3702
+ import { DefaultNcpAgentRuntime } from "@nextclaw/ncp-agent-runtime";
3703
+ import { createAgentClientFromServer, DefaultNcpAgentBackend } from "@nextclaw/ncp-toolkit";
3704
+
3705
+ // src/cli/commands/ncp/nextclaw-ncp-context-builder.ts
3706
+ import {
3707
+ ContextBuilder,
3708
+ InputBudgetPruner,
3709
+ getWorkspacePath as getWorkspacePath5,
3710
+ parseThinkingLevel,
3711
+ resolveThinkingLevel
3712
+ } from "@nextclaw/core";
3713
+
3714
+ // src/cli/commands/ncp/nextclaw-ncp-message-bridge.ts
3715
+ import {
3716
+ sanitizeAssistantReplyTags
3717
+ } from "@nextclaw/ncp";
3718
+ function normalizeString(value) {
3719
+ if (typeof value !== "string") {
3720
+ return null;
3721
+ }
3722
+ const trimmed = value.trim();
3723
+ return trimmed.length > 0 ? trimmed : null;
3724
+ }
3725
+ function isRecord3(value) {
3726
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3727
+ }
3728
+ function cloneMetadata(value) {
3729
+ return isRecord3(value) ? structuredClone(value) : void 0;
3730
+ }
3731
+ function readStringArray(value) {
3732
+ if (!Array.isArray(value)) {
3733
+ return null;
3734
+ }
3735
+ const deduped = /* @__PURE__ */ new Set();
3736
+ for (const item of value) {
3737
+ const normalized = normalizeString(item);
3738
+ if (normalized) {
3739
+ deduped.add(normalized);
3740
+ }
3741
+ }
3742
+ return [...deduped];
3743
+ }
3744
+ function mergeSessionMetadata(currentMetadata, inputMetadata) {
3745
+ if (!inputMetadata) {
3746
+ return currentMetadata;
3747
+ }
3748
+ const nextMetadata = {
3749
+ ...currentMetadata,
3750
+ ...structuredClone(inputMetadata)
3751
+ };
3752
+ const model = normalizeString(inputMetadata.model) ?? normalizeString(inputMetadata.preferred_model) ?? normalizeString(inputMetadata.preferredModel);
3753
+ if (model) {
3754
+ nextMetadata.model = model;
3755
+ nextMetadata.preferred_model = model;
3756
+ }
3757
+ const thinking = normalizeString(inputMetadata.thinking) ?? normalizeString(inputMetadata.preferred_thinking) ?? normalizeString(inputMetadata.thinking_level) ?? normalizeString(inputMetadata.thinkingLevel);
3758
+ if (thinking) {
3759
+ nextMetadata.thinking = thinking;
3760
+ nextMetadata.preferred_thinking = thinking;
3761
+ }
3762
+ const sessionType = normalizeString(inputMetadata.session_type) ?? normalizeString(inputMetadata.sessionType);
3763
+ if (sessionType) {
3764
+ nextMetadata.session_type = sessionType;
3765
+ }
3766
+ const label = normalizeString(inputMetadata.label) ?? normalizeString(inputMetadata.session_label);
3767
+ if (label) {
3768
+ nextMetadata.label = label;
3769
+ }
3770
+ const requestedSkills = readStringArray(inputMetadata.requested_skills) ?? readStringArray(inputMetadata.requestedSkills);
3771
+ if (requestedSkills) {
3772
+ nextMetadata.requested_skills = requestedSkills;
3773
+ }
3774
+ return nextMetadata;
3775
+ }
3776
+ function extractMessageMetadata(messages) {
3777
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
3778
+ const message = messages[index];
3779
+ if (message?.role !== "user") {
3780
+ continue;
3781
+ }
3782
+ const metadata = cloneMetadata(message.metadata);
3783
+ if (metadata) {
3784
+ return metadata;
3785
+ }
3786
+ }
3787
+ return void 0;
3788
+ }
3789
+ function ensureIsoTimestamp(value, fallback) {
3790
+ if (typeof value !== "string") {
3791
+ return fallback;
3792
+ }
3793
+ const timestamp = Date.parse(value);
3794
+ if (!Number.isFinite(timestamp)) {
3795
+ return fallback;
3796
+ }
3797
+ return new Date(timestamp).toISOString();
3798
+ }
3799
+ function serializeToolArgs(args) {
3800
+ if (typeof args === "string") {
3801
+ return args;
3802
+ }
3803
+ return JSON.stringify(args ?? {});
3804
+ }
3805
+ function serializeLegacyContent(parts) {
3806
+ const text = parts.filter((part) => part.type === "text").map((part) => part.text).join("");
3807
+ if (text.length > 0) {
3808
+ return text;
3809
+ }
3810
+ if (parts.length === 0) {
3811
+ return "";
3812
+ }
3813
+ return structuredClone(parts);
3814
+ }
3815
+ function extractTextFromNcpMessage(message) {
3816
+ if (!message) {
3817
+ return "";
3818
+ }
3819
+ const normalizedMessage = message.role === "assistant" ? sanitizeAssistantReplyTags(message) : message;
3820
+ return normalizedMessage.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
3821
+ }
3822
+ function toLegacyMessages(messages) {
3823
+ const legacyMessages = [];
3824
+ for (const rawMessage of messages) {
3825
+ const message = rawMessage.role === "assistant" ? sanitizeAssistantReplyTags(rawMessage) : rawMessage;
3826
+ const timestamp = ensureIsoTimestamp(message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
3827
+ if (message.role === "assistant") {
3828
+ const textContent = extractTextFromNcpMessage(message);
3829
+ const reasoningContent = message.parts.filter((part) => part.type === "reasoning").map((part) => part.text).join("");
3830
+ const toolInvocations = message.parts.filter(
3831
+ (part) => part.type === "tool-invocation"
3832
+ );
3833
+ const assistantMessage = {
3834
+ role: "assistant",
3835
+ content: textContent,
3836
+ timestamp,
3837
+ ncp_message_id: message.id,
3838
+ ncp_parts: structuredClone(message.parts)
3839
+ };
3840
+ if (typeof message.metadata?.reply_to === "string" && message.metadata.reply_to.trim().length > 0) {
3841
+ assistantMessage.reply_to = message.metadata.reply_to.trim();
3842
+ }
3843
+ if (reasoningContent.length > 0) {
3844
+ assistantMessage.reasoning_content = reasoningContent;
3845
+ }
3846
+ if (toolInvocations.length > 0) {
3847
+ assistantMessage.tool_calls = toolInvocations.map((toolInvocation, index) => ({
3848
+ id: toolInvocation.toolCallId ?? `${message.id}:tool:${index}`,
3849
+ type: "function",
3850
+ function: {
3851
+ name: toolInvocation.toolName,
3852
+ arguments: serializeToolArgs(toolInvocation.args)
3853
+ }
3854
+ }));
3855
+ }
3856
+ legacyMessages.push(assistantMessage);
3857
+ for (const toolInvocation of toolInvocations) {
3858
+ if (toolInvocation.state !== "result") {
3859
+ continue;
3860
+ }
3861
+ legacyMessages.push({
3862
+ role: "tool",
3863
+ name: toolInvocation.toolName,
3864
+ tool_call_id: toolInvocation.toolCallId,
3865
+ content: typeof toolInvocation.result === "string" ? toolInvocation.result : JSON.stringify(toolInvocation.result ?? null),
3866
+ timestamp,
3867
+ ncp_message_id: message.id
3868
+ });
3869
+ }
3870
+ continue;
3871
+ }
3872
+ legacyMessages.push({
3873
+ role: message.role,
3874
+ content: serializeLegacyContent(message.parts),
3875
+ timestamp,
3876
+ ncp_message_id: message.id
3877
+ });
3878
+ }
3879
+ return legacyMessages;
3880
+ }
3881
+
3882
+ // src/cli/commands/ncp/nextclaw-ncp-tool-registry.ts
3883
+ import {
3884
+ CronTool,
3885
+ EditFileTool,
3886
+ ExecTool,
3887
+ ExtensionToolAdapter,
3888
+ GatewayTool,
3889
+ ListDirTool,
3890
+ MemoryGetTool,
3891
+ MemorySearchTool,
3892
+ MessageTool,
3893
+ ReadFileTool,
3894
+ SessionsHistoryTool,
3895
+ SessionsListTool,
3896
+ SessionsSendTool,
3897
+ SpawnTool,
3898
+ SubagentManager,
3899
+ SubagentsTool,
3900
+ ToolRegistry,
3901
+ WebFetchTool,
3902
+ WebSearchTool,
3903
+ WriteFileTool
3904
+ } from "@nextclaw/core";
3905
+ function toToolParams(args) {
3906
+ if (isRecord3(args)) {
3907
+ return args;
3908
+ }
3909
+ if (typeof args === "string") {
3910
+ try {
3911
+ const parsed = JSON.parse(args);
3912
+ return isRecord3(parsed) ? parsed : {};
3913
+ } catch {
3914
+ return {};
3915
+ }
3916
+ }
3917
+ return {};
3918
+ }
3919
+ function readMetadataAccountId(metadata, sessionMetadata) {
3920
+ const candidates = [
3921
+ metadata.accountId,
3922
+ metadata.account_id,
3923
+ sessionMetadata.last_account_id
3924
+ ];
3925
+ for (const candidate of candidates) {
3926
+ const normalized = normalizeString(candidate);
3927
+ if (normalized) {
3928
+ return normalized;
3929
+ }
3930
+ }
3931
+ return void 0;
3932
+ }
3933
+ var CoreToolNcpAdapter = class {
3934
+ constructor(tool, executeTool) {
3935
+ this.tool = tool;
3936
+ this.executeTool = executeTool;
3937
+ }
3938
+ get name() {
3939
+ return this.tool.name;
3940
+ }
3941
+ get description() {
3942
+ return this.tool.description;
3943
+ }
3944
+ get parameters() {
3945
+ return this.tool.parameters;
3946
+ }
3947
+ async execute(args) {
3948
+ return this.executeTool(this.tool.name, args);
3949
+ }
3950
+ };
3951
+ var NextclawNcpToolRegistry = class {
3952
+ constructor(options) {
3953
+ this.options = options;
3954
+ const initialConfig = this.options.getConfig();
3955
+ this.subagents = new SubagentManager({
3956
+ providerManager: this.options.providerManager,
3957
+ workspace: initialConfig.agents.defaults.workspace,
3958
+ bus: this.options.bus,
3959
+ model: initialConfig.agents.defaults.model,
3960
+ contextTokens: initialConfig.agents.defaults.contextTokens,
3961
+ searchConfig: initialConfig.search,
3962
+ execConfig: initialConfig.tools.exec,
3963
+ restrictToWorkspace: initialConfig.tools.restrictToWorkspace
3964
+ });
3965
+ }
3966
+ subagents;
3967
+ registry = new ToolRegistry();
3968
+ tools = /* @__PURE__ */ new Map();
3969
+ currentExtensionToolContext = {};
3970
+ prepareForRun(context) {
3971
+ this.subagents.updateRuntimeOptions({
3972
+ model: context.model,
3973
+ maxTokens: context.maxTokens,
3974
+ contextTokens: context.contextTokens,
3975
+ searchConfig: context.searchConfig,
3976
+ execConfig: { timeout: context.execTimeoutSeconds },
3977
+ restrictToWorkspace: context.restrictToWorkspace
3978
+ });
3979
+ this.currentExtensionToolContext = {
3980
+ config: context.config,
3981
+ workspaceDir: context.workspace,
3982
+ sessionKey: context.sessionId,
3983
+ channel: context.channel,
3984
+ chatId: context.chatId,
3985
+ sandboxed: context.restrictToWorkspace
3986
+ };
3987
+ this.registry = new ToolRegistry();
3988
+ this.tools.clear();
3989
+ this.registerDefaultTools(context);
3990
+ this.registerExtensionTools(context);
3991
+ }
3992
+ listTools() {
3993
+ return [...this.tools.values()];
3994
+ }
3995
+ getTool(name) {
3996
+ return this.tools.get(name);
3997
+ }
3998
+ getToolDefinitions() {
3999
+ return this.listTools().map((tool) => ({
4000
+ name: tool.name,
4001
+ description: tool.description,
4002
+ parameters: tool.parameters
4003
+ }));
4004
+ }
4005
+ async execute(toolCallId, toolName, args) {
4006
+ return this.registry.execute(toolName, toToolParams(args), toolCallId);
4007
+ }
4008
+ registerDefaultTools(context) {
4009
+ const allowedDir = context.restrictToWorkspace ? context.workspace : void 0;
4010
+ this.registerTool(new ReadFileTool(allowedDir));
4011
+ this.registerTool(new WriteFileTool(allowedDir));
4012
+ this.registerTool(new EditFileTool(allowedDir));
4013
+ this.registerTool(new ListDirTool(allowedDir));
4014
+ const execTool = new ExecTool({
4015
+ workingDir: context.workspace,
4016
+ timeout: context.execTimeoutSeconds,
4017
+ restrictToWorkspace: context.restrictToWorkspace
4018
+ });
4019
+ execTool.setContext({
4020
+ sessionKey: context.sessionId,
4021
+ channel: context.channel,
4022
+ chatId: context.chatId
4023
+ });
4024
+ this.registerTool(execTool);
4025
+ this.registerTool(new WebSearchTool(context.searchConfig));
4026
+ this.registerTool(new WebFetchTool());
4027
+ const messageTool = new MessageTool((message) => this.options.bus.publishOutbound(message));
4028
+ messageTool.setContext(context.channel, context.chatId);
4029
+ this.registerTool(messageTool);
4030
+ const spawnTool = new SpawnTool(this.subagents);
4031
+ spawnTool.setContext(
4032
+ context.channel,
4033
+ context.chatId,
4034
+ context.model,
4035
+ context.sessionId,
4036
+ context.agentId
4037
+ );
4038
+ this.registerTool(spawnTool);
4039
+ this.registerTool(new SessionsListTool(this.options.sessionManager));
4040
+ this.registerTool(new SessionsHistoryTool(this.options.sessionManager));
4041
+ const sessionsSendTool = new SessionsSendTool(this.options.sessionManager, this.options.bus);
4042
+ sessionsSendTool.setContext({
4043
+ currentSessionKey: context.sessionId,
4044
+ currentAgentId: context.agentId,
4045
+ channel: context.channel,
4046
+ chatId: context.chatId,
4047
+ maxPingPongTurns: context.config.session?.agentToAgent?.maxPingPongTurns ?? 0,
4048
+ currentHandoffDepth: context.handoffDepth
4049
+ });
4050
+ this.registerTool(sessionsSendTool);
4051
+ this.registerTool(new MemorySearchTool(context.workspace));
4052
+ this.registerTool(new MemoryGetTool(context.workspace));
4053
+ this.registerTool(new SubagentsTool(this.subagents));
4054
+ const gatewayTool = new GatewayTool(this.options.gatewayController);
4055
+ gatewayTool.setContext({ sessionKey: context.sessionId });
4056
+ this.registerTool(gatewayTool);
4057
+ if (this.options.cronService) {
4058
+ const cronTool = new CronTool(this.options.cronService);
4059
+ cronTool.setContext(context.channel, context.chatId);
4060
+ this.registerTool(cronTool);
4061
+ }
4062
+ }
4063
+ registerExtensionTools(context) {
4064
+ const extensionRegistry = this.options.getExtensionRegistry?.();
4065
+ if (!extensionRegistry || extensionRegistry.tools.length === 0) {
4066
+ return;
4067
+ }
4068
+ const seen = new Set(this.registry.toolNames);
4069
+ for (const registration of extensionRegistry.tools) {
4070
+ for (const alias of registration.names) {
4071
+ if (seen.has(alias)) {
4072
+ continue;
4073
+ }
4074
+ seen.add(alias);
4075
+ this.registerTool(
4076
+ new ExtensionToolAdapter({
4077
+ registration,
4078
+ alias,
4079
+ config: context.config,
4080
+ workspaceDir: context.workspace,
4081
+ contextProvider: () => this.currentExtensionToolContext,
4082
+ diagnostics: extensionRegistry.diagnostics
4083
+ })
4084
+ );
4085
+ }
4086
+ }
4087
+ }
4088
+ registerTool(tool) {
4089
+ this.registry.register(tool);
4090
+ this.tools.set(
4091
+ tool.name,
4092
+ new CoreToolNcpAdapter(tool, async (toolName, args) => this.registry.execute(toolName, toToolParams(args)))
4093
+ );
4094
+ }
4095
+ };
4096
+ function resolveAgentHandoffDepth(metadata) {
4097
+ const rawDepth = Number(metadata.agent_handoff_depth ?? 0);
4098
+ if (!Number.isFinite(rawDepth) || rawDepth < 0) {
4099
+ return 0;
4100
+ }
4101
+ return Math.trunc(rawDepth);
4102
+ }
4103
+ function readAccountIdForHints(metadata, sessionMetadata) {
4104
+ return readMetadataAccountId(metadata, sessionMetadata);
4105
+ }
4106
+
4107
+ // src/cli/commands/ncp/nextclaw-ncp-context-builder.ts
4108
+ var TIME_HINT_TRIGGER_PATTERNS = [
4109
+ /\b(now|right now|current time|what time|today|tonight|tomorrow|yesterday|this morning|this afternoon|this evening|date)\b/i,
4110
+ /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/
4111
+ ];
4112
+ function isRecord4(value) {
4113
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4114
+ }
4115
+ function mergeInputMetadata(input) {
4116
+ const messageMetadata = input.messages.slice().reverse().find((message) => isRecord4(message.metadata))?.metadata;
4117
+ return {
4118
+ ...isRecord4(messageMetadata) ? structuredClone(messageMetadata) : {},
4119
+ ...isRecord4(input.metadata) ? structuredClone(input.metadata) : {}
4120
+ };
4121
+ }
4122
+ function resolveRequestedSkillNames(metadata) {
4123
+ const rawValue = metadata.requested_skills ?? metadata.requestedSkills;
4124
+ const values = [];
4125
+ if (Array.isArray(rawValue)) {
4126
+ for (const item of rawValue) {
4127
+ const normalized = normalizeString(item);
4128
+ if (normalized) {
4129
+ values.push(normalized);
4130
+ }
4131
+ }
4132
+ } else if (typeof rawValue === "string") {
4133
+ values.push(
4134
+ ...rawValue.split(/[,\s]+/g).map((item) => item.trim()).filter(Boolean)
4135
+ );
4136
+ }
4137
+ return Array.from(new Set(values)).slice(0, 8);
4138
+ }
4139
+ function resolveRequestedToolNames(metadata) {
4140
+ const rawValue = metadata.requested_tools ?? metadata.requestedTools;
4141
+ if (!Array.isArray(rawValue)) {
4142
+ return [];
4143
+ }
4144
+ return Array.from(
4145
+ new Set(
4146
+ rawValue.map((item) => normalizeString(item)).filter((item) => Boolean(item))
4147
+ )
4148
+ );
4149
+ }
4150
+ function normalizeOptionalString2(value) {
4151
+ return normalizeString(value) ?? void 0;
4152
+ }
4153
+ function readMetadataModel(metadata) {
4154
+ const candidates = [metadata.model, metadata.llm_model, metadata.agent_model, metadata.session_model];
4155
+ for (const candidate of candidates) {
4156
+ const normalized = normalizeString(candidate);
4157
+ if (normalized) {
4158
+ return normalized;
4159
+ }
4160
+ }
4161
+ return null;
4162
+ }
4163
+ function readMetadataThinking(metadata) {
4164
+ const candidates = [
4165
+ metadata.thinking,
4166
+ metadata.thinking_level,
4167
+ metadata.thinkingLevel,
4168
+ metadata.thinking_effort,
4169
+ metadata.thinkingEffort
4170
+ ];
4171
+ for (const candidate of candidates) {
4172
+ if (typeof candidate !== "string") {
4173
+ continue;
4174
+ }
4175
+ const normalized = candidate.trim().toLowerCase();
4176
+ if (!normalized) {
4177
+ continue;
4178
+ }
4179
+ if (normalized === "clear" || normalized === "reset" || normalized === "off!") {
4180
+ return "__clear__";
4181
+ }
4182
+ const level = parseThinkingLevel(normalized);
4183
+ if (level) {
4184
+ return level;
4185
+ }
4186
+ }
4187
+ return null;
4188
+ }
4189
+ function resolvePrimaryAgentProfile(config2) {
4190
+ const configuredDefaultAgentId = config2.agents.list.find((entry) => entry.default)?.id?.trim() || config2.agents.list[0]?.id?.trim() || "main";
4191
+ const profile = config2.agents.list.find((entry) => entry.id.trim() === configuredDefaultAgentId);
4192
+ return {
4193
+ agentId: configuredDefaultAgentId,
4194
+ workspace: getWorkspacePath5(profile?.workspace ?? config2.agents.defaults.workspace),
4195
+ model: profile?.model ?? config2.agents.defaults.model,
4196
+ maxIterations: profile?.maxToolIterations ?? config2.agents.defaults.maxToolIterations,
4197
+ contextTokens: profile?.contextTokens ?? config2.agents.defaults.contextTokens,
4198
+ restrictToWorkspace: config2.tools.restrictToWorkspace,
4199
+ searchConfig: config2.search,
4200
+ execTimeoutSeconds: config2.tools.exec.timeout
4201
+ };
4202
+ }
4203
+ function shouldAppendTimeHint(content) {
4204
+ const normalized = content.trim();
4205
+ if (!normalized) {
4206
+ return false;
4207
+ }
4208
+ return TIME_HINT_TRIGGER_PATTERNS.some((pattern) => pattern.test(normalized));
4209
+ }
4210
+ function buildMinutePrecisionTimeHint(date) {
4211
+ const year = date.getFullYear();
4212
+ const month = String(date.getMonth() + 1).padStart(2, "0");
4213
+ const day = String(date.getDate()).padStart(2, "0");
4214
+ const hour = String(date.getHours()).padStart(2, "0");
4215
+ const minute = String(date.getMinutes()).padStart(2, "0");
4216
+ const offsetMinutes = -date.getTimezoneOffset();
4217
+ const sign = offsetMinutes >= 0 ? "+" : "-";
4218
+ const absMinutes = Math.abs(offsetMinutes);
4219
+ const offsetHour = String(Math.floor(absMinutes / 60)).padStart(2, "0");
4220
+ const offsetMinute = String(absMinutes % 60).padStart(2, "0");
4221
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
4222
+ return `${year}-${month}-${day} ${hour}:${minute} ${sign}${offsetHour}:${offsetMinute} (${timezone})`;
4223
+ }
4224
+ function appendTimeHintForPrompt(content, timestamp) {
4225
+ if (!shouldAppendTimeHint(content)) {
4226
+ return content;
4227
+ }
4228
+ const date = Number.isNaN(timestamp.getTime()) ? /* @__PURE__ */ new Date() : timestamp;
4229
+ return `${content}
4230
+
4231
+ [time_hint_local_minute] ${buildMinutePrecisionTimeHint(date)}`;
4232
+ }
4233
+ function prependRequestedSkills(content, requestedSkillNames) {
4234
+ if (requestedSkillNames.length === 0) {
4235
+ return content;
4236
+ }
4237
+ return `[Requested skills for this turn: ${requestedSkillNames.join(", ")}]
4238
+
4239
+ ${content}`;
4240
+ }
4241
+ function filterTools(toolDefinitions, requestedToolNames) {
4242
+ if (toolDefinitions.length === 0) {
4243
+ return void 0;
4244
+ }
4245
+ if (requestedToolNames.length === 0) {
4246
+ return [...toolDefinitions];
4247
+ }
4248
+ const requested = new Set(requestedToolNames);
4249
+ const filtered = toolDefinitions.filter((tool) => requested.has(tool.function.name));
4250
+ return filtered.length > 0 ? filtered : void 0;
4251
+ }
4252
+ var NextclawNcpContextBuilder = class {
4253
+ constructor(options) {
4254
+ this.options = options;
4255
+ }
4256
+ inputBudgetPruner = new InputBudgetPruner();
4257
+ prepare(input, _options) {
4258
+ const config2 = this.options.getConfig();
4259
+ const profile = resolvePrimaryAgentProfile(config2);
4260
+ const requestMetadata = mergeInputMetadata(input);
4261
+ const session = this.options.sessionManager.getOrCreate(input.sessionId);
4262
+ const clearModel = requestMetadata.clear_model === true || requestMetadata.reset_model === true;
4263
+ if (clearModel) {
4264
+ delete session.metadata.preferred_model;
4265
+ }
4266
+ const inboundModel = readMetadataModel(requestMetadata);
4267
+ if (inboundModel) {
4268
+ session.metadata.preferred_model = inboundModel;
4269
+ }
4270
+ const effectiveModel = normalizeOptionalString2(session.metadata.preferred_model) ?? profile.model;
4271
+ const clearThinking = requestMetadata.clear_thinking === true || requestMetadata.reset_thinking === true;
4272
+ if (clearThinking) {
4273
+ delete session.metadata.preferred_thinking;
4274
+ }
4275
+ const inboundThinking = readMetadataThinking(requestMetadata);
4276
+ if (inboundThinking === "__clear__") {
4277
+ delete session.metadata.preferred_thinking;
4278
+ } else if (inboundThinking) {
4279
+ session.metadata.preferred_thinking = inboundThinking;
4280
+ }
4281
+ const runtimeThinking = resolveThinkingLevel({
4282
+ config: config2,
4283
+ agentId: profile.agentId,
4284
+ model: effectiveModel,
4285
+ sessionThinkingLevel: parseThinkingLevel(session.metadata.preferred_thinking) ?? null
4286
+ });
4287
+ const channel = normalizeOptionalString2(requestMetadata.channel) ?? normalizeOptionalString2(session.metadata.last_channel) ?? "ui";
4288
+ const chatId = normalizeOptionalString2(requestMetadata.chatId) ?? normalizeOptionalString2(requestMetadata.chat_id) ?? normalizeOptionalString2(session.metadata.last_to) ?? "web-ui";
4289
+ session.metadata.last_channel = channel;
4290
+ session.metadata.last_to = chatId;
4291
+ const requestedSkillNames = resolveRequestedSkillNames(requestMetadata);
4292
+ const requestedToolNames = resolveRequestedToolNames(requestMetadata);
4293
+ const currentUserText = extractTextFromNcpMessage(input.messages[input.messages.length - 1]);
4294
+ const currentMessage = appendTimeHintForPrompt(
4295
+ prependRequestedSkills(currentUserText, requestedSkillNames),
4296
+ new Date(
4297
+ ensureIsoTimestamp(
4298
+ input.messages[input.messages.length - 1]?.timestamp,
4299
+ (/* @__PURE__ */ new Date()).toISOString()
4300
+ )
4301
+ )
4302
+ );
4303
+ this.options.toolRegistry.prepareForRun({
4304
+ sessionId: input.sessionId,
4305
+ channel,
4306
+ chatId,
4307
+ agentId: profile.agentId,
4308
+ config: config2,
4309
+ contextTokens: profile.contextTokens,
4310
+ execTimeoutSeconds: profile.execTimeoutSeconds,
4311
+ handoffDepth: resolveAgentHandoffDepth(requestMetadata),
4312
+ maxTokens: void 0,
4313
+ metadata: requestMetadata,
4314
+ model: effectiveModel,
4315
+ restrictToWorkspace: profile.restrictToWorkspace,
4316
+ searchConfig: profile.searchConfig,
4317
+ workspace: profile.workspace
4318
+ });
4319
+ const accountId = readAccountIdForHints(requestMetadata, session.metadata);
4320
+ const messageToolHints = this.options.resolveMessageToolHints?.({
4321
+ sessionKey: input.sessionId,
4322
+ channel,
4323
+ chatId,
4324
+ accountId: accountId ?? null
4325
+ });
4326
+ const contextBuilder = new ContextBuilder(profile.workspace, config2.agents.context);
4327
+ const sessionMessages = _options?.sessionMessages ?? [];
4328
+ const messages = contextBuilder.buildMessages({
4329
+ history: toLegacyMessages([...sessionMessages]),
4330
+ currentMessage,
4331
+ channel,
4332
+ chatId,
4333
+ sessionKey: input.sessionId,
4334
+ thinkingLevel: runtimeThinking,
4335
+ skillNames: requestedSkillNames,
4336
+ messageToolHints
4337
+ });
4338
+ const pruned = this.inputBudgetPruner.prune({
4339
+ messages,
4340
+ contextTokens: profile.contextTokens
4341
+ });
4342
+ const toolDefinitions = this.options.toolRegistry.getToolDefinitions().map((tool) => ({
4343
+ type: "function",
4344
+ function: {
4345
+ name: tool.name,
4346
+ description: tool.description,
4347
+ parameters: tool.parameters
4348
+ }
4349
+ }));
4350
+ return {
4351
+ messages: pruned.messages,
4352
+ tools: filterTools(toolDefinitions, requestedToolNames),
4353
+ model: effectiveModel,
4354
+ thinkingLevel: runtimeThinking
4355
+ };
4356
+ }
4357
+ };
4358
+
4359
+ // src/cli/commands/ncp/nextclaw-agent-session-store.ts
4360
+ import { sanitizeAssistantReplyTags as sanitizeAssistantReplyTags2 } from "@nextclaw/ncp";
4361
+ function tryParseJson(value) {
4362
+ try {
4363
+ return JSON.parse(value);
4364
+ } catch {
4365
+ return value;
4366
+ }
4367
+ }
4368
+ function toTextPart(text) {
4369
+ return text.length > 0 ? { type: "text", text } : null;
4370
+ }
4371
+ function contentToParts(content) {
4372
+ if (typeof content === "string") {
4373
+ const textPart = toTextPart(content);
4374
+ return textPart ? [textPart] : [];
4375
+ }
4376
+ if (Array.isArray(content)) {
4377
+ return content.length > 0 ? [
4378
+ {
4379
+ type: "extension",
4380
+ extensionType: "nextclaw.legacy.content-array",
4381
+ data: structuredClone(content)
4382
+ }
4383
+ ] : [];
4384
+ }
4385
+ if (content && typeof content === "object") {
4386
+ return [
4387
+ {
4388
+ type: "extension",
4389
+ extensionType: "nextclaw.legacy.content-object",
4390
+ data: structuredClone(content)
4391
+ }
4392
+ ];
4393
+ }
4394
+ return [];
4395
+ }
4396
+ function parseLegacyToolCalls(value) {
4397
+ if (!Array.isArray(value)) {
4398
+ return [];
4399
+ }
4400
+ return value.map((entry) => {
4401
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
4402
+ return null;
4403
+ }
4404
+ const toolCall = entry;
4405
+ const id = normalizeString(toolCall.id);
4406
+ const rawFunction = toolCall.function;
4407
+ if (!id || !rawFunction || typeof rawFunction !== "object" || Array.isArray(rawFunction)) {
4408
+ return null;
4409
+ }
4410
+ const fn = rawFunction;
4411
+ const name = normalizeString(fn.name);
4412
+ const args = typeof fn.arguments === "string" ? fn.arguments : JSON.stringify(fn.arguments ?? {});
4413
+ if (!name) {
4414
+ return null;
4415
+ }
4416
+ return {
4417
+ id,
4418
+ type: "function",
4419
+ function: {
4420
+ name,
4421
+ arguments: args
4422
+ }
4423
+ };
4424
+ }).filter((entry) => entry !== null);
4425
+ }
4426
+ function createMessageId(sessionId, index, role, timestamp) {
4427
+ const safeRole = role.trim().toLowerCase() || "message";
4428
+ return `${sessionId}:${safeRole}:${index}:${timestamp}`;
4429
+ }
4430
+ function isNcpMessagePart(value) {
4431
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.type === "string";
4432
+ }
4433
+ function readStoredNcpParts(message) {
4434
+ const rawParts = message.ncp_parts;
4435
+ if (!Array.isArray(rawParts)) {
4436
+ return null;
4437
+ }
4438
+ const parts = rawParts.filter((part) => isNcpMessagePart(part));
4439
+ if (parts.length !== rawParts.length) {
4440
+ return null;
4441
+ }
4442
+ return structuredClone(parts);
4443
+ }
4444
+ function buildAssistantMessage(params) {
4445
+ const timestamp = ensureIsoTimestamp(params.message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
4446
+ const replyTo = typeof params.message.reply_to === "string" && params.message.reply_to.trim().length > 0 ? params.message.reply_to.trim() : void 0;
4447
+ const storedParts = readStoredNcpParts(params.message);
4448
+ if (storedParts) {
4449
+ return sanitizeAssistantReplyTags2({
4450
+ id: createMessageId(params.sessionId, params.index, "assistant", timestamp),
4451
+ sessionId: params.sessionId,
4452
+ role: "assistant",
4453
+ status: "final",
4454
+ timestamp,
4455
+ parts: storedParts,
4456
+ metadata: replyTo ? { reply_to: replyTo } : void 0
4457
+ });
4458
+ }
4459
+ const toolCalls = parseLegacyToolCalls(params.message.tool_calls);
4460
+ const parts = [...contentToParts(params.message.content)];
4461
+ const reasoning = normalizeString(params.message.reasoning_content);
4462
+ if (reasoning) {
4463
+ parts.push({
4464
+ type: "reasoning",
4465
+ text: reasoning
4466
+ });
4467
+ }
4468
+ for (const toolCall of toolCalls) {
4469
+ parts.push({
4470
+ type: "tool-invocation",
4471
+ toolCallId: toolCall.id,
4472
+ toolName: toolCall.function.name,
4473
+ state: "call",
4474
+ args: tryParseJson(toolCall.function.arguments)
4475
+ });
4476
+ }
4477
+ return sanitizeAssistantReplyTags2({
4478
+ id: createMessageId(params.sessionId, params.index, "assistant", timestamp),
4479
+ sessionId: params.sessionId,
4480
+ role: "assistant",
4481
+ status: "final",
4482
+ timestamp,
4483
+ parts,
4484
+ metadata: replyTo ? { reply_to: replyTo } : void 0
4485
+ });
4486
+ }
4487
+ function buildGenericMessage(params) {
4488
+ const timestamp = ensureIsoTimestamp(params.message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
4489
+ return {
4490
+ id: createMessageId(params.sessionId, params.index, params.role, timestamp),
4491
+ sessionId: params.sessionId,
4492
+ role: params.role,
4493
+ status: "final",
4494
+ timestamp,
4495
+ parts: contentToParts(params.message.content)
4496
+ };
4497
+ }
4498
+ function attachToolResult(target, toolCallId, result, toolName) {
4499
+ target.parts = target.parts.map((part) => {
4500
+ if (part.type !== "tool-invocation" || part.toolCallId !== toolCallId) {
4501
+ return part;
4502
+ }
4503
+ return {
4504
+ ...part,
4505
+ toolName: toolName ?? part.toolName,
4506
+ state: "result",
4507
+ result
4508
+ };
4509
+ });
4510
+ }
4511
+ function toNcpMessages(sessionId, messages) {
4512
+ const ncpMessages = [];
4513
+ const assistantIndexByToolCallId = /* @__PURE__ */ new Map();
4514
+ messages.forEach((message, index) => {
4515
+ const role = normalizeString(message.role)?.toLowerCase() ?? "assistant";
4516
+ if (role === "tool") {
4517
+ const toolCallId = normalizeString(message.tool_call_id);
4518
+ if (toolCallId) {
4519
+ const assistantIndex = assistantIndexByToolCallId.get(toolCallId);
4520
+ if (assistantIndex !== void 0) {
4521
+ attachToolResult(
4522
+ ncpMessages[assistantIndex],
4523
+ toolCallId,
4524
+ structuredClone(message.content),
4525
+ normalizeString(message.name) ?? void 0
4526
+ );
4527
+ return;
4528
+ }
4529
+ }
4530
+ ncpMessages.push(
4531
+ buildGenericMessage({
4532
+ sessionId,
4533
+ index,
4534
+ role: "tool",
4535
+ message
4536
+ })
4537
+ );
4538
+ return;
4539
+ }
4540
+ if (role === "assistant") {
4541
+ const assistant = buildAssistantMessage({ sessionId, index, message });
4542
+ const assistantPosition = ncpMessages.push(assistant) - 1;
4543
+ for (const part of assistant.parts) {
4544
+ if (part.type === "tool-invocation" && part.toolCallId) {
4545
+ assistantIndexByToolCallId.set(part.toolCallId, assistantPosition);
4546
+ }
4547
+ }
4548
+ return;
4549
+ }
4550
+ const normalizedRole = role === "system" || role === "user" || role === "service" ? role : "user";
4551
+ ncpMessages.push(
4552
+ buildGenericMessage({
4553
+ sessionId,
4554
+ index,
4555
+ role: normalizedRole,
4556
+ message
4557
+ })
4558
+ );
4559
+ });
4560
+ return ncpMessages;
4561
+ }
4562
+ function resolveLegacyEventType(message) {
4563
+ const role = normalizeString(message.role)?.toLowerCase() ?? "";
4564
+ if (role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
4565
+ return "assistant.tool_call";
4566
+ }
4567
+ if (role === "tool") {
4568
+ return "tool.result";
4569
+ }
4570
+ if (role === "assistant") {
4571
+ return "message.assistant";
4572
+ }
4573
+ if (role === "user") {
4574
+ return "message.user";
4575
+ }
4576
+ if (role === "system") {
4577
+ return "message.system";
4578
+ }
4579
+ return `message.${role || "other"}`;
4580
+ }
4581
+ var NextclawAgentSessionStore = class {
4582
+ constructor(sessionManager, options = {}) {
4583
+ this.sessionManager = sessionManager;
4584
+ this.options = options;
4585
+ }
4586
+ async getSession(sessionId) {
4587
+ const session = this.sessionManager.getIfExists(sessionId);
4588
+ if (!session) {
4589
+ return null;
4590
+ }
4591
+ return {
4592
+ sessionId,
4593
+ messages: toNcpMessages(sessionId, session.messages),
4594
+ updatedAt: session.updatedAt.toISOString(),
4595
+ metadata: structuredClone(session.metadata)
4596
+ };
4597
+ }
4598
+ async listSessions() {
4599
+ const records = this.sessionManager.listSessions();
4600
+ const sessions = [];
4601
+ for (const record of records) {
4602
+ const sessionId = normalizeString(record.key);
4603
+ if (!sessionId) {
4604
+ continue;
4605
+ }
4606
+ const session = this.sessionManager.getIfExists(sessionId);
4607
+ if (!session) {
4608
+ continue;
4609
+ }
4610
+ sessions.push({
4611
+ sessionId,
4612
+ messages: toNcpMessages(sessionId, session.messages),
4613
+ updatedAt: session.updatedAt.toISOString(),
4614
+ metadata: structuredClone(session.metadata)
4615
+ });
4616
+ }
4617
+ sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
4618
+ return sessions;
4619
+ }
4620
+ async saveSession(sessionRecord) {
4621
+ if (this.options.writeMode === "runtime-owned") {
4622
+ return;
4623
+ }
4624
+ const session = this.sessionManager.getIfExists(sessionRecord.sessionId) ?? this.sessionManager.getOrCreate(sessionRecord.sessionId);
4625
+ const legacyMessages = toLegacyMessages(sessionRecord.messages);
4626
+ const nextMetadata = mergeSessionMetadata(
4627
+ session.metadata,
4628
+ extractMessageMetadata(sessionRecord.messages)
4629
+ );
4630
+ session.metadata = mergeSessionMetadata(nextMetadata, cloneMetadata(sessionRecord.metadata));
4631
+ this.sessionManager.clear(session);
4632
+ for (const message of legacyMessages) {
4633
+ this.sessionManager.appendEvent(session, {
4634
+ type: resolveLegacyEventType(message),
4635
+ timestamp: ensureIsoTimestamp(message.timestamp, (/* @__PURE__ */ new Date()).toISOString()),
4636
+ data: {
4637
+ message
4638
+ }
4639
+ });
4640
+ }
4641
+ if (legacyMessages.length === 0) {
4642
+ session.updatedAt = new Date(ensureIsoTimestamp(sessionRecord.updatedAt, (/* @__PURE__ */ new Date()).toISOString()));
4643
+ }
4644
+ this.sessionManager.save(session);
4645
+ }
4646
+ async deleteSession(sessionId) {
4647
+ const existing = await this.getSession(sessionId);
4648
+ if (!existing) {
4649
+ return null;
4650
+ }
4651
+ this.sessionManager.delete(sessionId);
4652
+ return existing;
4653
+ }
4654
+ };
4655
+
4656
+ // src/cli/commands/ncp/provider-manager-ncp-llm-api.ts
4657
+ import { parseThinkingLevel as parseThinkingLevel2 } from "@nextclaw/core";
4658
+ function normalizeModel(value) {
4659
+ if (typeof value !== "string") {
4660
+ return null;
4661
+ }
4662
+ const trimmed = value.trim();
4663
+ return trimmed.length > 0 ? trimmed : null;
4664
+ }
4665
+ function normalizeThinkingLevel(value) {
4666
+ return parseThinkingLevel2(value);
4667
+ }
4668
+ function normalizeFinishReason(value) {
4669
+ if (typeof value !== "string") {
4670
+ return "stop";
4671
+ }
4672
+ const trimmed = value.trim();
4673
+ return trimmed.length > 0 ? trimmed : "stop";
4674
+ }
4675
+ function toToolCallDelta(toolCall, index) {
4676
+ return {
4677
+ index,
4678
+ id: toolCall.id,
4679
+ type: "function",
4680
+ function: {
4681
+ name: toolCall.name,
4682
+ arguments: JSON.stringify(toolCall.arguments ?? {})
4683
+ }
4684
+ };
4685
+ }
4686
+ function toFinalChunk(response, options) {
4687
+ const delta = {};
4688
+ if (options.includeText && typeof response.content === "string" && response.content.length > 0) {
4689
+ delta.content = response.content;
4690
+ }
4691
+ if (options.includeReasoning && typeof response.reasoningContent === "string" && response.reasoningContent.length > 0) {
4692
+ delta.reasoning_content = response.reasoningContent;
4693
+ }
4694
+ if (options.includeToolCalls && response.toolCalls.length > 0) {
4695
+ delta.tool_calls = response.toolCalls.map((toolCall, index) => toToolCallDelta(toolCall, index));
4696
+ }
4697
+ return {
4698
+ choices: [
4699
+ {
4700
+ delta,
4701
+ finish_reason: normalizeFinishReason(response.finishReason)
4702
+ }
4703
+ ],
4704
+ usage: response.usage
4705
+ };
4706
+ }
4707
+ var ProviderManagerNcpLLMApi = class {
4708
+ constructor(providerManager) {
4709
+ this.providerManager = providerManager;
4710
+ }
4711
+ async *generate(input, options) {
4712
+ const model = normalizeModel(input.model) ?? this.providerManager.get(null).getDefaultModel();
4713
+ const thinkingLevel = normalizeThinkingLevel(input.thinkingLevel);
4714
+ let sawTextDelta = false;
4715
+ let sawReasoningDelta = false;
4716
+ let sawToolCallDelta = false;
4717
+ for await (const event of this.providerManager.chatStream({
4718
+ messages: input.messages,
4719
+ tools: input.tools,
4720
+ model,
4721
+ ...thinkingLevel ? { thinkingLevel } : {},
4722
+ maxTokens: input.max_tokens,
4723
+ signal: options?.signal
4724
+ })) {
4725
+ if (event.type === "delta") {
4726
+ sawTextDelta = true;
4727
+ yield {
4728
+ choices: [
4729
+ {
4730
+ delta: {
4731
+ content: event.delta
4732
+ }
4733
+ }
4734
+ ]
4735
+ };
4736
+ continue;
4737
+ }
4738
+ if (event.type === "reasoning_delta") {
4739
+ sawReasoningDelta = true;
4740
+ yield {
4741
+ choices: [
4742
+ {
4743
+ delta: {
4744
+ reasoning_content: event.delta
4745
+ }
4746
+ }
4747
+ ]
4748
+ };
4749
+ continue;
4750
+ }
4751
+ if (event.type === "tool_call_delta") {
4752
+ sawToolCallDelta = true;
4753
+ yield {
4754
+ choices: [
4755
+ {
4756
+ delta: {
4757
+ tool_calls: event.toolCalls
4758
+ }
4759
+ }
4760
+ ]
4761
+ };
4762
+ continue;
4763
+ }
4764
+ yield toFinalChunk(event.response, {
4765
+ includeText: !sawTextDelta,
4766
+ includeReasoning: !sawReasoningDelta,
4767
+ includeToolCalls: !sawToolCallDelta
4768
+ });
4769
+ }
4770
+ }
4771
+ };
4772
+
4773
+ // src/cli/commands/ncp/create-ui-ncp-agent.ts
4774
+ async function createUiNcpAgent(params) {
4775
+ const sessionStore = new NextclawAgentSessionStore(params.sessionManager);
4776
+ const backend = new DefaultNcpAgentBackend({
4777
+ endpointId: "nextclaw-ui-agent",
4778
+ sessionStore,
4779
+ createRuntime: ({ sessionId: _sessionId, stateManager }) => {
4780
+ const toolRegistry = new NextclawNcpToolRegistry({
4781
+ bus: params.bus,
4782
+ providerManager: params.providerManager,
4783
+ sessionManager: params.sessionManager,
4784
+ cronService: params.cronService,
4785
+ gatewayController: params.gatewayController,
4786
+ getConfig: params.getConfig,
4787
+ getExtensionRegistry: params.getExtensionRegistry
4788
+ });
4789
+ return new DefaultNcpAgentRuntime({
4790
+ contextBuilder: new NextclawNcpContextBuilder({
4791
+ sessionManager: params.sessionManager,
4792
+ toolRegistry,
4793
+ getConfig: params.getConfig,
4794
+ resolveMessageToolHints: params.resolveMessageToolHints
4795
+ }),
4796
+ llmApi: new ProviderManagerNcpLLMApi(params.providerManager),
4797
+ toolRegistry,
4798
+ stateManager
4799
+ });
4800
+ }
4801
+ });
4802
+ await backend.start();
4803
+ return {
4804
+ basePath: "/api/ncp/agent",
4805
+ agentClientEndpoint: createAgentClientFromServer(backend),
4806
+ streamProvider: backend,
4807
+ sessionApi: backend
4808
+ };
4809
+ }
4810
+
3522
4811
  // 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";
4812
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
3524
4813
  import { join as join4 } from "path";
3525
4814
  import {
3526
4815
  getDataDir as getDataDir5,
@@ -3536,7 +4825,7 @@ function createRunId() {
3536
4825
  const rand = Math.random().toString(36).slice(2, 10);
3537
4826
  return `run-${now}-${rand}`;
3538
4827
  }
3539
- function isRecord2(value) {
4828
+ function isRecord5(value) {
3540
4829
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3541
4830
  }
3542
4831
  function readOptionalString(value) {
@@ -3726,7 +5015,7 @@ var UiChatRunCoordinator = class {
3726
5015
  const parsedAgentId = parseAgentScopedSessionKey2(sessionKey)?.agentId;
3727
5016
  const agentId = explicitAgentId ?? readOptionalString(parsedAgentId);
3728
5017
  const model = readOptionalString(input.model);
3729
- const metadata = isRecord2(input.metadata) ? { ...input.metadata } : {};
5018
+ const metadata = isRecord5(input.metadata) ? { ...input.metadata } : {};
3730
5019
  if (model) {
3731
5020
  metadata.model = model;
3732
5021
  }
@@ -3947,14 +5236,14 @@ var UiChatRunCoordinator = class {
3947
5236
  this.emitRunUpdated(run);
3948
5237
  }
3949
5238
  waitForRunUpdate(run, signal) {
3950
- return new Promise((resolve10) => {
5239
+ return new Promise((resolve11) => {
3951
5240
  const wake = () => {
3952
5241
  cleanup();
3953
- resolve10();
5242
+ resolve11();
3954
5243
  };
3955
5244
  const onAbort = () => {
3956
5245
  cleanup();
3957
- resolve10();
5246
+ resolve11();
3958
5247
  };
3959
5248
  const cleanup = () => {
3960
5249
  run.waiters.delete(wake);
@@ -4075,7 +5364,7 @@ var UiChatRunCoordinator = class {
4075
5364
  `);
4076
5365
  }
4077
5366
  loadPersistedRuns() {
4078
- if (!existsSync7(RUNS_DIR)) {
5367
+ if (!existsSync8(RUNS_DIR)) {
4079
5368
  return;
4080
5369
  }
4081
5370
  for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
@@ -4084,7 +5373,7 @@ var UiChatRunCoordinator = class {
4084
5373
  }
4085
5374
  const path = join4(RUNS_DIR, entry.name);
4086
5375
  try {
4087
- const parsed = JSON.parse(readFileSync7(path, "utf-8"));
5376
+ const parsed = JSON.parse(readFileSync8(path, "utf-8"));
4088
5377
  const runId = readOptionalString(parsed.runId);
4089
5378
  const sessionKey = readOptionalString(parsed.sessionKey);
4090
5379
  if (!runId || !sessionKey) {
@@ -4147,7 +5436,7 @@ var {
4147
5436
  getDataDir: getDataDir6,
4148
5437
  getProvider,
4149
5438
  getProviderName,
4150
- getWorkspacePath: getWorkspacePath5,
5439
+ getWorkspacePath: getWorkspacePath6,
4151
5440
  HeartbeatService,
4152
5441
  LiteLLMProvider,
4153
5442
  loadConfig: loadConfig6,
@@ -4211,7 +5500,7 @@ function buildMarketplaceSkillInstallArgs(params) {
4211
5500
  function resolveCliSubcommandEntry(params) {
4212
5501
  const argvEntry = params.argvEntry?.trim();
4213
5502
  if (argvEntry) {
4214
- return resolve7(argvEntry);
5503
+ return resolve8(argvEntry);
4215
5504
  }
4216
5505
  return fileURLToPath2(new URL("../index.js", params.importMetaUrl));
4217
5506
  }
@@ -4222,7 +5511,7 @@ var ServiceCommands = class {
4222
5511
  async startGateway(options = {}) {
4223
5512
  const runtimeConfigPath = getConfigPath3();
4224
5513
  const config2 = resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath });
4225
- const workspace = getWorkspacePath5(config2.agents.defaults.workspace);
5514
+ const workspace = getWorkspacePath6(config2.agents.defaults.workspace);
4226
5515
  let pluginRegistry = loadPluginRegistry(config2, workspace);
4227
5516
  let extensionRegistry = toExtensionRegistry(pluginRegistry);
4228
5517
  logPluginDiagnostics(pluginRegistry);
@@ -4313,7 +5602,7 @@ var ServiceCommands = class {
4313
5602
  });
4314
5603
  reloader.setApplyAgentRuntimeConfig((nextConfig) => runtimePool.applyRuntimeConfig(nextConfig));
4315
5604
  reloader.setReloadPlugins(async (nextConfig) => {
4316
- const nextWorkspace = getWorkspacePath5(nextConfig.agents.defaults.workspace);
5605
+ const nextWorkspace = getWorkspacePath6(nextConfig.agents.defaults.workspace);
4317
5606
  const nextPluginRegistry = loadPluginRegistry(nextConfig, nextWorkspace);
4318
5607
  const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
4319
5608
  logPluginDiagnostics(nextPluginRegistry);
@@ -4405,19 +5694,36 @@ var ServiceCommands = class {
4405
5694
  } else {
4406
5695
  console.log("Warning: No channels enabled");
4407
5696
  }
4408
- this.startUiIfEnabled(uiConfig, uiStaticDir, cron2, runtimePool, sessionManager);
5697
+ await this.startUiIfEnabled(
5698
+ uiConfig,
5699
+ uiStaticDir,
5700
+ cron2,
5701
+ runtimePool,
5702
+ sessionManager,
5703
+ providerManager,
5704
+ bus,
5705
+ gatewayController,
5706
+ () => resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }),
5707
+ () => extensionRegistry,
5708
+ ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
5709
+ registry: pluginRegistry,
5710
+ channel,
5711
+ cfg: resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }),
5712
+ accountId
5713
+ })
5714
+ );
4409
5715
  const cronStatus = cron2.status();
4410
5716
  if (cronStatus.jobs > 0) {
4411
5717
  console.log(`\u2713 Cron: ${cronStatus.jobs} scheduled jobs`);
4412
5718
  }
4413
5719
  console.log("\u2713 Heartbeat: every 30m");
4414
- const configPath = resolve7(getConfigPath3());
5720
+ const configPath = resolve8(getConfigPath3());
4415
5721
  const watcher = chokidar.watch(configPath, {
4416
5722
  ignoreInitial: true,
4417
5723
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
4418
5724
  });
4419
5725
  watcher.on("all", (event, changedPath) => {
4420
- if (resolve7(changedPath) !== configPath) {
5726
+ if (resolve8(changedPath) !== configPath) {
4421
5727
  return;
4422
5728
  }
4423
5729
  if (event === "add") {
@@ -4509,7 +5815,7 @@ var ServiceCommands = class {
4509
5815
  if (!sentinel) {
4510
5816
  return;
4511
5817
  }
4512
- await new Promise((resolve10) => setTimeout(resolve10, 750));
5818
+ await new Promise((resolve11) => setTimeout(resolve11, 750));
4513
5819
  const payload = sentinel.payload;
4514
5820
  const summary = formatRestartSentinelMessage(payload);
4515
5821
  const sentinelSessionKey = this.normalizeOptionalString(payload.sessionKey);
@@ -4616,8 +5922,19 @@ var ServiceCommands = class {
4616
5922
  if (!staticDir) {
4617
5923
  console.log("Warning: UI frontend not found in package assets.");
4618
5924
  }
5925
+ const healthUrl = `${apiUrl}/health`;
5926
+ const portPreflight = await this.checkUiPortPreflight({
5927
+ host: uiConfig.host,
5928
+ port: uiConfig.port,
5929
+ healthUrl
5930
+ });
5931
+ if (!portPreflight.ok) {
5932
+ console.error(`Error: Cannot start ${APP_NAME2} because UI port ${uiConfig.port} is already occupied.`);
5933
+ console.error(portPreflight.message);
5934
+ return;
5935
+ }
4619
5936
  const logPath = resolveServiceLogPath();
4620
- const logDir = resolve7(logPath, "..");
5937
+ const logDir = resolve8(logPath, "..");
4621
5938
  mkdirSync5(logDir, { recursive: true });
4622
5939
  const logFd = openSync(logPath, "a");
4623
5940
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
@@ -4645,13 +5962,12 @@ var ServiceCommands = class {
4645
5962
  this.printStartupFailureDiagnostics({
4646
5963
  uiUrl,
4647
5964
  apiUrl,
4648
- healthUrl: `${apiUrl}/health`,
5965
+ healthUrl,
4649
5966
  logPath,
4650
5967
  lastProbeError: null
4651
5968
  });
4652
5969
  return;
4653
5970
  }
4654
- const healthUrl = `${apiUrl}/health`;
4655
5971
  this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
4656
5972
  let readiness = await this.waitForBackgroundServiceReady({
4657
5973
  pid: child.pid,
@@ -4766,14 +6082,14 @@ var ServiceCommands = class {
4766
6082
  const probe = await this.probeHealthEndpoint(params.healthUrl);
4767
6083
  if (!probe.healthy) {
4768
6084
  lastProbeError = probe.error;
4769
- await new Promise((resolve10) => setTimeout(resolve10, 200));
6085
+ await new Promise((resolve11) => setTimeout(resolve11, 200));
4770
6086
  continue;
4771
6087
  }
4772
- await new Promise((resolve10) => setTimeout(resolve10, 300));
6088
+ await new Promise((resolve11) => setTimeout(resolve11, 300));
4773
6089
  if (isProcessRunning(params.pid)) {
4774
6090
  return { ready: true, lastProbeError: null };
4775
6091
  }
4776
- await new Promise((resolve10) => setTimeout(resolve10, 200));
6092
+ await new Promise((resolve11) => setTimeout(resolve11, 200));
4777
6093
  }
4778
6094
  return { ready: false, lastProbeError };
4779
6095
  }
@@ -4810,6 +6126,58 @@ var ServiceCommands = class {
4810
6126
  }
4811
6127
  console.error(lines.join("\n"));
4812
6128
  }
6129
+ async checkUiPortPreflight(params) {
6130
+ const availability = await this.checkPortAvailability({
6131
+ host: params.host,
6132
+ port: params.port
6133
+ });
6134
+ if (availability.available) {
6135
+ return { ok: true };
6136
+ }
6137
+ const probe = await this.probeHealthEndpoint(params.healthUrl);
6138
+ const lines = [
6139
+ `Port probe: ${availability.detail}`
6140
+ ];
6141
+ if (probe.healthy) {
6142
+ lines.push(
6143
+ `Health probe: ${params.healthUrl} is already healthy. Another process is already serving this UI/API port.`
6144
+ );
6145
+ } else if (probe.error) {
6146
+ lines.push(`Health probe: ${probe.error}`);
6147
+ lines.push(
6148
+ "The port is occupied by a process that does not answer as a healthy NextClaw HTTP server."
6149
+ );
6150
+ }
6151
+ lines.push(
6152
+ `Fix: free port ${params.port} or start NextClaw with another port via --ui-port <port>.`
6153
+ );
6154
+ lines.push(
6155
+ `Inspect locally with: ss -ltnp | grep ${params.port} || lsof -iTCP:${params.port} -sTCP:LISTEN -n -P`
6156
+ );
6157
+ return {
6158
+ ok: false,
6159
+ message: lines.join("\n")
6160
+ };
6161
+ }
6162
+ async checkPortAvailability(params) {
6163
+ return await new Promise((resolve11) => {
6164
+ const server = createNetServer2();
6165
+ server.once("error", (error) => {
6166
+ resolve11({
6167
+ available: false,
6168
+ detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
6169
+ });
6170
+ });
6171
+ server.listen(params.port, params.host, () => {
6172
+ server.close(() => {
6173
+ resolve11({
6174
+ available: true,
6175
+ detail: `bind ok on ${params.host}:${params.port}`
6176
+ });
6177
+ });
6178
+ });
6179
+ });
6180
+ }
4813
6181
  getHeaderValue(headers, key) {
4814
6182
  const value = headers[key];
4815
6183
  if (typeof value === "string") {
@@ -4838,7 +6206,7 @@ var ServiceCommands = class {
4838
6206
  return { healthy: false, error: "invalid health URL" };
4839
6207
  }
4840
6208
  const requestImpl = parsed.protocol === "https:" ? httpsRequest : httpRequest;
4841
- return new Promise((resolve10) => {
6209
+ return new Promise((resolve11) => {
4842
6210
  const req = requestImpl(
4843
6211
  {
4844
6212
  protocol: parsed.protocol,
@@ -4874,19 +6242,19 @@ var ServiceCommands = class {
4874
6242
  if (bodySnippet) {
4875
6243
  details.push(`body=${bodySnippet}`);
4876
6244
  }
4877
- resolve10({ healthy: false, error: details.join("; ") });
6245
+ resolve11({ healthy: false, error: details.join("; ") });
4878
6246
  return;
4879
6247
  }
4880
6248
  try {
4881
6249
  const payload = JSON.parse(responseText);
4882
6250
  const healthy = payload?.ok === true && payload?.data?.status === "ok";
4883
6251
  if (!healthy) {
4884
- resolve10({ healthy: false, error: "health payload not ok" });
6252
+ resolve11({ healthy: false, error: "health payload not ok" });
4885
6253
  return;
4886
6254
  }
4887
- resolve10({ healthy: true, error: null });
6255
+ resolve11({ healthy: true, error: null });
4888
6256
  } catch {
4889
- resolve10({ healthy: false, error: "invalid health JSON response" });
6257
+ resolve11({ healthy: false, error: "invalid health JSON response" });
4890
6258
  }
4891
6259
  });
4892
6260
  }
@@ -4895,7 +6263,7 @@ var ServiceCommands = class {
4895
6263
  req.destroy(new Error("probe timeout"));
4896
6264
  });
4897
6265
  req.on("error", (error) => {
4898
- resolve10({ healthy: false, error: error.message || String(error) });
6266
+ resolve11({ healthy: false, error: error.message || String(error) });
4899
6267
  });
4900
6268
  req.end();
4901
6269
  });
@@ -4945,13 +6313,22 @@ var ServiceCommands = class {
4945
6313
  const publicBase = `http://${publicIp}:${port}`;
4946
6314
  console.log(`Public UI (if firewall/NAT allows): ${publicBase}`);
4947
6315
  console.log(`Public API (if firewall/NAT allows): ${publicBase}/api`);
6316
+ console.log(
6317
+ `Public deploy note: NextClaw serves plain HTTP on ${port}.`
6318
+ );
6319
+ console.log(
6320
+ `For https:// or standard 80/443 access, terminate TLS in Nginx/Caddy and proxy to http://127.0.0.1:${port}.`
6321
+ );
6322
+ console.log(
6323
+ `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).`
6324
+ );
4948
6325
  }
4949
6326
  printServiceControlHints() {
4950
6327
  console.log("Service controls:");
4951
6328
  console.log(` - Check status: ${APP_NAME2} status`);
4952
6329
  console.log(` - If you need to stop the service, run: ${APP_NAME2} stop`);
4953
6330
  }
4954
- startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager) {
6331
+ async startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager, providerManager, bus, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints) {
4955
6332
  if (!uiConfig.enabled) {
4956
6333
  return;
4957
6334
  }
@@ -5022,6 +6399,19 @@ var ServiceCommands = class {
5022
6399
  publishUiEvent?.({ type: "run.updated", payload: { run } });
5023
6400
  }
5024
6401
  });
6402
+ const ncpAgent = await createUiNcpAgent({
6403
+ bus,
6404
+ providerManager,
6405
+ sessionManager,
6406
+ cronService,
6407
+ gatewayController,
6408
+ getConfig,
6409
+ getExtensionRegistry,
6410
+ resolveMessageToolHints: ({ channel, accountId }) => resolveMessageToolHints({
6411
+ channel,
6412
+ accountId
6413
+ })
6414
+ });
5025
6415
  const uiServer = startUiServer({
5026
6416
  host: uiConfig.host,
5027
6417
  port: uiConfig.port,
@@ -5040,6 +6430,7 @@ var ServiceCommands = class {
5040
6430
  uninstallSkill: (slug) => this.uninstallMarketplaceSkill(slug)
5041
6431
  }
5042
6432
  },
6433
+ ncpAgent,
5043
6434
  chatRuntime: {
5044
6435
  listSessionTypes: async () => {
5045
6436
  const options = runtimePool.listAvailableEngineKinds().map((value) => ({
@@ -5134,7 +6525,7 @@ var ServiceCommands = class {
5134
6525
  if (params.kind && params.kind !== "marketplace") {
5135
6526
  throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
5136
6527
  }
5137
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
6528
+ const workspace = getWorkspacePath6(loadConfig6().agents.defaults.workspace);
5138
6529
  const args = buildMarketplaceSkillInstallArgs({
5139
6530
  slug: params.slug,
5140
6531
  workspace,
@@ -5168,9 +6559,9 @@ var ServiceCommands = class {
5168
6559
  return { message: summary };
5169
6560
  }
5170
6561
  async uninstallMarketplaceSkill(slug) {
5171
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
6562
+ const workspace = getWorkspacePath6(loadConfig6().agents.defaults.workspace);
5172
6563
  const targetDir = join5(workspace, "skills", slug);
5173
- if (!existsSync8(targetDir)) {
6564
+ if (!existsSync9(targetDir)) {
5174
6565
  throw new Error(`Skill not installed in workspace: ${slug}`);
5175
6566
  }
5176
6567
  rmSync4(targetDir, { recursive: true, force: true });
@@ -5179,10 +6570,10 @@ var ServiceCommands = class {
5179
6570
  };
5180
6571
  }
5181
6572
  installBuiltinMarketplaceSkill(slug, force) {
5182
- const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
6573
+ const workspace = getWorkspacePath6(loadConfig6().agents.defaults.workspace);
5183
6574
  const destination = join5(workspace, "skills", slug);
5184
6575
  const destinationSkillFile = join5(destination, "SKILL.md");
5185
- if (existsSync8(destinationSkillFile) && !force) {
6576
+ if (existsSync9(destinationSkillFile) && !force) {
5186
6577
  return {
5187
6578
  message: `${slug} is already installed`
5188
6579
  };
@@ -5190,7 +6581,7 @@ var ServiceCommands = class {
5190
6581
  const loader = createSkillsLoader(workspace);
5191
6582
  const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
5192
6583
  if (!builtin) {
5193
- if (existsSync8(destinationSkillFile)) {
6584
+ if (existsSync9(destinationSkillFile)) {
5194
6585
  return {
5195
6586
  message: `${slug} is already installed`
5196
6587
  };
@@ -5257,9 +6648,9 @@ ${stderr}`.trim();
5257
6648
  };
5258
6649
 
5259
6650
  // 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";
6651
+ import { cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
5261
6652
  import { createRequire } from "module";
5262
- import { dirname as dirname3, join as join6, resolve as resolve8 } from "path";
6653
+ import { dirname as dirname3, join as join6, resolve as resolve9 } from "path";
5263
6654
  import { fileURLToPath as fileURLToPath3 } from "url";
5264
6655
  import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
5265
6656
  import { spawnSync as spawnSync3 } from "child_process";
@@ -5290,27 +6681,27 @@ var WorkspaceManager = class {
5290
6681
  ];
5291
6682
  for (const entry of templateFiles) {
5292
6683
  const filePath = join6(workspace, entry.target);
5293
- if (!force && existsSync9(filePath)) {
6684
+ if (!force && existsSync10(filePath)) {
5294
6685
  continue;
5295
6686
  }
5296
6687
  const templatePath = join6(templateDir, entry.source);
5297
- if (!existsSync9(templatePath)) {
6688
+ if (!existsSync10(templatePath)) {
5298
6689
  console.warn(`Warning: Template file missing: ${templatePath}`);
5299
6690
  continue;
5300
6691
  }
5301
- const raw = readFileSync8(templatePath, "utf-8");
6692
+ const raw = readFileSync9(templatePath, "utf-8");
5302
6693
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
5303
6694
  mkdirSync6(dirname3(filePath), { recursive: true });
5304
6695
  writeFileSync5(filePath, content);
5305
6696
  created.push(entry.target);
5306
6697
  }
5307
6698
  const memoryDir = join6(workspace, "memory");
5308
- if (!existsSync9(memoryDir)) {
6699
+ if (!existsSync10(memoryDir)) {
5309
6700
  mkdirSync6(memoryDir, { recursive: true });
5310
6701
  created.push(join6("memory", ""));
5311
6702
  }
5312
6703
  const skillsDir = join6(workspace, "skills");
5313
- if (!existsSync9(skillsDir)) {
6704
+ if (!existsSync10(skillsDir)) {
5314
6705
  mkdirSync6(skillsDir, { recursive: true });
5315
6706
  created.push(join6("skills", ""));
5316
6707
  }
@@ -5332,11 +6723,11 @@ var WorkspaceManager = class {
5332
6723
  continue;
5333
6724
  }
5334
6725
  const src = join6(sourceDir, entry.name);
5335
- if (!existsSync9(join6(src, "SKILL.md"))) {
6726
+ if (!existsSync10(join6(src, "SKILL.md"))) {
5336
6727
  continue;
5337
6728
  }
5338
6729
  const dest = join6(targetDir, entry.name);
5339
- if (!force && existsSync9(dest)) {
6730
+ if (!force && existsSync10(dest)) {
5340
6731
  continue;
5341
6732
  }
5342
6733
  try {
@@ -5353,13 +6744,13 @@ var WorkspaceManager = class {
5353
6744
  try {
5354
6745
  const require2 = createRequire(import.meta.url);
5355
6746
  const entry = require2.resolve("@nextclaw/core");
5356
- const pkgRoot = resolve8(dirname3(entry), "..");
6747
+ const pkgRoot = resolve9(dirname3(entry), "..");
5357
6748
  const distSkills = join6(pkgRoot, "dist", "skills");
5358
- if (existsSync9(distSkills)) {
6749
+ if (existsSync10(distSkills)) {
5359
6750
  return distSkills;
5360
6751
  }
5361
6752
  const srcSkills = join6(pkgRoot, "src", "agent", "skills");
5362
- if (existsSync9(srcSkills)) {
6753
+ if (existsSync10(srcSkills)) {
5363
6754
  return srcSkills;
5364
6755
  }
5365
6756
  return null;
@@ -5372,11 +6763,11 @@ var WorkspaceManager = class {
5372
6763
  if (override) {
5373
6764
  return override;
5374
6765
  }
5375
- const cliDir = resolve8(fileURLToPath3(new URL(".", import.meta.url)));
5376
- const pkgRoot = resolve8(cliDir, "..", "..");
6766
+ const cliDir = resolve9(fileURLToPath3(new URL(".", import.meta.url)));
6767
+ const pkgRoot = resolve9(cliDir, "..", "..");
5377
6768
  const candidates = [join6(pkgRoot, "templates")];
5378
6769
  for (const candidate of candidates) {
5379
- if (existsSync9(candidate)) {
6770
+ if (existsSync10(candidate)) {
5380
6771
  return candidate;
5381
6772
  }
5382
6773
  }
@@ -5384,21 +6775,21 @@ var WorkspaceManager = class {
5384
6775
  }
5385
6776
  getBridgeDir() {
5386
6777
  const userBridge = join6(getDataDir7(), "bridge");
5387
- if (existsSync9(join6(userBridge, "dist", "index.js"))) {
6778
+ if (existsSync10(join6(userBridge, "dist", "index.js"))) {
5388
6779
  return userBridge;
5389
6780
  }
5390
6781
  if (!which("npm")) {
5391
6782
  console.error("npm not found. Please install Node.js >= 18.");
5392
6783
  process.exit(1);
5393
6784
  }
5394
- const cliDir = resolve8(fileURLToPath3(new URL(".", import.meta.url)));
5395
- const pkgRoot = resolve8(cliDir, "..", "..");
6785
+ const cliDir = resolve9(fileURLToPath3(new URL(".", import.meta.url)));
6786
+ const pkgRoot = resolve9(cliDir, "..", "..");
5396
6787
  const pkgBridge = join6(pkgRoot, "bridge");
5397
6788
  const srcBridge = join6(pkgRoot, "..", "..", "bridge");
5398
6789
  let source = null;
5399
- if (existsSync9(join6(pkgBridge, "package.json"))) {
6790
+ if (existsSync10(join6(pkgBridge, "package.json"))) {
5400
6791
  source = pkgBridge;
5401
- } else if (existsSync9(join6(srcBridge, "package.json"))) {
6792
+ } else if (existsSync10(join6(srcBridge, "package.json"))) {
5402
6793
  source = srcBridge;
5403
6794
  }
5404
6795
  if (!source) {
@@ -5406,8 +6797,8 @@ var WorkspaceManager = class {
5406
6797
  process.exit(1);
5407
6798
  }
5408
6799
  console.log(`${this.logo} Setting up bridge...`);
5409
- mkdirSync6(resolve8(userBridge, ".."), { recursive: true });
5410
- if (existsSync9(userBridge)) {
6800
+ mkdirSync6(resolve9(userBridge, ".."), { recursive: true });
6801
+ if (existsSync10(userBridge)) {
5411
6802
  rmSync5(userBridge, { recursive: true, force: true });
5412
6803
  }
5413
6804
  cpSync3(source, userBridge, {
@@ -5443,7 +6834,7 @@ function resolveSkillsInstallWorkdir(params) {
5443
6834
  if (params.explicitWorkdir) {
5444
6835
  return expandHome2(params.explicitWorkdir);
5445
6836
  }
5446
- return getWorkspacePath6(params.configuredWorkspace);
6837
+ return getWorkspacePath7(params.configuredWorkspace);
5447
6838
  }
5448
6839
  var CliRuntime = class {
5449
6840
  logo;
@@ -5542,7 +6933,7 @@ var CliRuntime = class {
5542
6933
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
5543
6934
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath4(new URL("./index.js", import.meta.url));
5544
6935
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
5545
- const serviceStatePath = resolve9(getDataDir8(), "run", "service.json");
6936
+ const serviceStatePath = resolve10(getDataDir8(), "run", "service.json");
5546
6937
  const helperScript = [
5547
6938
  'const { spawnSync } = require("node:child_process");',
5548
6939
  'const { readFileSync } = require("node:fs");',
@@ -5672,7 +7063,7 @@ var CliRuntime = class {
5672
7063
  const force = Boolean(options.force);
5673
7064
  const configPath = getConfigPath4();
5674
7065
  let createdConfig = false;
5675
- if (!existsSync10(configPath)) {
7066
+ if (!existsSync11(configPath)) {
5676
7067
  const config3 = ConfigSchema2.parse({});
5677
7068
  saveConfig6(config3);
5678
7069
  createdConfig = true;
@@ -5680,7 +7071,7 @@ var CliRuntime = class {
5680
7071
  const config2 = loadConfig7();
5681
7072
  const workspaceSetting = config2.agents.defaults.workspace;
5682
7073
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
5683
- const workspaceExisted = existsSync10(workspacePath);
7074
+ const workspaceExisted = existsSync11(workspacePath);
5684
7075
  mkdirSync7(workspacePath, { recursive: true });
5685
7076
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
5686
7077
  workspacePath,
@@ -5871,7 +7262,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5871
7262
  async agent(opts) {
5872
7263
  const configPath = getConfigPath4();
5873
7264
  const config2 = resolveConfigSecrets3(loadConfig7(), { configPath });
5874
- const workspace = getWorkspacePath6(config2.agents.defaults.workspace);
7265
+ const workspace = getWorkspacePath7(config2.agents.defaults.workspace);
5875
7266
  const pluginRegistry = loadPluginRegistry(config2, workspace);
5876
7267
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
5877
7268
  logPluginDiagnostics(pluginRegistry);
@@ -5939,9 +7330,9 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
5939
7330
  `
5940
7331
  );
5941
7332
  const historyFile = join7(getDataDir8(), "history", "cli_history");
5942
- const historyDir = resolve9(historyFile, "..");
7333
+ const historyDir = resolve10(historyFile, "..");
5943
7334
  mkdirSync7(historyDir, { recursive: true });
5944
- const history = existsSync10(historyFile) ? readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean) : [];
7335
+ const history = existsSync11(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
5945
7336
  const rl = createInterface2({
5946
7337
  input: process.stdin,
5947
7338
  output: process.stdout
@@ -6118,6 +7509,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
6118
7509
  async skillsPublish(options) {
6119
7510
  const result = await publishMarketplaceSkill({
6120
7511
  skillDir: expandHome2(options.dir),
7512
+ metaFile: options.meta ? expandHome2(options.meta) : void 0,
6121
7513
  slug: options.slug,
6122
7514
  name: options.name,
6123
7515
  summary: options.summary,
@@ -6131,12 +7523,13 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
6131
7523
  apiBaseUrl: options.apiBaseUrl,
6132
7524
  token: options.token
6133
7525
  });
6134
- console.log(result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`);
6135
- console.log(` Files: ${result.fileCount}`);
7526
+ console.log(`${result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`}
7527
+ Files: ${result.fileCount}`);
6136
7528
  }
6137
7529
  async skillsUpdate(options) {
6138
7530
  const result = await publishMarketplaceSkill({
6139
7531
  skillDir: expandHome2(options.dir),
7532
+ metaFile: options.meta ? expandHome2(options.meta) : void 0,
6140
7533
  slug: options.slug,
6141
7534
  name: options.name,
6142
7535
  summary: options.summary,
@@ -6173,8 +7566,8 @@ program.command("update").description(`Update ${APP_NAME5}`).option("--timeout <
6173
7566
  var skills = program.command("skills").description("Manage skills");
6174
7567
  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
7568
  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 }));
7569
+ 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 }));
7570
+ 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
7571
  var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
6179
7572
  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
7573
  plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));