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.
- package/README.md +2 -0
- package/dist/cli/index.js +1584 -191
- package/package.json +9 -5
- package/templates/USAGE.md +57 -0
- package/ui-dist/assets/{ChannelsList-DF2U-LY1.js → ChannelsList-DBcoVJRW.js} +1 -1
- package/ui-dist/assets/ChatPage-CD3cxyyM.js +37 -0
- package/ui-dist/assets/{DocBrowser-B9ws5JL7.js → DocBrowser-DDX2HMXW.js} +1 -1
- package/ui-dist/assets/{LogoBadge-DvGAzkZ3.js → LogoBadge-J53F_3JA.js} +1 -1
- package/ui-dist/assets/{MarketplacePage-DG5mHWJ8.js → MarketplacePage-0BZ4bza0.js} +2 -2
- package/ui-dist/assets/{ModelConfig-BL_HsOsm.js → ModelConfig-Wzq9wGHV.js} +1 -1
- package/ui-dist/assets/{ProvidersList-CH5z00YT.js → ProvidersList-kwzRS8_M.js} +1 -1
- package/ui-dist/assets/RuntimeConfig-N771_AM6.js +1 -0
- package/ui-dist/assets/{SearchConfig-BhaI0fUf.js → SearchConfig-DVt5QVa_.js} +1 -1
- package/ui-dist/assets/{SecretsConfig-CFoimOh9.js → SecretsConfig-CkwauPa8.js} +2 -2
- package/ui-dist/assets/SessionsConfig-C3mnHzkZ.js +2 -0
- package/ui-dist/assets/{session-run-status-TkIuGbVw.js → chat-message-pxr79GDs.js} +3 -3
- package/ui-dist/assets/{index-X5J6Mm--.js → index-BIvFMkN4.js} +1 -1
- package/ui-dist/assets/index-CzkY1reu.js +8 -0
- package/ui-dist/assets/{index-uMsNsQX6.js → index-GdpEEKnz.js} +1 -1
- package/ui-dist/assets/index-RZ0kHHRI.css +1 -0
- package/ui-dist/assets/{label-D8ly4a2P.js → label-CmksBHgc.js} +1 -1
- package/ui-dist/assets/{page-layout-BSYfvwbp.js → page-layout-Db0GbnhS.js} +1 -1
- package/ui-dist/assets/security-config-CjLFME5Q.js +1 -0
- package/ui-dist/assets/skeleton-CkpQeVWN.js +1 -0
- package/ui-dist/assets/{switch-Ce_g9lpN.js → switch-C24d-UJU.js} +1 -1
- package/ui-dist/assets/tabs-custom-D89bh-fc.js +1 -0
- package/ui-dist/assets/{useConfirmDialog-A8Ek8Wu7.js → useConfirmDialog-BeP35LcG.js} +2 -2
- package/ui-dist/assets/{vendor-B7ozqnFC.js → vendor-psXJBy9u.js} +65 -70
- package/ui-dist/index.html +3 -3
- package/ui-dist/assets/ChatPage-BX39y0U5.js +0 -36
- package/ui-dist/assets/RuntimeConfig-BplBgkwo.js +0 -1
- package/ui-dist/assets/SessionsConfig-BHTAYn9T.js +0 -2
- package/ui-dist/assets/index-BLeJkJ0o.css +0 -1
- package/ui-dist/assets/index-DK4TS5ev.js +0 -8
- package/ui-dist/assets/security-config-DlKEYHNN.js +0 -1
- package/ui-dist/assets/skeleton-CWbsNx2h.js +0 -1
- 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
|
|
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
|
|
30
|
-
import { join as join7, resolve as
|
|
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
|
|
190
|
-
import { basename, dirname, isAbsolute, join, relative, resolve as
|
|
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 =
|
|
196
|
-
if (!
|
|
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) ?
|
|
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 &&
|
|
206
|
-
if (
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
311
|
-
if (!
|
|
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(
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
const
|
|
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() ||
|
|
344
|
-
homepage: options.homepage?.trim() ||
|
|
345
|
-
publishedAt: options.publishedAt?.trim() ||
|
|
346
|
-
updatedAt: options.updatedAt?.trim() ||
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
|
752
|
+
import { resolve as resolve5 } from "path";
|
|
575
753
|
|
|
576
754
|
// src/cli/utils.ts
|
|
577
|
-
import { existsSync as
|
|
578
|
-
import { join as join2, resolve as
|
|
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 (!
|
|
810
|
+
if (!existsSync4(path)) {
|
|
633
811
|
return null;
|
|
634
812
|
}
|
|
635
813
|
try {
|
|
636
|
-
const raw =
|
|
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(
|
|
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 (
|
|
827
|
+
if (existsSync4(path)) {
|
|
650
828
|
rmSync3(path, { force: true });
|
|
651
829
|
}
|
|
652
830
|
}
|
|
653
831
|
function resolveServiceStatePath() {
|
|
654
|
-
return
|
|
832
|
+
return resolve4(getDataDir2(), "run", "service.json");
|
|
655
833
|
}
|
|
656
834
|
function resolveServiceLogPath() {
|
|
657
|
-
return
|
|
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((
|
|
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 =
|
|
684
|
-
const pkgRoot =
|
|
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 (
|
|
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
|
|
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) =>
|
|
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 =
|
|
957
|
+
let current = resolve4(startDir);
|
|
780
958
|
while (current.length > 0) {
|
|
781
959
|
const pkgPath = join2(current, "package.json");
|
|
782
|
-
if (
|
|
960
|
+
if (existsSync4(pkgPath)) {
|
|
783
961
|
try {
|
|
784
|
-
const raw =
|
|
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 =
|
|
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 =
|
|
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((
|
|
813
|
-
rl.once("line", (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 ?
|
|
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
|
|
889
|
-
import { resolve as
|
|
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 ||
|
|
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 =
|
|
1432
|
+
const resolved = resolve6(expandHome(normalized));
|
|
1255
1433
|
const config2 = loadConfig();
|
|
1256
|
-
if (
|
|
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((
|
|
1371
|
-
rl.question(`${question} [y/N] `, (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
|
|
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 =
|
|
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
|
|
2221
|
-
import { resolve as
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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:
|
|
2647
|
+
configExists: existsSync6(configPath),
|
|
2470
2648
|
workspacePath,
|
|
2471
|
-
workspaceExists:
|
|
2649
|
+
workspaceExists: existsSync6(workspacePath),
|
|
2472
2650
|
model: config2.agents.defaults.model,
|
|
2473
2651
|
providers,
|
|
2474
2652
|
serviceStatePath,
|
|
2475
|
-
serviceStateExists:
|
|
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 (!
|
|
2703
|
+
if (!existsSync6(path)) {
|
|
2526
2704
|
return [];
|
|
2527
2705
|
}
|
|
2528
2706
|
try {
|
|
2529
|
-
const lines =
|
|
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((
|
|
2717
|
+
return await new Promise((resolve11) => {
|
|
2540
2718
|
const server = createNetServer();
|
|
2541
2719
|
server.once("error", (error) => {
|
|
2542
|
-
|
|
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
|
-
|
|
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
|
|
2570
|
-
import { dirname as dirname2, join as join5, resolve as
|
|
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
|
|
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 (
|
|
2592
|
-
raw =
|
|
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
|
|
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
|
|
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 =
|
|
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((
|
|
5239
|
+
return new Promise((resolve11) => {
|
|
3951
5240
|
const wake = () => {
|
|
3952
5241
|
cleanup();
|
|
3953
|
-
|
|
5242
|
+
resolve11();
|
|
3954
5243
|
};
|
|
3955
5244
|
const onAbort = () => {
|
|
3956
5245
|
cleanup();
|
|
3957
|
-
|
|
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 (!
|
|
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(
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 (
|
|
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((
|
|
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 =
|
|
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
|
|
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((
|
|
6085
|
+
await new Promise((resolve11) => setTimeout(resolve11, 200));
|
|
4770
6086
|
continue;
|
|
4771
6087
|
}
|
|
4772
|
-
await new Promise((
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
6252
|
+
resolve11({ healthy: false, error: "health payload not ok" });
|
|
4885
6253
|
return;
|
|
4886
6254
|
}
|
|
4887
|
-
|
|
6255
|
+
resolve11({ healthy: true, error: null });
|
|
4888
6256
|
} catch {
|
|
4889
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
6562
|
+
const workspace = getWorkspacePath6(loadConfig6().agents.defaults.workspace);
|
|
5172
6563
|
const targetDir = join5(workspace, "skills", slug);
|
|
5173
|
-
if (!
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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 &&
|
|
6684
|
+
if (!force && existsSync10(filePath)) {
|
|
5294
6685
|
continue;
|
|
5295
6686
|
}
|
|
5296
6687
|
const templatePath = join6(templateDir, entry.source);
|
|
5297
|
-
if (!
|
|
6688
|
+
if (!existsSync10(templatePath)) {
|
|
5298
6689
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
5299
6690
|
continue;
|
|
5300
6691
|
}
|
|
5301
|
-
const raw =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
6726
|
+
if (!existsSync10(join6(src, "SKILL.md"))) {
|
|
5336
6727
|
continue;
|
|
5337
6728
|
}
|
|
5338
6729
|
const dest = join6(targetDir, entry.name);
|
|
5339
|
-
if (!force &&
|
|
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 =
|
|
6747
|
+
const pkgRoot = resolve9(dirname3(entry), "..");
|
|
5357
6748
|
const distSkills = join6(pkgRoot, "dist", "skills");
|
|
5358
|
-
if (
|
|
6749
|
+
if (existsSync10(distSkills)) {
|
|
5359
6750
|
return distSkills;
|
|
5360
6751
|
}
|
|
5361
6752
|
const srcSkills = join6(pkgRoot, "src", "agent", "skills");
|
|
5362
|
-
if (
|
|
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 =
|
|
5376
|
-
const pkgRoot =
|
|
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 (
|
|
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 (
|
|
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 =
|
|
5395
|
-
const pkgRoot =
|
|
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 (
|
|
6790
|
+
if (existsSync10(join6(pkgBridge, "package.json"))) {
|
|
5400
6791
|
source = pkgBridge;
|
|
5401
|
-
} else if (
|
|
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(
|
|
5410
|
-
if (
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7333
|
+
const historyDir = resolve10(historyFile, "..");
|
|
5943
7334
|
mkdirSync7(historyDir, { recursive: true });
|
|
5944
|
-
const history =
|
|
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
|
-
|
|
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));
|