nextclaw 0.17.1 → 0.17.3

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 (69) hide show
  1. package/dist/cli/index.js +394 -194
  2. package/package.json +10 -10
  3. package/resources/USAGE.md +48 -30
  4. package/ui-dist/assets/ChannelsList-uKmkpD25.js +8 -0
  5. package/ui-dist/assets/ChatPage-CslhBPfT.js +43 -0
  6. package/ui-dist/assets/{DocBrowser-DxdSujSc.js → DocBrowser-C7-1sXqo.js} +1 -1
  7. package/ui-dist/assets/DocBrowser-DQjtSsY3.js +1 -0
  8. package/ui-dist/assets/{DocBrowserContext-CQ-8jMha.js → DocBrowserContext-DN5tjUoS.js} +1 -1
  9. package/ui-dist/assets/{LogoBadge-D-KQIN4U.js → LogoBadge-DDS1sU_U.js} +1 -1
  10. package/ui-dist/assets/MarketplacePage-BZQW70ti.js +1 -0
  11. package/ui-dist/assets/{MarketplacePage-CNeXwz7q.js → MarketplacePage-DE0QjYVv.js} +1 -1
  12. package/ui-dist/assets/{McpMarketplacePage-BGv2wwuk.js → McpMarketplacePage-CeLvv1xy.js} +1 -1
  13. package/ui-dist/assets/ModelConfig-D1JtGtQv.js +1 -0
  14. package/ui-dist/assets/ProviderScopedModelInput-SAJH6nkC.js +1 -0
  15. package/ui-dist/assets/{ProvidersList-BXeGOw0M.js → ProvidersList-1rKi3aQT.js} +1 -1
  16. package/ui-dist/assets/{RemoteAccessPage-vlBOKnMP.js → RemoteAccessPage-bIAKxDky.js} +1 -1
  17. package/ui-dist/assets/RuntimeConfig-BTk9319O.js +1 -0
  18. package/ui-dist/assets/SearchConfig-EjeszXbv.js +1 -0
  19. package/ui-dist/assets/{SecretsConfig-Jduzy42Q.js → SecretsConfig-cnAXvREZ.js} +1 -1
  20. package/ui-dist/assets/{SessionsConfig-CIwqHwvE.js → SessionsConfig-BIXiDaK2.js} +1 -1
  21. package/ui-dist/assets/{book-open-FnK2xCQd.js → book-open-DvWqOode.js} +1 -1
  22. package/ui-dist/assets/chat-session-display-D4bYa0b8.js +1 -0
  23. package/ui-dist/assets/{chunk-JZWAC4HX-B5l0hr_u.js → chunk-JZWAC4HX-CxfKRD7X.js} +1 -1
  24. package/ui-dist/assets/{config-JKmXfZ3q.js → config-BeGwf2Ao.js} +1 -1
  25. package/ui-dist/assets/{createLucideIcon-o1WWhwhd.js → createLucideIcon-C7MmdIX3.js} +1 -1
  26. package/ui-dist/assets/{dist-C_moWYv7.js → dist-B6VMuIQN.js} +1 -1
  27. package/ui-dist/assets/{dist-DazA6Wd_.js → dist-RWNFhxvR.js} +1 -1
  28. package/ui-dist/assets/{external-link-BKje3SiD.js → external-link-U86Acd1t.js} +1 -1
  29. package/ui-dist/assets/{hash-DfW4DT8O.js → hash-D-OVfV3Z.js} +1 -1
  30. package/ui-dist/assets/i18n-hM3v-3YG.js +1 -0
  31. package/ui-dist/assets/{index-BuTtTFjM.js → index-8XNPYwJu.js} +3 -3
  32. package/ui-dist/assets/index-CpxuJa9o.css +1 -0
  33. package/ui-dist/assets/{label-BzDWmdOe.js → label-CHJ1ATds.js} +1 -1
  34. package/ui-dist/assets/loader-circle-C8cpaL0w.js +1 -0
  35. package/ui-dist/assets/{logos-CTLlde_T.js → logos-U1_qDA3U.js} +1 -1
  36. package/ui-dist/assets/{page-layout-BagR3t59.js → page-layout-Z1klaUFW.js} +1 -1
  37. package/ui-dist/assets/plus-CrkO1kob.js +1 -0
  38. package/ui-dist/assets/{popover-5DWhNfd4.js → popover-xWbqMnIN.js} +1 -1
  39. package/ui-dist/assets/{react-C3yu5yge.js → react-3YE87-lE.js} +1 -1
  40. package/ui-dist/assets/{refresh-ccw-BAJf-h7w.js → refresh-ccw-JQh1lwq-.js} +1 -1
  41. package/ui-dist/assets/{save-aa6z4GJL.js → save-4VRlzkii.js} +1 -1
  42. package/ui-dist/assets/search-EX-Papzl.js +1 -0
  43. package/ui-dist/assets/{security-config-BrmOzvpp.js → security-config-CGazBahs.js} +1 -1
  44. package/ui-dist/assets/{select-BHJPiJWt.js → select-DF-AUoie.js} +1 -1
  45. package/ui-dist/assets/skeleton-B0mmt1vo.js +1 -0
  46. package/ui-dist/assets/{status-dot-DUwsTIdv.js → status-dot-Bq_8Ojvv.js} +1 -1
  47. package/ui-dist/assets/{switch-B6nCfcOB.js → switch-D7JF_RZ-.js} +1 -1
  48. package/ui-dist/assets/{tabs-custom-B57SMElx.js → tabs-custom-CLksZ2bO.js} +1 -1
  49. package/ui-dist/assets/{trash-2-CrjYH5ok.js → trash-2-VV8jvziy.js} +1 -1
  50. package/ui-dist/assets/{useConfirmDialog-RlLbo0NU.js → useConfirmDialog-D6HxybcM.js} +1 -1
  51. package/ui-dist/assets/{useMutation-oTTWXgLG.js → useMutation-DBTWPbTg.js} +1 -1
  52. package/ui-dist/assets/x-B4sxJkGY.js +1 -0
  53. package/ui-dist/index.html +18 -18
  54. package/ui-dist/assets/ChannelsList-BXuFNDBh.js +0 -8
  55. package/ui-dist/assets/ChatPage-CEuFBJgC.js +0 -43
  56. package/ui-dist/assets/DocBrowser-C8b2uPgL.js +0 -1
  57. package/ui-dist/assets/MarketplacePage-BGPF2sDu.js +0 -1
  58. package/ui-dist/assets/ModelConfig-CoNm556G.js +0 -1
  59. package/ui-dist/assets/RuntimeConfig-CEkrhzg7.js +0 -1
  60. package/ui-dist/assets/SearchConfig-BK0dn4Zp.js +0 -1
  61. package/ui-dist/assets/chat-session-display-BD_AN71I.js +0 -1
  62. package/ui-dist/assets/i18n-BK1w-oBy.js +0 -1
  63. package/ui-dist/assets/index-DaR9igPC.css +0 -1
  64. package/ui-dist/assets/loader-circle-DdZPxBUz.js +0 -1
  65. package/ui-dist/assets/plus-DP2PSCPO.js +0 -1
  66. package/ui-dist/assets/provider-models-DJ29qHuA.js +0 -1
  67. package/ui-dist/assets/search-pD6ZwQYF.js +0 -1
  68. package/ui-dist/assets/skeleton-D6kCk9Y6.js +0 -1
  69. package/ui-dist/assets/x-CTIQHUuD.js +0 -1
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import * as NextclawCore from "@nextclaw/core";
4
- import { APP_NAME, APP_TAGLINE, AgentLoop, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, NativeAgentEngine, ProviderManager, ReadFileTool, SessionsHistoryTool, SessionsListTool, SessionsSendTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createExternalCommandEnv, diffConfigPaths, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
4
+ import { APP_NAME, APP_TAGLINE, AgentLoop, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, NativeAgentEngine, ProviderManager, ReadFileTool, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createExternalCommandEnv, diffConfigPaths, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
5
5
  import { Command } from "commander";
6
6
  import { RemoteConnector, RemotePlatformClient, RemoteRuntimeActions, RemoteServiceModule, RemoteStatusStore, buildConfiguredRemoteState, readPlatformSessionTokenState, registerRemoteCommands, resolveRemoteStatusSnapshot } from "@nextclaw/remote";
7
7
  import { addPluginLoadPath, buildPluginStatusReport, disablePluginInConfig, discoverPluginStatusReport, enablePluginInConfig, getPluginChannelBindings, getPluginUiMetadataFromRegistry, installPluginFromNpmSpec, installPluginFromPath, loadOpenClawPlugins, loadOpenClawPluginsProgressively, mergePluginConfigView, recordPluginInstall, resolvePluginChannelMessageToolHints, resolveUninstallDirectoryTargets, setPluginRuntimeBridge, startPluginChannelGateways, stopPluginChannelGateways, toPluginConfigView, toPluginConfigView as toPluginConfigView$1, uninstallPlugin } from "@nextclaw/openclaw-compat";
@@ -983,7 +983,6 @@ const RESERVED_TOOL_NAMES = [
983
983
  "spawn",
984
984
  "sessions_list",
985
985
  "sessions_history",
986
- "sessions_send",
987
986
  "memory_search",
988
987
  "memory_get",
989
988
  "subagents",
@@ -2487,41 +2486,79 @@ var ChannelCommands = class {
2487
2486
  function createCronService() {
2488
2487
  return new CronService(join(getDataDir(), "cron", "jobs.json"));
2489
2488
  }
2489
+ function readTrimmed(value) {
2490
+ if (typeof value !== "string") return;
2491
+ return value.trim() || void 0;
2492
+ }
2490
2493
  function toSchedule(opts) {
2491
- if (opts.every) return {
2492
- kind: "every",
2493
- everyMs: Number(opts.every) * 1e3
2494
- };
2495
- if (opts.cron) return {
2494
+ const every = readTrimmed(opts.every);
2495
+ const cron = readTrimmed(opts.cron);
2496
+ const at = readTrimmed(opts.at);
2497
+ if ([
2498
+ every,
2499
+ cron,
2500
+ at
2501
+ ].filter((value) => value !== void 0).length !== 1) return { error: "Error: Must specify exactly one of --every, --cron, or --at" };
2502
+ if (every) {
2503
+ const everySeconds = Number(every);
2504
+ if (!Number.isFinite(everySeconds) || everySeconds <= 0) return { error: "Error: --every must be a positive number of seconds" };
2505
+ return { schedule: {
2506
+ kind: "every",
2507
+ everyMs: everySeconds * 1e3
2508
+ } };
2509
+ }
2510
+ if (cron) return { schedule: {
2496
2511
  kind: "cron",
2497
- expr: String(opts.cron)
2498
- };
2499
- if (opts.at) return {
2512
+ expr: cron
2513
+ } };
2514
+ const atMs = Date.parse(String(at));
2515
+ if (!Number.isFinite(atMs)) return { error: "Error: --at must be a valid ISO datetime" };
2516
+ return { schedule: {
2500
2517
  kind: "at",
2501
- atMs: Date.parse(String(opts.at))
2502
- };
2503
- return null;
2518
+ atMs
2519
+ } };
2520
+ }
2521
+ function createCronCreateRequest(opts) {
2522
+ const name = readTrimmed(opts.name);
2523
+ const message = readTrimmed(opts.message);
2524
+ if (!name || !message) return { error: "Error: name and message are required" };
2525
+ const schedule = toSchedule(opts);
2526
+ if (!schedule.schedule) return { error: schedule.error ?? "Error: Must specify --every, --cron, or --at" };
2527
+ return { request: {
2528
+ name,
2529
+ message,
2530
+ schedule: schedule.schedule,
2531
+ agentId: readTrimmed(opts.agent),
2532
+ deliver: Boolean(opts.deliver),
2533
+ channel: readTrimmed(opts.channel),
2534
+ to: readTrimmed(opts.to),
2535
+ accountId: readTrimmed(opts.account)
2536
+ } };
2504
2537
  }
2505
2538
  var CronLocalService = class {
2506
2539
  list = (all) => {
2507
2540
  return createCronService().listJobs(all);
2508
2541
  };
2542
+ addRequest = (request) => {
2543
+ return createCronService().addJob({
2544
+ name: request.name,
2545
+ schedule: request.schedule,
2546
+ message: request.message,
2547
+ agentId: request.agentId ?? void 0,
2548
+ deliver: request.deliver === true,
2549
+ channel: request.channel ?? void 0,
2550
+ to: request.to ?? void 0,
2551
+ accountId: request.accountId ?? void 0,
2552
+ deleteAfterRun: request.deleteAfterRun === true
2553
+ });
2554
+ };
2509
2555
  add = (opts) => {
2510
- const schedule = toSchedule(opts);
2511
- if (!schedule) return {
2556
+ const request = createCronCreateRequest(opts);
2557
+ if (!request.request) return {
2512
2558
  job: null,
2513
- error: "Error: Must specify --every, --cron, or --at"
2559
+ error: request.error
2514
2560
  };
2515
- return { job: createCronService().addJob({
2516
- name: opts.name,
2517
- schedule,
2518
- message: opts.message,
2519
- agentId: opts.agent,
2520
- deliver: Boolean(opts.deliver),
2521
- channel: opts.channel,
2522
- to: opts.to,
2523
- accountId: opts.account
2524
- }) };
2561
+ return { job: this.addRequest(request.request) };
2525
2562
  };
2526
2563
  remove = (jobId) => {
2527
2564
  return createCronService().removeJob(jobId);
@@ -2551,9 +2588,11 @@ function printCronJobs(jobs) {
2551
2588
  //#region src/cli/commands/shared/ui-bridge-api.service.ts
2552
2589
  function resolveManagedApiBase() {
2553
2590
  const state = readServiceState();
2554
- if (!state?.apiUrl || !state.pid) return null;
2591
+ if (!state?.pid) return null;
2555
2592
  if (!isProcessRunning(state.pid)) return null;
2556
- return state.apiUrl.replace(/\/+$/, "");
2593
+ if (typeof state.uiUrl === "string" && state.uiUrl.trim().length > 0) return state.uiUrl.replace(/\/+$/, "");
2594
+ if (typeof state.apiUrl === "string" && state.apiUrl.trim().length > 0) return state.apiUrl.replace(/\/api\/?$/, "").replace(/\/+$/, "");
2595
+ return null;
2557
2596
  }
2558
2597
  var UiBridgeApiClient = class {
2559
2598
  cookie;
@@ -2591,6 +2630,10 @@ var UiBridgeApiClient = class {
2591
2630
  };
2592
2631
  //#endregion
2593
2632
  //#region src/cli/commands/cron.ts
2633
+ function readErrorMessage(error) {
2634
+ if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
2635
+ return String(error ?? "unknown error");
2636
+ }
2594
2637
  var CronCommands = class {
2595
2638
  constructor(local = new CronLocalService()) {
2596
2639
  this.local = local;
@@ -2611,12 +2654,26 @@ var CronCommands = class {
2611
2654
  printCronJobs(this.local.list(includeDisabled));
2612
2655
  };
2613
2656
  cronAdd = async (opts) => {
2614
- const result = this.local.add(opts);
2615
- if (!result.job) {
2616
- console.error(result.error ?? "Error: Failed to add job");
2657
+ const request = createCronCreateRequest(opts);
2658
+ if (!request.request) {
2659
+ console.error(request.error ?? "Error: Failed to add job");
2617
2660
  return;
2618
2661
  }
2619
- console.log(`✓ Added job '${result.job.name}' (${result.job.id})`);
2662
+ const apiClient = this.createApiClient();
2663
+ if (apiClient) try {
2664
+ const data = await apiClient.request({
2665
+ path: "/api/cron",
2666
+ method: "POST",
2667
+ body: request.request
2668
+ });
2669
+ console.log(`✓ Added job '${data.job.name}' (${data.job.id})`);
2670
+ return;
2671
+ } catch (error) {
2672
+ console.error(`Error: ${readErrorMessage(error)}`);
2673
+ return;
2674
+ }
2675
+ const job = this.local.addRequest(request.request);
2676
+ console.log(`✓ Added job '${job.name}' (${job.id})`);
2620
2677
  };
2621
2678
  cronRemove = async (jobId) => {
2622
2679
  const apiClient = this.createApiClient();
@@ -2628,7 +2685,10 @@ var CronCommands = class {
2628
2685
  console.log(`✓ Removed job ${jobId}`);
2629
2686
  return;
2630
2687
  }
2631
- } catch {}
2688
+ } catch (error) {
2689
+ console.error(`Error: ${readErrorMessage(error)}`);
2690
+ return;
2691
+ }
2632
2692
  if (this.local.remove(jobId)) console.log(`✓ Removed job ${jobId}`);
2633
2693
  else console.log(`Job ${jobId} not found`);
2634
2694
  };
@@ -2645,7 +2705,10 @@ var CronCommands = class {
2645
2705
  console.log(`✓ Job '${data.job.name}' ${opts.disable ? "disabled" : "enabled"}`);
2646
2706
  return;
2647
2707
  }
2648
- } catch {}
2708
+ } catch (error) {
2709
+ console.error(`Error: ${readErrorMessage(error)}`);
2710
+ return;
2711
+ }
2649
2712
  const job = this.local.enable(jobId, enabled);
2650
2713
  if (job) console.log(`✓ Job '${job.name}' ${opts.disable ? "disabled" : "enabled"}`);
2651
2714
  else console.log(`Job ${jobId} not found`);
@@ -2660,12 +2723,128 @@ var CronCommands = class {
2660
2723
  });
2661
2724
  console.log(data.executed ? "✓ Job executed" : `Failed to run job ${jobId}`);
2662
2725
  return;
2663
- } catch {}
2726
+ } catch (error) {
2727
+ console.error(`Error: ${readErrorMessage(error)}`);
2728
+ return;
2729
+ }
2664
2730
  const ok = await this.local.run(jobId, Boolean(opts.force));
2665
2731
  console.log(ok ? "✓ Job executed" : `Failed to run job ${jobId}`);
2666
2732
  };
2667
2733
  };
2668
2734
  //#endregion
2735
+ //#region src/cli/commands/ncp/ui-ncp-runtime-registry.ts
2736
+ const DEFAULT_UI_NCP_RUNTIME_KIND = "native";
2737
+ function normalizeRuntimeKind(value) {
2738
+ if (typeof value !== "string") return null;
2739
+ const normalized = value.trim().toLowerCase();
2740
+ return normalized.length > 0 ? normalized : null;
2741
+ }
2742
+ function readRequestedRuntimeKind(sessionMetadata) {
2743
+ return normalizeRuntimeKind(sessionMetadata.runtime) ?? normalizeRuntimeKind(sessionMetadata.session_type) ?? normalizeRuntimeKind(sessionMetadata.sessionType) ?? null;
2744
+ }
2745
+ var UiNcpRuntimeRegistry = class {
2746
+ registrations = /* @__PURE__ */ new Map();
2747
+ constructor(defaultKind = DEFAULT_UI_NCP_RUNTIME_KIND) {
2748
+ this.defaultKind = defaultKind;
2749
+ }
2750
+ register(registration) {
2751
+ const normalizedKind = normalizeRuntimeKind(registration.kind);
2752
+ if (!normalizedKind) throw new Error("ui ncp runtime kind must be a non-empty string");
2753
+ const token = Symbol(normalizedKind);
2754
+ this.registrations.set(normalizedKind, {
2755
+ ...registration,
2756
+ kind: normalizedKind,
2757
+ token
2758
+ });
2759
+ return toDisposable(() => {
2760
+ const current = this.registrations.get(normalizedKind);
2761
+ if (!current || current.token !== token) return;
2762
+ this.registrations.delete(normalizedKind);
2763
+ });
2764
+ }
2765
+ createRuntime(params) {
2766
+ const requestedKind = readRequestedRuntimeKind(params.sessionMetadata) ?? this.defaultKind;
2767
+ const registration = this.registrations.get(requestedKind);
2768
+ if (!registration) throw new Error(`ncp runtime unavailable: ${requestedKind}`);
2769
+ const nextSessionMetadata = {
2770
+ ...params.sessionMetadata,
2771
+ session_type: registration.kind
2772
+ };
2773
+ params.setSessionMetadata(nextSessionMetadata);
2774
+ return registration.createRuntime({
2775
+ ...params,
2776
+ sessionMetadata: nextSessionMetadata
2777
+ });
2778
+ }
2779
+ async listSessionTypes(params) {
2780
+ const options = await Promise.all([...this.registrations.values()].map(async (registration) => {
2781
+ const descriptor = await registration.describeSessionType?.(params);
2782
+ return {
2783
+ value: registration.kind,
2784
+ label: registration.label,
2785
+ ready: descriptor?.ready ?? true,
2786
+ reason: descriptor?.reason ?? null,
2787
+ reasonMessage: descriptor?.reasonMessage ?? null,
2788
+ recommendedModel: descriptor?.recommendedModel ?? null,
2789
+ cta: descriptor?.cta ?? null,
2790
+ ...descriptor?.supportedModels ? { supportedModels: descriptor.supportedModels } : {}
2791
+ };
2792
+ }));
2793
+ return {
2794
+ defaultType: this.defaultKind,
2795
+ options: options.sort((left, right) => {
2796
+ if (left.value === this.defaultKind) return -1;
2797
+ if (right.value === this.defaultKind) return 1;
2798
+ return left.value.localeCompare(right.value);
2799
+ })
2800
+ };
2801
+ }
2802
+ };
2803
+ //#endregion
2804
+ //#region src/cli/commands/agent/agent-runtime.ts
2805
+ function createUnusedRuntime(_params) {
2806
+ throw new Error("runtime creation is not available during runtime listing");
2807
+ }
2808
+ async function listAvailableAgentRuntimes(params) {
2809
+ const config = loadConfig();
2810
+ const pluginRegistry = loadPluginRegistry(config, getWorkspacePath(config.agents.defaults.workspace));
2811
+ logPluginDiagnostics(pluginRegistry);
2812
+ const extensionRegistry = toExtensionRegistry(pluginRegistry);
2813
+ const runtimeRegistry = new UiNcpRuntimeRegistry(DEFAULT_UI_NCP_RUNTIME_KIND);
2814
+ const runtimeSourceByKind = /* @__PURE__ */ new Map();
2815
+ runtimeRegistry.register({
2816
+ kind: DEFAULT_UI_NCP_RUNTIME_KIND,
2817
+ label: "Native",
2818
+ createRuntime: createUnusedRuntime
2819
+ });
2820
+ runtimeSourceByKind.set(DEFAULT_UI_NCP_RUNTIME_KIND, { source: "builtin" });
2821
+ for (const registration of extensionRegistry.ncpAgentRuntimes) {
2822
+ runtimeRegistry.register({
2823
+ kind: registration.kind,
2824
+ label: registration.label,
2825
+ createRuntime: registration.createRuntime,
2826
+ describeSessionType: registration.describeSessionType
2827
+ });
2828
+ runtimeSourceByKind.set(registration.kind, {
2829
+ source: "plugin",
2830
+ pluginId: registration.pluginId
2831
+ });
2832
+ }
2833
+ const listed = await runtimeRegistry.listSessionTypes(params);
2834
+ return {
2835
+ defaultRuntime: listed.defaultType,
2836
+ runtimes: listed.options.map((runtime) => {
2837
+ const source = runtimeSourceByKind.get(runtime.value);
2838
+ return {
2839
+ ...runtime,
2840
+ default: runtime.value === listed.defaultType,
2841
+ source: source?.source ?? "plugin",
2842
+ ...source?.pluginId ? { pluginId: source.pluginId } : {}
2843
+ };
2844
+ })
2845
+ };
2846
+ }
2847
+ //#endregion
2669
2848
  //#region src/cli/commands/agents.ts
2670
2849
  var AgentCommands = class {
2671
2850
  constructor(deps) {
@@ -2684,6 +2863,31 @@ var AgentCommands = class {
2684
2863
  console.log(` description: ${agent.description ?? "-"}`);
2685
2864
  console.log(` home: ${agent.workspace}`);
2686
2865
  console.log(` avatar: ${agent.avatar ?? "-"}`);
2866
+ console.log(` runtime: ${agent.runtime ?? "-"}`);
2867
+ }
2868
+ };
2869
+ agentsRuntimes = async (opts = {}) => {
2870
+ const describeMode = opts.probe ? "probe" : "observation";
2871
+ const listed = await listAvailableAgentRuntimes({ describeMode });
2872
+ if (opts.json) {
2873
+ console.log(JSON.stringify({
2874
+ defaultRuntime: listed.defaultRuntime,
2875
+ describeMode,
2876
+ runtimes: listed.runtimes
2877
+ }, null, 2));
2878
+ return;
2879
+ }
2880
+ for (const runtime of listed.runtimes) {
2881
+ const head = runtime.default ? `${runtime.value} (default)` : runtime.value;
2882
+ console.log(head);
2883
+ console.log(` label: ${runtime.label}`);
2884
+ console.log(` source: ${runtime.source}`);
2885
+ if (runtime.pluginId) console.log(` pluginId: ${runtime.pluginId}`);
2886
+ console.log(` ready: ${runtime.ready === false ? "no" : "yes"}`);
2887
+ console.log(` reason: ${runtime.reason ?? "-"}`);
2888
+ console.log(` reasonMessage: ${runtime.reasonMessage ?? "-"}`);
2889
+ console.log(` recommendedModel: ${runtime.recommendedModel ?? "-"}`);
2890
+ console.log(` supportedModels: ${runtime.supportedModels?.join(", ") ?? "-"}`);
2687
2891
  }
2688
2892
  };
2689
2893
  agentsNew = async (agentId, opts = {}) => {
@@ -2692,48 +2896,38 @@ var AgentCommands = class {
2692
2896
  displayName: opts.name,
2693
2897
  description: opts.description,
2694
2898
  avatar: opts.avatar,
2695
- home: opts.home
2899
+ home: opts.home,
2900
+ runtime: opts.runtime
2696
2901
  }, { initializeHomeDirectory: this.deps.initializeAgentHomeDirectory });
2697
2902
  if (opts.json) {
2698
- console.log(JSON.stringify({
2699
- agent: created,
2700
- restartRequired: true
2701
- }, null, 2));
2903
+ console.log(JSON.stringify({ agent: created }, null, 2));
2702
2904
  return;
2703
2905
  }
2704
- await this.deps.requestRestart({
2705
- reason: "agents-updated",
2706
- manualMessage: `Created agent '${created.id}'. Restart ${this.deps.appName} to apply agent runtime changes.`
2707
- });
2708
2906
  console.log(`✓ Created agent ${created.id}`);
2709
2907
  console.log(` name: ${created.displayName ?? "-"}`);
2710
2908
  console.log(` description: ${created.description ?? "-"}`);
2711
2909
  console.log(` home: ${created.workspace}`);
2712
2910
  console.log(` avatar: ${created.avatar ?? "-"}`);
2911
+ console.log(` runtime: ${created.runtime ?? created.engine ?? "-"}`);
2713
2912
  };
2714
2913
  agentsUpdate = async (agentId, opts = {}) => {
2715
2914
  const updated = updateAgentProfile({
2716
2915
  id: agentId,
2717
2916
  displayName: opts.name,
2718
2917
  description: opts.description,
2719
- avatar: opts.avatar
2918
+ avatar: opts.avatar,
2919
+ runtime: opts.runtime
2720
2920
  });
2721
2921
  if (opts.json) {
2722
- console.log(JSON.stringify({
2723
- agent: updated,
2724
- restartRequired: true
2725
- }, null, 2));
2922
+ console.log(JSON.stringify({ agent: updated }, null, 2));
2726
2923
  return;
2727
2924
  }
2728
- await this.deps.requestRestart({
2729
- reason: "agents-updated",
2730
- manualMessage: `Updated agent '${updated.id}'. Restart ${this.deps.appName} to apply agent runtime changes.`
2731
- });
2732
2925
  console.log(`✓ Updated agent ${updated.id}`);
2733
2926
  console.log(` name: ${updated.displayName ?? "-"}`);
2734
2927
  console.log(` description: ${updated.description ?? "-"}`);
2735
2928
  console.log(` home: ${updated.workspace}`);
2736
2929
  console.log(` avatar: ${updated.avatar ?? "-"}`);
2930
+ console.log(` runtime: ${updated.runtime ?? updated.engine ?? "-"}`);
2737
2931
  };
2738
2932
  agentsRemove = async (agentId, opts = {}) => {
2739
2933
  if (agentId.trim().toLowerCase() === BUILTIN_MAIN_AGENT_ID) throw new Error(`agent id '${BUILTIN_MAIN_AGENT_ID}' is reserved`);
@@ -2741,15 +2935,10 @@ var AgentCommands = class {
2741
2935
  if (opts.json) {
2742
2936
  console.log(JSON.stringify({
2743
2937
  removed: true,
2744
- agentId,
2745
- restartRequired: true
2938
+ agentId
2746
2939
  }, null, 2));
2747
2940
  return;
2748
2941
  }
2749
- await this.deps.requestRestart({
2750
- reason: "agents-updated",
2751
- manualMessage: `Removed agent '${agentId}'. Restart ${this.deps.appName} to apply agent runtime changes.`
2752
- });
2753
2942
  console.log(`✓ Removed agent ${agentId}`);
2754
2943
  };
2755
2944
  toAgentListEntry = (agent) => {
@@ -2759,6 +2948,7 @@ var AgentCommands = class {
2759
2948
  description: agent.description ?? null,
2760
2949
  avatar: agent.avatar ?? null,
2761
2950
  workspace: agent.workspace,
2951
+ runtime: agent.runtime ?? agent.engine ?? null,
2762
2952
  builtIn: agent.builtIn === true
2763
2953
  };
2764
2954
  };
@@ -4185,13 +4375,14 @@ async function startGatewaySupportServices(params) {
4185
4375
  }
4186
4376
  function watchCronStoreFile(params) {
4187
4377
  const cronStorePath = resolve(params.cronStorePath);
4188
- chokidar.watch(cronStorePath, {
4378
+ const watcher = chokidar.watch(cronStorePath, {
4189
4379
  ignoreInitial: true,
4190
4380
  awaitWriteFinish: {
4191
4381
  stabilityThreshold: 200,
4192
4382
  pollInterval: 50
4193
4383
  }
4194
- }).on("all", (event, changedPath) => {
4384
+ });
4385
+ watcher.on("all", (event, changedPath) => {
4195
4386
  if (resolve(changedPath) !== cronStorePath) return;
4196
4387
  if (event === "add" || event === "change" || event === "unlink") try {
4197
4388
  params.reloadCronStore();
@@ -4199,6 +4390,85 @@ function watchCronStoreFile(params) {
4199
4390
  console.error(`Cron store reload failed (${event}): ${String(error)}`);
4200
4391
  }
4201
4392
  });
4393
+ return watcher;
4394
+ }
4395
+ function watchServiceConfigFile(params) {
4396
+ const configPath = resolve(params.configPath);
4397
+ const watcher = chokidar.watch(configPath, {
4398
+ ignoreInitial: true,
4399
+ awaitWriteFinish: {
4400
+ stabilityThreshold: 200,
4401
+ pollInterval: 50
4402
+ }
4403
+ });
4404
+ params.watcherRegistry.remember(watcher);
4405
+ watcher.on("all", (event, changedPath) => {
4406
+ if (resolve(changedPath) !== configPath) return;
4407
+ if (event === "add") {
4408
+ params.scheduleReload("config add");
4409
+ return;
4410
+ }
4411
+ if (event === "change") {
4412
+ params.scheduleReload("config change");
4413
+ return;
4414
+ }
4415
+ if (event === "unlink") params.scheduleReload("config unlink");
4416
+ });
4417
+ }
4418
+ var ServiceFileWatcherRegistry = class {
4419
+ watchers = [];
4420
+ remember = (watcher) => {
4421
+ this.watchers.push(watcher);
4422
+ };
4423
+ clear = async () => {
4424
+ const watchers = this.watchers.splice(0);
4425
+ await Promise.allSettled(watchers.map(async (watcher) => {
4426
+ try {
4427
+ await watcher.close();
4428
+ } catch {}
4429
+ }));
4430
+ };
4431
+ };
4432
+ function writeLocalServiceDiscoveryState(uiConfig, pid = process.pid) {
4433
+ const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
4434
+ const apiUrl = `${uiUrl}/api`;
4435
+ const existing = readServiceState();
4436
+ writeServiceState({
4437
+ pid,
4438
+ startedAt: existing?.pid === pid && typeof existing.startedAt === "string" ? existing.startedAt : (/* @__PURE__ */ new Date()).toISOString(),
4439
+ uiUrl,
4440
+ apiUrl,
4441
+ uiHost: uiConfig.host,
4442
+ uiPort: uiConfig.port,
4443
+ logPath: existing?.logPath ?? resolveServiceLogPath(),
4444
+ startupState: existing?.startupState ?? "ready",
4445
+ startupLastProbeError: existing?.startupLastProbeError ?? null,
4446
+ startupTimeoutMs: existing?.startupTimeoutMs,
4447
+ startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
4448
+ ...existing?.remote ? { remote: existing.remote } : {}
4449
+ });
4450
+ }
4451
+ function clearOwnedServiceState(pid = process.pid) {
4452
+ if (readServiceState()?.pid === pid) clearServiceState();
4453
+ }
4454
+ function finalizeLocalUiStartup(params) {
4455
+ const { setUiEventPublisher, uiConfig, uiStartup } = params;
4456
+ setUiEventPublisher(uiStartup?.publish);
4457
+ if (uiStartup) writeLocalServiceDiscoveryState(uiConfig);
4458
+ }
4459
+ async function startGatewayRuntimeSupport(params) {
4460
+ const { cronJobs, cronStorePath, reloadCronStore, remoteModule, startCron, startHeartbeat, watchConfigFile, watcherRegistry } = params;
4461
+ await startGatewaySupportServices({
4462
+ cronJobs,
4463
+ remoteModule,
4464
+ watchConfigFile,
4465
+ startCron,
4466
+ startHeartbeat
4467
+ });
4468
+ watcherRegistry.remember(watchCronStoreFile({
4469
+ cronStorePath,
4470
+ reloadCronStore
4471
+ }));
4202
4472
  }
4203
4473
  //#endregion
4204
4474
  //#region src/cli/commands/remote-support/remote-access-service-control.ts
@@ -4981,7 +5251,7 @@ var SpawnChildSessionTool = class extends Tool {
4981
5251
  return "spawn";
4982
5252
  }
4983
5253
  get description() {
4984
- return "Create a child session, delegate a task to it, and continue this session when it finishes.";
5254
+ return "Create a child session, start the delegated task in the background, return a running child-session handle immediately, and automatically continue this session with the child's final reply when it finishes.";
4985
5255
  }
4986
5256
  get parameters() {
4987
5257
  return {
@@ -4989,19 +5259,23 @@ var SpawnChildSessionTool = class extends Tool {
4989
5259
  properties: {
4990
5260
  task: {
4991
5261
  type: "string",
4992
- description: "Task to run inside the child session."
5262
+ description: "Task to start immediately inside the child session."
4993
5263
  },
4994
5264
  label: {
4995
5265
  type: "string",
4996
- description: "Optional child session title."
5266
+ description: "Optional child session title shown in the session list and tool card."
4997
5267
  },
4998
5268
  model: {
4999
5269
  type: "string",
5000
- description: "Optional model override for the child session."
5270
+ description: "Optional model override used only for the child session."
5271
+ },
5272
+ runtime: {
5273
+ type: "string",
5274
+ description: "Optional runtime override used only for the child session, for example native or codex."
5001
5275
  },
5002
5276
  agentId: {
5003
5277
  type: "string",
5004
- description: "Optional target agent id for the child session. Omit to use the default agent."
5278
+ description: "Optional target agent id for the child session. Omit to use the default agent for that child."
5005
5279
  }
5006
5280
  },
5007
5281
  required: ["task"]
@@ -5021,6 +5295,7 @@ var SpawnChildSessionTool = class extends Tool {
5021
5295
  task,
5022
5296
  title: readOptionalString$4(params, "label"),
5023
5297
  model: readOptionalString$4(params, "model"),
5298
+ runtime: readOptionalString$4(params, "runtime"),
5024
5299
  handoffDepth: this.handoffDepth,
5025
5300
  agentId: readOptionalString$4(params, "agentId")
5026
5301
  });
@@ -5142,6 +5417,10 @@ var SessionSpawnTool = class extends Tool {
5142
5417
  type: "string",
5143
5418
  description: "Optional model override for the new session."
5144
5419
  },
5420
+ runtime: {
5421
+ type: "string",
5422
+ description: "Optional runtime override for the new session, for example native or codex."
5423
+ },
5145
5424
  agentId: {
5146
5425
  type: "string",
5147
5426
  description: "Optional target agent id for the new session. Omit to use the default agent."
@@ -5161,7 +5440,8 @@ var SessionSpawnTool = class extends Tool {
5161
5440
  title: readOptionalString$3(params.title),
5162
5441
  sourceSessionMetadata: this.sourceSessionMetadata,
5163
5442
  agentId: readOptionalString$3(params.agentId),
5164
- model: readOptionalString$3(params.model)
5443
+ model: readOptionalString$3(params.model),
5444
+ runtime: readOptionalString$3(params.runtime)
5165
5445
  });
5166
5446
  return {
5167
5447
  kind: "nextclaw.session",
@@ -5298,16 +5578,6 @@ var NextclawNcpToolRegistry = class {
5298
5578
  this.registerTool(sessionsRequestTool);
5299
5579
  this.registerTool(new SessionsListTool(this.options.sessionManager));
5300
5580
  this.registerTool(new SessionsHistoryTool(this.options.sessionManager));
5301
- const sessionsSendTool = new SessionsSendTool(this.options.sessionManager, this.options.bus);
5302
- sessionsSendTool.setContext({
5303
- currentSessionKey: context.sessionId,
5304
- currentAgentId: context.agentId,
5305
- channel: context.channel,
5306
- chatId: context.chatId,
5307
- maxPingPongTurns: context.config.session?.agentToAgent?.maxPingPongTurns ?? 0,
5308
- currentHandoffDepth: context.handoffDepth
5309
- });
5310
- this.registerTool(sessionsSendTool);
5311
5581
  this.registerTool(new MemorySearchTool(context.workspace));
5312
5582
  this.registerTool(new MemoryGetTool(context.workspace));
5313
5583
  const gatewayTool = new GatewayTool(this.options.gatewayController);
@@ -5440,14 +5710,16 @@ function prependRequestedSkills(content, requestedSkillNames) {
5440
5710
  function buildSessionOrchestrationSection() {
5441
5711
  return [
5442
5712
  "## Session Orchestration",
5443
- "- `spawn` creates a child session for delegated sub-work that should report completion back into the current session.",
5444
- "- Use `spawn` when the work is a subtask of the current flow and the user expects this session to continue after that child finishes.",
5713
+ "- Before passing a non-default `runtime` to `spawn`, `sessions_spawn`, or agent creation/update flows, inspect the installed runtime kinds with `nextclaw agents runtimes --json`.",
5714
+ "- `spawn` creates a child session, starts the delegated task there immediately, and returns a running child-session handle right away instead of waiting for the child to finish.",
5715
+ "- When that child reaches its final reply, `spawn` writes the completed result back into the original tool call and resumes the current session with the child's result.",
5716
+ "- Use `spawn` when the work is a subtask of the current flow and the user expects this session to pause now and then continue after that child finishes.",
5445
5717
  "- `sessions_spawn` creates a standalone session. Use it when the work should live in its own thread, remain independently reviewable later, or continue outside the current flow.",
5446
5718
  "- `sessions_request` sends one task to another session. Use it to reuse an existing session, or immediately after `sessions_spawn` when a new standalone session should start working right away.",
5447
5719
  "- If the goal is 'open a new session and have it do something now', the usual sequence is: 1) call `sessions_spawn`; 2) call `sessions_request` with that returned `sessionId`.",
5448
5720
  "- `sessions_request.target` must be an object shaped like `{ \"session_id\": \"<target-session-id>\" }`. Do not pass a bare string.",
5449
5721
  "- Prefer `delivery=\"resume_source\"` when the current session should continue after the target session produces its final reply. Use `delivery=\"none\"` when you only want the target session to run independently.",
5450
- "- Do not use `spawn` for long-lived independent threads when `sessions_spawn` plus `sessions_request` would match the user's intent better."
5722
+ "- Do not use `spawn` for long-lived independent threads, fire-and-forget work, or work that should not automatically resume the current session; in those cases use `sessions_spawn` plus `sessions_request` instead."
5451
5723
  ].join("\n");
5452
5724
  }
5453
5725
  function filterTools(toolDefinitions, requestedToolNames) {
@@ -5956,6 +6228,7 @@ function summarizeTask(task) {
5956
6228
  function cloneInheritedMetadata(sourceMetadata) {
5957
6229
  const nextMetadata = {};
5958
6230
  for (const key of [
6231
+ "runtime",
5959
6232
  "session_type",
5960
6233
  "preferred_model",
5961
6234
  "preferred_thinking",
@@ -5980,10 +6253,11 @@ function resolveSessionTitle(params) {
5980
6253
  return readOptionalString$2(params.title) ?? summarizeTask(params.task);
5981
6254
  }
5982
6255
  function resolveSessionType(params) {
5983
- return readOptionalString$2(params.sessionType) ?? readOptionalString$2(params.metadata.session_type) ?? DEFAULT_SESSION_TYPE;
6256
+ return readOptionalString$2(params.runtime) ?? readOptionalString$2(params.metadata.runtime) ?? readOptionalString$2(params.sessionType) ?? readOptionalString$2(params.metadata.session_type) ?? DEFAULT_SESSION_TYPE;
5984
6257
  }
5985
6258
  function applySessionOverrides(params) {
5986
6259
  params.metadata.session_type = params.sessionType;
6260
+ params.metadata.runtime = params.sessionType;
5987
6261
  params.metadata[SESSION_METADATA_LABEL_KEY] = params.title;
5988
6262
  params.metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = params.lifecycle;
5989
6263
  if (params.parentSessionId) {
@@ -6023,6 +6297,7 @@ var SessionCreationService = class {
6023
6297
  const parentSessionId = readOptionalString$2(params.parentSessionId);
6024
6298
  const requestId = readOptionalString$2(params.requestId);
6025
6299
  const sessionType = resolveSessionType({
6300
+ runtime: params.runtime,
6026
6301
  sessionType: params.sessionType,
6027
6302
  metadata
6028
6303
  });
@@ -6046,7 +6321,7 @@ var SessionCreationService = class {
6046
6321
  sessionId,
6047
6322
  agentId: resolvedAgentId,
6048
6323
  sessionType,
6049
- runtimeFamily: "native",
6324
+ runtimeFamily: sessionType === DEFAULT_SESSION_TYPE ? "native" : "external",
6050
6325
  ...parentSessionId ? { parentSessionId } : {},
6051
6326
  ...requestId ? { spawnedByRequestId: requestId } : {},
6052
6327
  lifecycle: DEFAULT_LIFECYCLE,
@@ -6199,7 +6474,7 @@ var SessionRequestBroker = class {
6199
6474
  this.onSessionUpdated = onSessionUpdated;
6200
6475
  }
6201
6476
  spawnChildSessionAndRequest = async (params) => {
6202
- const { sourceSessionId, sourceToolCallId, sourceSessionMetadata, task, title, model, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId } = params;
6477
+ const { sourceSessionId, sourceToolCallId, sourceSessionMetadata, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId } = params;
6203
6478
  const requestId = randomUUID();
6204
6479
  const childSession = this.sessionCreationService.createChildSession({
6205
6480
  parentSessionId: sourceSessionId,
@@ -6208,6 +6483,7 @@ var SessionRequestBroker = class {
6208
6483
  sourceSessionMetadata,
6209
6484
  agentId,
6210
6485
  model,
6486
+ runtime,
6211
6487
  thinkingLevel,
6212
6488
  sessionType,
6213
6489
  projectRoot,
@@ -6490,10 +6766,6 @@ var SessionRequestDeliveryService = class {
6490
6766
  if (!params.request.sourceToolCallId?.trim()) return;
6491
6767
  const backend = this.resolveBackend();
6492
6768
  if (!backend) throw new Error("NCP backend is not ready for session request delivery.");
6493
- if (!await waitForSessionToBecomeIdle({
6494
- backend,
6495
- sessionId: params.request.sourceSessionId
6496
- })) return;
6497
6769
  await backend.updateToolCallResult(params.request.sourceSessionId, params.request.sourceToolCallId.trim(), params.result);
6498
6770
  };
6499
6771
  resumeSourceSession = async (params) => {
@@ -6502,7 +6774,10 @@ var SessionRequestDeliveryService = class {
6502
6774
  if (!await waitForSessionToBecomeIdle({
6503
6775
  backend,
6504
6776
  sessionId: params.request.sourceSessionId
6505
- })) return;
6777
+ })) {
6778
+ console.warn(`[session-request] resume skipped for ${params.request.sourceSessionId} because the source session did not become idle in time.`);
6779
+ return;
6780
+ }
6506
6781
  scheduleDetached(async () => consumeAgentRun(backend.send({
6507
6782
  sessionId: params.request.sourceSessionId,
6508
6783
  message: buildSessionRequestCompletionMessage(params)
@@ -6510,75 +6785,6 @@ var SessionRequestDeliveryService = class {
6510
6785
  };
6511
6786
  };
6512
6787
  //#endregion
6513
- //#region src/cli/commands/ncp/ui-ncp-runtime-registry.ts
6514
- const DEFAULT_UI_NCP_RUNTIME_KIND = "native";
6515
- function normalizeRuntimeKind(value) {
6516
- if (typeof value !== "string") return null;
6517
- const normalized = value.trim().toLowerCase();
6518
- return normalized.length > 0 ? normalized : null;
6519
- }
6520
- function readRequestedRuntimeKind(sessionMetadata) {
6521
- return normalizeRuntimeKind(sessionMetadata.session_type) ?? normalizeRuntimeKind(sessionMetadata.sessionType) ?? null;
6522
- }
6523
- var UiNcpRuntimeRegistry = class {
6524
- registrations = /* @__PURE__ */ new Map();
6525
- constructor(defaultKind = DEFAULT_UI_NCP_RUNTIME_KIND) {
6526
- this.defaultKind = defaultKind;
6527
- }
6528
- register(registration) {
6529
- const normalizedKind = normalizeRuntimeKind(registration.kind);
6530
- if (!normalizedKind) throw new Error("ui ncp runtime kind must be a non-empty string");
6531
- const token = Symbol(normalizedKind);
6532
- this.registrations.set(normalizedKind, {
6533
- ...registration,
6534
- kind: normalizedKind,
6535
- token
6536
- });
6537
- return toDisposable(() => {
6538
- const current = this.registrations.get(normalizedKind);
6539
- if (!current || current.token !== token) return;
6540
- this.registrations.delete(normalizedKind);
6541
- });
6542
- }
6543
- createRuntime(params) {
6544
- const requestedKind = readRequestedRuntimeKind(params.sessionMetadata) ?? this.defaultKind;
6545
- const registration = this.registrations.get(requestedKind);
6546
- if (!registration) throw new Error(`ncp runtime unavailable: ${requestedKind}`);
6547
- const nextSessionMetadata = {
6548
- ...params.sessionMetadata,
6549
- session_type: registration.kind
6550
- };
6551
- params.setSessionMetadata(nextSessionMetadata);
6552
- return registration.createRuntime({
6553
- ...params,
6554
- sessionMetadata: nextSessionMetadata
6555
- });
6556
- }
6557
- async listSessionTypes(params) {
6558
- const options = await Promise.all([...this.registrations.values()].map(async (registration) => {
6559
- const descriptor = await registration.describeSessionType?.(params);
6560
- return {
6561
- value: registration.kind,
6562
- label: registration.label,
6563
- ready: descriptor?.ready ?? true,
6564
- reason: descriptor?.reason ?? null,
6565
- reasonMessage: descriptor?.reasonMessage ?? null,
6566
- recommendedModel: descriptor?.recommendedModel ?? null,
6567
- cta: descriptor?.cta ?? null,
6568
- ...descriptor?.supportedModels ? { supportedModels: descriptor.supportedModels } : {}
6569
- };
6570
- }));
6571
- return {
6572
- defaultType: this.defaultKind,
6573
- options: options.sort((left, right) => {
6574
- if (left.value === this.defaultKind) return -1;
6575
- if (right.value === this.defaultKind) return 1;
6576
- return left.value.localeCompare(right.value);
6577
- })
6578
- };
6579
- }
6580
- };
6581
- //#endregion
6582
6788
  //#region src/cli/commands/ncp/runtime/ui-ncp-agent-handle.ts
6583
6789
  function createUiNcpAgentHandle(params) {
6584
6790
  return {
@@ -7688,8 +7894,12 @@ function createGatewayShellContext(params) {
7688
7894
  const runtimeConfigPath = getConfigPath$2();
7689
7895
  const config = resolveConfigSecrets$3(loadConfig$3(), { configPath: runtimeConfigPath });
7690
7896
  const workspace = getWorkspacePath$2(config.agents.defaults.workspace);
7897
+ const homeDir = getDataDir$1();
7691
7898
  const cronStorePath = join(getDataDir$1(), "cron", "jobs.json");
7692
- const sessionManager = measureStartupSync("service.gateway_shell_context.session_manager", () => new SessionManager$1(workspace));
7899
+ const sessionManager = measureStartupSync("service.gateway_shell_context.session_manager", () => new SessionManager$1({
7900
+ workspace,
7901
+ homeDir
7902
+ }));
7693
7903
  const cron = new CronService$1(cronStorePath);
7694
7904
  const uiConfig = resolveUiConfig(config, params.uiOverrides);
7695
7905
  return {
@@ -8671,11 +8881,13 @@ function createSkillsLoader(workspace) {
8671
8881
  var ServiceCommands = class {
8672
8882
  applyLiveConfigReload = null;
8673
8883
  liveUiNcpAgent = null;
8884
+ fileWatchers = new ServiceFileWatcherRegistry();
8674
8885
  constructor(deps) {
8675
8886
  this.deps = deps;
8676
8887
  }
8677
8888
  startGateway = async (options = {}) => {
8678
8889
  logStartupTrace("service.start_gateway.begin");
8890
+ await this.fileWatchers.clear();
8679
8891
  this.applyLiveConfigReload = null;
8680
8892
  this.liveUiNcpAgent = null;
8681
8893
  const shellContext = measureStartupSync("service.create_gateway_shell_context", () => createGatewayShellContext({
@@ -8719,7 +8931,11 @@ var ServiceCommands = class {
8719
8931
  ncpSessionService: ncpSessionRealtimeBridge.sessionService,
8720
8932
  initializeAgentHomeDirectory: this.deps.initializeAgentHomeDirectory
8721
8933
  }));
8722
- ncpSessionRealtimeBridge.setUiEventPublisher(uiStartup?.publish);
8934
+ finalizeLocalUiStartup({
8935
+ uiStartup,
8936
+ setUiEventPublisher: (publish) => ncpSessionRealtimeBridge.setUiEventPublisher(publish),
8937
+ uiConfig: shellContext.uiConfig
8938
+ });
8723
8939
  bootstrapStatus.markShellReady();
8724
8940
  await setImmediate();
8725
8941
  const gateway = measureStartupSync("service.create_gateway_startup_context", () => createGatewayStartupContext({
@@ -8753,17 +8969,20 @@ var ServiceCommands = class {
8753
8969
  publishUiEvent: uiStartup?.publish
8754
8970
  });
8755
8971
  console.log("✓ Capability hydration: scheduled in background");
8756
- await measureStartupAsync("service.start_gateway_support_services", async () => await startGatewaySupportServices({
8972
+ await measureStartupAsync("service.start_gateway_support_services", async () => await startGatewayRuntimeSupport({
8757
8973
  cronJobs: gateway.cron.status().jobs,
8758
8974
  remoteModule: gateway.remoteModule,
8759
- watchConfigFile: () => this.watchConfigFile(gateway.reloader),
8975
+ watchConfigFile: () => watchServiceConfigFile({
8976
+ configPath: resolve(getConfigPath$1()),
8977
+ watcherRegistry: this.fileWatchers,
8978
+ scheduleReload: (reason) => gateway.reloader.scheduleReload(reason)
8979
+ }),
8760
8980
  startCron: () => gateway.cron.start(),
8761
- startHeartbeat: () => gateway.heartbeat.start()
8762
- }));
8763
- watchCronStoreFile({
8981
+ startHeartbeat: () => gateway.heartbeat.start(),
8764
8982
  cronStorePath: resolve(join(NextclawCore.getDataDir(), "cron", "jobs.json")),
8765
- reloadCronStore: () => gateway.cron.reloadFromStore()
8766
- });
8983
+ reloadCronStore: () => gateway.cron.reloadFromStore(),
8984
+ watcherRegistry: this.fileWatchers
8985
+ }));
8767
8986
  const deferredGatewayStartupHooks = createDeferredGatewayStartupHooks({
8768
8987
  uiStartup,
8769
8988
  gateway,
@@ -8811,6 +9030,8 @@ var ServiceCommands = class {
8811
9030
  console.error(`Deferred startup failed: ${error instanceof Error ? error.message : String(error)}`);
8812
9031
  },
8813
9032
  cleanup: async () => {
9033
+ clearOwnedServiceState();
9034
+ await this.fileWatchers.clear();
8814
9035
  this.applyLiveConfigReload = null;
8815
9036
  this.liveUiNcpAgent = null;
8816
9037
  ncpSessionRealtimeBridge.clear();
@@ -8826,27 +9047,6 @@ var ServiceCommands = class {
8826
9047
  if (typeof value !== "string") return;
8827
9048
  return value.trim() || void 0;
8828
9049
  };
8829
- watchConfigFile = (reloader) => {
8830
- const configPath = resolve(getConfigPath$1());
8831
- chokidar.watch(configPath, {
8832
- ignoreInitial: true,
8833
- awaitWriteFinish: {
8834
- stabilityThreshold: 200,
8835
- pollInterval: 50
8836
- }
8837
- }).on("all", (event, changedPath) => {
8838
- if (resolve(changedPath) !== configPath) return;
8839
- if (event === "add") {
8840
- reloader.scheduleReload("config add");
8841
- return;
8842
- }
8843
- if (event === "change") {
8844
- reloader.scheduleReload("config change");
8845
- return;
8846
- }
8847
- if (event === "unlink") reloader.scheduleReload("config unlink");
8848
- });
8849
- };
8850
9050
  resolveMostRecentRoutableSessionKey = (sessionManager) => {
8851
9051
  let best = null;
8852
9052
  for (const session of sessionManager.listSessions()) {
@@ -9559,11 +9759,7 @@ var CliRuntime = class {
9559
9759
  this.mcpCommands = measureStartupSync("cli.runtime.mcp_commands", () => new McpCommands());
9560
9760
  this.secretsCommands = measureStartupSync("cli.runtime.secrets_commands", () => new SecretsCommands({ requestRestart: (params) => this.requestRestart(params) }));
9561
9761
  this.pluginCommands = measureStartupSync("cli.runtime.plugin_commands", () => new PluginCommands());
9562
- this.agentCommands = measureStartupSync("cli.runtime.agent_commands", () => new AgentCommands({
9563
- requestRestart: (params) => this.requestRestart(params),
9564
- initializeAgentHomeDirectory: (homeDirectory) => this.workspaceManager.createWorkspaceTemplates(homeDirectory),
9565
- appName: APP_NAME
9566
- }));
9762
+ this.agentCommands = measureStartupSync("cli.runtime.agent_commands", () => new AgentCommands({ initializeAgentHomeDirectory: (homeDirectory) => this.workspaceManager.createWorkspaceTemplates(homeDirectory) }));
9567
9763
  this.channelCommands = measureStartupSync("cli.runtime.channel_commands", () => new ChannelCommands({
9568
9764
  logo: this.logo,
9569
9765
  getBridgeDir: () => this.workspaceManager.getBridgeDir(),
@@ -9966,6 +10162,9 @@ var CliRuntime = class {
9966
10162
  agentsList = (opts = {}) => {
9967
10163
  this.agentCommands.agentsList(opts);
9968
10164
  };
10165
+ agentsRuntimes = async (opts = {}) => {
10166
+ await this.agentCommands.agentsRuntimes(opts);
10167
+ };
9969
10168
  agentsNew = async (agentId, opts = {}) => {
9970
10169
  await this.agentCommands.agentsNew(agentId, opts);
9971
10170
  };
@@ -10127,8 +10326,9 @@ var CliRuntime = class {
10127
10326
  function registerAgentsCommands(program, runtime) {
10128
10327
  const agents = program.command("agents").description("Manage agents");
10129
10328
  agents.command("list").description("List available agents").option("--json", "Output JSON", false).action((opts) => runtime.agentsList(opts));
10130
- 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) => runtime.agentsNew(agentId, opts));
10131
- 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) => runtime.agentsUpdate(agentId, opts));
10329
+ agents.command("runtimes").description("List available agent runtimes").option("--probe", "Actively probe runtime readiness", false).option("--json", "Output JSON", false).action(async (opts) => runtime.agentsRuntimes(opts));
10330
+ 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("--runtime <runtime>", "Agent runtime kind, for example native or codex").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime.agentsNew(agentId, opts));
10331
+ 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("--runtime <runtime>", "Agent runtime kind, for example native or codex").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime.agentsUpdate(agentId, opts));
10132
10332
  agents.command("remove <agentId>").description("Remove an agent").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime.agentsRemove(agentId, opts));
10133
10333
  }
10134
10334
  //#endregion