nextclaw 0.16.33 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli/index.js +1493 -1042
  2. package/package.json +15 -14
  3. package/{templates → resources}/USAGE.md +98 -47
  4. package/templates/AGENTS.md +1 -1
  5. package/ui-dist/assets/{ChannelsList-DVDu1xvz.js → ChannelsList-NKNKsf1J.js} +1 -1
  6. package/ui-dist/assets/ChatPage-p23OnnEI.js +43 -0
  7. package/ui-dist/assets/DocBrowser-C8b2uPgL.js +1 -0
  8. package/ui-dist/assets/{DocBrowser-BmtBLFU0.js → DocBrowser-DxdSujSc.js} +1 -1
  9. package/ui-dist/assets/{DocBrowserContext-YIKkPb76.js → DocBrowserContext-CQ-8jMha.js} +1 -1
  10. package/ui-dist/assets/{LogoBadge-F7ZWdxLT.js → LogoBadge-D-KQIN4U.js} +1 -1
  11. package/ui-dist/assets/{MarketplacePage-Buo9HrOz.js → MarketplacePage-CRNvxtvx.js} +2 -2
  12. package/ui-dist/assets/MarketplacePage-GGkEXowp.js +1 -0
  13. package/ui-dist/assets/{McpMarketplacePage-JnkYwK7p.js → McpMarketplacePage-Cu7GmCcc.js} +2 -2
  14. package/ui-dist/assets/{ModelConfig-BYRhgp0c.js → ModelConfig-CEpx9fro.js} +1 -1
  15. package/ui-dist/assets/{ProvidersList-DmLyyHvX.js → ProvidersList-BWbUb7-2.js} +1 -1
  16. package/ui-dist/assets/{RemoteAccessPage-CDSSvH7Z.js → RemoteAccessPage-NsawrZb0.js} +1 -1
  17. package/ui-dist/assets/RuntimeConfig-BJHBsVTd.js +1 -0
  18. package/ui-dist/assets/{SearchConfig-D5f1EkLE.js → SearchConfig-BsaX_WYy.js} +1 -1
  19. package/ui-dist/assets/{SecretsConfig-D61IKcYt.js → SecretsConfig-CgDZOd3w.js} +1 -1
  20. package/ui-dist/assets/{SessionsConfig-BRIxVTEv.js → SessionsConfig-Dd-KM7F7.js} +2 -2
  21. package/ui-dist/assets/{book-open-CXoF5nQC.js → book-open-FnK2xCQd.js} +1 -1
  22. package/ui-dist/assets/chat-session-display-BD_AN71I.js +1 -0
  23. package/ui-dist/assets/{chunk-JZWAC4HX-CvRWvTy5.js → chunk-JZWAC4HX-B5l0hr_u.js} +1 -1
  24. package/ui-dist/assets/{config-DJswxxE8.js → config-JKmXfZ3q.js} +1 -1
  25. package/ui-dist/assets/{createLucideIcon-CjGHOWb6.js → createLucideIcon-o1WWhwhd.js} +1 -1
  26. package/ui-dist/assets/{dist-nqTTbVdA.js → dist-C_moWYv7.js} +1 -1
  27. package/ui-dist/assets/{dist-Cl2QB-2y.js → dist-DazA6Wd_.js} +1 -1
  28. package/ui-dist/assets/{external-link-tIO7zING.js → external-link-BKje3SiD.js} +1 -1
  29. package/ui-dist/assets/{hash-JWUyl1pT.js → hash-DfW4DT8O.js} +1 -1
  30. package/ui-dist/assets/i18n-BK1w-oBy.js +1 -0
  31. package/ui-dist/assets/index-BZaB1TqM.js +6 -0
  32. package/ui-dist/assets/index-DaR9igPC.css +1 -0
  33. package/ui-dist/assets/{label-BIpeNu4r.js → label-BzDWmdOe.js} +1 -1
  34. package/ui-dist/assets/loader-circle-DdZPxBUz.js +1 -0
  35. package/ui-dist/assets/{logos-DThdM9lk.js → logos-CTLlde_T.js} +1 -1
  36. package/ui-dist/assets/{page-layout-D3Xo605Z.js → page-layout-BagR3t59.js} +1 -1
  37. package/ui-dist/assets/plus-DP2PSCPO.js +1 -0
  38. package/ui-dist/assets/{popover-BJRUGA_H.js → popover-5DWhNfd4.js} +1 -1
  39. package/ui-dist/assets/{provider-models-bz5y28rq.js → provider-models-DJ29qHuA.js} +1 -1
  40. package/ui-dist/assets/{react-7ZHqQtEV.js → react-C3yu5yge.js} +1 -1
  41. package/ui-dist/assets/{refresh-ccw-CC6-_QuL.js → refresh-ccw-BAJf-h7w.js} +1 -1
  42. package/ui-dist/assets/{save-DJM5RRWW.js → save-aa6z4GJL.js} +1 -1
  43. package/ui-dist/assets/search-pD6ZwQYF.js +1 -0
  44. package/ui-dist/assets/{security-config-DbUyWcQz.js → security-config-DRDxrApx.js} +1 -1
  45. package/ui-dist/assets/{select-DSkTc61S.js → select-BHJPiJWt.js} +1 -1
  46. package/ui-dist/assets/skeleton-D6kCk9Y6.js +1 -0
  47. package/ui-dist/assets/{status-dot-LNBlDu3q.js → status-dot-DUwsTIdv.js} +1 -1
  48. package/ui-dist/assets/{switch-Bo-Y46HZ.js → switch-B6nCfcOB.js} +1 -1
  49. package/ui-dist/assets/{tabs-custom-DXv507_2.js → tabs-custom-B57SMElx.js} +1 -1
  50. package/ui-dist/assets/{trash-2-DFZmW6Gg.js → trash-2-CrjYH5ok.js} +1 -1
  51. package/ui-dist/assets/{useConfirmDialog-COwYXDKm.js → useConfirmDialog-DsxnXB1B.js} +1 -1
  52. package/ui-dist/assets/{useMutation-DrZrOgVL.js → useMutation-oTTWXgLG.js} +1 -1
  53. package/ui-dist/assets/x-CTIQHUuD.js +1 -0
  54. package/ui-dist/index.html +18 -18
  55. package/ui-dist/assets/ChatPage-Z9tRzm_n.js +0 -43
  56. package/ui-dist/assets/DocBrowser-B9OaZjmg.js +0 -1
  57. package/ui-dist/assets/MarketplacePage-D6rVQEQR.js +0 -1
  58. package/ui-dist/assets/RuntimeConfig-v7a7Fe3x.js +0 -1
  59. package/ui-dist/assets/chat-session-display-D0WpnuRZ.js +0 -1
  60. package/ui-dist/assets/i18n-CDHMXlRZ.js +0 -1
  61. package/ui-dist/assets/index-BuwbBgmT.js +0 -6
  62. package/ui-dist/assets/index-bZ8cqQIS.css +0 -1
  63. package/ui-dist/assets/loader-circle-Cs8XVFTw.js +0 -1
  64. package/ui-dist/assets/plus-PHf8q-Ct.js +0 -1
  65. package/ui-dist/assets/search-C91yH_6y.js +0 -1
  66. package/ui-dist/assets/skeleton-Dzg-HOiN.js +0 -1
  67. package/ui-dist/assets/x-D7Q1yqSF.js +0 -1
package/dist/cli/index.js CHANGED
@@ -6,32 +6,18 @@ import { APP_NAME as APP_NAME6, APP_TAGLINE } from "@nextclaw/core";
6
6
  import { registerRemoteCommands } from "@nextclaw/remote";
7
7
 
8
8
  // src/cli/runtime.ts
9
- import {
10
- loadConfig as loadConfig21,
11
- saveConfig as saveConfig11,
12
- getConfigPath as getConfigPath11,
13
- getDataDir as getDataDir11,
14
- getWorkspacePath as getWorkspacePath13,
15
- expandHome as expandHome2,
16
- MessageBus as MessageBus3,
17
- AgentLoop,
18
- ProviderManager as ProviderManager2,
19
- resolveConfigSecrets as resolveConfigSecrets7,
20
- APP_NAME as APP_NAME5,
21
- DEFAULT_WORKSPACE_DIR,
22
- DEFAULT_WORKSPACE_PATH
23
- } from "@nextclaw/core";
9
+ import { loadConfig as loadConfig22, saveConfig as saveConfig11, getConfigPath as getConfigPath11, getDataDir as getDataDir11, getWorkspacePath as getWorkspacePath14, expandHome as expandHome3, MessageBus as MessageBus3, AgentLoop, ProviderManager as ProviderManager2, resolveConfigSecrets as resolveConfigSecrets7, APP_NAME as APP_NAME5, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH } from "@nextclaw/core";
24
10
  import { RemoteRuntimeActions } from "@nextclaw/remote";
25
11
  import {
26
12
  getPluginChannelBindings as getPluginChannelBindings6,
27
13
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints3,
28
14
  setPluginRuntimeBridge as setPluginRuntimeBridge3
29
15
  } from "@nextclaw/openclaw-compat";
30
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
31
- import { join as join9, resolve as resolve15 } from "path";
16
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
17
+ import { join as join9, resolve as resolve16 } from "path";
32
18
  import { createInterface as createInterface3 } from "readline";
33
19
  import { fileURLToPath as fileURLToPath5 } from "url";
34
- import { spawn as spawn4 } from "child_process";
20
+ import { spawn as spawn5 } from "child_process";
35
21
 
36
22
  // src/cli/restart-coordinator.ts
37
23
  var RestartCoordinator = class {
@@ -197,6 +183,26 @@ function parseSessionKey(sessionKey) {
197
183
  };
198
184
  }
199
185
 
186
+ // src/cli/runtime-helpers.ts
187
+ import { expandHome, getWorkspacePath } from "@nextclaw/core";
188
+ function resolveSkillsInstallWorkdir(params) {
189
+ if (params.explicitWorkdir) {
190
+ return expandHome(params.explicitWorkdir);
191
+ }
192
+ return getWorkspacePath(params.configuredWorkspace);
193
+ }
194
+ function parseStartTimeoutMs(value) {
195
+ if (value === void 0) {
196
+ return void 0;
197
+ }
198
+ const parsed = Number(value);
199
+ if (!Number.isFinite(parsed) || parsed <= 0) {
200
+ console.error("Invalid --start-timeout value. Provide milliseconds (e.g. 45000).");
201
+ process.exit(1);
202
+ }
203
+ return Math.floor(parsed);
204
+ }
205
+
200
206
  // src/cli/startup-trace.ts
201
207
  var STARTUP_TRACE_ENABLED = process.env.NEXTCLAW_STARTUP_TRACE === "1";
202
208
  var STARTUP_TRACE_ORIGIN_MS = Date.now();
@@ -238,7 +244,7 @@ async function measureStartupAsync(step, fn, fields) {
238
244
  }
239
245
 
240
246
  // src/cli/skills/marketplace.ts
241
- import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
247
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
242
248
  import { basename, dirname, isAbsolute, join, relative, resolve as resolve3 } from "path";
243
249
  import { SkillsLoader } from "@nextclaw/core";
244
250
 
@@ -456,8 +462,8 @@ function isRecord(value) {
456
462
  var MARKETPLACE_NETWORK_RETRY_ATTEMPTS = 5;
457
463
  var MARKETPLACE_NETWORK_RETRY_BASE_MS = 350;
458
464
  function sleepMs(ms) {
459
- return new Promise((resolve16) => {
460
- setTimeout(resolve16, ms);
465
+ return new Promise((resolve17) => {
466
+ setTimeout(resolve17, ms);
461
467
  });
462
468
  }
463
469
  function isRetryableMarketplaceNetworkError(error) {
@@ -503,36 +509,54 @@ async function installMarketplaceSkill(options) {
503
509
  if (!existsSync4(workdir)) {
504
510
  throw new Error(`Workdir does not exist: ${workdir}`);
505
511
  }
506
- const dirName = options.dir?.trim() || "skills";
507
- const destinationDir = isAbsolute(dirName) ? resolve3(dirName, slug) : resolve3(workdir, dirName, slug);
508
- const skillFile = join(destinationDir, "SKILL.md");
512
+ const destinationDir = resolveMarketplaceSkillDestinationDir({
513
+ workdir,
514
+ slug,
515
+ dir: options.dir
516
+ });
509
517
  const apiBase = resolveMarketplaceApiBase(options.apiBaseUrl);
510
518
  const item = await fetchMarketplaceSkillItem(apiBase, slug);
511
519
  if (item.install.kind === "builtin") {
512
- if (!options.force && existsSync4(destinationDir)) {
513
- if (existsSync4(skillFile)) {
514
- return {
515
- slug,
516
- destinationDir,
517
- alreadyInstalled: true,
518
- source: "builtin"
519
- };
520
- }
521
- throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
522
- }
523
- if (existsSync4(destinationDir) && options.force) {
524
- rmSync2(destinationDir, { recursive: true, force: true });
525
- }
526
- installBuiltinSkill(workdir, destinationDir, slug);
527
- return {
528
- slug,
529
- destinationDir,
530
- source: "builtin"
531
- };
520
+ return resolveBuiltinMarketplaceInstallResult({
521
+ workdir,
522
+ slug
523
+ });
532
524
  }
533
525
  const filesPayload = await fetchMarketplaceSkillFiles(apiBase, slug);
534
- if (!options.force && existsSync4(destinationDir)) {
535
- const existingDirState = inspectMarketplaceSkillDirectory(destinationDir, filesPayload.files);
526
+ const existingInstall = prepareMarketplaceSkillDestinationDir({
527
+ destinationDir,
528
+ files: filesPayload.files,
529
+ force: options.force,
530
+ slug
531
+ });
532
+ if (existingInstall) {
533
+ return existingInstall;
534
+ }
535
+ await writeMarketplaceSkillFiles({
536
+ destinationDir,
537
+ files: filesPayload.files,
538
+ apiBase,
539
+ slug
540
+ });
541
+ ensureInstalledMarketplaceSkill(destinationDir, slug);
542
+ return buildMarketplaceInstallResult(slug, destinationDir);
543
+ }
544
+ function resolveMarketplaceSkillDestinationDir(params) {
545
+ const dirName = params.dir?.trim() || "skills";
546
+ return isAbsolute(dirName) ? resolve3(dirName, params.slug) : resolve3(params.workdir, dirName, params.slug);
547
+ }
548
+ function resolveBuiltinMarketplaceInstallResult(params) {
549
+ return {
550
+ slug: params.slug,
551
+ destinationDir: resolveBuiltinSkillDir(params.workdir, params.slug),
552
+ alreadyInstalled: true,
553
+ source: "builtin"
554
+ };
555
+ }
556
+ function prepareMarketplaceSkillDestinationDir(params) {
557
+ const { destinationDir, files, force, slug } = params;
558
+ if (!force && existsSync4(destinationDir)) {
559
+ const existingDirState = inspectMarketplaceSkillDirectory(destinationDir, files);
536
560
  if (existingDirState === "installed") {
537
561
  return {
538
562
  slug,
@@ -541,17 +565,20 @@ async function installMarketplaceSkill(options) {
541
565
  source: "marketplace"
542
566
  };
543
567
  }
544
- if (existingDirState === "recoverable") {
545
- rmSync2(destinationDir, { recursive: true, force: true });
546
- } else {
568
+ if (existingDirState !== "recoverable") {
547
569
  throw new Error(`Skill directory already exists: ${destinationDir} (use --force)`);
548
570
  }
571
+ rmSync2(destinationDir, { recursive: true, force: true });
549
572
  }
550
- if (existsSync4(destinationDir) && options.force) {
573
+ if (force && existsSync4(destinationDir)) {
551
574
  rmSync2(destinationDir, { recursive: true, force: true });
552
575
  }
553
576
  mkdirSync2(destinationDir, { recursive: true });
554
- for (const file of filesPayload.files) {
577
+ return null;
578
+ }
579
+ async function writeMarketplaceSkillFiles(params) {
580
+ const { destinationDir, files, apiBase, slug } = params;
581
+ for (const file of files) {
555
582
  const targetPath = resolve3(destinationDir, ...file.path.split("/"));
556
583
  const rel = relative(destinationDir, targetPath);
557
584
  if (rel.startsWith("..") || isAbsolute(rel)) {
@@ -561,9 +588,13 @@ async function installMarketplaceSkill(options) {
561
588
  const bytes = file.contentBase64 ? decodeMarketplaceFileContent(file.path, file.contentBase64) : await fetchMarketplaceSkillFileBlob(apiBase, slug, file);
562
589
  writeFileSync2(targetPath, bytes);
563
590
  }
591
+ }
592
+ function ensureInstalledMarketplaceSkill(destinationDir, slug) {
564
593
  if (!existsSync4(join(destinationDir, "SKILL.md"))) {
565
594
  throw new Error(`Marketplace skill ${slug} does not include SKILL.md`);
566
595
  }
596
+ }
597
+ function buildMarketplaceInstallResult(slug, destinationDir) {
567
598
  return {
568
599
  slug,
569
600
  destinationDir,
@@ -696,13 +727,13 @@ function collectFiles(rootDir) {
696
727
  walk(rootDir, "");
697
728
  return output;
698
729
  }
699
- function installBuiltinSkill(workdir, destinationDir, skillName) {
730
+ function resolveBuiltinSkillDir(workdir, skillName) {
700
731
  const loader = new SkillsLoader(workdir);
701
- const workspaceSkill = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "workspace");
702
- if (!workspaceSkill) {
703
- throw new Error(`Workspace skill not found in local installation: ${skillName}`);
732
+ const builtinSkill = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "builtin");
733
+ if (!builtinSkill) {
734
+ throw new Error(`Built-in skill not found in local installation: ${skillName}`);
704
735
  }
705
- cpSync(dirname(workspaceSkill.path), destinationDir, { recursive: true, force: true });
736
+ return dirname(builtinSkill.path);
706
737
  }
707
738
  async function fetchMarketplaceSkillItem(apiBase, slug) {
708
739
  return runWithMarketplaceNetworkRetry(async () => {
@@ -909,10 +940,6 @@ async function resolvePublicIp(timeoutMs = 1500) {
909
940
  }
910
941
  return null;
911
942
  }
912
- function buildServeArgs(options) {
913
- const cliPath = fileURLToPath(new URL("./index.js", import.meta.url));
914
- return [cliPath, "serve", "--ui-port", String(options.uiPort)];
915
- }
916
943
  function readServiceState() {
917
944
  const path2 = resolveServiceStatePath();
918
945
  if (!existsSync5(path2)) {
@@ -965,7 +992,7 @@ async function waitForExit(pid, timeoutMs) {
965
992
  if (!isProcessRunning(pid)) {
966
993
  return true;
967
994
  }
968
- await new Promise((resolve16) => setTimeout(resolve16, 200));
995
+ await new Promise((resolve17) => setTimeout(resolve17, 200));
969
996
  }
970
997
  return !isProcessRunning(pid);
971
998
  }
@@ -1096,8 +1123,8 @@ function printAgentResponse(response) {
1096
1123
  async function prompt(rl, question) {
1097
1124
  rl.setPrompt(question);
1098
1125
  rl.prompt();
1099
- return new Promise((resolve16) => {
1100
- rl.once("line", (line) => resolve16(line));
1126
+ return new Promise((resolve17) => {
1127
+ rl.once("line", (line) => resolve17(line));
1101
1128
  });
1102
1129
  }
1103
1130
 
@@ -1159,7 +1186,7 @@ import {
1159
1186
  resolveUninstallDirectoryTargets
1160
1187
  } from "@nextclaw/openclaw-compat";
1161
1188
 
1162
- // src/cli/commands/plugin-command-utils.ts
1189
+ // src/cli/commands/plugin/plugin-command-utils.ts
1163
1190
  import { builtinProviderIds } from "@nextclaw/runtime";
1164
1191
  var RESERVED_PROVIDER_IDS = builtinProviderIds();
1165
1192
  var RESERVED_TOOL_NAMES = [
@@ -1208,7 +1235,7 @@ function appendPluginCapabilityLines(lines, plugin) {
1208
1235
  }
1209
1236
  }
1210
1237
 
1211
- // src/cli/commands/dev-first-party-plugin-load-paths.ts
1238
+ // src/cli/commands/plugin/dev-first-party-plugin-load-paths.ts
1212
1239
  import fs from "fs";
1213
1240
  import path from "path";
1214
1241
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1362,12 +1389,12 @@ var applyDevFirstPartyPluginLoadPaths = (config2, workspaceExtensionsDir) => {
1362
1389
  // src/cli/commands/plugins.ts
1363
1390
  import {
1364
1391
  loadConfig as loadConfig3,
1365
- getWorkspacePath as getWorkspacePath2
1392
+ getWorkspacePath as getWorkspacePath3
1366
1393
  } from "@nextclaw/core";
1367
1394
  import { createInterface } from "readline";
1368
1395
  import { resolve as resolve7 } from "path";
1369
1396
 
1370
- // src/cli/commands/plugin-mutation-actions.ts
1397
+ // src/cli/commands/plugin/plugin-mutation-actions.ts
1371
1398
  import {
1372
1399
  addPluginLoadPath,
1373
1400
  buildPluginStatusReport,
@@ -1380,7 +1407,11 @@ import {
1380
1407
  } from "@nextclaw/openclaw-compat";
1381
1408
  import { existsSync as existsSync6 } from "fs";
1382
1409
  import { resolve as resolve6 } from "path";
1383
- import { expandHome, getWorkspacePath, loadConfig as loadConfig2, saveConfig } from "@nextclaw/core";
1410
+ import { expandHome as expandHome2, getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig2, saveConfig } from "@nextclaw/core";
1411
+ var pluginInstallLogger = {
1412
+ info: (message) => console.log(message),
1413
+ warn: (message) => console.warn(message)
1414
+ };
1384
1415
  function resolveFileNpmSpecToLocalPath(raw) {
1385
1416
  const trimmed = raw.trim();
1386
1417
  if (!trimmed.toLowerCase().startsWith("file:")) {
@@ -1411,6 +1442,79 @@ function isArchivePath(filePath) {
1411
1442
  const lower = filePath.toLowerCase();
1412
1443
  return lower.endsWith(".zip") || lower.endsWith(".tgz") || lower.endsWith(".tar.gz") || lower.endsWith(".tar");
1413
1444
  }
1445
+ function saveLinkedPluginInstall(config2, params) {
1446
+ let next = addPluginLoadPath(config2, params.resolvedPath);
1447
+ next = enablePluginInConfig(next, params.pluginId);
1448
+ next = recordPluginInstall(next, {
1449
+ pluginId: params.pluginId,
1450
+ source: "path",
1451
+ sourcePath: params.resolvedPath,
1452
+ installPath: params.resolvedPath,
1453
+ version: params.version ?? void 0
1454
+ });
1455
+ saveConfig(next);
1456
+ return {
1457
+ message: `Linked plugin path: ${params.resolvedPath}`
1458
+ };
1459
+ }
1460
+ function saveInstalledPluginResult(config2, params) {
1461
+ let next = enablePluginInConfig(config2, params.pluginId);
1462
+ next = recordPluginInstall(next, {
1463
+ pluginId: params.pluginId,
1464
+ source: params.source,
1465
+ sourcePath: params.sourcePath,
1466
+ spec: params.spec,
1467
+ installPath: params.installPath,
1468
+ version: params.version ?? void 0
1469
+ });
1470
+ saveConfig(next);
1471
+ return {
1472
+ message: `Installed plugin: ${params.pluginId}`
1473
+ };
1474
+ }
1475
+ async function installPluginFromLocalPath(config2, resolvedPath, link) {
1476
+ if (link) {
1477
+ const probe = await installPluginFromPath({ path: resolvedPath, dryRun: true });
1478
+ if (!probe.ok) {
1479
+ throw new Error(probe.error);
1480
+ }
1481
+ return saveLinkedPluginInstall(config2, {
1482
+ resolvedPath,
1483
+ pluginId: probe.pluginId,
1484
+ version: probe.version
1485
+ });
1486
+ }
1487
+ const result = await installPluginFromPath({
1488
+ path: resolvedPath,
1489
+ logger: pluginInstallLogger
1490
+ });
1491
+ if (!result.ok) {
1492
+ throw new Error(result.error);
1493
+ }
1494
+ return saveInstalledPluginResult(config2, {
1495
+ pluginId: result.pluginId,
1496
+ source: isArchivePath(resolvedPath) ? "archive" : "path",
1497
+ sourcePath: resolvedPath,
1498
+ installPath: result.targetDir,
1499
+ version: result.version
1500
+ });
1501
+ }
1502
+ async function installPluginFromRegistrySpec(config2, spec) {
1503
+ const result = await installPluginFromNpmSpec({
1504
+ spec,
1505
+ logger: pluginInstallLogger
1506
+ });
1507
+ if (!result.ok) {
1508
+ throw new Error(result.error);
1509
+ }
1510
+ return saveInstalledPluginResult(config2, {
1511
+ pluginId: result.pluginId,
1512
+ source: "npm",
1513
+ spec,
1514
+ installPath: result.targetDir,
1515
+ version: result.version
1516
+ });
1517
+ }
1414
1518
  async function enablePluginMutation(id) {
1415
1519
  const config2 = loadConfig2();
1416
1520
  const next = enablePluginInConfig(config2, id);
@@ -1429,7 +1533,7 @@ async function disablePluginMutation(id) {
1429
1533
  }
1430
1534
  async function uninstallPluginMutation(id, opts = {}) {
1431
1535
  const config2 = loadConfig2();
1432
- const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1536
+ const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1433
1537
  const report = buildPluginStatusReport({
1434
1538
  config: config2,
1435
1539
  workspaceDir,
@@ -1484,50 +1588,10 @@ async function installPluginMutation(pathOrSpec, opts = {}) {
1484
1588
  throw new Error(fileSpec.error);
1485
1589
  }
1486
1590
  const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
1487
- const resolved = resolve6(expandHome(normalized));
1591
+ const resolved = resolve6(expandHome2(normalized));
1488
1592
  const config2 = loadConfig2();
1489
1593
  if (existsSync6(resolved)) {
1490
- if (opts.link) {
1491
- const probe = await installPluginFromPath({ path: resolved, dryRun: true });
1492
- if (!probe.ok) {
1493
- throw new Error(probe.error);
1494
- }
1495
- let next3 = addPluginLoadPath(config2, resolved);
1496
- next3 = enablePluginInConfig(next3, probe.pluginId);
1497
- next3 = recordPluginInstall(next3, {
1498
- pluginId: probe.pluginId,
1499
- source: "path",
1500
- sourcePath: resolved,
1501
- installPath: resolved,
1502
- version: probe.version
1503
- });
1504
- saveConfig(next3);
1505
- return {
1506
- message: `Linked plugin path: ${resolved}`
1507
- };
1508
- }
1509
- const result2 = await installPluginFromPath({
1510
- path: resolved,
1511
- logger: {
1512
- info: (message) => console.log(message),
1513
- warn: (message) => console.warn(message)
1514
- }
1515
- });
1516
- if (!result2.ok) {
1517
- throw new Error(result2.error);
1518
- }
1519
- let next2 = enablePluginInConfig(config2, result2.pluginId);
1520
- next2 = recordPluginInstall(next2, {
1521
- pluginId: result2.pluginId,
1522
- source: isArchivePath(resolved) ? "archive" : "path",
1523
- sourcePath: resolved,
1524
- installPath: result2.targetDir,
1525
- version: result2.version
1526
- });
1527
- saveConfig(next2);
1528
- return {
1529
- message: `Installed plugin: ${result2.pluginId}`
1530
- };
1594
+ return installPluginFromLocalPath(config2, resolved, Boolean(opts.link));
1531
1595
  }
1532
1596
  if (opts.link) {
1533
1597
  throw new Error("`--link` requires a local path.");
@@ -1535,31 +1599,10 @@ async function installPluginMutation(pathOrSpec, opts = {}) {
1535
1599
  if (looksLikePath(pathOrSpec)) {
1536
1600
  throw new Error(`Path not found: ${resolved}`);
1537
1601
  }
1538
- const result = await installPluginFromNpmSpec({
1539
- spec: pathOrSpec,
1540
- logger: {
1541
- info: (message) => console.log(message),
1542
- warn: (message) => console.warn(message)
1543
- }
1544
- });
1545
- if (!result.ok) {
1546
- throw new Error(result.error);
1547
- }
1548
- let next = enablePluginInConfig(config2, result.pluginId);
1549
- next = recordPluginInstall(next, {
1550
- pluginId: result.pluginId,
1551
- source: "npm",
1552
- spec: pathOrSpec,
1553
- installPath: result.targetDir,
1554
- version: result.version
1555
- });
1556
- saveConfig(next);
1557
- return {
1558
- message: `Installed plugin: ${result.pluginId}`
1559
- };
1602
+ return installPluginFromRegistrySpec(config2, pathOrSpec);
1560
1603
  }
1561
1604
 
1562
- // src/cli/commands/plugin-extension-registry.ts
1605
+ // src/cli/commands/plugin/plugin-extension-registry.ts
1563
1606
  function toExtensionRegistry(pluginRegistry) {
1564
1607
  return {
1565
1608
  tools: pluginRegistry.tools.map((tool) => ({
@@ -1647,7 +1690,7 @@ var PluginCommands = class {
1647
1690
  }
1648
1691
  pluginsList(opts = {}) {
1649
1692
  const config2 = loadConfig3();
1650
- const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1693
+ const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
1651
1694
  const report = buildPluginStatusReport2({
1652
1695
  config: config2,
1653
1696
  workspaceDir,
@@ -1699,7 +1742,7 @@ var PluginCommands = class {
1699
1742
  }
1700
1743
  pluginsInfo(id, opts = {}) {
1701
1744
  const config2 = loadConfig3();
1702
- const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1745
+ const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
1703
1746
  const report = buildPluginStatusReport2({
1704
1747
  config: config2,
1705
1748
  workspaceDir,
@@ -1780,7 +1823,7 @@ var PluginCommands = class {
1780
1823
  console.log("`--keep-config` is deprecated, use `--keep-files`.");
1781
1824
  }
1782
1825
  const config2 = loadConfig3();
1783
- const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1826
+ const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
1784
1827
  const report = buildPluginStatusReport2({
1785
1828
  config: config2,
1786
1829
  workspaceDir,
@@ -1864,7 +1907,7 @@ var PluginCommands = class {
1864
1907
  }
1865
1908
  pluginsDoctor() {
1866
1909
  const config2 = loadConfig3();
1867
- const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1910
+ const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
1868
1911
  const report = buildPluginStatusReport2({
1869
1912
  config: config2,
1870
1913
  workspaceDir,
@@ -1898,8 +1941,8 @@ var PluginCommands = class {
1898
1941
  input: process.stdin,
1899
1942
  output: process.stdout
1900
1943
  });
1901
- const answer = await new Promise((resolve16) => {
1902
- rl.question(`${question} [y/N] `, (line) => resolve16(line));
1944
+ const answer = await new Promise((resolve17) => {
1945
+ rl.question(`${question} [y/N] `, (line) => resolve17(line));
1903
1946
  });
1904
1947
  rl.close();
1905
1948
  const normalized = answer.trim().toLowerCase();
@@ -1908,7 +1951,7 @@ var PluginCommands = class {
1908
1951
  };
1909
1952
 
1910
1953
  // src/cli/commands/config.ts
1911
- import { buildReloadPlan, diffConfigPaths, getWorkspacePath as getWorkspacePath3, loadConfig as loadConfig4, saveConfig as saveConfig2 } from "@nextclaw/core";
1954
+ import { buildReloadPlan, diffConfigPaths, getWorkspacePath as getWorkspacePath4, loadConfig as loadConfig4, saveConfig as saveConfig2 } from "@nextclaw/core";
1912
1955
  import { getPluginChannelBindings } from "@nextclaw/openclaw-compat";
1913
1956
 
1914
1957
  // src/cli/config-path.ts
@@ -2016,33 +2059,48 @@ function setAtConfigPath(root, pathSegments, value) {
2016
2059
  const next = pathSegments[i + 1];
2017
2060
  const nextIsIndex = Boolean(next && isIndexSegment(next));
2018
2061
  if (Array.isArray(current)) {
2019
- if (!isIndexSegment(segment)) {
2020
- throw new Error(`Expected numeric index for array segment "${segment}"`);
2021
- }
2022
- const index = Number.parseInt(segment, 10);
2023
- const existing2 = current[index];
2024
- if (!existing2 || typeof existing2 !== "object") {
2025
- current[index] = nextIsIndex ? [] : {};
2026
- }
2027
- current = current[index];
2062
+ current = getOrCreateArraySegment(current, segment, nextIsIndex, pathSegments.slice(0, i));
2028
2063
  continue;
2029
2064
  }
2030
- if (!current || typeof current !== "object") {
2031
- throw new Error(`Cannot traverse into "${segment}" (not an object)`);
2032
- }
2033
- const record = current;
2034
- const existing = record[segment];
2035
- if (!existing || typeof existing !== "object") {
2036
- record[segment] = nextIsIndex ? [] : {};
2037
- }
2038
- current = record[segment];
2065
+ current = getOrCreateObjectSegment(current, segment, nextIsIndex);
2039
2066
  }
2040
- const last = pathSegments[pathSegments.length - 1];
2067
+ setConfigPathValue(current, pathSegments[pathSegments.length - 1], value, pathSegments.slice(0, -1));
2068
+ }
2069
+ function ensureWritableArrayIndex(current, index, parentPath) {
2070
+ if (!Number.isFinite(index) || index < 0) {
2071
+ throw new Error(`Invalid array index "${index}"`);
2072
+ }
2073
+ if (index <= current.length) {
2074
+ return;
2075
+ }
2076
+ const parentPathExpr = formatConfigPath(parentPath);
2077
+ const parentLabel = parentPathExpr ? `"${parentPathExpr}"` : "the root array";
2078
+ throw new Error(`Cannot set sparse array index ${index} under ${parentLabel}. Set indices in order.`);
2079
+ }
2080
+ function getOrCreateArraySegment(current, segment, nextIsIndex, parentPath) {
2081
+ const index = parseArrayIndexSegment(segment);
2082
+ ensureWritableArrayIndex(current, index, parentPath);
2083
+ const existing = current[index];
2084
+ if (!existing || typeof existing !== "object") {
2085
+ current[index] = nextIsIndex ? [] : {};
2086
+ }
2087
+ return current[index];
2088
+ }
2089
+ function getOrCreateObjectSegment(current, segment, nextIsIndex) {
2090
+ if (!current || typeof current !== "object") {
2091
+ throw new Error(`Cannot traverse into "${segment}" (not an object)`);
2092
+ }
2093
+ const record = current;
2094
+ const existing = record[segment];
2095
+ if (!existing || typeof existing !== "object") {
2096
+ record[segment] = nextIsIndex ? [] : {};
2097
+ }
2098
+ return record[segment];
2099
+ }
2100
+ function setConfigPathValue(current, last, value, parentPath) {
2041
2101
  if (Array.isArray(current)) {
2042
- if (!isIndexSegment(last)) {
2043
- throw new Error(`Expected numeric index for array segment "${last}"`);
2044
- }
2045
- const index = Number.parseInt(last, 10);
2102
+ const index = parseArrayIndexSegment(last);
2103
+ ensureWritableArrayIndex(current, index, parentPath);
2046
2104
  current[index] = value;
2047
2105
  return;
2048
2106
  }
@@ -2051,6 +2109,23 @@ function setAtConfigPath(root, pathSegments, value) {
2051
2109
  }
2052
2110
  current[last] = value;
2053
2111
  }
2112
+ function parseArrayIndexSegment(segment) {
2113
+ if (!isIndexSegment(segment)) {
2114
+ throw new Error(`Expected numeric index for array segment "${segment}"`);
2115
+ }
2116
+ return Number.parseInt(segment, 10);
2117
+ }
2118
+ function formatConfigPath(pathSegments) {
2119
+ let expr = "";
2120
+ for (const segment of pathSegments) {
2121
+ if (isIndexSegment(segment)) {
2122
+ expr += `[${segment}]`;
2123
+ continue;
2124
+ }
2125
+ expr += expr ? `.${segment}` : segment;
2126
+ }
2127
+ return expr;
2128
+ }
2054
2129
  function unsetAtConfigPath(root, pathSegments) {
2055
2130
  let current = root;
2056
2131
  for (let i = 0; i < pathSegments.length - 1; i += 1) {
@@ -2098,7 +2173,7 @@ function unsetAtConfigPath(root, pathSegments) {
2098
2173
  return true;
2099
2174
  }
2100
2175
 
2101
- // src/cli/commands/channel-config-view.ts
2176
+ // src/cli/commands/channel/channel-config-view.ts
2102
2177
  import { toPluginConfigView as toPluginConfigView2 } from "@nextclaw/openclaw-compat";
2103
2178
  function resolveChannelConfigView(config2, bindings) {
2104
2179
  return toPluginConfigView2(config2, bindings);
@@ -2221,7 +2296,7 @@ var ConfigCommands = class {
2221
2296
  };
2222
2297
  }
2223
2298
  loadPluginChannelBindings(config2) {
2224
- const workspaceDir = getWorkspacePath3(config2.agents.defaults.workspace);
2299
+ const workspaceDir = getWorkspacePath4(config2.agents.defaults.workspace);
2225
2300
  const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
2226
2301
  return {
2227
2302
  bindings: getPluginChannelBindings(pluginRegistry)
@@ -2797,7 +2872,7 @@ var SecretsCommands = class {
2797
2872
 
2798
2873
  // src/cli/commands/channels.ts
2799
2874
  import { spawnSync as spawnSync2 } from "child_process";
2800
- import { getWorkspacePath as getWorkspacePath4, loadConfig as loadConfig7, saveConfig as saveConfig5 } from "@nextclaw/core";
2875
+ import { getWorkspacePath as getWorkspacePath5, loadConfig as loadConfig7, saveConfig as saveConfig5 } from "@nextclaw/core";
2801
2876
  import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds as builtinProviderIds2 } from "@nextclaw/runtime";
2802
2877
  import { buildPluginStatusReport as buildPluginStatusReport3, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings as getPluginChannelBindings2 } from "@nextclaw/openclaw-compat";
2803
2878
  var CHANNEL_LABELS = {
@@ -2820,7 +2895,7 @@ var ChannelCommands = class {
2820
2895
  }
2821
2896
  channelsStatus() {
2822
2897
  const config2 = loadConfig7();
2823
- const workspaceDir = getWorkspacePath4(config2.agents.defaults.workspace);
2898
+ const workspaceDir = getWorkspacePath5(config2.agents.defaults.workspace);
2824
2899
  const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
2825
2900
  const channelConfig = resolveChannelConfigView(config2, getPluginChannelBindings2(pluginRegistry));
2826
2901
  console.log("Channel Status");
@@ -2878,7 +2953,7 @@ var ChannelCommands = class {
2878
2953
  }
2879
2954
  }
2880
2955
  resolvePluginChannelContext(config2, channelId) {
2881
- const workspaceDir = getWorkspacePath4(config2.agents.defaults.workspace);
2956
+ const workspaceDir = getWorkspacePath5(config2.agents.defaults.workspace);
2882
2957
  const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
2883
2958
  const bindings = getPluginChannelBindings2(pluginRegistry);
2884
2959
  const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
@@ -2954,7 +3029,7 @@ var ChannelCommands = class {
2954
3029
  process.exit(1);
2955
3030
  }
2956
3031
  const config2 = loadConfig7();
2957
- const workspaceDir = getWorkspacePath4(config2.agents.defaults.workspace);
3032
+ const workspaceDir = getWorkspacePath5(config2.agents.defaults.workspace);
2958
3033
  const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
2959
3034
  const bindings = getPluginChannelBindings2(pluginRegistry);
2960
3035
  const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
@@ -3005,7 +3080,7 @@ var ChannelCommands = class {
3005
3080
  }
3006
3081
  };
3007
3082
 
3008
- // src/cli/commands/cron/cron-local.service.ts
3083
+ // src/cli/commands/cron-support/cron-local.service.ts
3009
3084
  import { CronService, getDataDir as getDataDir3 } from "@nextclaw/core";
3010
3085
  import { join as join3 } from "path";
3011
3086
  function createCronService() {
@@ -3039,6 +3114,7 @@ var CronLocalService = class {
3039
3114
  name: opts.name,
3040
3115
  schedule,
3041
3116
  message: opts.message,
3117
+ agentId: opts.agent,
3042
3118
  deliver: Boolean(opts.deliver),
3043
3119
  channel: opts.channel,
3044
3120
  to: opts.to,
@@ -3060,7 +3136,7 @@ var CronLocalService = class {
3060
3136
  };
3061
3137
  };
3062
3138
 
3063
- // src/cli/commands/cron/cron-job.utils.ts
3139
+ // src/cli/commands/cron-support/cron-job.utils.ts
3064
3140
  function formatCronSchedule(schedule) {
3065
3141
  if (schedule.kind === "every") {
3066
3142
  return `every ${Math.round((schedule.everyMs ?? 0) / 1e3)}s`;
@@ -3239,11 +3315,128 @@ var CronCommands = class {
3239
3315
  };
3240
3316
  };
3241
3317
 
3318
+ // src/cli/commands/agents.ts
3319
+ import {
3320
+ BUILTIN_MAIN_AGENT_ID,
3321
+ createAgentProfile,
3322
+ loadConfig as loadConfig8,
3323
+ removeAgentProfile,
3324
+ resolveEffectiveAgentProfiles,
3325
+ updateAgentProfile
3326
+ } from "@nextclaw/core";
3327
+ var AgentCommands = class {
3328
+ constructor(deps) {
3329
+ this.deps = deps;
3330
+ }
3331
+ agentsList = (opts = {}) => {
3332
+ const config2 = loadConfig8();
3333
+ const agents = resolveEffectiveAgentProfiles(config2).map((agent) => this.toAgentListEntry(agent));
3334
+ if (opts.json) {
3335
+ console.log(JSON.stringify(agents, null, 2));
3336
+ return;
3337
+ }
3338
+ for (const agent of agents) {
3339
+ const head = agent.builtIn ? `${agent.id} (built-in)` : agent.id;
3340
+ console.log(head);
3341
+ console.log(` name: ${agent.displayName ?? "-"}`);
3342
+ console.log(` description: ${agent.description ?? "-"}`);
3343
+ console.log(` home: ${agent.workspace}`);
3344
+ console.log(` avatar: ${agent.avatar ?? "-"}`);
3345
+ }
3346
+ };
3347
+ agentsNew = async (agentId, opts = {}) => {
3348
+ const created = createAgentProfile(
3349
+ {
3350
+ id: agentId,
3351
+ displayName: opts.name,
3352
+ description: opts.description,
3353
+ avatar: opts.avatar,
3354
+ home: opts.home
3355
+ },
3356
+ {
3357
+ initializeHomeDirectory: this.deps.initializeAgentHomeDirectory
3358
+ }
3359
+ );
3360
+ if (opts.json) {
3361
+ console.log(JSON.stringify({
3362
+ agent: created,
3363
+ restartRequired: true
3364
+ }, null, 2));
3365
+ return;
3366
+ }
3367
+ await this.deps.requestRestart({
3368
+ reason: "agents-updated",
3369
+ manualMessage: `Created agent '${created.id}'. Restart ${this.deps.appName} to apply agent runtime changes.`
3370
+ });
3371
+ console.log(`\u2713 Created agent ${created.id}`);
3372
+ console.log(` name: ${created.displayName ?? "-"}`);
3373
+ console.log(` description: ${created.description ?? "-"}`);
3374
+ console.log(` home: ${created.workspace}`);
3375
+ console.log(` avatar: ${created.avatar ?? "-"}`);
3376
+ };
3377
+ agentsUpdate = async (agentId, opts = {}) => {
3378
+ const updated = updateAgentProfile({
3379
+ id: agentId,
3380
+ displayName: opts.name,
3381
+ description: opts.description,
3382
+ avatar: opts.avatar
3383
+ });
3384
+ if (opts.json) {
3385
+ console.log(JSON.stringify({
3386
+ agent: updated,
3387
+ restartRequired: true
3388
+ }, null, 2));
3389
+ return;
3390
+ }
3391
+ await this.deps.requestRestart({
3392
+ reason: "agents-updated",
3393
+ manualMessage: `Updated agent '${updated.id}'. Restart ${this.deps.appName} to apply agent runtime changes.`
3394
+ });
3395
+ console.log(`\u2713 Updated agent ${updated.id}`);
3396
+ console.log(` name: ${updated.displayName ?? "-"}`);
3397
+ console.log(` description: ${updated.description ?? "-"}`);
3398
+ console.log(` home: ${updated.workspace}`);
3399
+ console.log(` avatar: ${updated.avatar ?? "-"}`);
3400
+ };
3401
+ agentsRemove = async (agentId, opts = {}) => {
3402
+ if (agentId.trim().toLowerCase() === BUILTIN_MAIN_AGENT_ID) {
3403
+ throw new Error(`agent id '${BUILTIN_MAIN_AGENT_ID}' is reserved`);
3404
+ }
3405
+ const removed = removeAgentProfile(agentId);
3406
+ if (!removed) {
3407
+ throw new Error(`agent '${agentId}' not found`);
3408
+ }
3409
+ if (opts.json) {
3410
+ console.log(JSON.stringify({
3411
+ removed: true,
3412
+ agentId,
3413
+ restartRequired: true
3414
+ }, null, 2));
3415
+ return;
3416
+ }
3417
+ await this.deps.requestRestart({
3418
+ reason: "agents-updated",
3419
+ manualMessage: `Removed agent '${agentId}'. Restart ${this.deps.appName} to apply agent runtime changes.`
3420
+ });
3421
+ console.log(`\u2713 Removed agent ${agentId}`);
3422
+ };
3423
+ toAgentListEntry = (agent) => {
3424
+ return {
3425
+ id: agent.id,
3426
+ displayName: agent.displayName ?? null,
3427
+ description: agent.description ?? null,
3428
+ avatar: agent.avatar ?? null,
3429
+ workspace: agent.workspace,
3430
+ builtIn: agent.builtIn === true
3431
+ };
3432
+ };
3433
+ };
3434
+
3242
3435
  // src/cli/commands/platform-auth.ts
3243
- import { getConfigPath as getConfigPath3, loadConfig as loadConfig8, saveConfig as saveConfig6 } from "@nextclaw/core";
3436
+ import { getConfigPath as getConfigPath3, loadConfig as loadConfig9, saveConfig as saveConfig6 } from "@nextclaw/core";
3244
3437
  import { createInterface as createInterface2 } from "readline";
3245
3438
 
3246
- // src/cli/commands/platform-api-base.ts
3439
+ // src/cli/commands/remote-support/platform-api-base.ts
3247
3440
  var DEFAULT_PLATFORM_API_BASE = "https://ai-gateway-api.nextclaw.io/v1";
3248
3441
  var INVALID_PLATFORM_HINT = `Use ${DEFAULT_PLATFORM_API_BASE} or the platform root URL without a trailing path.`;
3249
3442
  function trimTrailingSlash(value) {
@@ -3294,7 +3487,7 @@ function buildPlatformApiBaseErrorMessage(inputApiBase, rawMessage) {
3294
3487
  // src/cli/commands/platform-auth.ts
3295
3488
  function resolveProviderConfig(opts) {
3296
3489
  const configPath = getConfigPath3();
3297
- const config2 = loadConfig8(configPath);
3490
+ const config2 = loadConfig9(configPath);
3298
3491
  const providers = config2.providers;
3299
3492
  const nextclawProvider = providers.nextclaw ?? {
3300
3493
  displayName: "",
@@ -3542,14 +3735,14 @@ var PlatformAuthCommands = class {
3542
3735
  };
3543
3736
 
3544
3737
  // src/cli/commands/remote.ts
3545
- import { getConfigPath as getConfigPath5, loadConfig as loadConfig10, saveConfig as saveConfig7 } from "@nextclaw/core";
3738
+ import { getConfigPath as getConfigPath5, loadConfig as loadConfig11, saveConfig as saveConfig7 } from "@nextclaw/core";
3546
3739
  import {
3547
3740
  readPlatformSessionTokenState
3548
3741
  } from "@nextclaw/remote";
3549
3742
  import { hostname } from "os";
3550
3743
 
3551
- // src/cli/commands/remote-runtime-support.ts
3552
- import { getConfigPath as getConfigPath4, getDataDir as getDataDir4, loadConfig as loadConfig9 } from "@nextclaw/core";
3744
+ // src/cli/commands/remote-support/remote-runtime-support.ts
3745
+ import { getConfigPath as getConfigPath4, getDataDir as getDataDir4, loadConfig as loadConfig10 } from "@nextclaw/core";
3553
3746
  import {
3554
3747
  RemoteConnector,
3555
3748
  RemotePlatformClient,
@@ -3564,7 +3757,7 @@ function hasRunningNextclawManagedService() {
3564
3757
  }
3565
3758
  function createNextclawRemotePlatformClient() {
3566
3759
  return new RemotePlatformClient({
3567
- loadConfig: () => loadConfig9(getConfigPath4()),
3760
+ loadConfig: () => loadConfig10(getConfigPath4()),
3568
3761
  getDataDir: getDataDir4,
3569
3762
  getPackageVersion,
3570
3763
  resolvePlatformBase: (rawApiBase) => resolvePlatformApiBase({
@@ -3695,7 +3888,7 @@ var RemoteCommands = class {
3695
3888
  this.deps = deps;
3696
3889
  }
3697
3890
  updateConfig(params = {}) {
3698
- const config2 = loadConfig10(getConfigPath5());
3891
+ const config2 = loadConfig11(getConfigPath5());
3699
3892
  const nextEnabled = typeof params.enabled === "boolean" ? params.enabled : config2.remote.enabled;
3700
3893
  const nextPlatformApiBase = typeof params.apiBase === "string" ? params.apiBase.trim() : config2.remote.platformApiBase;
3701
3894
  const nextDeviceName = typeof params.name === "string" ? params.name.trim() : config2.remote.deviceName;
@@ -3732,7 +3925,7 @@ var RemoteCommands = class {
3732
3925
  });
3733
3926
  }
3734
3927
  getStatusView() {
3735
- const config2 = loadConfig10(getConfigPath5());
3928
+ const config2 = loadConfig11(getConfigPath5());
3736
3929
  const snapshot = resolveNextclawRemoteStatusSnapshot(config2);
3737
3930
  const resolvedLocalOrigin = snapshot.runtime?.localOrigin ?? this.deps.currentLocalOrigin ?? resolveConfiguredLocalOrigin(config2);
3738
3931
  return {
@@ -3768,7 +3961,7 @@ var RemoteCommands = class {
3768
3961
  }
3769
3962
  }
3770
3963
  async getDoctorView() {
3771
- const config2 = loadConfig10(getConfigPath5());
3964
+ const config2 = loadConfig11(getConfigPath5());
3772
3965
  const snapshot = resolveNextclawRemoteStatusSnapshot(config2);
3773
3966
  const localOrigin = snapshot.runtime?.localOrigin ?? this.deps.currentLocalOrigin ?? resolveConfiguredLocalOrigin(config2);
3774
3967
  const localUi = await probeLocalUi(localOrigin);
@@ -3824,13 +4017,13 @@ import {
3824
4017
  APP_NAME as APP_NAME2,
3825
4018
  getConfigPath as getConfigPath6,
3826
4019
  getDataDir as getDataDir5,
3827
- getWorkspacePath as getWorkspacePath5,
4020
+ getWorkspacePath as getWorkspacePath6,
3828
4021
  hasSecretRef,
3829
- loadConfig as loadConfig11
4022
+ loadConfig as loadConfig12
3830
4023
  } from "@nextclaw/core";
3831
4024
  import { listBuiltinProviders } from "@nextclaw/runtime";
3832
4025
 
3833
- // src/cli/commands/diagnostics-render.ts
4026
+ // src/cli/commands/diagnostics-support/diagnostics-render.ts
3834
4027
  import { APP_NAME } from "@nextclaw/core";
3835
4028
  function printStatusReport(params) {
3836
4029
  const { logo, report, verbose } = params;
@@ -4012,8 +4205,8 @@ var DiagnosticsCommands = class {
4012
4205
  }
4013
4206
  async collectRuntimeStatus(params) {
4014
4207
  const configPath = getConfigPath6();
4015
- const config2 = loadConfig11();
4016
- const workspacePath = getWorkspacePath5(config2.agents.defaults.workspace);
4208
+ const config2 = loadConfig12();
4209
+ const workspacePath = getWorkspacePath6(config2.agents.defaults.workspace);
4017
4210
  const serviceStatePath = resolve8(getDataDir5(), "run", "service.json");
4018
4211
  const fixActions = [];
4019
4212
  let serviceState = readServiceState();
@@ -4183,17 +4376,17 @@ var DiagnosticsCommands = class {
4183
4376
  }
4184
4377
  }
4185
4378
  async checkPortAvailability(params) {
4186
- return await new Promise((resolve16) => {
4379
+ return await new Promise((resolve17) => {
4187
4380
  const server = createNetServer();
4188
4381
  server.once("error", (error) => {
4189
- resolve16({
4382
+ resolve17({
4190
4383
  available: false,
4191
4384
  detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
4192
4385
  });
4193
4386
  });
4194
4387
  server.listen(params.port, params.host, () => {
4195
4388
  server.close(() => {
4196
- resolve16({
4389
+ resolve17({
4197
4390
  available: true,
4198
4391
  detail: `bind ok on ${params.host}:${params.port}`
4199
4392
  });
@@ -4210,9 +4403,9 @@ import {
4210
4403
  setPluginRuntimeBridge as setPluginRuntimeBridge2,
4211
4404
  stopPluginChannelGateways as stopPluginChannelGateways2
4212
4405
  } from "@nextclaw/openclaw-compat";
4213
- import { appendFileSync, closeSync as closeSync2, cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync5, openSync as openSync2 } from "fs";
4214
- import { dirname as dirname3, join as join7, resolve as resolve13 } from "path";
4215
- import { spawn as spawn3 } from "child_process";
4406
+ import { appendFileSync } from "fs";
4407
+ import { join as join7, resolve as resolve14 } from "path";
4408
+ import { spawn as spawn4 } from "child_process";
4216
4409
  import { request as httpRequest } from "http";
4217
4410
  import { request as httpsRequest } from "https";
4218
4411
  import { createServer as createNetServer2 } from "net";
@@ -4237,12 +4430,12 @@ var MissingProvider = class extends LLMProvider {
4237
4430
  }
4238
4431
  };
4239
4432
 
4240
- // src/cli/commands/service-marketplace-installer.ts
4241
- import { getWorkspacePath as getWorkspacePath6, loadConfig as loadConfig13 } from "@nextclaw/core";
4433
+ // src/cli/commands/service-support/marketplace/service-marketplace-installer.ts
4434
+ import { getWorkspacePath as getWorkspacePath7, loadConfig as loadConfig14 } from "@nextclaw/core";
4242
4435
  import { existsSync as existsSync8, rmSync as rmSync4 } from "fs";
4243
4436
  import { join as join4 } from "path";
4244
4437
 
4245
- // src/cli/commands/service-marketplace-helpers.ts
4438
+ // src/cli/commands/service-support/marketplace/service-marketplace-helpers.ts
4246
4439
  var containsAbsoluteFsPath = (line) => {
4247
4440
  const normalized = line.trim();
4248
4441
  if (!normalized) {
@@ -4287,8 +4480,8 @@ var buildMarketplaceSkillInstallArgs = (params) => {
4287
4480
  return args;
4288
4481
  };
4289
4482
 
4290
- // src/cli/commands/service-mcp-marketplace-ops.ts
4291
- import { loadConfig as loadConfig12, saveConfig as saveConfig8 } from "@nextclaw/core";
4483
+ // src/cli/commands/service-support/marketplace/service-mcp-marketplace-ops.ts
4484
+ import { loadConfig as loadConfig13, saveConfig as saveConfig8 } from "@nextclaw/core";
4292
4485
  import { McpDoctorFacade as McpDoctorFacade2, McpMutationService as McpMutationService2 } from "@nextclaw/mcp";
4293
4486
  var ServiceMcpMarketplaceOps = class {
4294
4487
  constructor(options) {
@@ -4356,18 +4549,18 @@ var ServiceMcpMarketplaceOps = class {
4356
4549
  }
4357
4550
  createMutationService() {
4358
4551
  return new McpMutationService2({
4359
- getConfig: () => loadConfig12(),
4552
+ getConfig: () => loadConfig13(),
4360
4553
  saveConfig: (config2) => saveConfig8(config2)
4361
4554
  });
4362
4555
  }
4363
4556
  createDoctorFacade() {
4364
4557
  return new McpDoctorFacade2({
4365
- getConfig: () => loadConfig12()
4558
+ getConfig: () => loadConfig13()
4366
4559
  });
4367
4560
  }
4368
4561
  };
4369
4562
 
4370
- // src/cli/commands/service-marketplace-installer.ts
4563
+ // src/cli/commands/service-support/marketplace/service-marketplace-installer.ts
4371
4564
  var ServiceMarketplaceInstaller = class {
4372
4565
  constructor(deps) {
4373
4566
  this.deps = deps;
@@ -4403,7 +4596,7 @@ var ServiceMarketplaceInstaller = class {
4403
4596
  if (params.kind && params.kind !== "marketplace") {
4404
4597
  throw new Error(`Unsupported marketplace skill kind: ${params.kind}`);
4405
4598
  }
4406
- const workspace = getWorkspacePath6(loadConfig13().agents.defaults.workspace);
4599
+ const workspace = getWorkspacePath7(loadConfig14().agents.defaults.workspace);
4407
4600
  const args = buildMarketplaceSkillInstallArgs({
4408
4601
  slug: params.slug,
4409
4602
  workspace,
@@ -4442,7 +4635,7 @@ var ServiceMarketplaceInstaller = class {
4442
4635
  return { message: result.message };
4443
4636
  }
4444
4637
  async uninstallSkill(slug) {
4445
- const workspace = getWorkspacePath6(loadConfig13().agents.defaults.workspace);
4638
+ const workspace = getWorkspacePath7(loadConfig14().agents.defaults.workspace);
4446
4639
  const targetDir = join4(workspace, "skills", slug);
4447
4640
  if (!existsSync8(targetDir)) {
4448
4641
  throw new Error(`Skill not installed in workspace: ${slug}`);
@@ -4471,70 +4664,12 @@ var ServiceMarketplaceInstaller = class {
4471
4664
  }
4472
4665
  };
4473
4666
 
4474
- // src/cli/commands/service-startup-support.ts
4475
- import chokidar from "chokidar";
4476
- import { resolve as resolve9 } from "path";
4477
- var pluginGatewayLogger = {
4478
- info: (message) => console.log(`[plugins] ${message}`),
4479
- warn: (message) => console.warn(`[plugins] ${message}`),
4480
- error: (message) => console.error(`[plugins] ${message}`),
4481
- debug: (message) => console.debug(`[plugins] ${message}`)
4482
- };
4483
- function logPluginGatewayDiagnostics(diagnostics) {
4484
- for (const diag of diagnostics) {
4485
- const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
4486
- const text = `${prefix}${diag.message}`;
4487
- if (diag.level === "error") {
4488
- console.error(`[plugins] ${text}`);
4489
- } else {
4490
- console.warn(`[plugins] ${text}`);
4491
- }
4492
- }
4493
- }
4494
- async function startGatewaySupportServices(params) {
4495
- if (params.cronJobs > 0) {
4496
- console.log(`\u2713 Cron: ${params.cronJobs} scheduled jobs`);
4497
- }
4498
- console.log("\u2713 Heartbeat: every 30m");
4499
- params.remoteModule?.start();
4500
- params.watchConfigFile();
4501
- await params.startCron();
4502
- await params.startHeartbeat();
4503
- }
4504
- function watchCronStoreFile(params) {
4505
- const cronStorePath = resolve9(params.cronStorePath);
4506
- const watcher = chokidar.watch(cronStorePath, {
4507
- ignoreInitial: true,
4508
- awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
4509
- });
4510
- watcher.on("all", (event, changedPath) => {
4511
- if (resolve9(changedPath) !== cronStorePath) {
4512
- return;
4513
- }
4514
- if (event === "add" || event === "change" || event === "unlink") {
4515
- try {
4516
- params.reloadCronStore();
4517
- } catch (error) {
4518
- console.error(`Cron store reload failed (${event}): ${String(error)}`);
4519
- }
4520
- }
4521
- });
4522
- }
4523
-
4524
- // src/cli/commands/cli-subcommand-launch.ts
4525
- import { createRequire } from "module";
4526
- import { extname, resolve as resolve10 } from "path";
4527
- import { fileURLToPath as fileURLToPath3 } from "url";
4528
- var require2 = createRequire(import.meta.url);
4529
- var resolveCliSubcommandEntry = (params) => {
4530
- const argvEntry = params.argvEntry?.trim();
4531
- if (argvEntry) {
4532
- return resolve10(argvEntry);
4533
- }
4534
- return fileURLToPath3(new URL("../index.js", params.importMetaUrl));
4535
- };
4667
+ // src/cli/commands/service-support/runtime/service-managed-startup.ts
4668
+ import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync2 } from "fs";
4669
+ import { resolve as resolve10 } from "path";
4670
+ import { spawn as spawn2 } from "child_process";
4536
4671
 
4537
- // src/cli/commands/service-remote-runtime.ts
4672
+ // src/cli/commands/service-support/runtime/service-remote-runtime.ts
4538
4673
  import { RemoteServiceModule } from "@nextclaw/remote";
4539
4674
  import {
4540
4675
  closeSync,
@@ -4546,10 +4681,10 @@ import {
4546
4681
  unlinkSync,
4547
4682
  writeFileSync as writeFileSync4
4548
4683
  } from "fs";
4549
- import { dirname as dirname2, resolve as resolve11 } from "path";
4684
+ import { dirname as dirname2, resolve as resolve9 } from "path";
4550
4685
  import { getDataDir as getDataDir6 } from "@nextclaw/core";
4551
4686
  function resolveRemoteOwnershipLockPath() {
4552
- return resolve11(getDataDir6(), "run", "remote-owner.lock.json");
4687
+ return resolve9(getDataDir6(), "run", "remote-owner.lock.json");
4553
4688
  }
4554
4689
  function readRemoteOwnershipRecord(lockPath) {
4555
4690
  try {
@@ -4736,20 +4871,230 @@ function writeReadyManagedServiceState(params) {
4736
4871
  return state;
4737
4872
  }
4738
4873
 
4739
- // src/cli/commands/remote-access-host.ts
4740
- import { getConfigPath as getConfigPath8, loadConfig as loadConfig15 } from "@nextclaw/core";
4741
- import { readPlatformSessionTokenState as readPlatformSessionTokenState2 } from "@nextclaw/remote";
4742
-
4743
- // src/cli/commands/remote-access-service-control.ts
4744
- import { getConfigPath as getConfigPath7, loadConfig as loadConfig14 } from "@nextclaw/core";
4745
- import { spawn as spawn2 } from "child_process";
4746
- var FORCED_PUBLIC_UI_HOST = "0.0.0.0";
4747
- function resolveRemoteServiceView(currentUi) {
4748
- if (currentUi) {
4749
- return {
4750
- running: true,
4751
- currentProcess: true,
4752
- pid: process.pid,
4874
+ // src/cli/commands/service-support/runtime/service-managed-startup.ts
4875
+ function toObjectRecord(value) {
4876
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4877
+ return null;
4878
+ }
4879
+ return value;
4880
+ }
4881
+ function hasSessionRoutingMetadata(params) {
4882
+ const context = toObjectRecord(params.metadata.last_delivery_context) ?? {};
4883
+ const hasPrimaryRoute = Boolean(params.normalizeOptionalString(context.channel)) && Boolean(params.normalizeOptionalString(context.chatId));
4884
+ const hasFallbackRoute = Boolean(params.normalizeOptionalString(params.metadata.last_channel)) && Boolean(params.normalizeOptionalString(params.metadata.last_to));
4885
+ return hasPrimaryRoute || hasFallbackRoute;
4886
+ }
4887
+ function resolveManagedServiceUiBinding(state) {
4888
+ try {
4889
+ const parsed = new URL(state.uiUrl);
4890
+ const parsedPort = Number(parsed.port || 80);
4891
+ return {
4892
+ host: state.uiHost ?? parsed.hostname,
4893
+ port: Number.isFinite(parsedPort) ? parsedPort : state.uiPort ?? 55667
4894
+ };
4895
+ } catch {
4896
+ return {
4897
+ host: state.uiHost ?? "127.0.0.1",
4898
+ port: state.uiPort ?? 55667
4899
+ };
4900
+ }
4901
+ }
4902
+ function resolveSessionRouteCandidate(params) {
4903
+ const sessionRecord = toObjectRecord(params.session);
4904
+ const key = params.normalizeOptionalString(sessionRecord?.key);
4905
+ if (!key || key.startsWith("cli:")) {
4906
+ return null;
4907
+ }
4908
+ const metadata = toObjectRecord(sessionRecord?.metadata) ?? {};
4909
+ if (!hasSessionRoutingMetadata({ metadata, normalizeOptionalString: params.normalizeOptionalString })) {
4910
+ return null;
4911
+ }
4912
+ const updatedAtRaw = params.normalizeOptionalString(sessionRecord?.updated_at);
4913
+ const updatedAt = updatedAtRaw ? Date.parse(updatedAtRaw) : Number.NaN;
4914
+ return {
4915
+ key,
4916
+ updatedAt: Number.isFinite(updatedAt) ? updatedAt : 0
4917
+ };
4918
+ }
4919
+ function spawnManagedService(params) {
4920
+ const logPath = params.resolveServiceLogPath();
4921
+ const logDir = resolve10(logPath, "..");
4922
+ mkdirSync5(logDir, { recursive: true });
4923
+ const logFd = openSync2(logPath, "a");
4924
+ const readinessTimeoutMs = params.resolveStartupTimeoutMs(params.startupTimeoutMs);
4925
+ const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
4926
+ const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);
4927
+ params.appendStartupStage(
4928
+ logPath,
4929
+ `start requested: ui=${params.uiConfig.host}:${params.uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`
4930
+ );
4931
+ console.log(`Starting ${params.appName} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
4932
+ const serveArgs = ["serve", "--ui-port", String(params.uiConfig.port)];
4933
+ params.appendStartupStage(logPath, `spawning background process: ${process.execPath} ${[...process.execArgv, ...serveArgs].join(" ")}`);
4934
+ const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
4935
+ env: process.env,
4936
+ stdio: ["ignore", logFd, logFd],
4937
+ detached: true
4938
+ });
4939
+ params.appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
4940
+ closeSync2(logFd);
4941
+ if (!child.pid) {
4942
+ params.appendStartupStage(logPath, "spawn failed: child pid missing");
4943
+ console.error("Error: Failed to start background service.");
4944
+ params.printStartupFailureDiagnostics({
4945
+ uiUrl: params.uiUrl,
4946
+ apiUrl: params.apiUrl,
4947
+ healthUrl: params.healthUrl,
4948
+ logPath,
4949
+ lastProbeError: null
4950
+ });
4951
+ return null;
4952
+ }
4953
+ const snapshot = {
4954
+ pid: child.pid,
4955
+ uiUrl: params.uiUrl,
4956
+ apiUrl: params.apiUrl,
4957
+ uiHost: params.uiConfig.host,
4958
+ uiPort: params.uiConfig.port,
4959
+ logPath
4960
+ };
4961
+ writeInitialManagedServiceState({
4962
+ config: params.config,
4963
+ readinessTimeoutMs,
4964
+ snapshot
4965
+ });
4966
+ return {
4967
+ child,
4968
+ logPath,
4969
+ readinessTimeoutMs,
4970
+ quickPhaseTimeoutMs,
4971
+ extendedPhaseTimeoutMs,
4972
+ snapshot
4973
+ };
4974
+ }
4975
+ async function waitForManagedServiceReadiness(params) {
4976
+ params.appendStartupStage(params.logPath, `health probe started: ${params.healthUrl} (phase=quick, timeoutMs=${params.quickPhaseTimeoutMs})`);
4977
+ let readiness = await params.waitForBackgroundServiceReady({
4978
+ pid: params.childPid,
4979
+ healthUrl: params.healthUrl,
4980
+ timeoutMs: params.quickPhaseTimeoutMs
4981
+ });
4982
+ if (!readiness.ready && params.isProcessRunning(params.childPid) && params.extendedPhaseTimeoutMs > 0) {
4983
+ console.warn(
4984
+ `Warning: Background service is still running but not ready after ${Math.ceil(params.quickPhaseTimeoutMs / 1e3)}s; waiting up to ${Math.ceil(params.extendedPhaseTimeoutMs / 1e3)}s more.`
4985
+ );
4986
+ params.appendStartupStage(
4987
+ params.logPath,
4988
+ `health probe entering extended phase (timeoutMs=${params.extendedPhaseTimeoutMs}, lastError=${readiness.lastProbeError ?? "none"})`
4989
+ );
4990
+ readiness = await params.waitForBackgroundServiceReady({
4991
+ pid: params.childPid,
4992
+ healthUrl: params.healthUrl,
4993
+ timeoutMs: params.extendedPhaseTimeoutMs
4994
+ });
4995
+ }
4996
+ if (!readiness.ready && params.isProcessRunning(params.childPid)) {
4997
+ params.appendStartupStage(
4998
+ params.logPath,
4999
+ `startup degraded: process alive but health probe timed out after ${params.readinessTimeoutMs}ms (lastError=${readiness.lastProbeError ?? "none"})`
5000
+ );
5001
+ }
5002
+ return readiness;
5003
+ }
5004
+ async function reportManagedServiceStart(params) {
5005
+ if (!params.readiness.ready) {
5006
+ const hint = params.readiness.lastProbeError ? ` Last probe error: ${params.readiness.lastProbeError}` : "";
5007
+ console.warn(
5008
+ `Warning: ${params.appName} is running (PID ${params.state.pid}) but not healthy yet after ${Math.ceil(params.readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
5009
+ );
5010
+ console.warn(`Tip: Run "${params.appName} status --json" and check logs: ${params.state.logPath}`);
5011
+ } else {
5012
+ console.log(`\u2713 ${params.appName} started in background (PID ${params.state.pid})`);
5013
+ }
5014
+ console.log(`UI: ${params.uiUrl}`);
5015
+ console.log(`API: ${params.apiUrl}`);
5016
+ await params.printPublicUiUrls(params.uiConfig.host, params.uiConfig.port);
5017
+ console.log(`Logs: ${params.state.logPath}`);
5018
+ params.printServiceControlHints();
5019
+ }
5020
+
5021
+ // src/cli/commands/service-support/gateway/service-startup-support.ts
5022
+ import chokidar from "chokidar";
5023
+ import { resolve as resolve11 } from "path";
5024
+ var pluginGatewayLogger = {
5025
+ info: (message) => console.log(`[plugins] ${message}`),
5026
+ warn: (message) => console.warn(`[plugins] ${message}`),
5027
+ error: (message) => console.error(`[plugins] ${message}`),
5028
+ debug: (message) => console.debug(`[plugins] ${message}`)
5029
+ };
5030
+ function logPluginGatewayDiagnostics(diagnostics) {
5031
+ for (const diag of diagnostics) {
5032
+ const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
5033
+ const text = `${prefix}${diag.message}`;
5034
+ if (diag.level === "error") {
5035
+ console.error(`[plugins] ${text}`);
5036
+ } else {
5037
+ console.warn(`[plugins] ${text}`);
5038
+ }
5039
+ }
5040
+ }
5041
+ async function startGatewaySupportServices(params) {
5042
+ if (params.cronJobs > 0) {
5043
+ console.log(`\u2713 Cron: ${params.cronJobs} scheduled jobs`);
5044
+ }
5045
+ console.log("\u2713 Heartbeat: every 30m");
5046
+ params.remoteModule?.start();
5047
+ params.watchConfigFile();
5048
+ await params.startCron();
5049
+ await params.startHeartbeat();
5050
+ }
5051
+ function watchCronStoreFile(params) {
5052
+ const cronStorePath = resolve11(params.cronStorePath);
5053
+ const watcher = chokidar.watch(cronStorePath, {
5054
+ ignoreInitial: true,
5055
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
5056
+ });
5057
+ watcher.on("all", (event, changedPath) => {
5058
+ if (resolve11(changedPath) !== cronStorePath) {
5059
+ return;
5060
+ }
5061
+ if (event === "add" || event === "change" || event === "unlink") {
5062
+ try {
5063
+ params.reloadCronStore();
5064
+ } catch (error) {
5065
+ console.error(`Cron store reload failed (${event}): ${String(error)}`);
5066
+ }
5067
+ }
5068
+ });
5069
+ }
5070
+
5071
+ // src/cli/commands/service-support/marketplace/cli-subcommand-launch.ts
5072
+ import { createRequire } from "module";
5073
+ import { extname, resolve as resolve12 } from "path";
5074
+ import { fileURLToPath as fileURLToPath3 } from "url";
5075
+ var require2 = createRequire(import.meta.url);
5076
+ var resolveCliSubcommandEntry = (params) => {
5077
+ const argvEntry = params.argvEntry?.trim();
5078
+ if (argvEntry) {
5079
+ return resolve12(argvEntry);
5080
+ }
5081
+ return fileURLToPath3(new URL("../index.js", params.importMetaUrl));
5082
+ };
5083
+
5084
+ // src/cli/commands/remote-support/remote-access-host.ts
5085
+ import { getConfigPath as getConfigPath8, loadConfig as loadConfig16 } from "@nextclaw/core";
5086
+ import { readPlatformSessionTokenState as readPlatformSessionTokenState2 } from "@nextclaw/remote";
5087
+
5088
+ // src/cli/commands/remote-support/remote-access-service-control.ts
5089
+ import { getConfigPath as getConfigPath7, loadConfig as loadConfig15 } from "@nextclaw/core";
5090
+ import { spawn as spawn3 } from "child_process";
5091
+ var FORCED_PUBLIC_UI_HOST = "0.0.0.0";
5092
+ function resolveRemoteServiceView(currentUi) {
5093
+ if (currentUi) {
5094
+ return {
5095
+ running: true,
5096
+ currentProcess: true,
5097
+ pid: process.pid,
4753
5098
  uiUrl: resolveUiApiBase(currentUi.host, currentUi.port),
4754
5099
  uiPort: currentUi.port
4755
5100
  };
@@ -4830,7 +5175,7 @@ async function controlManagedService(action, deps) {
4830
5175
  return { accepted: true, action, message: "Managed service restarted." };
4831
5176
  }
4832
5177
  function resolveManagedUiOverrides() {
4833
- const config2 = loadConfig14(getConfigPath7());
5178
+ const config2 = loadConfig15(getConfigPath7());
4834
5179
  const resolved = resolveUiConfig(config2, {
4835
5180
  enabled: true,
4836
5181
  host: FORCED_PUBLIC_UI_HOST,
@@ -4891,7 +5236,7 @@ function launchManagedSelfControl(params = {}) {
4891
5236
  " process.exit(0);",
4892
5237
  "}, 250);"
4893
5238
  ].join("");
4894
- const helper = spawn2(process.execPath, ["-e", script], {
5239
+ const helper = spawn3(process.execPath, ["-e", script], {
4895
5240
  detached: true,
4896
5241
  stdio: "ignore",
4897
5242
  env: process.env,
@@ -4900,7 +5245,7 @@ function launchManagedSelfControl(params = {}) {
4900
5245
  helper.unref();
4901
5246
  }
4902
5247
 
4903
- // src/cli/commands/remote-access-host.ts
5248
+ // src/cli/commands/remote-support/remote-access-host.ts
4904
5249
  function normalizeOptionalString4(value) {
4905
5250
  if (typeof value !== "string") {
4906
5251
  return null;
@@ -4930,7 +5275,7 @@ var RemoteAccessHost = class {
4930
5275
  this.deps = deps;
4931
5276
  }
4932
5277
  getStatus() {
4933
- const config2 = loadConfig15(getConfigPath8());
5278
+ const config2 = loadConfig16(getConfigPath8());
4934
5279
  const status = this.deps.remoteCommands.getStatusView();
4935
5280
  const account = this.readAccountView({
4936
5281
  token: normalizeOptionalString4(config2.providers.nextclaw?.apiKey),
@@ -4967,7 +5312,7 @@ var RemoteAccessHost = class {
4967
5312
  };
4968
5313
  }
4969
5314
  async pollBrowserAuth(input) {
4970
- const config2 = loadConfig15(getConfigPath8());
5315
+ const config2 = loadConfig16(getConfigPath8());
4971
5316
  const result = await this.deps.platformAuthCommands.pollBrowserAuth({
4972
5317
  apiBase: normalizeOptionalString4(input.apiBase) ?? normalizeOptionalString4(config2.remote.platformApiBase) ?? normalizeOptionalString4(config2.providers.nextclaw?.apiBase) ?? void 0,
4973
5318
  sessionId: input.sessionId
@@ -5029,7 +5374,7 @@ var RemoteAccessHost = class {
5029
5374
  }
5030
5375
  };
5031
5376
 
5032
- // src/cli/commands/service-remote-access.ts
5377
+ // src/cli/commands/service-support/runtime/service-remote-access.ts
5033
5378
  function requestManagedServiceRestart(requestRestart, options = {}) {
5034
5379
  const uiPort = typeof options.uiPort === "number" && Number.isFinite(options.uiPort) ? Math.floor(options.uiPort) : void 0;
5035
5380
  const reason = options.reason?.trim() || "remote access service restart";
@@ -5077,13 +5422,10 @@ import {
5077
5422
  readAssistantReasoningNormalizationModeFromMetadata,
5078
5423
  writeAssistantReasoningNormalizationModeToMetadata
5079
5424
  } from "@nextclaw/ncp";
5080
- import {
5081
- createAgentClientFromServer,
5082
- DefaultNcpAgentBackend
5083
- } from "@nextclaw/ncp-toolkit";
5425
+ import { DefaultNcpAgentBackend } from "@nextclaw/ncp-toolkit";
5084
5426
 
5085
- // src/cli/commands/ncp/ncp-asset-tools.ts
5086
- import { resolve as resolve12 } from "path";
5427
+ // src/cli/commands/ncp/runtime/ncp-asset-tools.ts
5428
+ import { resolve as resolve13 } from "path";
5087
5429
  import {
5088
5430
  buildAssetContentPath
5089
5431
  } from "@nextclaw/ncp-agent-runtime";
@@ -5199,7 +5541,7 @@ var AssetExportTool = class {
5199
5541
  if (!assetUri || !targetPath) {
5200
5542
  throw new Error("asset_export requires assetUri and targetPath.");
5201
5543
  }
5202
- const exportedPath = await this.assetStore.export({ uri: assetUri }, resolve12(targetPath));
5544
+ const exportedPath = await this.assetStore.export({ uri: assetUri }, resolve13(targetPath));
5203
5545
  return {
5204
5546
  ok: true,
5205
5547
  assetUri,
@@ -5258,10 +5600,12 @@ function createAssetTools(params) {
5258
5600
  import {
5259
5601
  buildToolCatalogEntries,
5260
5602
  ContextBuilder,
5603
+ findEffectiveAgentProfile,
5261
5604
  InputBudgetPruner,
5262
- getWorkspacePath as getWorkspacePath7,
5605
+ getWorkspacePath as getWorkspacePath8,
5263
5606
  parseThinkingLevel as parseThinkingLevel2,
5264
5607
  readSessionProjectRoot,
5608
+ resolveDefaultAgentProfileId,
5265
5609
  resolveSessionWorkspacePath,
5266
5610
  resolveThinkingLevel
5267
5611
  } from "@nextclaw/core";
@@ -5456,7 +5800,7 @@ function toLegacyMessages(messages, options = {}) {
5456
5800
  return legacyMessages;
5457
5801
  }
5458
5802
 
5459
- // src/cli/commands/ncp/nextclaw-ncp-session-preferences.ts
5803
+ // src/cli/commands/ncp/context/nextclaw-ncp-session-preferences.ts
5460
5804
  import { parseThinkingLevel } from "@nextclaw/core";
5461
5805
  function normalizeOptionalString5(value) {
5462
5806
  if (typeof value !== "string") {
@@ -5534,7 +5878,7 @@ function resolveSessionChannelContext(params) {
5534
5878
  return { channel, chatId };
5535
5879
  }
5536
5880
 
5537
- // src/cli/commands/ncp/nextclaw-ncp-current-turn.ts
5881
+ // src/cli/commands/ncp/context/nextclaw-ncp-current-turn.ts
5538
5882
  function isTextLikePart2(part) {
5539
5883
  return part.type === "text" || part.type === "rich-text";
5540
5884
  }
@@ -5670,7 +6014,6 @@ var SpawnChildSessionTool = class extends Tool {
5670
6014
  }
5671
6015
  sourceSessionId = "";
5672
6016
  sourceSessionMetadata = {};
5673
- agentId;
5674
6017
  handoffDepth = 0;
5675
6018
  get name() {
5676
6019
  return "spawn";
@@ -5684,7 +6027,11 @@ var SpawnChildSessionTool = class extends Tool {
5684
6027
  properties: {
5685
6028
  task: { type: "string", description: "Task to run inside the child session." },
5686
6029
  label: { type: "string", description: "Optional child session title." },
5687
- model: { type: "string", description: "Optional model override for the child session." }
6030
+ model: { type: "string", description: "Optional model override for the child session." },
6031
+ agentId: {
6032
+ type: "string",
6033
+ description: "Optional target agent id for the child session. Omit to use the default agent."
6034
+ }
5688
6035
  },
5689
6036
  required: ["task"]
5690
6037
  };
@@ -5692,7 +6039,6 @@ var SpawnChildSessionTool = class extends Tool {
5692
6039
  setContext = (params) => {
5693
6040
  this.sourceSessionId = params.sourceSessionId;
5694
6041
  this.sourceSessionMetadata = structuredClone(params.sourceSessionMetadata);
5695
- this.agentId = params.agentId;
5696
6042
  this.handoffDepth = params.handoffDepth ?? 0;
5697
6043
  };
5698
6044
  execute = async (params, toolCallId) => {
@@ -5705,7 +6051,7 @@ var SpawnChildSessionTool = class extends Tool {
5705
6051
  title: readOptionalString2(params, "label"),
5706
6052
  model: readOptionalString2(params, "model"),
5707
6053
  handoffDepth: this.handoffDepth,
5708
- agentId: this.agentId
6054
+ agentId: readOptionalString2(params, "agentId")
5709
6055
  });
5710
6056
  };
5711
6057
  };
@@ -5809,7 +6155,6 @@ var SessionSpawnTool = class extends Tool2 {
5809
6155
  }
5810
6156
  sourceSessionId = "";
5811
6157
  sourceSessionMetadata = {};
5812
- agentId;
5813
6158
  get name() {
5814
6159
  return "sessions_spawn";
5815
6160
  }
@@ -5831,6 +6176,10 @@ var SessionSpawnTool = class extends Tool2 {
5831
6176
  model: {
5832
6177
  type: "string",
5833
6178
  description: "Optional model override for the new session."
6179
+ },
6180
+ agentId: {
6181
+ type: "string",
6182
+ description: "Optional target agent id for the new session. Omit to use the default agent."
5834
6183
  }
5835
6184
  },
5836
6185
  required: ["task"]
@@ -5839,7 +6188,6 @@ var SessionSpawnTool = class extends Tool2 {
5839
6188
  setContext = (params) => {
5840
6189
  this.sourceSessionId = params.sourceSessionId;
5841
6190
  this.sourceSessionMetadata = structuredClone(params.sourceSessionMetadata);
5842
- this.agentId = params.agentId;
5843
6191
  };
5844
6192
  execute = async (params) => {
5845
6193
  const task = readRequiredString2(params.task, "task");
@@ -5847,12 +6195,13 @@ var SessionSpawnTool = class extends Tool2 {
5847
6195
  task,
5848
6196
  title: readOptionalString3(params.title),
5849
6197
  sourceSessionMetadata: this.sourceSessionMetadata,
5850
- agentId: this.agentId,
6198
+ agentId: readOptionalString3(params.agentId),
5851
6199
  model: readOptionalString3(params.model)
5852
6200
  });
5853
6201
  return {
5854
6202
  kind: "nextclaw.session",
5855
6203
  sessionId: session.sessionId,
6204
+ ...session.agentId ? { agentId: session.agentId } : {},
5856
6205
  ...session.parentSessionId ? { parentSessionId: session.parentSessionId } : {},
5857
6206
  isChildSession: false,
5858
6207
  lifecycle: session.lifecycle,
@@ -5978,7 +6327,6 @@ var NextclawNcpToolRegistry = class {
5978
6327
  spawnTool.setContext({
5979
6328
  sourceSessionId: context.sessionId,
5980
6329
  sourceSessionMetadata: context.metadata,
5981
- agentId: context.agentId,
5982
6330
  handoffDepth: context.handoffDepth
5983
6331
  });
5984
6332
  this.registerTool(spawnTool);
@@ -5987,8 +6335,7 @@ var NextclawNcpToolRegistry = class {
5987
6335
  );
5988
6336
  sessionsSpawnTool.setContext({
5989
6337
  sourceSessionId: context.sessionId,
5990
- sourceSessionMetadata: context.metadata,
5991
- agentId: context.agentId
6338
+ sourceSessionMetadata: context.metadata
5992
6339
  });
5993
6340
  this.registerTool(sessionsSpawnTool);
5994
6341
  const sessionsRequestTool = new SessionRequestTool(
@@ -6128,18 +6475,25 @@ function resolveRequestedToolNames(metadata) {
6128
6475
  )
6129
6476
  );
6130
6477
  }
6131
- function resolvePrimaryAgentProfile(config2) {
6132
- const configuredDefaultAgentId = config2.agents.list.find((entry) => entry.default)?.id?.trim() || config2.agents.list[0]?.id?.trim() || "main";
6133
- const profile = config2.agents.list.find((entry) => entry.id.trim() === configuredDefaultAgentId);
6478
+ function readRequestedAgentId(metadata) {
6479
+ return normalizeString(metadata.agent_id)?.toLowerCase() ?? normalizeString(metadata.agentId)?.toLowerCase() ?? null;
6480
+ }
6481
+ function resolveAgentProfile(params) {
6482
+ const defaultAgentId = resolveDefaultAgentProfileId(params.config);
6483
+ const candidateAgentId = normalizeString(params.storedAgentId)?.toLowerCase() ?? readRequestedAgentId(params.requestMetadata) ?? defaultAgentId;
6484
+ const profile = findEffectiveAgentProfile(params.config, candidateAgentId) ?? findEffectiveAgentProfile(params.config, defaultAgentId);
6485
+ if (!profile) {
6486
+ throw new Error(`default agent profile not found: ${defaultAgentId}`);
6487
+ }
6134
6488
  return {
6135
- agentId: configuredDefaultAgentId,
6136
- workspace: getWorkspacePath7(profile?.workspace ?? config2.agents.defaults.workspace),
6137
- model: profile?.model ?? config2.agents.defaults.model,
6138
- maxIterations: profile?.maxToolIterations ?? config2.agents.defaults.maxToolIterations,
6139
- contextTokens: profile?.contextTokens ?? config2.agents.defaults.contextTokens,
6140
- restrictToWorkspace: config2.tools.restrictToWorkspace,
6141
- searchConfig: config2.search,
6142
- execTimeoutSeconds: config2.tools.exec.timeout
6489
+ agentId: profile.id,
6490
+ workspace: getWorkspacePath8(profile.workspace ?? params.config.agents.defaults.workspace),
6491
+ model: profile.model ?? params.config.agents.defaults.model,
6492
+ maxIterations: profile.maxToolIterations ?? params.config.agents.defaults.maxToolIterations,
6493
+ contextTokens: profile.contextTokens ?? params.config.agents.defaults.contextTokens,
6494
+ restrictToWorkspace: params.config.tools.restrictToWorkspace,
6495
+ searchConfig: params.config.search,
6496
+ execTimeoutSeconds: params.config.tools.exec.timeout
6143
6497
  };
6144
6498
  }
6145
6499
  function shouldAppendTimeHint(content) {
@@ -6224,9 +6578,13 @@ var NextclawNcpContextBuilder = class {
6224
6578
  inputBudgetPruner = new InputBudgetPruner();
6225
6579
  prepare = (input, _options) => {
6226
6580
  const config2 = this.options.getConfig();
6227
- const profile = resolvePrimaryAgentProfile(config2);
6228
6581
  const requestMetadata = mergeInputMetadata(input);
6229
6582
  const session = this.options.sessionManager.getOrCreate(input.sessionId);
6583
+ const profile = resolveAgentProfile({
6584
+ config: config2,
6585
+ storedAgentId: session.agentId,
6586
+ requestMetadata
6587
+ });
6230
6588
  let effectiveModel = resolveEffectiveModel({
6231
6589
  session,
6232
6590
  requestMetadata,
@@ -6325,17 +6683,8 @@ var NextclawNcpContextBuilder = class {
6325
6683
  };
6326
6684
  };
6327
6685
 
6328
- // src/cli/commands/ncp/nextclaw-agent-session-store.ts
6686
+ // src/cli/commands/ncp/session/nextclaw-agent-session-message-adapter.ts
6329
6687
  import { sanitizeAssistantReplyTags as sanitizeAssistantReplyTags2 } from "@nextclaw/ncp";
6330
-
6331
- // src/cli/commands/ncp/nextclaw-agent-session-metadata.utils.ts
6332
- function resolvePersistedSessionMetadata(params) {
6333
- const messageMetadata = extractMessageMetadata(params.sessionRecord.messages);
6334
- const nextMetadata = params.preserveExistingMetadata ? mergeSessionMetadata(params.currentMetadata, messageMetadata) : mergeSessionMetadata({}, messageMetadata);
6335
- return mergeSessionMetadata(nextMetadata, cloneMetadata(params.sessionRecord.metadata));
6336
- }
6337
-
6338
- // src/cli/commands/ncp/nextclaw-agent-session-store.ts
6339
6688
  function tryParseJson(value) {
6340
6689
  try {
6341
6690
  return JSON.parse(value);
@@ -6587,6 +6936,21 @@ function resolveLegacyEventType(message) {
6587
6936
  }
6588
6937
  return `message.${role || "other"}`;
6589
6938
  }
6939
+
6940
+ // src/cli/commands/ncp/session/nextclaw-agent-session-metadata.utils.ts
6941
+ function resolvePersistedSessionMetadata(params) {
6942
+ const messageMetadata = extractMessageMetadata(params.sessionRecord.messages);
6943
+ const nextMetadata = params.preserveExistingMetadata ? mergeSessionMetadata(params.currentMetadata, messageMetadata) : mergeSessionMetadata({}, messageMetadata);
6944
+ return mergeSessionMetadata(nextMetadata, cloneMetadata(params.sessionRecord.metadata));
6945
+ }
6946
+
6947
+ // src/cli/commands/ncp/nextclaw-agent-session-store.ts
6948
+ function readAgentIdFromMetadata(metadata) {
6949
+ return normalizeString(metadata?.agent_id)?.toLowerCase() ?? normalizeString(metadata?.agentId)?.toLowerCase() ?? void 0;
6950
+ }
6951
+ function resolveSessionRecordAgentId(record) {
6952
+ return normalizeString(record.agentId)?.toLowerCase() ?? readAgentIdFromMetadata(record.metadata);
6953
+ }
6590
6954
  var NextclawAgentSessionStore = class {
6591
6955
  constructor(sessionManager, options = {}) {
6592
6956
  this.sessionManager = sessionManager;
@@ -6597,7 +6961,13 @@ var NextclawAgentSessionStore = class {
6597
6961
  if (!session) {
6598
6962
  return null;
6599
6963
  }
6600
- return { sessionId, messages: toNcpMessages(sessionId, session.messages), updatedAt: session.updatedAt.toISOString(), metadata: structuredClone(session.metadata) };
6964
+ return {
6965
+ sessionId,
6966
+ ...session.agentId ? { agentId: session.agentId } : {},
6967
+ messages: toNcpMessages(sessionId, session.messages),
6968
+ updatedAt: session.updatedAt.toISOString(),
6969
+ metadata: structuredClone(session.metadata)
6970
+ };
6601
6971
  };
6602
6972
  listSessions = async () => {
6603
6973
  const records = this.sessionManager.listSessions();
@@ -6611,7 +6981,13 @@ var NextclawAgentSessionStore = class {
6611
6981
  if (!session) {
6612
6982
  continue;
6613
6983
  }
6614
- sessions.push({ sessionId, messages: toNcpMessages(sessionId, session.messages), updatedAt: session.updatedAt.toISOString(), metadata: structuredClone(session.metadata) });
6984
+ sessions.push({
6985
+ sessionId,
6986
+ ...session.agentId ? { agentId: session.agentId } : {},
6987
+ messages: toNcpMessages(sessionId, session.messages),
6988
+ updatedAt: session.updatedAt.toISOString(),
6989
+ metadata: structuredClone(session.metadata)
6990
+ });
6615
6991
  }
6616
6992
  sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
6617
6993
  return sessions;
@@ -6622,6 +6998,10 @@ var NextclawAgentSessionStore = class {
6622
6998
  }
6623
6999
  const session = this.sessionManager.getIfExists(sessionRecord.sessionId) ?? this.sessionManager.getOrCreate(sessionRecord.sessionId);
6624
7000
  const legacyMessages = toLegacyMessages(sessionRecord.messages);
7001
+ const nextAgentId = resolveSessionRecordAgentId(sessionRecord);
7002
+ if (nextAgentId) {
7003
+ session.agentId = nextAgentId;
7004
+ }
6625
7005
  session.metadata = resolvePersistedSessionMetadata({
6626
7006
  currentMetadata: session.metadata,
6627
7007
  sessionRecord,
@@ -6664,7 +7044,7 @@ var NextclawAgentSessionStore = class {
6664
7044
  };
6665
7045
  };
6666
7046
 
6667
- // src/cli/commands/ncp/provider-manager-ncp-llm-api.ts
7047
+ // src/cli/commands/ncp/provider/provider-manager-ncp-llm-api.ts
6668
7048
  import { parseThinkingLevel as parseThinkingLevel3 } from "@nextclaw/core";
6669
7049
  function normalizeModel(value) {
6670
7050
  if (typeof value !== "string") {
@@ -6782,6 +7162,7 @@ var ProviderManagerNcpLLMApi = class {
6782
7162
  };
6783
7163
 
6784
7164
  // src/cli/commands/ncp/session-request/session-creation.service.ts
7165
+ import { resolveDefaultAgentProfileId as resolveDefaultAgentProfileId2 } from "@nextclaw/core";
6785
7166
  import { randomUUID } from "crypto";
6786
7167
  var DEFAULT_SESSION_TYPE = "native";
6787
7168
  var DEFAULT_LIFECYCLE = "persistent";
@@ -6827,51 +7208,85 @@ function cloneInheritedMetadata(sourceMetadata) {
6827
7208
  }
6828
7209
  return nextMetadata;
6829
7210
  }
6830
- function buildSessionId(agentId) {
6831
- void agentId;
7211
+ function buildSessionId() {
6832
7212
  return `ncp-${Date.now().toString(36)}-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
6833
7213
  }
7214
+ function resolveSessionAgentId(params) {
7215
+ return readOptionalString4(params.agentId) ?? resolveDefaultAgentProfileId2(params.getConfig());
7216
+ }
7217
+ function resolveSessionTitle(params) {
7218
+ return readOptionalString4(params.title) ?? summarizeTask(params.task);
7219
+ }
7220
+ function resolveSessionType(params) {
7221
+ return readOptionalString4(params.sessionType) ?? readOptionalString4(params.metadata.session_type) ?? DEFAULT_SESSION_TYPE;
7222
+ }
7223
+ function applySessionOverrides(params) {
7224
+ params.metadata.session_type = params.sessionType;
7225
+ params.metadata[SESSION_METADATA_LABEL_KEY] = params.title;
7226
+ params.metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = params.lifecycle;
7227
+ if (params.parentSessionId) {
7228
+ params.metadata[CHILD_SESSION_PARENT_METADATA_KEY] = params.parentSessionId;
7229
+ params.metadata[CHILD_SESSION_PROMOTED_METADATA_KEY] = false;
7230
+ }
7231
+ if (params.requestId) {
7232
+ params.metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = params.requestId;
7233
+ }
7234
+ if (readOptionalString4(params.model)) {
7235
+ params.metadata.model = params.model?.trim();
7236
+ params.metadata.preferred_model = params.model?.trim();
7237
+ }
7238
+ if (readOptionalString4(params.thinkingLevel)) {
7239
+ params.metadata.thinking = params.thinkingLevel?.trim();
7240
+ params.metadata.preferred_thinking = params.thinkingLevel?.trim();
7241
+ }
7242
+ if (readOptionalString4(params.projectRoot)) {
7243
+ params.metadata.project_root = params.projectRoot?.trim();
7244
+ }
7245
+ }
6834
7246
  var SessionCreationService = class {
6835
- constructor(sessionManager, onSessionUpdated) {
7247
+ constructor(sessionManager, getConfig, onSessionUpdated) {
6836
7248
  this.sessionManager = sessionManager;
7249
+ this.getConfig = getConfig;
6837
7250
  this.onSessionUpdated = onSessionUpdated;
6838
7251
  }
6839
7252
  createSession = (params) => {
6840
- const sessionId = buildSessionId(params.agentId);
7253
+ const sessionId = buildSessionId();
6841
7254
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
6842
7255
  const session = this.sessionManager.getOrCreate(sessionId);
6843
- const title = readOptionalString4(params.title) ?? summarizeTask(params.task);
7256
+ const resolvedAgentId = resolveSessionAgentId({
7257
+ agentId: params.agentId,
7258
+ getConfig: this.getConfig
7259
+ });
7260
+ const title = resolveSessionTitle({
7261
+ title: params.title,
7262
+ task: params.task
7263
+ });
6844
7264
  const metadata = cloneInheritedMetadata(params.sourceSessionMetadata);
6845
7265
  const parentSessionId = readOptionalString4(params.parentSessionId);
6846
7266
  const requestId = readOptionalString4(params.requestId);
6847
- const sessionType = readOptionalString4(params.sessionType) ?? readOptionalString4(metadata.session_type) ?? DEFAULT_SESSION_TYPE;
6848
- metadata.session_type = sessionType;
6849
- metadata[SESSION_METADATA_LABEL_KEY] = title;
6850
- metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = DEFAULT_LIFECYCLE;
6851
- if (parentSessionId) {
6852
- metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
6853
- metadata[CHILD_SESSION_PROMOTED_METADATA_KEY] = false;
6854
- }
6855
- if (requestId) {
6856
- metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
6857
- }
6858
- if (readOptionalString4(params.model)) {
6859
- metadata.model = params.model?.trim();
6860
- metadata.preferred_model = params.model?.trim();
6861
- }
6862
- if (readOptionalString4(params.thinkingLevel)) {
6863
- metadata.thinking = params.thinkingLevel?.trim();
6864
- metadata.preferred_thinking = params.thinkingLevel?.trim();
6865
- }
6866
- if (readOptionalString4(params.projectRoot)) {
6867
- metadata.project_root = params.projectRoot?.trim();
6868
- }
7267
+ const sessionType = resolveSessionType({
7268
+ sessionType: params.sessionType,
7269
+ metadata
7270
+ });
7271
+ applySessionOverrides({
7272
+ metadata,
7273
+ sessionType,
7274
+ title,
7275
+ lifecycle: DEFAULT_LIFECYCLE,
7276
+ parentSessionId,
7277
+ requestId,
7278
+ model: params.model,
7279
+ thinkingLevel: params.thinkingLevel,
7280
+ projectRoot: params.projectRoot
7281
+ });
7282
+ session.agentId = resolvedAgentId;
6869
7283
  session.metadata = metadata;
6870
7284
  session.updatedAt = new Date(now3);
6871
7285
  this.sessionManager.save(session);
6872
7286
  this.onSessionUpdated?.(sessionId);
6873
7287
  return {
6874
7288
  sessionId,
7289
+ agentId: resolvedAgentId,
6875
7290
  sessionType,
6876
7291
  runtimeFamily: "native",
6877
7292
  ...parentSessionId ? { parentSessionId } : {},
@@ -6910,6 +7325,8 @@ import {
6910
7325
  NcpEventType
6911
7326
  } from "@nextclaw/ncp";
6912
7327
  import { randomUUID as randomUUID2 } from "crypto";
7328
+
7329
+ // src/cli/commands/ncp/session-request/session-request-result.ts
6913
7330
  function readOptionalString5(value) {
6914
7331
  if (typeof value !== "string") {
6915
7332
  return null;
@@ -6917,7 +7334,7 @@ function readOptionalString5(value) {
6917
7334
  const trimmed = value.trim();
6918
7335
  return trimmed.length > 0 ? trimmed : null;
6919
7336
  }
6920
- function summarizeTask2(task) {
7337
+ function summarizeSessionRequestTask(task) {
6921
7338
  const normalized = task.trim().replace(/\s+/g, " ");
6922
7339
  if (!normalized) {
6923
7340
  return "Session request";
@@ -6927,21 +7344,7 @@ function summarizeTask2(task) {
6927
7344
  }
6928
7345
  return `${normalized.slice(0, 69)}...`;
6929
7346
  }
6930
- function buildUserMessage(params) {
6931
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6932
- return {
6933
- id: `${params.sessionId}:user:session-request:${params.requestId}`,
6934
- sessionId: params.sessionId,
6935
- role: "user",
6936
- status: "final",
6937
- timestamp,
6938
- parts: [{ type: "text", text: params.task }],
6939
- metadata: {
6940
- session_request_id: params.requestId
6941
- }
6942
- };
6943
- }
6944
- function extractMessageText(message) {
7347
+ function extractSessionMessageText(message) {
6945
7348
  if (!message) {
6946
7349
  return void 0;
6947
7350
  }
@@ -6968,26 +7371,110 @@ function findLatestAssistantMessage(messages) {
6968
7371
  function readParentSessionId(metadata) {
6969
7372
  return readOptionalString5(metadata?.[CHILD_SESSION_PARENT_METADATA_KEY]) ?? void 0;
6970
7373
  }
6971
- function buildToolResult(params) {
7374
+ function buildSessionRequestToolResult(params) {
7375
+ const {
7376
+ request,
7377
+ task,
7378
+ title,
7379
+ agentId,
7380
+ isChildSession,
7381
+ parentSessionId,
7382
+ spawnedByRequestId,
7383
+ message
7384
+ } = params;
6972
7385
  return {
6973
7386
  kind: "nextclaw.session_request",
6974
- requestId: params.request.requestId,
6975
- sessionId: params.request.targetSessionId,
6976
- targetKind: params.isChildSession ? "child" : "session",
6977
- ...params.parentSessionId ? { parentSessionId: params.parentSessionId } : {},
6978
- ...params.spawnedByRequestId ? { spawnedByRequestId: params.spawnedByRequestId } : {},
6979
- isChildSession: params.isChildSession,
7387
+ requestId: request.requestId,
7388
+ sessionId: request.targetSessionId,
7389
+ ...agentId ? { agentId } : {},
7390
+ targetKind: isChildSession ? "child" : "session",
7391
+ ...parentSessionId ? { parentSessionId } : {},
7392
+ ...spawnedByRequestId ? { spawnedByRequestId } : {},
7393
+ isChildSession,
6980
7394
  lifecycle: "persistent",
6981
- ...params.title.trim() ? { title: params.title } : {},
6982
- task: params.task,
6983
- status: params.request.status,
6984
- awaitMode: params.request.awaitMode,
6985
- deliveryMode: params.request.deliveryMode,
6986
- ...params.request.finalResponseText ? { finalResponseText: params.request.finalResponseText } : {},
6987
- ...params.request.error ? { error: params.request.error } : {},
6988
- ...params.message ? { message: params.message } : {}
7395
+ ...title.trim() ? { title } : {},
7396
+ task,
7397
+ status: request.status,
7398
+ awaitMode: request.awaitMode,
7399
+ deliveryMode: request.deliveryMode,
7400
+ ...request.finalResponseText ? { finalResponseText: request.finalResponseText } : {},
7401
+ ...request.error ? { error: request.error } : {},
7402
+ ...message ? { message } : {}
7403
+ };
7404
+ }
7405
+
7406
+ // src/cli/commands/ncp/session-request/session-request-execution.ts
7407
+ function buildSessionRequestUserMessage(params) {
7408
+ const { sessionId, requestId, task } = params;
7409
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
7410
+ return {
7411
+ id: `${sessionId}:user:session-request:${requestId}`,
7412
+ sessionId,
7413
+ role: "user",
7414
+ status: "final",
7415
+ timestamp,
7416
+ parts: [{ type: "text", text: task }],
7417
+ metadata: {
7418
+ session_request_id: requestId
7419
+ }
7420
+ };
7421
+ }
7422
+ function createRunningSessionRequest(params) {
7423
+ const {
7424
+ requestId,
7425
+ sourceSessionId,
7426
+ targetSessionId,
7427
+ sourceToolCallId,
7428
+ handoffDepth,
7429
+ awaitMode,
7430
+ deliveryMode,
7431
+ title,
7432
+ task,
7433
+ isChildSession,
7434
+ parentSessionId
7435
+ } = params;
7436
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7437
+ return {
7438
+ requestId,
7439
+ sourceSessionId,
7440
+ targetSessionId,
7441
+ sourceToolCallId,
7442
+ rootRequestId: requestId,
7443
+ handoffDepth,
7444
+ awaitMode,
7445
+ deliveryMode,
7446
+ status: "running",
7447
+ createdAt,
7448
+ startedAt: createdAt,
7449
+ metadata: {
7450
+ title,
7451
+ task,
7452
+ is_child_session: isChildSession,
7453
+ ...parentSessionId ? { parent_session_id: parentSessionId } : {}
7454
+ }
7455
+ };
7456
+ }
7457
+ function createCompletedSessionRequest(params) {
7458
+ const { request, completedMessage, finalResponseText } = params;
7459
+ return {
7460
+ ...request,
7461
+ status: "completed",
7462
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7463
+ finalResponseMessageId: completedMessage?.id,
7464
+ finalResponseText
7465
+ };
7466
+ }
7467
+ function createFailedSessionRequest(params) {
7468
+ const { request, error } = params;
7469
+ return {
7470
+ ...request,
7471
+ status: "failed",
7472
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7473
+ error: error instanceof Error ? error.message : String(error)
6989
7474
  };
6990
7475
  }
7476
+
7477
+ // src/cli/commands/ncp/session-request/session-request-broker.ts
6991
7478
  var SessionRequestBroker = class {
6992
7479
  constructor(sessionManager, sessionCreationService, deliveryService, resolveBackend, onSessionUpdated) {
6993
7480
  this.sessionManager = sessionManager;
@@ -6997,200 +7484,275 @@ var SessionRequestBroker = class {
6997
7484
  this.onSessionUpdated = onSessionUpdated;
6998
7485
  }
6999
7486
  spawnChildSessionAndRequest = async (params) => {
7487
+ const {
7488
+ sourceSessionId,
7489
+ sourceToolCallId,
7490
+ sourceSessionMetadata,
7491
+ task,
7492
+ title,
7493
+ model,
7494
+ handoffDepth,
7495
+ sessionType,
7496
+ thinkingLevel,
7497
+ projectRoot,
7498
+ agentId
7499
+ } = params;
7000
7500
  const requestId = randomUUID2();
7001
7501
  const childSession = this.sessionCreationService.createChildSession({
7002
- parentSessionId: params.sourceSessionId,
7003
- task: params.task,
7004
- title: params.title,
7005
- sourceSessionMetadata: params.sourceSessionMetadata,
7006
- agentId: params.agentId,
7007
- model: params.model,
7008
- thinkingLevel: params.thinkingLevel,
7009
- sessionType: params.sessionType,
7010
- projectRoot: params.projectRoot,
7502
+ parentSessionId: sourceSessionId,
7503
+ task,
7504
+ title,
7505
+ sourceSessionMetadata,
7506
+ agentId,
7507
+ model,
7508
+ thinkingLevel,
7509
+ sessionType,
7510
+ projectRoot,
7011
7511
  requestId
7012
7512
  });
7013
7513
  return this.dispatchRequest({
7014
7514
  requestId,
7015
- sourceSessionId: params.sourceSessionId,
7016
- sourceToolCallId: params.sourceToolCallId,
7515
+ sourceSessionId,
7516
+ sourceToolCallId,
7017
7517
  targetSessionId: childSession.sessionId,
7018
- task: params.task,
7019
- title: childSession.title ?? summarizeTask2(params.task),
7020
- handoffDepth: params.handoffDepth ?? 0,
7518
+ task,
7519
+ title: childSession.title ?? summarizeSessionRequestTask(task),
7520
+ handoffDepth: handoffDepth ?? 0,
7021
7521
  awaitMode: "final_reply",
7022
7522
  deliveryMode: "resume_source",
7523
+ agentId: childSession.agentId,
7023
7524
  isChildSession: true,
7024
- parentSessionId: params.sourceSessionId,
7525
+ parentSessionId: sourceSessionId,
7025
7526
  spawnedByRequestId: requestId
7026
7527
  });
7027
7528
  };
7028
7529
  requestSession = async (params) => {
7029
- if (params.targetSessionId.trim() === params.sourceSessionId.trim()) {
7530
+ const {
7531
+ sourceSessionId,
7532
+ sourceToolCallId,
7533
+ targetSessionId,
7534
+ task,
7535
+ title,
7536
+ awaitMode,
7537
+ deliveryMode,
7538
+ handoffDepth
7539
+ } = params;
7540
+ const normalizedTargetSessionId = targetSessionId.trim();
7541
+ if (normalizedTargetSessionId === sourceSessionId.trim()) {
7030
7542
  throw new Error("sessions_request cannot target the current session.");
7031
7543
  }
7032
7544
  const backend = this.resolveBackend();
7033
7545
  if (!backend) {
7034
7546
  throw new Error("NCP backend is not ready for session requests.");
7035
7547
  }
7036
- const targetSummary = await backend.getSession(params.targetSessionId.trim());
7548
+ const targetSummary = await backend.getSession(normalizedTargetSessionId);
7037
7549
  if (!targetSummary) {
7038
- throw new Error(`Target session not found: ${params.targetSessionId}`);
7550
+ throw new Error(`Target session not found: ${targetSessionId}`);
7039
7551
  }
7040
7552
  const parentSessionId = readParentSessionId(targetSummary.metadata);
7041
7553
  return this.dispatchRequest({
7042
7554
  requestId: randomUUID2(),
7043
- sourceSessionId: params.sourceSessionId,
7044
- sourceToolCallId: params.sourceToolCallId,
7045
- targetSessionId: params.targetSessionId.trim(),
7046
- task: params.task,
7047
- title: readOptionalString5(params.title) ?? readOptionalString5(targetSummary.metadata?.label) ?? summarizeTask2(params.task),
7048
- handoffDepth: params.handoffDepth ?? 0,
7049
- awaitMode: params.awaitMode,
7050
- deliveryMode: params.deliveryMode,
7555
+ sourceSessionId,
7556
+ sourceToolCallId,
7557
+ targetSessionId: normalizedTargetSessionId,
7558
+ task,
7559
+ title: readOptionalString5(title) ?? readOptionalString5(targetSummary.metadata?.label) ?? summarizeSessionRequestTask(task),
7560
+ handoffDepth: handoffDepth ?? 0,
7561
+ awaitMode,
7562
+ deliveryMode,
7563
+ agentId: targetSummary.agentId,
7051
7564
  isChildSession: Boolean(parentSessionId),
7052
7565
  parentSessionId: parentSessionId ?? void 0,
7053
7566
  spawnedByRequestId: void 0
7054
7567
  });
7055
7568
  };
7056
7569
  dispatchRequest = async (params) => {
7057
- const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7058
- const request = {
7059
- requestId: params.requestId,
7060
- sourceSessionId: params.sourceSessionId,
7061
- targetSessionId: params.targetSessionId,
7062
- sourceToolCallId: params.sourceToolCallId,
7063
- rootRequestId: params.requestId,
7064
- handoffDepth: params.handoffDepth,
7065
- awaitMode: params.awaitMode,
7066
- deliveryMode: params.deliveryMode,
7067
- status: "running",
7068
- createdAt,
7069
- startedAt: createdAt,
7070
- metadata: {
7071
- title: params.title,
7072
- task: params.task,
7073
- is_child_session: params.isChildSession,
7074
- ...params.parentSessionId ? { parent_session_id: params.parentSessionId } : {}
7075
- }
7076
- };
7570
+ const {
7571
+ requestId,
7572
+ sourceSessionId,
7573
+ sourceToolCallId,
7574
+ targetSessionId,
7575
+ task,
7576
+ title,
7577
+ handoffDepth,
7578
+ awaitMode,
7579
+ deliveryMode,
7580
+ agentId,
7581
+ isChildSession,
7582
+ parentSessionId,
7583
+ spawnedByRequestId
7584
+ } = params;
7585
+ const request = createRunningSessionRequest({
7586
+ requestId,
7587
+ sourceSessionId,
7588
+ targetSessionId,
7589
+ sourceToolCallId,
7590
+ handoffDepth,
7591
+ awaitMode,
7592
+ deliveryMode,
7593
+ title,
7594
+ task,
7595
+ isChildSession,
7596
+ parentSessionId
7597
+ });
7077
7598
  void this.runRequest({
7078
7599
  request,
7079
- task: params.task,
7080
- title: params.title,
7081
- isChildSession: params.isChildSession,
7082
- parentSessionId: params.parentSessionId
7600
+ task,
7601
+ title,
7602
+ agentId,
7603
+ isChildSession,
7604
+ parentSessionId
7083
7605
  }).catch((error) => {
7084
7606
  console.error(
7085
- `[session-request] Background request ${params.requestId} crashed: ${error instanceof Error ? error.message : String(error)}`
7607
+ `[session-request] Background request ${requestId} crashed: ${error instanceof Error ? error.message : String(error)}`
7086
7608
  );
7087
7609
  });
7088
- return buildToolResult({
7610
+ return buildSessionRequestToolResult({
7089
7611
  request,
7090
- task: params.task,
7091
- title: params.title,
7092
- isChildSession: params.isChildSession,
7093
- parentSessionId: params.parentSessionId,
7094
- spawnedByRequestId: params.spawnedByRequestId,
7612
+ task,
7613
+ title,
7614
+ agentId,
7615
+ isChildSession,
7616
+ parentSessionId,
7617
+ spawnedByRequestId,
7095
7618
  message: `Session request started. You'll receive the final reply when it finishes.`
7096
7619
  });
7097
7620
  };
7098
- runRequest = async (params) => {
7621
+ resolveBackendOrThrow = () => {
7622
+ const backend = this.resolveBackend();
7623
+ if (!backend) {
7624
+ throw new Error("NCP backend is not ready for session request execution.");
7625
+ }
7626
+ return backend;
7627
+ };
7628
+ readCompletedMessageFromStream = async (params) => {
7629
+ const { backend, request, task } = params;
7099
7630
  let completedMessage;
7100
- try {
7101
- const backend = this.resolveBackend();
7102
- if (!backend) {
7103
- throw new Error("NCP backend is not ready for session request execution.");
7104
- }
7105
- const message = buildUserMessage({
7106
- sessionId: params.request.targetSessionId,
7107
- requestId: params.request.requestId,
7108
- task: params.task
7109
- });
7110
- for await (const event of backend.send({
7111
- sessionId: params.request.targetSessionId,
7112
- message
7113
- })) {
7114
- if (event.type === NcpEventType.MessageAccepted) {
7115
- this.handleRequestEvent(params.request, event);
7116
- continue;
7117
- }
7118
- if (event.type === NcpEventType.MessageFailed) {
7119
- throw new Error(event.payload.error.message);
7120
- }
7121
- if (event.type === NcpEventType.RunError) {
7122
- throw new Error(event.payload.error ?? "Session request failed.");
7123
- }
7124
- if (event.type === NcpEventType.MessageCompleted) {
7125
- completedMessage = event.payload.message;
7126
- }
7631
+ const message = buildSessionRequestUserMessage({
7632
+ sessionId: request.targetSessionId,
7633
+ requestId: request.requestId,
7634
+ task
7635
+ });
7636
+ for await (const event of backend.send({
7637
+ sessionId: request.targetSessionId,
7638
+ message
7639
+ })) {
7640
+ if (event.type === NcpEventType.MessageAccepted) {
7641
+ this.handleRequestEvent(request, event);
7642
+ continue;
7127
7643
  }
7128
- if (!completedMessage) {
7129
- const targetMessages = await backend.listSessionMessages(
7130
- params.request.targetSessionId
7131
- );
7132
- completedMessage = findLatestAssistantMessage(targetMessages);
7133
- }
7134
- if (!completedMessage) {
7135
- throw new Error("Session request completed without a final reply.");
7136
- }
7137
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
7138
- const finalResponseText = extractMessageText(completedMessage);
7139
- const completedRequest = {
7140
- ...params.request,
7141
- status: "completed",
7142
- completedAt,
7143
- finalResponseMessageId: completedMessage?.id,
7644
+ if (event.type === NcpEventType.MessageFailed) {
7645
+ throw new Error(event.payload.error.message);
7646
+ }
7647
+ if (event.type === NcpEventType.RunError) {
7648
+ throw new Error(event.payload.error ?? "Session request failed.");
7649
+ }
7650
+ if (event.type === NcpEventType.MessageCompleted) {
7651
+ completedMessage = event.payload.message;
7652
+ }
7653
+ }
7654
+ return completedMessage;
7655
+ };
7656
+ resolveCompletedMessage = async (params) => {
7657
+ const { request, task } = params;
7658
+ const backend = this.resolveBackendOrThrow();
7659
+ const streamedMessage = await this.readCompletedMessageFromStream({
7660
+ backend,
7661
+ request,
7662
+ task
7663
+ });
7664
+ if (streamedMessage) {
7665
+ return streamedMessage;
7666
+ }
7667
+ const targetMessages = await backend.listSessionMessages(
7668
+ request.targetSessionId
7669
+ );
7670
+ const fallbackMessage = findLatestAssistantMessage(targetMessages);
7671
+ if (fallbackMessage) {
7672
+ return fallbackMessage;
7673
+ }
7674
+ throw new Error("Session request completed without a final reply.");
7675
+ };
7676
+ appendRequestEvents = (request, type) => {
7677
+ this.appendRequestEvent(request.sourceSessionId, type, request);
7678
+ this.appendRequestEvent(request.targetSessionId, type, request);
7679
+ };
7680
+ publishRequestOutcome = async (params) => {
7681
+ const {
7682
+ request,
7683
+ task,
7684
+ title,
7685
+ agentId,
7686
+ isChildSession,
7687
+ parentSessionId,
7688
+ spawnedByRequestId
7689
+ } = params;
7690
+ const result = buildSessionRequestToolResult({
7691
+ request,
7692
+ task,
7693
+ title,
7694
+ agentId,
7695
+ isChildSession,
7696
+ parentSessionId,
7697
+ spawnedByRequestId
7698
+ });
7699
+ await this.deliveryService.publishToolResult({
7700
+ request,
7701
+ result
7702
+ });
7703
+ if (request.deliveryMode === "resume_source") {
7704
+ await this.deliveryService.resumeSourceSession({
7705
+ request,
7706
+ result
7707
+ });
7708
+ }
7709
+ };
7710
+ runRequest = async (params) => {
7711
+ const {
7712
+ request,
7713
+ task,
7714
+ title,
7715
+ agentId,
7716
+ isChildSession,
7717
+ parentSessionId,
7718
+ spawnedByRequestId
7719
+ } = params;
7720
+ try {
7721
+ const completedMessage = await this.resolveCompletedMessage({
7722
+ request,
7723
+ task
7724
+ });
7725
+ const finalResponseText = extractSessionMessageText(completedMessage);
7726
+ const completedRequest = createCompletedSessionRequest({
7727
+ request,
7728
+ completedMessage,
7144
7729
  finalResponseText
7145
- };
7146
- this.appendRequestEvent(params.request.sourceSessionId, "session.request.completed", completedRequest);
7147
- this.appendRequestEvent(params.request.targetSessionId, "session.request.completed", completedRequest);
7148
- const result = buildToolResult({
7149
- request: completedRequest,
7150
- task: params.task,
7151
- title: params.title,
7152
- isChildSession: params.isChildSession,
7153
- parentSessionId: params.parentSessionId,
7154
- spawnedByRequestId: params.spawnedByRequestId
7155
7730
  });
7156
- await this.deliveryService.publishToolResult({
7731
+ this.appendRequestEvents(completedRequest, "session.request.completed");
7732
+ await this.publishRequestOutcome({
7157
7733
  request: completedRequest,
7158
- result
7734
+ task,
7735
+ title,
7736
+ agentId,
7737
+ isChildSession,
7738
+ parentSessionId,
7739
+ spawnedByRequestId
7159
7740
  });
7160
- if (completedRequest.deliveryMode === "resume_source") {
7161
- await this.deliveryService.resumeSourceSession({
7162
- request: completedRequest,
7163
- result
7164
- });
7165
- }
7166
7741
  } catch (error) {
7167
- const completedAt = (/* @__PURE__ */ new Date()).toISOString();
7168
- const failedRequest = {
7169
- ...params.request,
7170
- status: "failed",
7171
- completedAt,
7172
- error: error instanceof Error ? error.message : String(error)
7173
- };
7174
- this.appendRequestEvent(params.request.sourceSessionId, "session.request.failed", failedRequest);
7175
- this.appendRequestEvent(params.request.targetSessionId, "session.request.failed", failedRequest);
7176
- const result = buildToolResult({
7177
- request: failedRequest,
7178
- task: params.task,
7179
- title: params.title,
7180
- isChildSession: params.isChildSession,
7181
- parentSessionId: params.parentSessionId,
7182
- spawnedByRequestId: params.spawnedByRequestId
7742
+ const failedRequest = createFailedSessionRequest({
7743
+ request,
7744
+ error
7183
7745
  });
7184
- await this.deliveryService.publishToolResult({
7746
+ this.appendRequestEvents(failedRequest, "session.request.failed");
7747
+ await this.publishRequestOutcome({
7185
7748
  request: failedRequest,
7186
- result
7749
+ task,
7750
+ title,
7751
+ agentId,
7752
+ isChildSession,
7753
+ parentSessionId,
7754
+ spawnedByRequestId
7187
7755
  });
7188
- if (failedRequest.deliveryMode === "resume_source") {
7189
- await this.deliveryService.resumeSourceSession({
7190
- request: failedRequest,
7191
- result
7192
- });
7193
- }
7194
7756
  }
7195
7757
  };
7196
7758
  handleRequestEvent = (request, event) => {
@@ -7240,7 +7802,7 @@ function scheduleDetached(taskFactory, label) {
7240
7802
  }, 0);
7241
7803
  }
7242
7804
  async function sleep(ms) {
7243
- await new Promise((resolve16) => setTimeout(resolve16, ms));
7805
+ await new Promise((resolve17) => setTimeout(resolve17, ms));
7244
7806
  }
7245
7807
  async function waitForSessionToBecomeIdle(params) {
7246
7808
  const timeoutMs = params.timeoutMs ?? 15e3;
@@ -7428,6 +7990,34 @@ var UiNcpRuntimeRegistry = class {
7428
7990
  }
7429
7991
  };
7430
7992
 
7993
+ // src/cli/commands/ncp/runtime/ui-ncp-agent-handle.ts
7994
+ import { createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
7995
+ function createUiNcpAgentHandle(params) {
7996
+ return {
7997
+ basePath: "/api/ncp/agent",
7998
+ agentClientEndpoint: createAgentClientFromServer(params.backend),
7999
+ streamProvider: params.backend,
8000
+ sessionApi: params.backend,
8001
+ listSessionTypes: (describeParams) => {
8002
+ params.refreshPluginRuntimeRegistrations();
8003
+ return params.runtimeRegistry.listSessionTypes(describeParams);
8004
+ },
8005
+ assetApi: {
8006
+ put: (input) => params.assetStore.putBytes({
8007
+ fileName: input.fileName,
8008
+ mimeType: input.mimeType,
8009
+ bytes: input.bytes,
8010
+ createdAt: input.createdAt
8011
+ }),
8012
+ stat: (uri) => params.assetStore.statRecord(uri),
8013
+ resolveContentPath: (uri) => params.assetStore.resolveContentPath(uri)
8014
+ },
8015
+ applyExtensionRegistry: params.applyExtensionRegistry,
8016
+ applyMcpConfig: params.applyMcpConfig,
8017
+ dispose: params.dispose
8018
+ };
8019
+ }
8020
+
7431
8021
  // src/cli/commands/ncp/create-ui-ncp-agent.ts
7432
8022
  import { join as join5 } from "path";
7433
8023
  var CODEX_RUNTIME_KIND = "codex";
@@ -7617,31 +8207,6 @@ var PluginRuntimeRegistrationController = class {
7617
8207
  this.pluginRuntimeSnapshotKey = "";
7618
8208
  };
7619
8209
  };
7620
- function createUiNcpAgentHandle(params) {
7621
- return {
7622
- basePath: "/api/ncp/agent",
7623
- agentClientEndpoint: createAgentClientFromServer(params.backend),
7624
- streamProvider: params.backend,
7625
- sessionApi: params.backend,
7626
- listSessionTypes: (describeParams) => {
7627
- params.refreshPluginRuntimeRegistrations();
7628
- return params.runtimeRegistry.listSessionTypes(describeParams);
7629
- },
7630
- assetApi: {
7631
- put: (input) => params.assetStore.putBytes({
7632
- fileName: input.fileName,
7633
- mimeType: input.mimeType,
7634
- bytes: input.bytes,
7635
- createdAt: input.createdAt
7636
- }),
7637
- stat: (uri) => params.assetStore.statRecord(uri),
7638
- resolveContentPath: (uri) => params.assetStore.resolveContentPath(uri)
7639
- },
7640
- applyExtensionRegistry: params.applyExtensionRegistry,
7641
- applyMcpConfig: params.applyMcpConfig,
7642
- dispose: params.dispose
7643
- };
7644
- }
7645
8210
  async function createUiNcpAgent(params) {
7646
8211
  const sessionStore = new NextclawAgentSessionStore(params.sessionManager, {
7647
8212
  onSessionUpdated: params.onSessionUpdated
@@ -7654,6 +8219,7 @@ async function createUiNcpAgent(params) {
7654
8219
  let backend = null;
7655
8220
  const sessionCreationService = new SessionCreationService(
7656
8221
  params.sessionManager,
8222
+ params.getConfig,
7657
8223
  params.onSessionUpdated
7658
8224
  );
7659
8225
  const sessionRequestBroker = new SessionRequestBroker(
@@ -7705,7 +8271,7 @@ async function createUiNcpAgent(params) {
7705
8271
  });
7706
8272
  }
7707
8273
 
7708
- // src/cli/commands/service-gateway-context.ts
8274
+ // src/cli/commands/service-support/gateway/service-gateway-context.ts
7709
8275
  import * as NextclawCore from "@nextclaw/core";
7710
8276
  import { getPluginChannelBindings as getPluginChannelBindings3, resolvePluginChannelMessageToolHints } from "@nextclaw/openclaw-compat";
7711
8277
  import { join as join6 } from "path";
@@ -8169,14 +8735,16 @@ var ConfigReloader = class {
8169
8735
  }
8170
8736
  };
8171
8737
 
8172
- // src/cli/commands/agent-runtime-pool.ts
8738
+ // src/cli/commands/agent/agent-runtime-pool.ts
8173
8739
  import {
8174
8740
  NativeAgentEngine,
8175
8741
  CommandRegistry,
8176
8742
  createAssistantStreamDeltaControlMessage,
8177
8743
  createAssistantStreamResetControlMessage,
8178
8744
  AgentRouteResolver,
8179
- getWorkspacePath as getWorkspacePath8,
8745
+ getWorkspacePath as getWorkspacePath9,
8746
+ resolveDefaultAgentProfileId as resolveDefaultAgentProfileId3,
8747
+ resolveEffectiveAgentProfiles as resolveEffectiveAgentProfiles2,
8180
8748
  parseAgentScopedSessionKey
8181
8749
  } from "@nextclaw/core";
8182
8750
  function normalizeAgentId(value) {
@@ -8198,33 +8766,21 @@ function toRecord(value) {
8198
8766
  }
8199
8767
  function resolveAgentProfiles(config2) {
8200
8768
  const defaults = config2.agents.defaults;
8201
- const listed = Array.isArray(config2.agents.list) ? config2.agents.list.map((entry) => ({
8202
- id: normalizeAgentId(entry.id),
8203
- default: entry.default,
8204
- workspace: entry.workspace,
8205
- model: entry.model,
8206
- engine: entry.engine,
8207
- engineConfig: toRecord(entry.engineConfig),
8208
- maxToolIterations: entry.maxToolIterations,
8209
- contextTokens: entry.contextTokens
8210
- })).filter((entry) => Boolean(entry.id)) : [];
8211
- const defaultAgentId = listed.find((entry) => entry.default)?.id ?? listed[0]?.id ?? "main";
8212
- const seed = listed.length > 0 ? listed : [{ id: defaultAgentId }];
8769
+ const defaultAgentId = normalizeAgentId(resolveDefaultAgentProfileId3(config2));
8770
+ const listed = resolveEffectiveAgentProfiles2(config2);
8771
+ const seed = listed.length > 0 ? listed : [{ id: defaultAgentId, workspace: defaults.workspace }];
8213
8772
  const unique = /* @__PURE__ */ new Map();
8214
8773
  for (const entry of seed) {
8215
8774
  if (!unique.has(entry.id)) {
8216
8775
  unique.set(entry.id, entry);
8217
8776
  }
8218
8777
  }
8219
- if (!unique.has(defaultAgentId)) {
8220
- unique.set(defaultAgentId, { id: defaultAgentId });
8221
- }
8222
8778
  return Array.from(unique.values()).map((entry) => ({
8223
8779
  id: entry.id,
8224
- workspace: getWorkspacePath8(entry.workspace ?? defaults.workspace),
8780
+ workspace: getWorkspacePath9(entry.workspace),
8225
8781
  model: entry.model ?? defaults.model,
8226
8782
  engine: normalizeEngineKind(entry.engine ?? defaults.engine),
8227
- engineConfig: entry.engineConfig ?? toRecord(defaults.engineConfig),
8783
+ engineConfig: toRecord(entry.engineConfig) ?? toRecord(defaults.engineConfig),
8228
8784
  maxIterations: entry.maxToolIterations ?? defaults.maxToolIterations,
8229
8785
  contextTokens: entry.contextTokens ?? defaults.contextTokens
8230
8786
  }));
@@ -8489,7 +9045,7 @@ var GatewayAgentRuntimePool = class {
8489
9045
  const normalizedAgentId = normalizeAgentId(agentId);
8490
9046
  return this.resolvedProfiles.find((profile) => profile.id === normalizedAgentId) ?? this.resolvedProfiles.find((profile) => profile.id === this.defaultAgentId) ?? this.resolvedProfiles[0] ?? {
8491
9047
  id: this.defaultAgentId,
8492
- workspace: getWorkspacePath8(this.options.config.agents.defaults.workspace),
9048
+ workspace: getWorkspacePath9(this.options.config.agents.defaults.workspace),
8493
9049
  model: this.options.config.agents.defaults.model,
8494
9050
  maxIterations: this.options.config.agents.defaults.maxToolIterations,
8495
9051
  contextTokens: this.options.config.agents.defaults.contextTokens,
@@ -8666,7 +9222,7 @@ function formatUserFacingError(error, maxChars = 320) {
8666
9222
  return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
8667
9223
  }
8668
9224
 
8669
- // src/cli/commands/service-cron-job-handler.ts
9225
+ // src/cli/commands/service-support/gateway/service-cron-job-handler.ts
8670
9226
  function normalizeOptionalString6(value) {
8671
9227
  if (typeof value !== "string") {
8672
9228
  return void 0;
@@ -8684,13 +9240,14 @@ function createCronJobHandler(params) {
8684
9240
  return async (job) => {
8685
9241
  const accountId = normalizeOptionalString6(job.payload.accountId);
8686
9242
  const metadata = buildCronJobMetadata(accountId);
9243
+ const agentId = normalizeOptionalString6(job.payload.agentId) ?? params.runtimePool.primaryAgentId;
8687
9244
  const response = await params.runtimePool.processDirect({
8688
9245
  content: job.payload.message,
8689
9246
  sessionKey: `cron:${job.id}`,
8690
9247
  channel: job.payload.channel ?? "cli",
8691
9248
  chatId: job.payload.to ?? "direct",
8692
9249
  metadata,
8693
- agentId: params.runtimePool.primaryAgentId
9250
+ agentId
8694
9251
  });
8695
9252
  if (job.payload.deliver && job.payload.to) {
8696
9253
  await params.bus.publishOutbound({
@@ -8705,15 +9262,15 @@ function createCronJobHandler(params) {
8705
9262
  };
8706
9263
  }
8707
9264
 
8708
- // src/cli/commands/service-gateway-context.ts
9265
+ // src/cli/commands/service-support/gateway/service-gateway-context.ts
8709
9266
  var {
8710
9267
  ChannelManager: ChannelManager2,
8711
9268
  CronService: CronService2,
8712
9269
  getConfigPath: getConfigPath9,
8713
9270
  getDataDir: getDataDir8,
8714
- getWorkspacePath: getWorkspacePath9,
9271
+ getWorkspacePath: getWorkspacePath10,
8715
9272
  HeartbeatService,
8716
- loadConfig: loadConfig16,
9273
+ loadConfig: loadConfig17,
8717
9274
  MessageBus,
8718
9275
  ProviderManager,
8719
9276
  resolveConfigSecrets: resolveConfigSecrets2,
@@ -8727,8 +9284,8 @@ function applyGatewayCapabilityState(gateway, next) {
8727
9284
  }
8728
9285
  function createGatewayShellContext(params) {
8729
9286
  const runtimeConfigPath = getConfigPath9();
8730
- const config2 = resolveConfigSecrets2(loadConfig16(), { configPath: runtimeConfigPath });
8731
- const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
9287
+ const config2 = resolveConfigSecrets2(loadConfig17(), { configPath: runtimeConfigPath });
9288
+ const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
8732
9289
  const cronStorePath = join6(getDataDir8(), "cron", "jobs.json");
8733
9290
  const sessionManager = measureStartupSync(
8734
9291
  "service.gateway_shell_context.session_manager",
@@ -8738,7 +9295,7 @@ function createGatewayShellContext(params) {
8738
9295
  const uiConfig = resolveUiConfig(config2, params.uiOverrides);
8739
9296
  const uiStaticDir = params.uiStaticDir === void 0 ? resolveUiStaticDir() : params.uiStaticDir;
8740
9297
  const remoteModule = createManagedRemoteModuleForUi({
8741
- loadConfig: () => resolveConfigSecrets2(loadConfig16(), { configPath: runtimeConfigPath }),
9298
+ loadConfig: () => resolveConfigSecrets2(loadConfig17(), { configPath: runtimeConfigPath }),
8742
9299
  uiConfig
8743
9300
  });
8744
9301
  return {
@@ -8808,7 +9365,7 @@ function createGatewayStartupContext(params) {
8808
9365
  sessionManager: state.sessionManager,
8809
9366
  providerManager: state.providerManager,
8810
9367
  makeProvider: (nextConfig) => params.makeProvider(nextConfig, { allowMissing: true }) ?? params.makeMissingProvider(nextConfig),
8811
- loadConfig: () => resolveConfigSecrets2(loadConfig16(), { configPath: state.runtimeConfigPath }),
9368
+ loadConfig: () => resolveConfigSecrets2(loadConfig17(), { configPath: state.runtimeConfigPath }),
8812
9369
  resolveChannelConfig: (nextConfig) => resolveChannelConfigView(nextConfig, state.pluginChannelBindings),
8813
9370
  getExtensionChannels: () => state.extensionRegistry.channels,
8814
9371
  onRestartRequired: (paths) => {
@@ -8821,7 +9378,7 @@ function createGatewayStartupContext(params) {
8821
9378
  })
8822
9379
  );
8823
9380
  state.applyLiveConfigReload = async () => {
8824
- await state.reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig16(), { configPath: state.runtimeConfigPath }));
9381
+ await state.reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig17(), { configPath: state.runtimeConfigPath }));
8825
9382
  };
8826
9383
  state.gatewayController = measureStartupSync(
8827
9384
  "service.gateway_context.gateway_controller",
@@ -8859,7 +9416,7 @@ function createGatewayStartupContext(params) {
8859
9416
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
8860
9417
  registry: state.pluginRegistry,
8861
9418
  channel,
8862
- cfg: resolveConfigSecrets2(loadConfig16(), { configPath: state.runtimeConfigPath }),
9419
+ cfg: resolveConfigSecrets2(loadConfig17(), { configPath: state.runtimeConfigPath }),
8863
9420
  accountId
8864
9421
  })
8865
9422
  })
@@ -8878,12 +9435,12 @@ function createGatewayStartupContext(params) {
8878
9435
  return state;
8879
9436
  }
8880
9437
 
8881
- // src/cli/commands/service-gateway-startup.ts
9438
+ // src/cli/commands/service-support/gateway/service-gateway-startup.ts
8882
9439
  import {
8883
9440
  startUiServer
8884
9441
  } from "@nextclaw/server";
8885
9442
 
8886
- // src/cli/commands/service-deferred-ncp-agent.ts
9443
+ // src/cli/commands/service-support/session/service-deferred-ncp-agent.ts
8887
9444
  var DEFAULT_BASE_PATH = "/api/ncp/agent";
8888
9445
  var DEFERRED_NCP_AGENT_UNAVAILABLE = "ncp agent unavailable during startup";
8889
9446
  function createUnavailableError() {
@@ -8979,7 +9536,7 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8979
9536
  return new DeferredUiNcpAgentControllerOwner(basePath);
8980
9537
  }
8981
9538
 
8982
- // src/cli/commands/service-gateway-startup.ts
9539
+ // src/cli/commands/service-support/gateway/service-gateway-startup.ts
8983
9540
  function wireSystemSessionUpdatedPublisher(params) {
8984
9541
  params.runtimePool.setSystemSessionUpdatedHandler(({ sessionKey }) => {
8985
9542
  params.publishUiEvent?.({
@@ -9002,6 +9559,7 @@ async function startUiShell(params) {
9002
9559
  productVersion: params.productVersion,
9003
9560
  staticDir: params.uiStaticDir ?? void 0,
9004
9561
  applyLiveConfigReload: params.applyLiveConfigReload,
9562
+ initializeAgentHomeDirectory: params.initializeAgentHomeDirectory,
9005
9563
  cronService: params.cronService,
9006
9564
  marketplace: params.marketplace,
9007
9565
  remoteAccess: params.remoteAccess,
@@ -9087,7 +9645,7 @@ async function runGatewayRuntimeLoop(params) {
9087
9645
  }
9088
9646
  }
9089
9647
 
9090
- // src/cli/commands/ncp/ncp-session-realtime-change.ts
9648
+ // src/cli/commands/ncp/session/ncp-session-realtime-change.ts
9091
9649
  function toNcpSessionRealtimeEvent(change) {
9092
9650
  if (change.kind === "upsert") {
9093
9651
  return {
@@ -9127,10 +9685,11 @@ function createNcpSessionRealtimeChangePublisher(params) {
9127
9685
  };
9128
9686
  }
9129
9687
 
9130
- // src/cli/commands/ncp/ncp-session-summary.ts
9688
+ // src/cli/commands/ncp/session/ncp-session-summary.ts
9131
9689
  function createNcpSessionSummary(params) {
9132
9690
  return {
9133
9691
  sessionId: params.sessionId,
9692
+ ...params.agentId ? { agentId: params.agentId } : {},
9134
9693
  messageCount: params.messages.length,
9135
9694
  updatedAt: params.updatedAt,
9136
9695
  status: params.status,
@@ -9170,6 +9729,7 @@ var UiSessionService = class {
9170
9729
  sessions.map(
9171
9730
  (session) => createNcpSessionSummary({
9172
9731
  sessionId: session.sessionId,
9732
+ agentId: session.agentId,
9173
9733
  messages: session.messages,
9174
9734
  updatedAt: session.updatedAt,
9175
9735
  status: "idle",
@@ -9193,6 +9753,7 @@ var UiSessionService = class {
9193
9753
  }
9194
9754
  return createNcpSessionSummary({
9195
9755
  sessionId,
9756
+ agentId: session.agentId,
9196
9757
  messages: session.messages,
9197
9758
  updatedAt: session.updatedAt,
9198
9759
  status: "idle",
@@ -9217,7 +9778,7 @@ var UiSessionService = class {
9217
9778
  };
9218
9779
  };
9219
9780
 
9220
- // src/cli/commands/service-deferred-ncp-session-service.ts
9781
+ // src/cli/commands/service-support/session/service-deferred-ncp-session-service.ts
9221
9782
  function createDeferredUiNcpSessionService(fallbackService) {
9222
9783
  let activeService = null;
9223
9784
  const resolveService = () => activeService ?? fallbackService;
@@ -9242,7 +9803,7 @@ function createDeferredUiNcpSessionService(fallbackService) {
9242
9803
  };
9243
9804
  }
9244
9805
 
9245
- // src/cli/commands/service-ncp-session-realtime-bridge.ts
9806
+ // src/cli/commands/service-support/session/service-ncp-session-realtime-bridge.ts
9246
9807
  function createLatestOnlySessionChangePublisher(publishSessionChange) {
9247
9808
  const inFlightTasks = /* @__PURE__ */ new Map();
9248
9809
  const rerunKeys = /* @__PURE__ */ new Set();
@@ -9305,7 +9866,7 @@ function createServiceNcpSessionRealtimeBridge(params) {
9305
9866
  };
9306
9867
  }
9307
9868
 
9308
- // src/cli/commands/plugin-registry-loader.ts
9869
+ // src/cli/commands/plugin/plugin-registry-loader.ts
9309
9870
  import {
9310
9871
  discoverPluginStatusReport,
9311
9872
  loadOpenClawPluginsProgressively
@@ -9357,14 +9918,14 @@ function createEmptyPluginRegistry() {
9357
9918
  };
9358
9919
  }
9359
9920
 
9360
- // src/cli/commands/service-gateway-bootstrap.ts
9921
+ // src/cli/commands/service-support/gateway/service-gateway-bootstrap.ts
9361
9922
  import * as NextclawCore2 from "@nextclaw/core";
9362
9923
  import {
9363
9924
  getPluginUiMetadataFromRegistry as getPluginUiMetadataFromRegistry2,
9364
9925
  startPluginChannelGateways as startPluginChannelGateways2
9365
9926
  } from "@nextclaw/openclaw-compat";
9366
9927
 
9367
- // src/cli/commands/service-bootstrap-status.ts
9928
+ // src/cli/commands/service-support/gateway/service-bootstrap-status.ts
9368
9929
  function now2() {
9369
9930
  return (/* @__PURE__ */ new Date()).toISOString();
9370
9931
  }
@@ -9488,14 +10049,14 @@ var ServiceBootstrapStatusStore = class {
9488
10049
  }
9489
10050
  };
9490
10051
 
9491
- // src/cli/commands/service-capability-hydration.ts
9492
- import { getWorkspacePath as getWorkspacePath10, loadConfig as loadConfig17, resolveConfigSecrets as resolveConfigSecrets3 } from "@nextclaw/core";
10052
+ // src/cli/commands/service-support/gateway/service-capability-hydration.ts
10053
+ import { getWorkspacePath as getWorkspacePath11, loadConfig as loadConfig18, resolveConfigSecrets as resolveConfigSecrets3 } from "@nextclaw/core";
9493
10054
  import {
9494
10055
  getPluginChannelBindings as getPluginChannelBindings4,
9495
10056
  getPluginUiMetadataFromRegistry
9496
10057
  } from "@nextclaw/openclaw-compat";
9497
10058
 
9498
- // src/cli/commands/plugin-reload.ts
10059
+ // src/cli/commands/plugin/plugin-reload.ts
9499
10060
  function buildPluginChannelBindingSignature(binding) {
9500
10061
  return `${binding.pluginId}:${binding.channelId}`;
9501
10062
  }
@@ -9568,7 +10129,7 @@ function shouldRestartChannelsForPluginReload(params) {
9568
10129
  return false;
9569
10130
  }
9570
10131
 
9571
- // src/cli/commands/service-ui-shell-grace.ts
10132
+ // src/cli/commands/service-support/gateway/service-ui-shell-grace.ts
9572
10133
  import { setTimeout as delay } from "timers/promises";
9573
10134
  var DEFAULT_UI_SHELL_GRACE_MS = 3e3;
9574
10135
  async function waitForUiShellGraceWindow(uiStartup) {
@@ -9580,14 +10141,14 @@ async function waitForUiShellGraceWindow(uiStartup) {
9580
10141
  });
9581
10142
  }
9582
10143
 
9583
- // src/cli/commands/service-capability-hydration.ts
10144
+ // src/cli/commands/service-support/gateway/service-capability-hydration.ts
9584
10145
  function countEnabledPlugins(config2, workspaceDir) {
9585
10146
  return discoverPluginRegistryStatus(config2, workspaceDir).plugins.filter((plugin) => plugin.enabled).length;
9586
10147
  }
9587
10148
  async function hydrateServiceCapabilities(params) {
9588
10149
  await waitForUiShellGraceWindow(params.uiStartup);
9589
- const nextConfig = resolveConfigSecrets3(loadConfig17(), { configPath: params.gateway.runtimeConfigPath });
9590
- const nextWorkspace = getWorkspacePath10(nextConfig.agents.defaults.workspace);
10150
+ const nextConfig = resolveConfigSecrets3(loadConfig18(), { configPath: params.gateway.runtimeConfigPath });
10151
+ const nextWorkspace = getWorkspacePath11(nextConfig.agents.defaults.workspace);
9591
10152
  const totalPluginCount = countEnabledPlugins(nextConfig, nextWorkspace);
9592
10153
  let loadedPluginCount = 0;
9593
10154
  params.bootstrapStatus.markPluginHydrationRunning({
@@ -9643,18 +10204,18 @@ async function hydrateServiceCapabilities(params) {
9643
10204
  }
9644
10205
  }
9645
10206
 
9646
- // src/cli/commands/service-plugin-runtime-bridge.ts
9647
- import { loadConfig as loadConfig18, resolveConfigSecrets as resolveConfigSecrets4, saveConfig as saveConfig10 } from "@nextclaw/core";
10207
+ // src/cli/commands/service-support/plugin/service-plugin-runtime-bridge.ts
10208
+ import { loadConfig as loadConfig19, resolveConfigSecrets as resolveConfigSecrets4, saveConfig as saveConfig10 } from "@nextclaw/core";
9648
10209
  import { setPluginRuntimeBridge } from "@nextclaw/openclaw-compat";
9649
10210
  function installPluginRuntimeBridge(params) {
9650
10211
  const { runtimePool, runtimeConfigPath, getPluginChannelBindings: getPluginChannelBindings7 } = params;
9651
10212
  setPluginRuntimeBridge({
9652
- loadConfig: () => toPluginConfigView(resolveConfigSecrets4(loadConfig18(), { configPath: runtimeConfigPath }), getPluginChannelBindings7()),
10213
+ loadConfig: () => toPluginConfigView(resolveConfigSecrets4(loadConfig19(), { configPath: runtimeConfigPath }), getPluginChannelBindings7()),
9653
10214
  writeConfigFile: async (nextConfigView) => {
9654
10215
  if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
9655
10216
  throw new Error("plugin runtime writeConfigFile expects an object config");
9656
10217
  }
9657
- const current = loadConfig18();
10218
+ const current = loadConfig19();
9658
10219
  const next = mergePluginConfigView(current, nextConfigView, getPluginChannelBindings7());
9659
10220
  saveConfig10(next);
9660
10221
  },
@@ -9759,15 +10320,15 @@ function resolvePluginRuntimeAttachments(ctx) {
9759
10320
  return attachments;
9760
10321
  }
9761
10322
 
9762
- // src/cli/commands/service-plugin-reload.ts
9763
- import { getWorkspacePath as getWorkspacePath11 } from "@nextclaw/core";
10323
+ // src/cli/commands/service-support/plugin/service-plugin-reload.ts
10324
+ import { getWorkspacePath as getWorkspacePath12 } from "@nextclaw/core";
9764
10325
  import {
9765
10326
  getPluginChannelBindings as getPluginChannelBindings5,
9766
10327
  startPluginChannelGateways,
9767
10328
  stopPluginChannelGateways
9768
10329
  } from "@nextclaw/openclaw-compat";
9769
10330
  async function reloadServicePlugins(params) {
9770
- const nextWorkspace = getWorkspacePath11(params.nextConfig.agents.defaults.workspace);
10331
+ const nextWorkspace = getWorkspacePath12(params.nextConfig.agents.defaults.workspace);
9771
10332
  const nextPluginRegistry = loadPluginRegistry(params.nextConfig, nextWorkspace);
9772
10333
  const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
9773
10334
  const nextPluginChannelBindings = getPluginChannelBindings5(nextPluginRegistry);
@@ -9799,8 +10360,8 @@ async function reloadServicePlugins(params) {
9799
10360
  };
9800
10361
  }
9801
10362
 
9802
- // src/cli/commands/service-gateway-bootstrap.ts
9803
- var { loadConfig: loadConfig19, resolveConfigSecrets: resolveConfigSecrets5 } = NextclawCore2;
10363
+ // src/cli/commands/service-support/gateway/service-gateway-bootstrap.ts
10364
+ var { loadConfig: loadConfig20, resolveConfigSecrets: resolveConfigSecrets5 } = NextclawCore2;
9804
10365
  function createBootstrapStatus(remoteEnabled) {
9805
10366
  const bootstrapStatus = new ServiceBootstrapStatusStore();
9806
10367
  bootstrapStatus.markPluginHydrationPending();
@@ -9878,7 +10439,7 @@ function createDeferredGatewayStartupHooks(params) {
9878
10439
  startPluginGateways: async () => {
9879
10440
  const startedPluginGateways = await startPluginChannelGateways2({
9880
10441
  registry: params.state.pluginRegistry,
9881
- config: resolveConfigSecrets5(loadConfig19(), { configPath: params.gateway.runtimeConfigPath }),
10442
+ config: resolveConfigSecrets5(loadConfig20(), { configPath: params.gateway.runtimeConfigPath }),
9882
10443
  logger: pluginGatewayLogger
9883
10444
  });
9884
10445
  params.state.pluginGatewayHandles = startedPluginGateways.handles;
@@ -9909,9 +10470,9 @@ var {
9909
10470
  getConfigPath: getConfigPath10,
9910
10471
  getProvider,
9911
10472
  getProviderName,
9912
- getWorkspacePath: getWorkspacePath12,
10473
+ getWorkspacePath: getWorkspacePath13,
9913
10474
  LiteLLMProvider,
9914
- loadConfig: loadConfig20,
10475
+ loadConfig: loadConfig21,
9915
10476
  MessageBus: MessageBus2,
9916
10477
  resolveConfigSecrets: resolveConfigSecrets6,
9917
10478
  SessionManager: SessionManager2,
@@ -9930,7 +10491,7 @@ var ServiceCommands = class {
9930
10491
  }
9931
10492
  applyLiveConfigReload = null;
9932
10493
  liveUiNcpAgent = null;
9933
- async startGateway(options = {}) {
10494
+ startGateway = async (options = {}) => {
9934
10495
  logStartupTrace("service.start_gateway.begin");
9935
10496
  this.applyLiveConfigReload = null;
9936
10497
  this.liveUiNcpAgent = null;
@@ -9952,7 +10513,7 @@ var ServiceCommands = class {
9952
10513
  uiConfig: shellContext.uiConfig,
9953
10514
  uiStaticDir: shellContext.uiStaticDir,
9954
10515
  cronService: shellContext.cron,
9955
- getConfig: () => resolveConfigSecrets6(loadConfig20(), { configPath: shellContext.runtimeConfigPath }),
10516
+ getConfig: () => resolveConfigSecrets6(loadConfig21(), { configPath: shellContext.runtimeConfigPath }),
9956
10517
  configPath: getConfigPath10(),
9957
10518
  productVersion: getPackageVersion(),
9958
10519
  getPluginChannelBindings: () => runtimeState?.pluginChannelBindings ?? [],
@@ -9962,7 +10523,8 @@ var ServiceCommands = class {
9962
10523
  getBootstrapStatus: () => bootstrapStatus.getStatus(),
9963
10524
  openBrowserWindow: shellContext.uiConfig.open,
9964
10525
  applyLiveConfigReload,
9965
- ncpSessionService: ncpSessionRealtimeBridge.sessionService
10526
+ ncpSessionService: ncpSessionRealtimeBridge.sessionService,
10527
+ initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory
9966
10528
  })
9967
10529
  );
9968
10530
  ncpSessionRealtimeBridge.setUiEventPublisher(uiStartup?.publish);
@@ -9999,7 +10561,7 @@ var ServiceCommands = class {
9999
10561
  startHeartbeat: () => gateway.heartbeat.start()
10000
10562
  })
10001
10563
  );
10002
- watchCronStoreFile({ cronStorePath: resolve13(join7(NextclawCore3.getDataDir(), "cron", "jobs.json")), reloadCronStore: () => gateway.cron.reloadFromStore() });
10564
+ watchCronStoreFile({ cronStorePath: resolve14(join7(NextclawCore3.getDataDir(), "cron", "jobs.json")), reloadCronStore: () => gateway.cron.reloadFromStore() });
10003
10565
  const deferredGatewayStartupHooks = createDeferredGatewayStartupHooks({
10004
10566
  uiStartup,
10005
10567
  gateway,
@@ -10022,12 +10584,12 @@ var ServiceCommands = class {
10022
10584
  providerManager: gateway.providerManager,
10023
10585
  cronService: gateway.cron,
10024
10586
  gatewayController: gateway.gatewayController,
10025
- getConfig: () => resolveConfigSecrets6(loadConfig20(), { configPath: gateway.runtimeConfigPath }),
10587
+ getConfig: () => resolveConfigSecrets6(loadConfig21(), { configPath: gateway.runtimeConfigPath }),
10026
10588
  getExtensionRegistry: () => gatewayRuntimeState.extensionRegistry,
10027
10589
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
10028
10590
  registry: gatewayRuntimeState.pluginRegistry,
10029
10591
  channel,
10030
- cfg: resolveConfigSecrets6(loadConfig20(), { configPath: gateway.runtimeConfigPath }),
10592
+ cfg: resolveConfigSecrets6(loadConfig21(), { configPath: gateway.runtimeConfigPath }),
10031
10593
  accountId
10032
10594
  }),
10033
10595
  hydrateCapabilities: deferredGatewayStartupHooks.hydrateCapabilities,
@@ -10056,22 +10618,22 @@ var ServiceCommands = class {
10056
10618
  }
10057
10619
  });
10058
10620
  logStartupTrace("service.start_gateway.end");
10059
- }
10060
- normalizeOptionalString(value) {
10621
+ };
10622
+ normalizeOptionalString = (value) => {
10061
10623
  if (typeof value !== "string") {
10062
10624
  return void 0;
10063
10625
  }
10064
10626
  const trimmed = value.trim();
10065
10627
  return trimmed || void 0;
10066
- }
10067
- watchConfigFile(reloader) {
10068
- const configPath = resolve13(getConfigPath10());
10628
+ };
10629
+ watchConfigFile = (reloader) => {
10630
+ const configPath = resolve14(getConfigPath10());
10069
10631
  const watcher = chokidar2.watch(configPath, {
10070
10632
  ignoreInitial: true,
10071
10633
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
10072
10634
  });
10073
10635
  watcher.on("all", (event, changedPath) => {
10074
- if (resolve13(changedPath) !== configPath) {
10636
+ if (resolve14(changedPath) !== configPath) {
10075
10637
  return;
10076
10638
  }
10077
10639
  if (event === "add") {
@@ -10086,34 +10648,24 @@ var ServiceCommands = class {
10086
10648
  reloader.scheduleReload("config unlink");
10087
10649
  }
10088
10650
  });
10089
- }
10090
- resolveMostRecentRoutableSessionKey(sessionManager) {
10091
- const sessions = sessionManager.listSessions();
10651
+ };
10652
+ resolveMostRecentRoutableSessionKey = (sessionManager) => {
10092
10653
  let best = null;
10093
- for (const session of sessions) {
10094
- const key = this.normalizeOptionalString(session.key);
10095
- if (!key || key.startsWith("cli:")) {
10096
- continue;
10097
- }
10098
- const metadataRaw = session.metadata;
10099
- const metadata = metadataRaw && typeof metadataRaw === "object" && !Array.isArray(metadataRaw) ? metadataRaw : {};
10100
- const contextRaw = metadata.last_delivery_context;
10101
- const context = contextRaw && typeof contextRaw === "object" && !Array.isArray(contextRaw) ? contextRaw : {};
10102
- const hasRoute = Boolean(this.normalizeOptionalString(context.channel)) && Boolean(this.normalizeOptionalString(context.chatId));
10103
- const hasFallbackRoute = Boolean(this.normalizeOptionalString(metadata.last_channel)) && Boolean(this.normalizeOptionalString(metadata.last_to));
10104
- if (!hasRoute && !hasFallbackRoute) {
10654
+ for (const session of sessionManager.listSessions()) {
10655
+ const candidate = resolveSessionRouteCandidate({
10656
+ session,
10657
+ normalizeOptionalString: (value) => this.normalizeOptionalString(value)
10658
+ });
10659
+ if (!candidate) {
10105
10660
  continue;
10106
10661
  }
10107
- const updatedAtRaw = this.normalizeOptionalString(session.updated_at);
10108
- const updatedAt = updatedAtRaw ? Date.parse(updatedAtRaw) : Number.NaN;
10109
- const score = Number.isFinite(updatedAt) ? updatedAt : 0;
10110
- if (!best || score >= best.updatedAt) {
10111
- best = { key, updatedAt: score };
10662
+ if (!best || candidate.updatedAt >= best.updatedAt) {
10663
+ best = candidate;
10112
10664
  }
10113
10665
  }
10114
10666
  return best?.key;
10115
- }
10116
- buildRestartWakePrompt(params) {
10667
+ };
10668
+ buildRestartWakePrompt = (params) => {
10117
10669
  const lines = [
10118
10670
  "System event: the gateway has restarted successfully.",
10119
10671
  "Please send one short confirmation to the user that you are back online.",
@@ -10134,13 +10686,13 @@ var ServiceCommands = class {
10134
10686
  lines.push(`Reply target message id: ${replyTo}. If suitable, include [[reply_to:${replyTo}]].`);
10135
10687
  }
10136
10688
  return lines.join("\n");
10137
- }
10138
- async wakeFromRestartSentinel(params) {
10689
+ };
10690
+ wakeFromRestartSentinel = async (params) => {
10139
10691
  const sentinel = await consumeRestartSentinel();
10140
10692
  if (!sentinel) {
10141
10693
  return;
10142
10694
  }
10143
- await new Promise((resolve16) => setTimeout(resolve16, 750));
10695
+ await new Promise((resolve17) => setTimeout(resolve17, 750));
10144
10696
  const payload = sentinel.payload;
10145
10697
  const summary = formatRestartSentinelMessage(payload);
10146
10698
  const sentinelSessionKey = this.normalizeOptionalString(payload.sessionKey);
@@ -10184,9 +10736,9 @@ var ServiceCommands = class {
10184
10736
  attachments: [],
10185
10737
  metadata
10186
10738
  });
10187
- }
10188
- async runForeground(options) {
10189
- const config2 = loadConfig20();
10739
+ };
10740
+ runForeground = async (options) => {
10741
+ const config2 = loadConfig21();
10190
10742
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
10191
10743
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
10192
10744
  if (options.open) {
@@ -10197,48 +10749,39 @@ var ServiceCommands = class {
10197
10749
  allowMissingProvider: true,
10198
10750
  uiStaticDir: resolveUiStaticDir()
10199
10751
  });
10200
- }
10201
- async startService(options) {
10202
- const config2 = loadConfig20();
10752
+ };
10753
+ handleExistingManagedService = async (params) => {
10754
+ console.log(`\u2713 ${APP_NAME3} is already running (PID ${params.existing.pid})`);
10755
+ console.log(`UI: ${params.existing.uiUrl}`);
10756
+ console.log(`API: ${params.existing.apiUrl}`);
10757
+ const binding = resolveManagedServiceUiBinding(params.existing);
10758
+ if (binding.host !== params.uiConfig.host || binding.port !== params.uiConfig.port) {
10759
+ console.log(
10760
+ `Detected running service UI bind (${binding.host}:${binding.port}); enforcing (${params.uiConfig.host}:${params.uiConfig.port})...`
10761
+ );
10762
+ await this.stopService();
10763
+ const stateAfterStop = readServiceState();
10764
+ if (stateAfterStop && isProcessRunning(stateAfterStop.pid)) {
10765
+ console.error("Error: Failed to stop running service while enforcing public UI exposure.");
10766
+ return true;
10767
+ }
10768
+ await this.startService(params.options);
10769
+ return true;
10770
+ }
10771
+ await this.printPublicUiUrls(binding.host, binding.port);
10772
+ console.log(`Logs: ${params.existing.logPath}`);
10773
+ this.printServiceControlHints();
10774
+ return true;
10775
+ };
10776
+ startService = async (options) => {
10777
+ const config2 = loadConfig21();
10203
10778
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
10204
10779
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
10205
10780
  const apiUrl = `${uiUrl}/api`;
10206
10781
  const staticDir = resolveUiStaticDir();
10207
10782
  const existing = readServiceState();
10208
10783
  if (existing && isProcessRunning(existing.pid)) {
10209
- console.log(`\u2713 ${APP_NAME3} is already running (PID ${existing.pid})`);
10210
- console.log(`UI: ${existing.uiUrl}`);
10211
- console.log(`API: ${existing.apiUrl}`);
10212
- const parsedUi = (() => {
10213
- try {
10214
- const parsed = new URL(existing.uiUrl);
10215
- const port = Number(parsed.port || 80);
10216
- return {
10217
- host: existing.uiHost ?? parsed.hostname,
10218
- port: Number.isFinite(port) ? port : existing.uiPort ?? 55667
10219
- };
10220
- } catch {
10221
- return {
10222
- host: existing.uiHost ?? "127.0.0.1",
10223
- port: existing.uiPort ?? 55667
10224
- };
10225
- }
10226
- })();
10227
- if (parsedUi.host !== uiConfig.host || parsedUi.port !== uiConfig.port) {
10228
- console.log(
10229
- `Detected running service UI bind (${parsedUi.host}:${parsedUi.port}); enforcing (${uiConfig.host}:${uiConfig.port})...`
10230
- );
10231
- await this.stopService();
10232
- const stateAfterStop = readServiceState();
10233
- if (stateAfterStop && isProcessRunning(stateAfterStop.pid)) {
10234
- console.error("Error: Failed to stop running service while enforcing public UI exposure.");
10235
- return;
10236
- }
10237
- return this.startService(options);
10238
- }
10239
- await this.printPublicUiUrls(parsedUi.host, parsedUi.port);
10240
- console.log(`Logs: ${existing.logPath}`);
10241
- this.printServiceControlHints();
10784
+ await this.handleExistingManagedService({ existing, uiConfig, options });
10242
10785
  return;
10243
10786
  }
10244
10787
  if (existing) {
@@ -10258,109 +10801,72 @@ var ServiceCommands = class {
10258
10801
  console.error(portPreflight.message);
10259
10802
  return;
10260
10803
  }
10261
- const logPath = resolveServiceLogPath();
10262
- const logDir = resolve13(logPath, "..");
10263
- mkdirSync5(logDir, { recursive: true });
10264
- const logFd = openSync2(logPath, "a");
10265
- const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
10266
- const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
10267
- const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);
10268
- this.appendStartupStage(
10269
- logPath,
10270
- `start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`
10271
- );
10272
- console.log(`Starting ${APP_NAME3} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
10273
- const serveArgs = buildServeArgs({ uiPort: uiConfig.port });
10274
- this.appendStartupStage(logPath, `spawning background process: ${process.execPath} ${[...process.execArgv, ...serveArgs].join(" ")}`);
10275
- const child = spawn3(process.execPath, [...process.execArgv, ...serveArgs], {
10276
- env: process.env,
10277
- stdio: ["ignore", logFd, logFd],
10278
- detached: true
10279
- });
10280
- this.appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
10281
- closeSync2(logFd);
10282
- if (!child.pid) {
10283
- this.appendStartupStage(logPath, "spawn failed: child pid missing");
10284
- console.error("Error: Failed to start background service.");
10285
- this.printStartupFailureDiagnostics({
10286
- uiUrl,
10287
- apiUrl,
10288
- healthUrl,
10289
- logPath,
10290
- lastProbeError: null
10291
- });
10292
- return;
10293
- }
10294
- writeInitialManagedServiceState({
10804
+ const startup = spawnManagedService({
10805
+ appName: APP_NAME3,
10295
10806
  config: config2,
10296
- readinessTimeoutMs,
10297
- snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
10298
- });
10299
- this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
10300
- let readiness = await this.waitForBackgroundServiceReady({
10301
- pid: child.pid,
10807
+ uiConfig,
10808
+ uiUrl,
10809
+ apiUrl,
10302
10810
  healthUrl,
10303
- timeoutMs: quickPhaseTimeoutMs
10811
+ startupTimeoutMs: options.startupTimeoutMs,
10812
+ resolveStartupTimeoutMs: this.resolveStartupTimeoutMs,
10813
+ appendStartupStage: this.appendStartupStage,
10814
+ printStartupFailureDiagnostics: this.printStartupFailureDiagnostics,
10815
+ resolveServiceLogPath
10304
10816
  });
10305
- if (!readiness.ready && isProcessRunning(child.pid) && extendedPhaseTimeoutMs > 0) {
10306
- console.warn(
10307
- `Warning: Background service is still running but not ready after ${Math.ceil(quickPhaseTimeoutMs / 1e3)}s; waiting up to ${Math.ceil(extendedPhaseTimeoutMs / 1e3)}s more.`
10308
- );
10309
- this.appendStartupStage(
10310
- logPath,
10311
- `health probe entering extended phase (timeoutMs=${extendedPhaseTimeoutMs}, lastError=${readiness.lastProbeError ?? "none"})`
10312
- );
10313
- readiness = await this.waitForBackgroundServiceReady({
10314
- pid: child.pid,
10315
- healthUrl,
10316
- timeoutMs: extendedPhaseTimeoutMs
10317
- });
10817
+ if (!startup) {
10818
+ return;
10318
10819
  }
10820
+ const readiness = await waitForManagedServiceReadiness({
10821
+ appName: APP_NAME3,
10822
+ childPid: startup.snapshot.pid,
10823
+ healthUrl,
10824
+ logPath: startup.logPath,
10825
+ readinessTimeoutMs: startup.readinessTimeoutMs,
10826
+ quickPhaseTimeoutMs: startup.quickPhaseTimeoutMs,
10827
+ extendedPhaseTimeoutMs: startup.extendedPhaseTimeoutMs,
10828
+ appendStartupStage: this.appendStartupStage,
10829
+ waitForBackgroundServiceReady: this.waitForBackgroundServiceReady,
10830
+ isProcessRunning
10831
+ });
10319
10832
  if (!readiness.ready) {
10320
- if (!isProcessRunning(child.pid)) {
10833
+ if (!isProcessRunning(startup.snapshot.pid)) {
10321
10834
  clearServiceState();
10322
10835
  const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
10323
- this.appendStartupStage(logPath, `startup failed: process exited before ready.${hint}`);
10324
- console.error(`Error: Failed to start background service. Check logs: ${logPath}.${hint}`);
10836
+ this.appendStartupStage(startup.logPath, `startup failed: process exited before ready.${hint}`);
10837
+ console.error(`Error: Failed to start background service. Check logs: ${startup.logPath}.${hint}`);
10325
10838
  this.printStartupFailureDiagnostics({
10326
10839
  uiUrl,
10327
10840
  apiUrl,
10328
10841
  healthUrl,
10329
- logPath,
10842
+ logPath: startup.logPath,
10330
10843
  lastProbeError: readiness.lastProbeError
10331
10844
  });
10332
10845
  return;
10333
10846
  }
10334
- this.appendStartupStage(
10335
- logPath,
10336
- `startup degraded: process alive but health probe timed out after ${readinessTimeoutMs}ms (lastError=${readiness.lastProbeError ?? "none"})`
10337
- );
10338
10847
  }
10339
- child.unref();
10848
+ startup.child.unref();
10340
10849
  const state = writeReadyManagedServiceState({
10341
- readinessTimeoutMs,
10850
+ readinessTimeoutMs: startup.readinessTimeoutMs,
10342
10851
  readiness,
10343
- snapshot: { pid: child.pid, uiUrl, apiUrl, uiHost: uiConfig.host, uiPort: uiConfig.port, logPath }
10852
+ snapshot: startup.snapshot
10853
+ });
10854
+ await reportManagedServiceStart({
10855
+ appName: APP_NAME3,
10856
+ state,
10857
+ uiConfig,
10858
+ uiUrl,
10859
+ apiUrl,
10860
+ readinessTimeoutMs: startup.readinessTimeoutMs,
10861
+ readiness,
10862
+ printPublicUiUrls: this.printPublicUiUrls,
10863
+ printServiceControlHints: this.printServiceControlHints
10344
10864
  });
10345
- if (!readiness.ready) {
10346
- const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
10347
- console.warn(
10348
- `Warning: ${APP_NAME3} is running (PID ${state.pid}) but not healthy yet after ${Math.ceil(readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
10349
- );
10350
- console.warn(`Tip: Run "${APP_NAME3} status --json" and check logs: ${logPath}`);
10351
- } else {
10352
- console.log(`\u2713 ${APP_NAME3} started in background (PID ${state.pid})`);
10353
- }
10354
- console.log(`UI: ${uiUrl}`);
10355
- console.log(`API: ${apiUrl}`);
10356
- await this.printPublicUiUrls(uiConfig.host, uiConfig.port);
10357
- console.log(`Logs: ${logPath}`);
10358
- this.printServiceControlHints();
10359
10865
  if (options.open) {
10360
10866
  openBrowser(uiUrl);
10361
10867
  }
10362
- }
10363
- async stopService() {
10868
+ };
10869
+ stopService = async () => {
10364
10870
  const state = readServiceState();
10365
10871
  if (!state) {
10366
10872
  console.log("No running service found.");
@@ -10390,8 +10896,8 @@ var ServiceCommands = class {
10390
10896
  }
10391
10897
  clearServiceState();
10392
10898
  console.log(`\u2713 ${APP_NAME3} stopped`);
10393
- }
10394
- async waitForBackgroundServiceReady(params) {
10899
+ };
10900
+ waitForBackgroundServiceReady = async (params) => {
10395
10901
  const startedAt = Date.now();
10396
10902
  let lastProbeError = null;
10397
10903
  while (Date.now() - startedAt < params.timeoutMs) {
@@ -10401,18 +10907,18 @@ var ServiceCommands = class {
10401
10907
  const probe = await this.probeHealthEndpoint(params.healthUrl);
10402
10908
  if (!probe.healthy) {
10403
10909
  lastProbeError = probe.error;
10404
- await new Promise((resolve16) => setTimeout(resolve16, 200));
10910
+ await new Promise((resolve17) => setTimeout(resolve17, 200));
10405
10911
  continue;
10406
10912
  }
10407
- await new Promise((resolve16) => setTimeout(resolve16, 300));
10913
+ await new Promise((resolve17) => setTimeout(resolve17, 300));
10408
10914
  if (isProcessRunning(params.pid)) {
10409
10915
  return { ready: true, lastProbeError: null };
10410
10916
  }
10411
- await new Promise((resolve16) => setTimeout(resolve16, 200));
10917
+ await new Promise((resolve17) => setTimeout(resolve17, 200));
10412
10918
  }
10413
10919
  return { ready: false, lastProbeError };
10414
- }
10415
- resolveStartupTimeoutMs(overrideTimeoutMs) {
10920
+ };
10921
+ resolveStartupTimeoutMs = (overrideTimeoutMs) => {
10416
10922
  const fallback = process.platform === "win32" ? 28e3 : 33e3;
10417
10923
  const envRaw = process.env.NEXTCLAW_START_TIMEOUT_MS?.trim();
10418
10924
  const envValue = envRaw ? Number(envRaw) : Number.NaN;
@@ -10420,8 +10926,8 @@ var ServiceCommands = class {
10420
10926
  const fromOverride = Number.isFinite(overrideTimeoutMs) && Number(overrideTimeoutMs) > 0 ? Math.floor(Number(overrideTimeoutMs)) : null;
10421
10927
  const resolved = fromOverride ?? fromEnv ?? fallback;
10422
10928
  return Math.max(3e3, resolved);
10423
- }
10424
- appendStartupStage(logPath, message) {
10929
+ };
10930
+ appendStartupStage = (logPath, message) => {
10425
10931
  try {
10426
10932
  appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [startup] ${message}
10427
10933
  `, "utf-8");
@@ -10429,8 +10935,8 @@ var ServiceCommands = class {
10429
10935
  const detail = error instanceof Error ? error.message : String(error);
10430
10936
  console.error(`Warning: failed to write startup diagnostics log (${logPath}): ${detail}`);
10431
10937
  }
10432
- }
10433
- printStartupFailureDiagnostics(params) {
10938
+ };
10939
+ printStartupFailureDiagnostics = (params) => {
10434
10940
  const statePath = resolveServiceStatePath();
10435
10941
  const lines = [
10436
10942
  "Startup diagnostics:",
@@ -10444,8 +10950,8 @@ var ServiceCommands = class {
10444
10950
  lines.push(`- Last probe detail: ${params.lastProbeError}`);
10445
10951
  }
10446
10952
  console.error(lines.join("\n"));
10447
- }
10448
- async checkUiPortPreflight(params) {
10953
+ };
10954
+ checkUiPortPreflight = async (params) => {
10449
10955
  const availability = await this.checkPortAvailability({
10450
10956
  host: params.host,
10451
10957
  port: params.port
@@ -10477,27 +10983,27 @@ var ServiceCommands = class {
10477
10983
  ok: false,
10478
10984
  message: lines.join("\n")
10479
10985
  };
10480
- }
10481
- async checkPortAvailability(params) {
10482
- return await new Promise((resolve16) => {
10986
+ };
10987
+ checkPortAvailability = async (params) => {
10988
+ return await new Promise((resolve17) => {
10483
10989
  const server = createNetServer2();
10484
10990
  server.once("error", (error) => {
10485
- resolve16({
10991
+ resolve17({
10486
10992
  available: false,
10487
10993
  detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
10488
10994
  });
10489
10995
  });
10490
10996
  server.listen(params.port, params.host, () => {
10491
10997
  server.close(() => {
10492
- resolve16({
10998
+ resolve17({
10493
10999
  available: true,
10494
11000
  detail: `bind ok on ${params.host}:${params.port}`
10495
11001
  });
10496
11002
  });
10497
11003
  });
10498
11004
  });
10499
- }
10500
- getHeaderValue(headers, key) {
11005
+ };
11006
+ getHeaderValue = (headers, key) => {
10501
11007
  const value = headers[key];
10502
11008
  if (typeof value === "string") {
10503
11009
  const normalized = value.trim();
@@ -10508,16 +11014,16 @@ var ServiceCommands = class {
10508
11014
  return joined.length > 0 ? joined : null;
10509
11015
  }
10510
11016
  return null;
10511
- }
10512
- formatProbeBodySnippet(raw, maxLength = 180) {
11017
+ };
11018
+ formatProbeBodySnippet = (raw, maxLength = 180) => {
10513
11019
  const normalized = raw.replace(/\s+/g, " ").trim();
10514
11020
  if (!normalized) {
10515
11021
  return null;
10516
11022
  }
10517
11023
  const clipped = normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;
10518
11024
  return JSON.stringify(clipped);
10519
- }
10520
- async probeHealthEndpoint(healthUrl) {
11025
+ };
11026
+ probeHealthEndpoint = async (healthUrl) => {
10521
11027
  let parsed;
10522
11028
  try {
10523
11029
  parsed = new URL(healthUrl);
@@ -10525,7 +11031,7 @@ var ServiceCommands = class {
10525
11031
  return { healthy: false, error: "invalid health URL" };
10526
11032
  }
10527
11033
  const requestImpl = parsed.protocol === "https:" ? httpsRequest : httpRequest;
10528
- return new Promise((resolve16) => {
11034
+ return new Promise((resolve17) => {
10529
11035
  const req = requestImpl(
10530
11036
  {
10531
11037
  protocol: parsed.protocol,
@@ -10561,19 +11067,19 @@ var ServiceCommands = class {
10561
11067
  if (bodySnippet) {
10562
11068
  details.push(`body=${bodySnippet}`);
10563
11069
  }
10564
- resolve16({ healthy: false, error: details.join("; ") });
11070
+ resolve17({ healthy: false, error: details.join("; ") });
10565
11071
  return;
10566
11072
  }
10567
11073
  try {
10568
11074
  const payload = JSON.parse(responseText);
10569
11075
  const healthy = payload?.ok === true && payload?.data?.status === "ok";
10570
11076
  if (!healthy) {
10571
- resolve16({ healthy: false, error: "health payload not ok" });
11077
+ resolve17({ healthy: false, error: "health payload not ok" });
10572
11078
  return;
10573
11079
  }
10574
- resolve16({ healthy: true, error: null });
11080
+ resolve17({ healthy: true, error: null });
10575
11081
  } catch {
10576
- resolve16({ healthy: false, error: "invalid health JSON response" });
11082
+ resolve17({ healthy: false, error: "invalid health JSON response" });
10577
11083
  }
10578
11084
  });
10579
11085
  }
@@ -10582,24 +11088,24 @@ var ServiceCommands = class {
10582
11088
  req.destroy(new Error("probe timeout"));
10583
11089
  });
10584
11090
  req.on("error", (error) => {
10585
- resolve16({ healthy: false, error: error.message || String(error) });
11091
+ resolve17({ healthy: false, error: error.message || String(error) });
10586
11092
  });
10587
11093
  req.end();
10588
11094
  });
10589
- }
10590
- createMissingProvider(config2) {
11095
+ };
11096
+ createMissingProvider = (config2) => {
10591
11097
  return this.makeMissingProvider(config2);
10592
- }
10593
- createProvider(config2, options) {
11098
+ };
11099
+ createProvider = (config2, options) => {
10594
11100
  if (options?.allowMissing) {
10595
11101
  return this.makeProvider(config2, { allowMissing: true });
10596
11102
  }
10597
11103
  return this.makeProvider(config2);
10598
- }
10599
- makeMissingProvider(config2) {
11104
+ };
11105
+ makeMissingProvider = (config2) => {
10600
11106
  return new MissingProvider(config2.agents.defaults.model);
10601
- }
10602
- makeProvider(config2, options) {
11107
+ };
11108
+ makeProvider = (config2, options) => {
10603
11109
  const provider = getProvider(config2);
10604
11110
  const model = config2.agents.defaults.model;
10605
11111
  if (!provider?.apiKey && !model.startsWith("bedrock/")) {
@@ -10618,8 +11124,8 @@ var ServiceCommands = class {
10618
11124
  providerName: getProviderName(config2),
10619
11125
  wireApi: provider?.wireApi ?? null
10620
11126
  });
10621
- }
10622
- async printPublicUiUrls(host, port) {
11127
+ };
11128
+ printPublicUiUrls = async (host, port) => {
10623
11129
  if (isLoopbackHost(host)) {
10624
11130
  console.log("Public URL: disabled (UI host is loopback). Current release expects public exposure; run nextclaw restart.");
10625
11131
  return;
@@ -10641,42 +11147,28 @@ var ServiceCommands = class {
10641
11147
  console.log(
10642
11148
  `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).`
10643
11149
  );
10644
- }
10645
- printServiceControlHints() {
11150
+ };
11151
+ printServiceControlHints = () => {
10646
11152
  console.log("Service controls:");
10647
11153
  console.log(` - Check status: ${APP_NAME3} status`);
10648
11154
  console.log(` - If you need to stop the service, run: ${APP_NAME3} stop`);
10649
- }
10650
- installBuiltinMarketplaceSkill(slug, force) {
10651
- const workspace = getWorkspacePath12(loadConfig20().agents.defaults.workspace);
10652
- const destination = join7(workspace, "skills", slug);
10653
- const destinationSkillFile = join7(destination, "SKILL.md");
10654
- if (existsSync11(destinationSkillFile) && !force) {
10655
- return {
10656
- message: `${slug} is already installed`
10657
- };
10658
- }
11155
+ };
11156
+ installBuiltinMarketplaceSkill = (slug, _force) => {
11157
+ const workspace = getWorkspacePath13(loadConfig21().agents.defaults.workspace);
10659
11158
  const loader = createSkillsLoader(workspace);
10660
11159
  const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
10661
11160
  if (!builtin) {
10662
- if (existsSync11(destinationSkillFile)) {
10663
- return {
10664
- message: `${slug} is already installed`
10665
- };
10666
- }
10667
11161
  return null;
10668
11162
  }
10669
- mkdirSync5(join7(workspace, "skills"), { recursive: true });
10670
- cpSync2(dirname3(builtin.path), destination, { recursive: true, force: true });
10671
11163
  return {
10672
- message: `Installed skill: ${slug}`
11164
+ message: `${slug} is already available (built-in)`
10673
11165
  };
10674
- }
10675
- mergeCommandOutput(stdout, stderr) {
11166
+ };
11167
+ mergeCommandOutput = (stdout, stderr) => {
10676
11168
  return `${stdout}
10677
11169
  ${stderr}`.trim();
10678
- }
10679
- runCliSubcommand(args, timeoutMs = 18e4) {
11170
+ };
11171
+ runCliSubcommand = (args, timeoutMs = 18e4) => {
10680
11172
  const cliEntry = resolveCliSubcommandEntry({
10681
11173
  argvEntry: process.argv[1],
10682
11174
  importMetaUrl: import.meta.url
@@ -10685,11 +11177,11 @@ ${stderr}`.trim();
10685
11177
  cwd: process.cwd(),
10686
11178
  timeoutMs
10687
11179
  }).then((result) => this.mergeCommandOutput(result.stdout, result.stderr));
10688
- }
10689
- runCommand(command, args, options = {}) {
11180
+ };
11181
+ runCommand = (command, args, options = {}) => {
10690
11182
  const timeoutMs = options.timeoutMs ?? 18e4;
10691
11183
  return new Promise((resolvePromise, rejectPromise) => {
10692
- const child = spawn3(command, args, {
11184
+ const child = spawn4(command, args, {
10693
11185
  cwd: options.cwd ?? process.cwd(),
10694
11186
  env: process.env,
10695
11187
  stdio: ["ignore", "pipe", "pipe"]
@@ -10722,13 +11214,12 @@ ${stderr}`.trim();
10722
11214
  rejectPromise(new Error(output || `command failed with code ${code ?? 1}`));
10723
11215
  });
10724
11216
  });
10725
- }
11217
+ };
10726
11218
  };
10727
11219
 
10728
11220
  // src/cli/workspace.ts
10729
- import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "fs";
10730
- import { createRequire as createRequire2 } from "module";
10731
- import { dirname as dirname4, join as join8, resolve as resolve14 } from "path";
11221
+ import { cpSync, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "fs";
11222
+ import { dirname as dirname3, join as join8, resolve as resolve15 } from "path";
10732
11223
  import { fileURLToPath as fileURLToPath4 } from "url";
10733
11224
  import { APP_NAME as APP_NAME4, getDataDir as getDataDir10 } from "@nextclaw/core";
10734
11225
  import { spawnSync as spawnSync3 } from "child_process";
@@ -10750,7 +11241,6 @@ var WorkspaceManager = class {
10750
11241
  { source: "USER.md", target: "USER.md" },
10751
11242
  { source: "IDENTITY.md", target: "IDENTITY.md" },
10752
11243
  { source: "TOOLS.md", target: "TOOLS.md" },
10753
- { source: "USAGE.md", target: "USAGE.md" },
10754
11244
  { source: "BOOT.md", target: "BOOT.md" },
10755
11245
  { source: "BOOTSTRAP.md", target: "BOOTSTRAP.md" },
10756
11246
  { source: "HEARTBEAT.md", target: "HEARTBEAT.md" },
@@ -10759,93 +11249,42 @@ var WorkspaceManager = class {
10759
11249
  ];
10760
11250
  for (const entry of templateFiles) {
10761
11251
  const filePath = join8(workspace, entry.target);
10762
- if (!force && existsSync12(filePath)) {
11252
+ if (!force && existsSync11(filePath)) {
10763
11253
  continue;
10764
11254
  }
10765
11255
  const templatePath = join8(templateDir, entry.source);
10766
- if (!existsSync12(templatePath)) {
11256
+ if (!existsSync11(templatePath)) {
10767
11257
  console.warn(`Warning: Template file missing: ${templatePath}`);
10768
11258
  continue;
10769
11259
  }
10770
11260
  const raw = readFileSync9(templatePath, "utf-8");
10771
11261
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME4);
10772
- mkdirSync6(dirname4(filePath), { recursive: true });
11262
+ mkdirSync6(dirname3(filePath), { recursive: true });
10773
11263
  writeFileSync5(filePath, content);
10774
11264
  created.push(entry.target);
10775
11265
  }
10776
11266
  const memoryDir = join8(workspace, "memory");
10777
- if (!existsSync12(memoryDir)) {
11267
+ if (!existsSync11(memoryDir)) {
10778
11268
  mkdirSync6(memoryDir, { recursive: true });
10779
11269
  created.push(join8("memory", ""));
10780
11270
  }
10781
11271
  const skillsDir = join8(workspace, "skills");
10782
- if (!existsSync12(skillsDir)) {
11272
+ if (!existsSync11(skillsDir)) {
10783
11273
  mkdirSync6(skillsDir, { recursive: true });
10784
11274
  created.push(join8("skills", ""));
10785
11275
  }
10786
- const seeded = this.seedBuiltinSkills(skillsDir, { force });
10787
- if (seeded > 0) {
10788
- created.push(`skills (seeded ${seeded} built-ins)`);
10789
- }
10790
11276
  return { created };
10791
11277
  }
10792
- seedBuiltinSkills(targetDir, options = {}) {
10793
- const sourceDir = this.resolveBuiltinSkillsDir();
10794
- if (!sourceDir) {
10795
- return 0;
10796
- }
10797
- const force = Boolean(options.force);
10798
- let seeded = 0;
10799
- for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
10800
- if (!entry.isDirectory()) {
10801
- continue;
10802
- }
10803
- const src = join8(sourceDir, entry.name);
10804
- if (!existsSync12(join8(src, "SKILL.md"))) {
10805
- continue;
10806
- }
10807
- const dest = join8(targetDir, entry.name);
10808
- if (!force && existsSync12(dest)) {
10809
- continue;
10810
- }
10811
- try {
10812
- cpSync3(src, dest, { recursive: true, force: true });
10813
- seeded += 1;
10814
- } catch (error) {
10815
- const message = error instanceof Error ? error.message : String(error);
10816
- console.warn(`Warning: Failed to seed builtin skill '${entry.name}': ${message}`);
10817
- }
10818
- }
10819
- return seeded;
10820
- }
10821
- resolveBuiltinSkillsDir() {
10822
- try {
10823
- const require3 = createRequire2(import.meta.url);
10824
- const entry = require3.resolve("@nextclaw/core");
10825
- const pkgRoot = resolve14(dirname4(entry), "..");
10826
- const distSkills = join8(pkgRoot, "dist", "skills");
10827
- if (existsSync12(distSkills)) {
10828
- return distSkills;
10829
- }
10830
- const srcSkills = join8(pkgRoot, "src", "agent", "skills");
10831
- if (existsSync12(srcSkills)) {
10832
- return srcSkills;
10833
- }
10834
- return null;
10835
- } catch {
10836
- return null;
10837
- }
10838
- }
10839
11278
  resolveTemplateDir() {
10840
11279
  const override = process.env.NEXTCLAW_TEMPLATE_DIR?.trim();
10841
11280
  if (override) {
10842
11281
  return override;
10843
11282
  }
10844
- const cliDir = resolve14(fileURLToPath4(new URL(".", import.meta.url)));
10845
- const pkgRoot = resolve14(cliDir, "..", "..");
11283
+ const cliDir = resolve15(fileURLToPath4(new URL(".", import.meta.url)));
11284
+ const pkgRoot = resolve15(cliDir, "..", "..");
10846
11285
  const candidates = [join8(pkgRoot, "templates")];
10847
11286
  for (const candidate of candidates) {
10848
- if (existsSync12(candidate)) {
11287
+ if (existsSync11(candidate)) {
10849
11288
  return candidate;
10850
11289
  }
10851
11290
  }
@@ -10853,21 +11292,21 @@ var WorkspaceManager = class {
10853
11292
  }
10854
11293
  getBridgeDir() {
10855
11294
  const userBridge = join8(getDataDir10(), "bridge");
10856
- if (existsSync12(join8(userBridge, "dist", "index.js"))) {
11295
+ if (existsSync11(join8(userBridge, "dist", "index.js"))) {
10857
11296
  return userBridge;
10858
11297
  }
10859
11298
  if (!which("npm")) {
10860
11299
  console.error("npm not found. Please install Node.js >= 18.");
10861
11300
  process.exit(1);
10862
11301
  }
10863
- const cliDir = resolve14(fileURLToPath4(new URL(".", import.meta.url)));
10864
- const pkgRoot = resolve14(cliDir, "..", "..");
11302
+ const cliDir = resolve15(fileURLToPath4(new URL(".", import.meta.url)));
11303
+ const pkgRoot = resolve15(cliDir, "..", "..");
10865
11304
  const pkgBridge = join8(pkgRoot, "bridge");
10866
11305
  const srcBridge = join8(pkgRoot, "..", "..", "bridge");
10867
11306
  let source = null;
10868
- if (existsSync12(join8(pkgBridge, "package.json"))) {
11307
+ if (existsSync11(join8(pkgBridge, "package.json"))) {
10869
11308
  source = pkgBridge;
10870
- } else if (existsSync12(join8(srcBridge, "package.json"))) {
11309
+ } else if (existsSync11(join8(srcBridge, "package.json"))) {
10871
11310
  source = srcBridge;
10872
11311
  }
10873
11312
  if (!source) {
@@ -10875,11 +11314,11 @@ var WorkspaceManager = class {
10875
11314
  process.exit(1);
10876
11315
  }
10877
11316
  console.log(`${this.logo} Setting up bridge...`);
10878
- mkdirSync6(resolve14(userBridge, ".."), { recursive: true });
10879
- if (existsSync12(userBridge)) {
11317
+ mkdirSync6(resolve15(userBridge, ".."), { recursive: true });
11318
+ if (existsSync11(userBridge)) {
10880
11319
  rmSync6(userBridge, { recursive: true, force: true });
10881
11320
  }
10882
- cpSync3(source, userBridge, {
11321
+ cpSync(source, userBridge, {
10883
11322
  recursive: true,
10884
11323
  filter: (src) => !src.includes("node_modules") && !src.includes("dist")
10885
11324
  });
@@ -10908,12 +11347,6 @@ var WorkspaceManager = class {
10908
11347
  var LOGO = "\u{1F916}";
10909
11348
  var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "/exit", "/quit", ":q"]);
10910
11349
  var FORCED_PUBLIC_UI_HOST2 = "0.0.0.0";
10911
- function resolveSkillsInstallWorkdir(params) {
10912
- if (params.explicitWorkdir) {
10913
- return expandHome2(params.explicitWorkdir);
10914
- }
10915
- return getWorkspacePath13(params.configuredWorkspace);
10916
- }
10917
11350
  var CliRuntime = class {
10918
11351
  logo;
10919
11352
  restartCoordinator;
@@ -10925,6 +11358,7 @@ var CliRuntime = class {
10925
11358
  mcpCommands;
10926
11359
  secretsCommands;
10927
11360
  pluginCommands;
11361
+ agentCommands;
10928
11362
  channelCommands;
10929
11363
  cronCommands;
10930
11364
  platformAuthCommands;
@@ -10936,7 +11370,8 @@ var CliRuntime = class {
10936
11370
  this.logo = options.logo ?? LOGO;
10937
11371
  this.workspaceManager = measureStartupSync("cli.runtime.workspace_manager", () => new WorkspaceManager(this.logo));
10938
11372
  this.serviceCommands = measureStartupSync("cli.runtime.service_commands", () => new ServiceCommands({
10939
- requestRestart: (params) => this.requestRestart(params)
11373
+ requestRestart: (params) => this.requestRestart(params),
11374
+ initializeAgentHomeDirectory: (homeDirectory) => this.workspaceManager.createWorkspaceTemplates(homeDirectory)
10940
11375
  }));
10941
11376
  this.configCommands = measureStartupSync("cli.runtime.config_commands", () => new ConfigCommands({
10942
11377
  requestRestart: (params) => this.requestRestart(params)
@@ -10946,6 +11381,11 @@ var CliRuntime = class {
10946
11381
  requestRestart: (params) => this.requestRestart(params)
10947
11382
  }));
10948
11383
  this.pluginCommands = measureStartupSync("cli.runtime.plugin_commands", () => new PluginCommands());
11384
+ this.agentCommands = measureStartupSync("cli.runtime.agent_commands", () => new AgentCommands({
11385
+ requestRestart: (params) => this.requestRestart(params),
11386
+ initializeAgentHomeDirectory: (homeDirectory) => this.workspaceManager.createWorkspaceTemplates(homeDirectory),
11387
+ appName: APP_NAME5
11388
+ }));
10949
11389
  this.channelCommands = measureStartupSync("cli.runtime.channel_commands", () => new ChannelCommands({
10950
11390
  logo: this.logo,
10951
11391
  getBridgeDir: () => this.workspaceManager.getBridgeDir(),
@@ -10974,13 +11414,13 @@ var CliRuntime = class {
10974
11414
  get version() {
10975
11415
  return getPackageVersion();
10976
11416
  }
10977
- scheduleProcessExit(delayMs, reason) {
11417
+ scheduleProcessExit = (delayMs, reason) => {
10978
11418
  console.warn(`Gateway restart requested (${reason}).`);
10979
11419
  setTimeout(() => {
10980
11420
  process.exit(0);
10981
11421
  }, delayMs);
10982
- }
10983
- async restartBackgroundService(reason) {
11422
+ };
11423
+ restartBackgroundService = async (reason) => {
10984
11424
  if (this.serviceRestartTask) {
10985
11425
  return this.serviceRestartTask;
10986
11426
  }
@@ -11010,8 +11450,8 @@ var CliRuntime = class {
11010
11450
  } finally {
11011
11451
  this.serviceRestartTask = null;
11012
11452
  }
11013
- }
11014
- armManagedServiceRelaunch(params) {
11453
+ };
11454
+ armManagedServiceRelaunch = (params) => {
11015
11455
  const strategy = params.strategy ?? "background-service-or-manual";
11016
11456
  if (strategy !== "background-service-or-exit" && strategy !== "exit-process") {
11017
11457
  return;
@@ -11027,7 +11467,7 @@ var CliRuntime = class {
11027
11467
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
11028
11468
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath5(new URL("./index.js", import.meta.url));
11029
11469
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
11030
- const serviceStatePath = resolve15(getDataDir11(), "run", "service.json");
11470
+ const serviceStatePath = resolve16(getDataDir11(), "run", "service.json");
11031
11471
  const helperScript = [
11032
11472
  'const { spawnSync } = require("node:child_process");',
11033
11473
  'const { readFileSync } = require("node:fs");',
@@ -11086,7 +11526,7 @@ var CliRuntime = class {
11086
11526
  "}, delayMs);"
11087
11527
  ].join("\n");
11088
11528
  try {
11089
- const helper = spawn4(process.execPath, ["-e", helperScript], {
11529
+ const helper = spawn5(process.execPath, ["-e", helperScript], {
11090
11530
  detached: true,
11091
11531
  stdio: "ignore",
11092
11532
  env: process.env
@@ -11097,8 +11537,8 @@ var CliRuntime = class {
11097
11537
  } catch (error) {
11098
11538
  console.error(`Failed to arm gateway self-restart: ${String(error)}`);
11099
11539
  }
11100
- }
11101
- async requestRestart(params) {
11540
+ };
11541
+ requestRestart = async (params) => {
11102
11542
  this.armManagedServiceRelaunch({
11103
11543
  reason: params.reason,
11104
11544
  strategy: params.strategy,
@@ -11121,8 +11561,8 @@ var CliRuntime = class {
11121
11561
  return;
11122
11562
  }
11123
11563
  console.warn(result.message);
11124
- }
11125
- async writeRestartSentinelFromExecContext(reason) {
11564
+ };
11565
+ writeRestartSentinelFromExecContext = async (reason) => {
11126
11566
  const sessionKeyRaw = process.env.NEXTCLAW_RUNTIME_SESSION_KEY;
11127
11567
  const sessionKey = typeof sessionKeyRaw === "string" ? sessionKeyRaw.trim() : "";
11128
11568
  if (!sessionKey) {
@@ -11144,23 +11584,23 @@ var CliRuntime = class {
11144
11584
  `Warning: failed to write restart sentinel from exec context: ${String(error)}`
11145
11585
  );
11146
11586
  }
11147
- }
11148
- async onboard() {
11587
+ };
11588
+ onboard = async () => {
11149
11589
  console.warn(
11150
11590
  `Warning: ${APP_NAME5} onboard is deprecated. Use "${APP_NAME5} init" instead.`
11151
11591
  );
11152
11592
  await this.init({ source: "onboard" });
11153
- }
11154
- async init(options = {}) {
11593
+ };
11594
+ init = async (options = {}) => {
11155
11595
  const source = options.source ?? "init";
11156
11596
  const prefix = options.auto ? "Auto init" : "Init";
11157
11597
  const force = Boolean(options.force);
11158
11598
  const configPath = getConfigPath11();
11159
11599
  const createdConfig = initializeConfigIfMissing(configPath);
11160
- const config2 = loadConfig21();
11600
+ const config2 = loadConfig22();
11161
11601
  const workspaceSetting = config2.agents.defaults.workspace;
11162
- const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir11(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
11163
- const workspaceExisted = existsSync13(workspacePath);
11602
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir11(), DEFAULT_WORKSPACE_DIR) : expandHome3(workspaceSetting);
11603
+ const workspaceExisted = existsSync12(workspacePath);
11164
11604
  mkdirSync7(workspacePath, { recursive: true });
11165
11605
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
11166
11606
  workspacePath,
@@ -11189,12 +11629,12 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11189
11629
  `Tip: Run "${APP_NAME5} init${force ? " --force" : ""}" to re-run initialization if needed.`
11190
11630
  );
11191
11631
  }
11192
- }
11193
- async login(opts = {}) {
11632
+ };
11633
+ login = async (opts = {}) => {
11194
11634
  await this.init({ source: "login", auto: true });
11195
11635
  await this.platformAuthCommands.login(opts);
11196
- }
11197
- async gateway(opts) {
11636
+ };
11637
+ gateway = async (opts) => {
11198
11638
  const uiOverrides = {
11199
11639
  host: FORCED_PUBLIC_UI_HOST2
11200
11640
  };
@@ -11208,8 +11648,8 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11208
11648
  uiOverrides.open = true;
11209
11649
  }
11210
11650
  await this.serviceCommands.startGateway({ uiOverrides });
11211
- }
11212
- async ui(opts) {
11651
+ };
11652
+ ui = async (opts) => {
11213
11653
  const uiOverrides = {
11214
11654
  enabled: true,
11215
11655
  host: FORCED_PUBLIC_UI_HOST2,
@@ -11222,9 +11662,9 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11222
11662
  uiOverrides,
11223
11663
  allowMissingProvider: true
11224
11664
  });
11225
- }
11226
- async start(opts) {
11227
- const startupTimeoutMs = this.parseStartTimeoutMs(opts.startTimeout);
11665
+ };
11666
+ start = async (opts) => {
11667
+ const startupTimeoutMs = parseStartTimeoutMs(opts.startTimeout);
11228
11668
  await this.init({ source: "start", auto: true });
11229
11669
  const uiOverrides = {
11230
11670
  enabled: true,
@@ -11239,8 +11679,8 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11239
11679
  open: Boolean(opts.open),
11240
11680
  startupTimeoutMs
11241
11681
  });
11242
- }
11243
- async restart(opts) {
11682
+ };
11683
+ restart = async (opts) => {
11244
11684
  await this.writeRestartSentinelFromExecContext("cli.restart");
11245
11685
  const state = readServiceState();
11246
11686
  if (state && isProcessRunning(state.pid)) {
@@ -11253,8 +11693,8 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11253
11693
  console.log("No running service found. Starting a new service.");
11254
11694
  }
11255
11695
  await this.start(opts);
11256
- }
11257
- async serve(opts) {
11696
+ };
11697
+ serve = async (opts) => {
11258
11698
  const uiOverrides = {
11259
11699
  enabled: true,
11260
11700
  host: FORCED_PUBLIC_UI_HOST2,
@@ -11267,32 +11707,21 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11267
11707
  uiOverrides,
11268
11708
  open: Boolean(opts.open)
11269
11709
  });
11270
- }
11271
- parseStartTimeoutMs(value) {
11272
- if (value === void 0) {
11273
- return void 0;
11274
- }
11275
- const parsed = Number(value);
11276
- if (!Number.isFinite(parsed) || parsed <= 0) {
11277
- console.error("Invalid --start-timeout value. Provide milliseconds (e.g. 45000).");
11278
- process.exit(1);
11279
- }
11280
- return Math.floor(parsed);
11281
- }
11282
- async stop() {
11710
+ };
11711
+ stop = async () => {
11283
11712
  await this.serviceCommands.stopService();
11284
- }
11285
- async agent(opts) {
11713
+ };
11714
+ agent = async (opts) => {
11286
11715
  const configPath = getConfigPath11();
11287
- const config2 = resolveConfigSecrets7(loadConfig21(), { configPath });
11288
- const workspace = getWorkspacePath13(config2.agents.defaults.workspace);
11716
+ const config2 = resolveConfigSecrets7(loadConfig22(), { configPath });
11717
+ const workspace = getWorkspacePath14(config2.agents.defaults.workspace);
11289
11718
  const pluginRegistry = loadPluginRegistry(config2, workspace);
11290
11719
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
11291
11720
  logPluginDiagnostics(pluginRegistry);
11292
11721
  const pluginChannelBindings = getPluginChannelBindings6(pluginRegistry);
11293
11722
  setPluginRuntimeBridge3({
11294
11723
  loadConfig: () => toPluginConfigView(
11295
- resolveConfigSecrets7(loadConfig21(), { configPath }),
11724
+ resolveConfigSecrets7(loadConfig22(), { configPath }),
11296
11725
  pluginChannelBindings
11297
11726
  ),
11298
11727
  writeConfigFile: async (nextConfigView) => {
@@ -11301,7 +11730,7 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11301
11730
  "plugin runtime writeConfigFile expects an object config"
11302
11731
  );
11303
11732
  }
11304
- const current = loadConfig21();
11733
+ const current = loadConfig22();
11305
11734
  const next = mergePluginConfigView(
11306
11735
  current,
11307
11736
  nextConfigView,
@@ -11333,7 +11762,7 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11333
11762
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints3({
11334
11763
  registry: pluginRegistry,
11335
11764
  channel,
11336
- cfg: resolveConfigSecrets7(loadConfig21(), { configPath }),
11765
+ cfg: resolveConfigSecrets7(loadConfig22(), { configPath }),
11337
11766
  accountId
11338
11767
  })
11339
11768
  });
@@ -11353,9 +11782,9 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11353
11782
  `
11354
11783
  );
11355
11784
  const historyFile = join9(getDataDir11(), "history", "cli_history");
11356
- const historyDir = resolve15(historyFile, "..");
11785
+ const historyDir = resolve16(historyFile, "..");
11357
11786
  mkdirSync7(historyDir, { recursive: true });
11358
- const history = existsSync13(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
11787
+ const history = existsSync12(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
11359
11788
  const rl = createInterface3({
11360
11789
  input: process.stdin,
11361
11790
  output: process.stdout
@@ -11389,8 +11818,8 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11389
11818
  } finally {
11390
11819
  setPluginRuntimeBridge3(null);
11391
11820
  }
11392
- }
11393
- async update(opts) {
11821
+ };
11822
+ update = async (opts) => {
11394
11823
  let timeoutMs;
11395
11824
  if (opts.timeout !== void 0) {
11396
11825
  const parsed = Number(opts.timeout);
@@ -11436,76 +11865,88 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11436
11865
  if (state && isProcessRunning(state.pid)) {
11437
11866
  console.log(`Tip: restart ${APP_NAME5} to apply the update.`);
11438
11867
  }
11439
- }
11440
- pluginsList(opts = {}) {
11868
+ };
11869
+ agentsList = (opts = {}) => {
11870
+ this.agentCommands.agentsList(opts);
11871
+ };
11872
+ agentsNew = async (agentId, opts = {}) => {
11873
+ await this.agentCommands.agentsNew(agentId, opts);
11874
+ };
11875
+ agentsUpdate = async (agentId, opts = {}) => {
11876
+ await this.agentCommands.agentsUpdate(agentId, opts);
11877
+ };
11878
+ agentsRemove = async (agentId, opts = {}) => {
11879
+ await this.agentCommands.agentsRemove(agentId, opts);
11880
+ };
11881
+ pluginsList = (opts = {}) => {
11441
11882
  this.pluginCommands.pluginsList(opts);
11442
- }
11443
- pluginsInfo(id, opts = {}) {
11883
+ };
11884
+ pluginsInfo = (id, opts = {}) => {
11444
11885
  this.pluginCommands.pluginsInfo(id, opts);
11445
- }
11446
- async pluginsEnable(id) {
11886
+ };
11887
+ pluginsEnable = async (id) => {
11447
11888
  await this.pluginCommands.pluginsEnable(id);
11448
- }
11449
- async pluginsDisable(id) {
11889
+ };
11890
+ pluginsDisable = async (id) => {
11450
11891
  await this.pluginCommands.pluginsDisable(id);
11451
- }
11452
- async pluginsUninstall(id, opts = {}) {
11892
+ };
11893
+ pluginsUninstall = async (id, opts = {}) => {
11453
11894
  await this.pluginCommands.pluginsUninstall(id, opts);
11454
- }
11455
- async pluginsInstall(pathOrSpec, opts = {}) {
11895
+ };
11896
+ pluginsInstall = async (pathOrSpec, opts = {}) => {
11456
11897
  await this.pluginCommands.pluginsInstall(pathOrSpec, opts);
11457
- }
11458
- pluginsDoctor() {
11898
+ };
11899
+ pluginsDoctor = () => {
11459
11900
  this.pluginCommands.pluginsDoctor();
11460
- }
11461
- configGet(pathExpr, opts = {}) {
11901
+ };
11902
+ configGet = (pathExpr, opts = {}) => {
11462
11903
  this.configCommands.configGet(pathExpr, opts);
11463
- }
11464
- async configSet(pathExpr, value, opts = {}) {
11904
+ };
11905
+ configSet = async (pathExpr, value, opts = {}) => {
11465
11906
  await this.configCommands.configSet(pathExpr, value, opts);
11466
- }
11467
- async configUnset(pathExpr) {
11907
+ };
11908
+ configUnset = async (pathExpr) => {
11468
11909
  await this.configCommands.configUnset(pathExpr);
11469
- }
11470
- mcpList(opts = {}) {
11910
+ };
11911
+ mcpList = (opts = {}) => {
11471
11912
  this.mcpCommands.mcpList(opts);
11472
- }
11473
- async mcpAdd(name, command, opts = {}) {
11913
+ };
11914
+ mcpAdd = async (name, command, opts = {}) => {
11474
11915
  await this.mcpCommands.mcpAdd(name, command, opts);
11475
- }
11476
- async mcpRemove(name) {
11916
+ };
11917
+ mcpRemove = async (name) => {
11477
11918
  await this.mcpCommands.mcpRemove(name);
11478
- }
11479
- async mcpEnable(name) {
11919
+ };
11920
+ mcpEnable = async (name) => {
11480
11921
  await this.mcpCommands.mcpEnable(name);
11481
- }
11482
- async mcpDisable(name) {
11922
+ };
11923
+ mcpDisable = async (name) => {
11483
11924
  await this.mcpCommands.mcpDisable(name);
11484
- }
11485
- async mcpDoctor(name, opts = {}) {
11925
+ };
11926
+ mcpDoctor = async (name, opts = {}) => {
11486
11927
  await this.mcpCommands.mcpDoctor(name, opts);
11487
- }
11488
- secretsAudit(opts = {}) {
11928
+ };
11929
+ secretsAudit = (opts = {}) => {
11489
11930
  this.secretsCommands.secretsAudit(opts);
11490
- }
11491
- async secretsConfigure(opts) {
11931
+ };
11932
+ secretsConfigure = async (opts) => {
11492
11933
  await this.secretsCommands.secretsConfigure(opts);
11493
- }
11494
- async secretsApply(opts) {
11934
+ };
11935
+ secretsApply = async (opts) => {
11495
11936
  await this.secretsCommands.secretsApply(opts);
11496
- }
11497
- async secretsReload(opts = {}) {
11937
+ };
11938
+ secretsReload = async (opts = {}) => {
11498
11939
  await this.secretsCommands.secretsReload(opts);
11499
- }
11500
- channelsStatus() {
11940
+ };
11941
+ channelsStatus = () => {
11501
11942
  this.channelCommands.channelsStatus();
11502
- }
11503
- async channelsLogin(opts) {
11943
+ };
11944
+ channelsLogin = async (opts) => {
11504
11945
  await this.channelCommands.channelsLogin(opts);
11505
- }
11506
- async channelsAdd(opts) {
11946
+ };
11947
+ channelsAdd = async (opts) => {
11507
11948
  await this.channelCommands.channelsAdd(opts);
11508
- }
11949
+ };
11509
11950
  cronList = async (opts) => {
11510
11951
  await this.cronCommands.cronList(opts);
11511
11952
  };
@@ -11521,14 +11962,14 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11521
11962
  cronRun = async (jobId, opts) => {
11522
11963
  await this.cronCommands.cronRun(jobId, opts);
11523
11964
  };
11524
- async status(opts = {}) {
11965
+ status = async (opts = {}) => {
11525
11966
  await this.diagnosticsCommands.status(opts);
11526
- }
11527
- async doctor(opts = {}) {
11967
+ };
11968
+ doctor = async (opts = {}) => {
11528
11969
  await this.diagnosticsCommands.doctor(opts);
11529
- }
11530
- async skillsInstall(options) {
11531
- const config2 = loadConfig21();
11970
+ };
11971
+ skillsInstall = async (options) => {
11972
+ const config2 = loadConfig22();
11532
11973
  const workdir = resolveSkillsInstallWorkdir({
11533
11974
  explicitWorkdir: options.workdir,
11534
11975
  configuredWorkspace: config2.agents.defaults.workspace
@@ -11546,11 +11987,11 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11546
11987
  console.log(`\u2713 Installed ${result.slug} (${result.source})`);
11547
11988
  }
11548
11989
  console.log(` Path: ${result.destinationDir}`);
11549
- }
11550
- async skillsPublish(options) {
11990
+ };
11991
+ skillsPublish = async (options) => {
11551
11992
  const result = await publishMarketplaceSkill({
11552
- skillDir: expandHome2(options.dir),
11553
- metaFile: options.meta ? expandHome2(options.meta) : void 0,
11993
+ skillDir: expandHome3(options.dir),
11994
+ metaFile: options.meta ? expandHome3(options.meta) : void 0,
11554
11995
  slug: options.slug,
11555
11996
  name: options.name,
11556
11997
  summary: options.summary,
@@ -11566,11 +12007,11 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11566
12007
  });
11567
12008
  console.log(`${result.created ? `\u2713 Published new skill: ${result.slug}` : `\u2713 Updated skill: ${result.slug}`}
11568
12009
  Files: ${result.fileCount}`);
11569
- }
11570
- async skillsUpdate(options) {
12010
+ };
12011
+ skillsUpdate = async (options) => {
11571
12012
  const result = await publishMarketplaceSkill({
11572
- skillDir: expandHome2(options.dir),
11573
- metaFile: options.meta ? expandHome2(options.meta) : void 0,
12013
+ skillDir: expandHome3(options.dir),
12014
+ metaFile: options.meta ? expandHome3(options.meta) : void 0,
11574
12015
  slug: options.slug,
11575
12016
  name: options.name,
11576
12017
  summary: options.summary,
@@ -11586,9 +12027,18 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
11586
12027
  });
11587
12028
  console.log(`\u2713 Updated skill: ${result.slug}`);
11588
12029
  console.log(` Files: ${result.fileCount}`);
11589
- }
12030
+ };
11590
12031
  };
11591
12032
 
12033
+ // src/cli/register-agents-commands.ts
12034
+ function registerAgentsCommands(program2, runtime2) {
12035
+ const agents = program2.command("agents").description("Manage agents");
12036
+ agents.command("list").description("List available agents").option("--json", "Output JSON", false).action((opts) => runtime2.agentsList(opts));
12037
+ agents.command("new <agentId>").description("Create a new agent").option("--name <name>", "Agent display name").option("--description <description>", "Agent description").option("--avatar <avatar>", "Remote avatar URL or local image path").option("--home <path>", "Agent home directory").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime2.agentsNew(agentId, opts));
12038
+ agents.command("update <agentId>").description("Update an existing agent").option("--name <name>", "Agent display name").option("--description <description>", "Agent description").option("--avatar <avatar>", "Remote avatar URL or local image path").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime2.agentsUpdate(agentId, opts));
12039
+ agents.command("remove <agentId>").description("Remove an agent").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime2.agentsRemove(agentId, opts));
12040
+ }
12041
+
11592
12042
  // src/cli/index.ts
11593
12043
  logStartupTrace("cli.index.module_loaded");
11594
12044
  var program = new Command();
@@ -11611,6 +12061,7 @@ skills.command("install <slug>").description("Install a skill from NextClaw mark
11611
12061
  var withRepeatableTag = (value, previous = []) => [...previous, value];
11612
12062
  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 }));
11613
12063
  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 }));
12064
+ registerAgentsCommands(program, runtime);
11614
12065
  var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
11615
12066
  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));
11616
12067
  plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));
@@ -11646,7 +12097,7 @@ channels.command("status").description("Show channel status").action(() => runti
11646
12097
  channels.command("login").description("Link device via QR code").option("--channel <id>", "Plugin channel id").option("--account <id>", "Channel account id").option("--url <url>", "Channel API base URL").option("--http-url <url>", "Alias for --url").option("-v, --verbose", "Verbose output", false).action(async (opts) => runtime.channelsLogin(opts));
11647
12098
  var cron = program.command("cron").description("Manage scheduled tasks");
11648
12099
  cron.command("list").option("--enabled-only", "Show only enabled jobs", false).option("-a, --all", "Deprecated: list all jobs (default behavior)", false).action(async (opts) => runtime.cronList({ enabledOnly: Boolean(opts.enabledOnly) }));
11649
- cron.command("add").requiredOption("-n, --name <name>", "Job name").requiredOption("-m, --message <message>", "Message for agent").option("-e, --every <seconds>", "Run every N seconds").option("-c, --cron <expr>", "Cron expression").option("--at <iso>", "Run once at time (ISO format)").option("-d, --deliver", "Deliver response to channel").option("--to <recipient>", "Recipient for delivery").option("--channel <channel>", "Channel for delivery").option("--account <id>", "Account id for channel delivery").action(async (opts) => runtime.cronAdd(opts));
12100
+ cron.command("add").requiredOption("-n, --name <name>", "Job name").requiredOption("-m, --message <message>", "Message for agent").option("--agent <id>", "Target agent id").option("-e, --every <seconds>", "Run every N seconds").option("-c, --cron <expr>", "Cron expression").option("--at <iso>", "Run once at time (ISO format)").option("-d, --deliver", "Deliver response to channel").option("--to <recipient>", "Recipient for delivery").option("--channel <channel>", "Channel for delivery").option("--account <id>", "Account id for channel delivery").action(async (opts) => runtime.cronAdd(opts));
11650
12101
  cron.command("remove <jobId>").action(async (jobId) => runtime.cronRemove(jobId));
11651
12102
  cron.command("enable <jobId>").option("--disable", "Disable instead of enable").action(async (jobId, opts) => runtime.cronEnable(jobId, opts));
11652
12103
  cron.command("disable <jobId>").action(async (jobId) => runtime.cronEnable(jobId, { disable: true }));