nextclaw 0.17.11 → 0.17.12

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 (85) hide show
  1. package/dist/cli/index.js +1623 -299
  2. package/package.json +14 -12
  3. package/resources/USAGE.md +2 -1
  4. package/ui-dist/assets/ChannelsList-Ita2Zm1_.js +8 -0
  5. package/ui-dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-6ReNjvzF.js} +1 -1
  6. package/ui-dist/assets/DocBrowser-BNwbPHf4.js +1 -0
  7. package/ui-dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-B6SpA7Qs.js} +1 -1
  8. package/ui-dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-ByNLYg65.js} +1 -1
  9. package/ui-dist/assets/MarketplacePage-CjX2MWww.js +1 -0
  10. package/ui-dist/assets/{MarketplacePage-BySqkYDh.js → MarketplacePage-D0sDlYX4.js} +1 -1
  11. package/ui-dist/assets/McpMarketplacePage-BGKJm1sJ.js +40 -0
  12. package/ui-dist/assets/{ModelConfig-IrmzoslW.js → ModelConfig-BzZenCH-.js} +1 -1
  13. package/ui-dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-Da7khnBA.js} +1 -1
  14. package/ui-dist/assets/{ProvidersList-8_Kalfwl.js → ProvidersList-BbVzRxjY.js} +1 -1
  15. package/ui-dist/assets/RemoteAccessPage-BaDH_X1Q.js +1 -0
  16. package/ui-dist/assets/RuntimeConfig-F_XKGgLm.js +1 -0
  17. package/ui-dist/assets/{SearchConfig-DNBR-UbE.js → SearchConfig-BGkzXQP-.js} +1 -1
  18. package/ui-dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-D281Rotl.js} +2 -2
  19. package/ui-dist/assets/{SessionsConfig-Doqp5ghH.js → SessionsConfig-ChHQ7M5c.js} +2 -2
  20. package/ui-dist/assets/{app-query-client-DniXoIN5.js → app-query-client-VnFElj4E.js} +1 -1
  21. package/ui-dist/assets/{book-open-DocgeQtR.js → book-open-BdcxxoQu.js} +1 -1
  22. package/ui-dist/assets/chat-page-Doe0yTtB.js +58 -0
  23. package/ui-dist/assets/chat-session-display-cw78aiI_.js +1 -0
  24. package/ui-dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-DK5HPmIK.js} +1 -1
  25. package/ui-dist/assets/{client-CVqPF5ie.js → client-_i4MU2bB.js} +1 -1
  26. package/ui-dist/assets/{config-Bop2oB18.js → config-DtIQwrHF.js} +1 -1
  27. package/ui-dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-BSeTgkZW.js} +1 -1
  28. package/ui-dist/assets/desktop-update-config-Dpcf4BKG.js +1 -0
  29. package/ui-dist/assets/{dist-Da5Gm_pO.js → dist-6TrrnPCR.js} +1 -1
  30. package/ui-dist/assets/{dist-DmAlInRu.js → dist-ccBFUi-o.js} +1 -1
  31. package/ui-dist/assets/download-BhDxnyvU.js +1 -0
  32. package/ui-dist/assets/{external-link-DFjw3x1B.js → external-link-BgErLCNT.js} +1 -1
  33. package/ui-dist/assets/{hash-DJtaCejM.js → hash-Bl7dr_UG.js} +1 -1
  34. package/ui-dist/assets/i18n-eDHeDY0n.js +1 -0
  35. package/ui-dist/assets/index-CF9xve0E.js +6 -0
  36. package/ui-dist/assets/index-FgA52VBt.css +1 -0
  37. package/ui-dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-ZDS92Qpp.js} +1 -1
  38. package/ui-dist/assets/loader-circle-ACM1s51e.js +1 -0
  39. package/ui-dist/assets/{logos-DEFUIR12.js → logos-x89HbrZ4.js} +1 -1
  40. package/ui-dist/assets/{page-layout-Da3i3r6G.js → page-layout-vZnghcFy.js} +1 -1
  41. package/ui-dist/assets/play-CFUwCA2E.js +1 -0
  42. package/ui-dist/assets/plus-rYsv72JG.js +1 -0
  43. package/ui-dist/assets/{popover-C_mWOFzI.js → popover-Bg1VoTZ6.js} +1 -1
  44. package/ui-dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-DT98i__E.js} +1 -1
  45. package/ui-dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-C47QSEwg.js} +1 -1
  46. package/ui-dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-JtFzpNn6.js} +1 -1
  47. package/ui-dist/assets/{save-DHGmi2e9.js → save-3S6-H3Xw.js} +1 -1
  48. package/ui-dist/assets/search-3kFR_zh9.js +1 -0
  49. package/ui-dist/assets/{security-config-CbXfPZzr.js → security-config-BWaiARNk.js} +1 -1
  50. package/ui-dist/assets/{select-Caud8QvU.js → select-DJ2MUjBB.js} +1 -1
  51. package/ui-dist/assets/skeleton-ByQepn0M.js +1 -0
  52. package/ui-dist/assets/{status-dot-DurKKSwA.js → status-dot-vbanNPFU.js} +1 -1
  53. package/ui-dist/assets/{switch-0rmPBRKI.js → switch-BsLtHOH-.js} +1 -1
  54. package/ui-dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-D3HYMt6k.js} +1 -1
  55. package/ui-dist/assets/{trash-2-C6caKPoz.js → trash-2-G48scll7.js} +1 -1
  56. package/ui-dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DkNhD-42.js} +1 -1
  57. package/ui-dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BkvTN-vd.js} +1 -1
  58. package/ui-dist/assets/{useMutation-BmxxvCNf.js → useMutation-CBWjE2uj.js} +1 -1
  59. package/ui-dist/assets/x-ByDbItbq.js +1 -0
  60. package/ui-dist/index.html +95 -21
  61. package/ui-dist/manifest.webmanifest +30 -0
  62. package/ui-dist/offline.html +102 -0
  63. package/ui-dist/pwa-192.png +0 -0
  64. package/ui-dist/pwa-512.png +0 -0
  65. package/ui-dist/sw.js +80 -0
  66. package/ui-dist/assets/ChannelsList-KIQIxluX.js +0 -8
  67. package/ui-dist/assets/DocBrowser-CyDgAtO9.js +0 -1
  68. package/ui-dist/assets/MarketplacePage-C0olZaek.js +0 -1
  69. package/ui-dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
  70. package/ui-dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
  71. package/ui-dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
  72. package/ui-dist/assets/chat-page-Bph8M5zo.js +0 -58
  73. package/ui-dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
  74. package/ui-dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
  75. package/ui-dist/assets/i18n-CwHZ-9vt.js +0 -1
  76. package/ui-dist/assets/index-DafCdM4F.css +0 -1
  77. package/ui-dist/assets/index-DdksE6U3.js +0 -6
  78. package/ui-dist/assets/loader-circle-PsSP0H9n.js +0 -1
  79. package/ui-dist/assets/play-DBQbBxTA.js +0 -1
  80. package/ui-dist/assets/plus-DUOVbsyQ.js +0 -1
  81. package/ui-dist/assets/search-MChQRYR1.js +0 -1
  82. package/ui-dist/assets/skeleton-B-4vRq_Z.js +0 -1
  83. package/ui-dist/assets/x-DuMhMATD.js +0 -1
  84. /package/ui-dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
  85. /package/ui-dist/assets/{config-layout-DmlGaay2.js → config-layout-CHs0mAaR.js} +0 -0
package/dist/cli/index.js CHANGED
@@ -1,10 +1,10 @@
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, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, FileLogSink, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderManager, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createExternalCommandEnv, createTypingStopControlMessage, diffConfigPaths, expandHome, findEffectiveAgentProfile, getAppLogger, getConfigPath, getDataDir, getLoggingRuntime, getLogsPath, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveAppLogPath, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveLocalUiBaseUrl, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
4
+ import { APP_NAME, APP_TAGLINE, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, FileLogSink, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderManager, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createExternalCommandEnv, createGlobalTypedEventBus, createTypedEventKey, createTypingStopControlMessage, diffConfigPaths, expandHome, findEffectiveAgentProfile, getAppLogger, getConfigPath, getDataDir, getLoggingRuntime, getLogsPath, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveAppLogPath, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveLocalUiBaseUrl, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
5
5
  import { Command } from "commander";
6
- import fs, { appendFileSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
7
- import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
6
+ import fs, { appendFileSync, closeSync, constants, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
7
+ import path, { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
8
8
  import { hostname, platform } from "node:os";
9
9
  import { ensureUiBridgeSecret, startUiServer } from "@nextclaw/server";
10
10
  import { addPluginLoadPath, buildPluginStatusReport, disablePluginInConfig, discoverPluginStatusReport, enablePluginInConfig, getPackageManifestExtensions, getPluginChannelBindings, getPluginUiMetadataFromRegistry, installPluginFromNpmSpec, installPluginFromPath, loadOpenClawPlugins, loadOpenClawPluginsProgressively, loadPluginManifest, mergePluginConfigView, recordPluginInstall, resolvePluginChannelMessageToolHints, resolveUninstallDirectoryTargets, setPluginRuntimeBridge, startPluginChannelGateways, stopPluginChannelGateways, toPluginConfigView, toPluginConfigView as toPluginConfigView$1, uninstallPlugin } from "@nextclaw/openclaw-compat";
@@ -15,6 +15,9 @@ import { createInterface } from "node:readline";
15
15
  import { createServer, isIP } from "node:net";
16
16
  import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds, listBuiltinProviders } from "@nextclaw/runtime";
17
17
  import { McpDoctorFacade, McpMutationService, McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
18
+ import { access, readFile } from "node:fs/promises";
19
+ import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
20
+ import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRuntime } from "@nextclaw/nextclaw-ncp-runtime-stdio-client";
18
21
  import { request } from "node:http";
19
22
  import { request as request$1 } from "node:https";
20
23
  import { setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -24,7 +27,7 @@ import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
24
27
  import { NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
25
28
  import { DefaultNcpAgentBackend, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
26
29
  import { createHash, randomUUID } from "node:crypto";
27
- import { readFile } from "node:fs/promises";
30
+ import { DatabaseSync } from "node:sqlite";
28
31
  //#region \0rolldown/runtime.js
29
32
  var __create = Object.create;
30
33
  var __defProp = Object.defineProperty;
@@ -4711,17 +4714,24 @@ var LlmUsageQueryService = class {
4711
4714
  let totalTokens = 0;
4712
4715
  let totalCachedTokens = 0;
4713
4716
  let cacheHitRecords = 0;
4717
+ let usageRecordCount = 0;
4718
+ let promptTokenRecordCount = 0;
4714
4719
  for (const record of records) {
4720
+ if (Object.keys(record.usage).length > 0) usageRecordCount += 1;
4715
4721
  totalPromptTokens += record.summary.promptTokens;
4716
4722
  totalCompletionTokens += record.summary.completionTokens;
4717
4723
  totalTokens += record.summary.totalTokens;
4718
4724
  totalCachedTokens += record.summary.cachedTokens;
4725
+ if (record.summary.promptTokens > 0) promptTokenRecordCount += 1;
4719
4726
  if (record.summary.cacheHit) cacheHitRecords += 1;
4720
4727
  this.bumpCounter(sources, record.source);
4721
4728
  this.bumpCounter(models, record.model ?? "unknown");
4722
4729
  }
4723
4730
  return {
4724
4731
  totalRecords: records.length,
4732
+ usageRecordCount,
4733
+ emptyUsageRecordCount: records.length - usageRecordCount,
4734
+ promptTokenRecordCount,
4725
4735
  oldestObservedAt: records[0]?.observedAt ?? null,
4726
4736
  latestObservedAt: records.at(-1)?.observedAt ?? null,
4727
4737
  totalPromptTokens,
@@ -4729,7 +4739,8 @@ var LlmUsageQueryService = class {
4729
4739
  totalTokens,
4730
4740
  totalCachedTokens,
4731
4741
  cacheHitRecords,
4732
- cacheHitRate: records.length > 0 ? cacheHitRecords / records.length : 0,
4742
+ cacheHitRate: promptTokenRecordCount > 0 ? cacheHitRecords / promptTokenRecordCount : 0,
4743
+ tokenCacheRate: totalPromptTokens > 0 ? totalCachedTokens / totalPromptTokens : 0,
4733
4744
  sources: this.toSortedCounts(sources),
4734
4745
  models: this.toSortedCounts(models)
4735
4746
  };
@@ -4892,13 +4903,17 @@ var LlmUsageCommands = class {
4892
4903
  "LLM usage history stats",
4893
4904
  `History path: ${this.queryService.historyPath}`,
4894
4905
  `Records: ${stats.totalRecords}`,
4906
+ `Usage records: ${stats.usageRecordCount}`,
4907
+ `Empty usage records: ${stats.emptyUsageRecordCount}`,
4908
+ `Prompt-bearing records: ${stats.promptTokenRecordCount}`,
4895
4909
  `Oldest observed at: ${stats.oldestObservedAt ?? "n/a"}`,
4896
4910
  `Latest observed at: ${stats.latestObservedAt ?? "n/a"}`,
4897
4911
  `Prompt tokens: ${stats.totalPromptTokens}`,
4898
4912
  `Completion tokens: ${stats.totalCompletionTokens}`,
4899
4913
  `Total tokens: ${stats.totalTokens}`,
4900
4914
  `Cached tokens: ${stats.totalCachedTokens}`,
4901
- `Cache hits: ${stats.cacheHitRecords}/${stats.totalRecords} (${this.toPercent(stats.cacheHitRate)})`
4915
+ `Cache hit records: ${stats.cacheHitRecords}/${stats.promptTokenRecordCount} (${this.toPercent(stats.cacheHitRate)})`,
4916
+ `Cache token rate: ${this.toPercent(stats.tokenCacheRate)}`
4902
4917
  ];
4903
4918
  if (stats.sources.length > 0) lines.push(`Sources: ${stats.sources.map((item) => `${item.value}=${item.count}`).join(", ")}`);
4904
4919
  if (stats.models.length > 0) lines.push(`Models: ${stats.models.map((item) => `${item.value}=${item.count}`).join(", ")}`);
@@ -5145,7 +5160,7 @@ function parseSkillFrontmatter(raw) {
5145
5160
  const message = error instanceof Error ? error.message : String(error);
5146
5161
  throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
5147
5162
  }
5148
- if (!isRecord$5(parsed)) return {};
5163
+ if (!isRecord$6(parsed)) return {};
5149
5164
  const summaryI18n = readLocalizedTextMapField(parsed, [["summaryi18n"], ["summary_i18n"]]);
5150
5165
  const descriptionI18n = readLocalizedTextMapField(parsed, [["descriptioni18n"], ["description_i18n"]]);
5151
5166
  const summaryZh = readFrontmatterStringField(parsed, [["summaryzh"], ["summary_zh"]]);
@@ -5176,7 +5191,7 @@ function readMarketplaceMetadataFile(skillDir, explicitMetaFile) {
5176
5191
  const message = error instanceof Error ? error.message : String(error);
5177
5192
  throw new Error(`Invalid marketplace metadata file: ${metadataPath} (${message})`);
5178
5193
  }
5179
- if (!isRecord$5(parsed)) throw new Error(`Invalid marketplace metadata file: ${metadataPath} (root must be an object)`);
5194
+ if (!isRecord$6(parsed)) throw new Error(`Invalid marketplace metadata file: ${metadataPath} (root must be an object)`);
5180
5195
  return {
5181
5196
  slug: readMetadataString(parsed, "slug"),
5182
5197
  name: readMetadataString(parsed, "name"),
@@ -5224,7 +5239,7 @@ function readMetadataStringArray(record, fieldName) {
5224
5239
  function readMetadataLocalizedTextMap(record, fieldName) {
5225
5240
  const value = record[fieldName];
5226
5241
  if (value == null) return;
5227
- if (!isRecord$5(value)) throw new Error(`Invalid marketplace metadata field: ${fieldName} must be an object`);
5242
+ if (!isRecord$6(value)) throw new Error(`Invalid marketplace metadata field: ${fieldName} must be an object`);
5228
5243
  const localized = {};
5229
5244
  for (const [locale, text] of Object.entries(value)) {
5230
5245
  if (typeof text !== "string") throw new Error(`Invalid marketplace metadata field: ${fieldName}.${locale} must be a string`);
@@ -5245,7 +5260,7 @@ function readFrontmatterStringField(record, keyPaths) {
5245
5260
  function readLocalizedTextMapField(record, keyPaths) {
5246
5261
  for (const keyPath of keyPaths) {
5247
5262
  const value = readNestedFrontmatterValue(record, keyPath);
5248
- if (!isRecord$5(value)) continue;
5263
+ if (!isRecord$6(value)) continue;
5249
5264
  const normalized = {};
5250
5265
  for (const [locale, text] of Object.entries(value)) {
5251
5266
  if (typeof text !== "string") continue;
@@ -5269,7 +5284,7 @@ function readFrontmatterTags(record) {
5269
5284
  function readNestedFrontmatterValue(record, keyPath) {
5270
5285
  let current = record;
5271
5286
  for (const rawKey of keyPath) {
5272
- if (!isRecord$5(current)) return;
5287
+ if (!isRecord$6(current)) return;
5273
5288
  const normalizedKey = normalizeFrontmatterKey(rawKey);
5274
5289
  const matchingKey = Object.keys(current).find((candidate) => normalizeFrontmatterKey(candidate) === normalizedKey);
5275
5290
  if (!matchingKey) return;
@@ -5283,7 +5298,7 @@ function normalizeFrontmatterKey(raw) {
5283
5298
  function normalizeLocaleTag(raw) {
5284
5299
  return raw.trim().toLowerCase();
5285
5300
  }
5286
- function isRecord$5(value) {
5301
+ function isRecord$6(value) {
5287
5302
  return typeof value === "object" && value !== null && !Array.isArray(value);
5288
5303
  }
5289
5304
  //#endregion
@@ -5503,11 +5518,17 @@ function openBrowser(url) {
5503
5518
  command = "xdg-open";
5504
5519
  args = [url];
5505
5520
  }
5506
- spawn(command, args, {
5507
- stdio: "ignore",
5508
- detached: true,
5509
- env: createExternalCommandEnv(process.env)
5510
- }).unref();
5521
+ if (!which(command)) return false;
5522
+ try {
5523
+ spawn(command, args, {
5524
+ stdio: "ignore",
5525
+ detached: true,
5526
+ env: createExternalCommandEnv(process.env)
5527
+ }).unref();
5528
+ return true;
5529
+ } catch {
5530
+ return false;
5531
+ }
5511
5532
  }
5512
5533
  function normalizePathEntries(rawPath, platform) {
5513
5534
  const delimiter = platform === "win32" ? ";" : ":";
@@ -5558,9 +5579,9 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
5558
5579
  if (existsSync(pkgPath)) try {
5559
5580
  const raw = readFileSync(pkgPath, "utf-8");
5560
5581
  const parsed = JSON.parse(raw);
5561
- if (typeof parsed.version === "string") {
5562
- if (!expectedName || parsed.name === expectedName) return parsed.version;
5563
- }
5582
+ const version = typeof parsed.version === "string" ? parsed.version : null;
5583
+ const matchesExpectedName = !expectedName || parsed.name === expectedName;
5584
+ if (version && matchesExpectedName) return version;
5564
5585
  } catch {}
5565
5586
  const parent = resolve(current, "..");
5566
5587
  if (parent === current) break;
@@ -5642,6 +5663,71 @@ function persistPlatformToken(params) {
5642
5663
  saveConfig(config, configPath);
5643
5664
  }
5644
5665
  var PlatformAuthCommands = class {
5666
+ shouldUseBrowserLogin = (opts) => {
5667
+ const email = typeof opts.email === "string" ? opts.email.trim() : "";
5668
+ const password = typeof opts.password === "string" ? opts.password : "";
5669
+ return !email && !password;
5670
+ };
5671
+ waitFor = async (delayMs) => {
5672
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
5673
+ };
5674
+ formatLoginDeadline = (expiresAt) => {
5675
+ const value = Date.parse(expiresAt);
5676
+ return Number.isNaN(value) ? expiresAt : new Date(value).toLocaleString();
5677
+ };
5678
+ printLoginSuccess = (result) => {
5679
+ console.log(`✓ Logged in to NextClaw platform (${result.platformBase})`);
5680
+ console.log(`✓ Account: ${result.email} (${result.role})`);
5681
+ console.log("✓ Token saved into providers.nextclaw.apiKey");
5682
+ };
5683
+ printBrowserLoginIntro = (params) => {
5684
+ const { expiresAt, open, openedBrowser, verificationUri } = params;
5685
+ const expiresText = this.formatLoginDeadline(expiresAt);
5686
+ console.log("NextClaw browser sign-in");
5687
+ console.log(`Open this link to continue: ${verificationUri}`);
5688
+ if (open) if (openedBrowser) console.log("✓ Opened the default browser. Finish sign-in there and this terminal will complete automatically.");
5689
+ else console.log("Browser did not open automatically. Open the link above in any browser to continue.");
5690
+ else console.log("Automatic browser opening is disabled. Open the link above in any browser to continue.");
5691
+ console.log("This link can be opened on this machine or on another device if your CLI is running remotely.");
5692
+ console.log(`Waiting for authorization until ${expiresText}...`);
5693
+ };
5694
+ waitForBrowserLoginResult = async (params) => {
5695
+ while (true) {
5696
+ const result = await this.pollBrowserAuth({
5697
+ apiBase: params.apiBase,
5698
+ sessionId: params.sessionId
5699
+ });
5700
+ if (result.status === "pending") {
5701
+ await this.waitFor(result.nextPollMs);
5702
+ continue;
5703
+ }
5704
+ if (result.status === "expired") throw new Error(`${result.message} Run \`nextclaw login\` again to generate a new sign-in link.`);
5705
+ return {
5706
+ token: result.token,
5707
+ role: result.role,
5708
+ email: result.email,
5709
+ platformBase: result.platformBase,
5710
+ v1Base: result.v1Base
5711
+ };
5712
+ }
5713
+ };
5714
+ loginWithBrowserResult = async (opts = {}) => {
5715
+ const startResult = await this.startBrowserAuth({ apiBase: opts.apiBase });
5716
+ const shouldOpenBrowser = opts.open !== false;
5717
+ const openedBrowser = shouldOpenBrowser ? openBrowser(startResult.verificationUri) : false;
5718
+ this.printBrowserLoginIntro({
5719
+ verificationUri: startResult.verificationUri,
5720
+ expiresAt: startResult.expiresAt,
5721
+ open: shouldOpenBrowser,
5722
+ openedBrowser
5723
+ });
5724
+ const result = await this.waitForBrowserLoginResult({
5725
+ apiBase: opts.apiBase,
5726
+ sessionId: startResult.sessionId
5727
+ });
5728
+ console.log("✓ Browser authorization completed.");
5729
+ return result;
5730
+ };
5645
5731
  readStoredToken = (params = {}) => {
5646
5732
  const resolved = resolveProviderConfig({ apiBase: params.apiBase });
5647
5733
  const token = resolved.nextclawProvider.apiKey?.trim() ?? "";
@@ -5732,10 +5818,8 @@ var PlatformAuthCommands = class {
5732
5818
  };
5733
5819
  };
5734
5820
  login = async (opts = {}) => {
5735
- const result = await this.loginResult(opts);
5736
- console.log(`✓ Logged in to NextClaw platform (${result.platformBase})`);
5737
- console.log(`✓ Account: ${result.email} (${result.role})`);
5738
- console.log(`✓ Token saved into providers.nextclaw.apiKey`);
5821
+ const result = this.shouldUseBrowserLogin(opts) ? await this.loginWithBrowserResult(opts) : await this.loginResult(opts);
5822
+ this.printLoginSuccess(result);
5739
5823
  };
5740
5824
  me = async (params = {}) => {
5741
5825
  const { platformBase, v1Base, inputApiBase, token } = this.readStoredToken(params);
@@ -5852,9 +5936,9 @@ async function fetchMarketplaceSkillFiles(apiBase, slug) {
5852
5936
  const message = payload.error?.message || `marketplace skill file fetch failed: ${response.status}`;
5853
5937
  throw new Error(message);
5854
5938
  }
5855
- if (!isRecord$4(payload.data) || !Array.isArray(payload.data.files)) throw new Error("Invalid marketplace skill file manifest response");
5939
+ if (!isRecord$5(payload.data) || !Array.isArray(payload.data.files)) throw new Error("Invalid marketplace skill file manifest response");
5856
5940
  return { files: payload.data.files.map((entry, index) => {
5857
- if (!isRecord$4(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) throw new Error(`Invalid marketplace skill file manifest at index ${index}`);
5941
+ if (!isRecord$5(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) throw new Error(`Invalid marketplace skill file manifest at index ${index}`);
5858
5942
  const normalized = { path: entry.path.trim() };
5859
5943
  if (typeof entry.downloadPath === "string" && entry.downloadPath.trim().length > 0) normalized.downloadPath = entry.downloadPath.trim();
5860
5944
  if (typeof entry.contentBase64 === "string" && entry.contentBase64.trim().length > 0) normalized.contentBase64 = entry.contentBase64.trim();
@@ -5881,7 +5965,7 @@ async function readMarketplaceEnvelope(response) {
5881
5965
  } catch {
5882
5966
  throw new Error(`Invalid marketplace response: ${response.status}`);
5883
5967
  }
5884
- if (!isRecord$4(payload) || typeof payload.ok !== "boolean") throw new Error(`Invalid marketplace response shape: ${response.status}`);
5968
+ if (!isRecord$5(payload) || typeof payload.ok !== "boolean") throw new Error(`Invalid marketplace response shape: ${response.status}`);
5885
5969
  return payload;
5886
5970
  }
5887
5971
  function resolveSkillFileDownloadUrl(apiBase, slug, file) {
@@ -5896,7 +5980,7 @@ function extractMarketplaceErrorMessage(raw, fallbackStatus) {
5896
5980
  return raw || `Request failed (${fallbackStatus})`;
5897
5981
  }
5898
5982
  }
5899
- function isRecord$4(value) {
5983
+ function isRecord$5(value) {
5900
5984
  return typeof value === "object" && value !== null && !Array.isArray(value);
5901
5985
  }
5902
5986
  //#endregion
@@ -6433,10 +6517,14 @@ const readJsonFile = (filePath) => {
6433
6517
  return null;
6434
6518
  }
6435
6519
  };
6436
- const readString = (value) => {
6520
+ const readString$3 = (value) => {
6437
6521
  if (typeof value !== "string") return;
6438
6522
  return value.trim() || void 0;
6439
6523
  };
6524
+ const normalizeInstallRecordPath = (value) => {
6525
+ const filePath = readString$3(value);
6526
+ return filePath ? path.resolve(filePath) : void 0;
6527
+ };
6440
6528
  const resolveDevFirstPartyPluginDir = (explicitDir, moduleDir = path.dirname(fileURLToPath(import.meta.url))) => {
6441
6529
  const configured = explicitDir?.trim();
6442
6530
  if (configured) return configured;
@@ -6478,7 +6566,7 @@ const readWorkspacePluginPackages = (workspaceExtensionsDir) => {
6478
6566
  const packageDir = path.join(workspaceExtensionsDir, entry.name);
6479
6567
  const pkg = readJsonFile(path.join(packageDir, "package.json"));
6480
6568
  if (!pkg || !hasOpenClawExtensions(pkg)) continue;
6481
- const packageName = readString(pkg.name);
6569
+ const packageName = readString$3(pkg.name);
6482
6570
  if (!packageName?.startsWith("@nextclaw/")) continue;
6483
6571
  packages.push({
6484
6572
  packageName,
@@ -6493,14 +6581,22 @@ const mergeLoadPaths$1 = (existingLoadPaths, devLoadPaths) => {
6493
6581
  for (const entry of existingLoadPaths) if (!mergedLoadPaths.includes(entry)) mergedLoadPaths.push(entry);
6494
6582
  return mergedLoadPaths;
6495
6583
  };
6584
+ const findWorkspacePackageForInstallRecord = (installRecord, workspacePackages, packageByName) => {
6585
+ const packageName = normalizePackageSpec(readString$3(installRecord.spec) ?? "");
6586
+ if (packageName) {
6587
+ const matchedByPackageName = packageByName.get(packageName);
6588
+ if (matchedByPackageName) return matchedByPackageName;
6589
+ }
6590
+ const installPathCandidates = new Set([installRecord.sourcePath, installRecord.installPath].map(normalizeInstallRecordPath).filter((entry) => Boolean(entry)));
6591
+ if (installPathCandidates.size === 0) return;
6592
+ return workspacePackages.find((workspacePackage) => installPathCandidates.has(path.resolve(workspacePackage.dir)));
6593
+ };
6496
6594
  const buildDevelopmentSourceEntryDefaults = (config, workspacePackages) => {
6497
6595
  const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
6498
6596
  const nextEntries = { ...config.plugins.entries ?? {} };
6499
6597
  let didDefaultDevelopmentSource = false;
6500
6598
  for (const [pluginId, installRecord] of Object.entries(config.plugins.installs ?? {})) {
6501
- const packageName = normalizePackageSpec(installRecord.spec ?? "");
6502
- if (!packageName) continue;
6503
- if (!packageByName.get(packageName)?.supportsDevelopmentSource) continue;
6599
+ if (!findWorkspacePackageForInstallRecord(installRecord, workspacePackages, packageByName)?.supportsDevelopmentSource) continue;
6504
6600
  const existingEntry = nextEntries[pluginId];
6505
6601
  if (existingEntry?.source) continue;
6506
6602
  nextEntries[pluginId] = {
@@ -6520,12 +6616,12 @@ const resolveDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) =>
6520
6616
  const workspacePackages = readWorkspacePluginPackages(rootDir);
6521
6617
  if (workspacePackages.length === 0) return [];
6522
6618
  const packageDirByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry.dir]));
6619
+ const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
6523
6620
  const loadPaths = [];
6524
6621
  const installs = config.plugins.installs ?? {};
6525
6622
  for (const installRecord of Object.values(installs)) {
6526
- const packageName = normalizePackageSpec(installRecord.spec ?? "");
6527
- if (!packageName) continue;
6528
- const packageDir = packageDirByName.get(packageName);
6623
+ const matchedWorkspacePackage = findWorkspacePackageForInstallRecord(installRecord, workspacePackages, packageByName);
6624
+ const packageDir = matchedWorkspacePackage ? matchedWorkspacePackage.dir : packageDirByName.get(normalizePackageSpec(readString$3(installRecord.spec) ?? "") ?? "");
6529
6625
  if (!packageDir || loadPaths.includes(packageDir)) continue;
6530
6626
  loadPaths.push(packageDir);
6531
6627
  }
@@ -6537,12 +6633,15 @@ const resolveDevFirstPartyPluginInstallRoots = (config, workspaceExtensionsDir)
6537
6633
  const workspacePackages = readWorkspacePluginPackages(rootDir);
6538
6634
  if (workspacePackages.length === 0) return [];
6539
6635
  const packageNames = new Set(workspacePackages.map((entry) => entry.packageName));
6636
+ const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
6540
6637
  const installRoots = [];
6541
6638
  for (const installRecord of Object.values(config.plugins.installs ?? {})) {
6542
- const packageName = normalizePackageSpec(installRecord.spec ?? "");
6543
- if (!packageName || !packageNames.has(packageName)) continue;
6544
- const installPath = readString(installRecord.installPath);
6639
+ const workspacePackage = findWorkspacePackageForInstallRecord(installRecord, workspacePackages, packageByName);
6640
+ const packageName = normalizePackageSpec(readString$3(installRecord.spec) ?? "");
6641
+ if (!workspacePackage && (!packageName || !packageNames.has(packageName))) continue;
6642
+ const installPath = readString$3(installRecord.installPath);
6545
6643
  if (!installPath || installRoots.includes(installPath)) continue;
6644
+ if (workspacePackage && path.resolve(installPath) === path.resolve(workspacePackage.dir)) continue;
6546
6645
  installRoots.push(installPath);
6547
6646
  }
6548
6647
  return installRoots;
@@ -6571,7 +6670,7 @@ const applyDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
6571
6670
  //#endregion
6572
6671
  //#region src/cli/commands/plugin/development-source/dev-plugin-overrides.utils.ts
6573
6672
  const DEV_PLUGIN_OVERRIDES_ENV = "NEXTCLAW_DEV_PLUGIN_OVERRIDES";
6574
- function isRecord$3(value) {
6673
+ function isRecord$4(value) {
6575
6674
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6576
6675
  }
6577
6676
  function readOptionalString$6(value) {
@@ -6600,7 +6699,7 @@ function assertOverridePluginReadable(override) {
6600
6699
  }
6601
6700
  }
6602
6701
  function readOverrideRecord(value, index) {
6603
- if (!isRecord$3(value)) throw new Error(`[dev-plugin-override] override[${index}] must be an object`);
6702
+ if (!isRecord$4(value)) throw new Error(`[dev-plugin-override] override[${index}] must be an object`);
6604
6703
  const pluginId = readOptionalString$6(value.pluginId);
6605
6704
  const pluginPath = readOptionalString$6(value.pluginPath);
6606
6705
  const source = value.source === "development" ? "development" : "production";
@@ -6851,7 +6950,9 @@ function toExtensionRegistry(pluginRegistry) {
6851
6950
  kind: runtime.kind,
6852
6951
  label: runtime.label,
6853
6952
  createRuntime: runtime.createRuntime,
6953
+ createRuntimeForEntry: runtime.createRuntimeForEntry,
6854
6954
  describeSessionType: runtime.describeSessionType,
6955
+ describeSessionTypeForEntry: runtime.describeSessionTypeForEntry,
6855
6956
  source: runtime.source
6856
6957
  })),
6857
6958
  diagnostics: pluginRegistry.diagnostics.map((diag) => ({
@@ -8369,55 +8470,110 @@ var CronCommands = class {
8369
8470
  };
8370
8471
  //#endregion
8371
8472
  //#region src/cli/commands/ncp/ui-ncp-runtime-registry.ts
8372
- const DEFAULT_UI_NCP_RUNTIME_KIND = "native";
8373
- function normalizeRuntimeKind(value) {
8473
+ const DEFAULT_UI_NCP_RUNTIME_ENTRY_ID = "native";
8474
+ function normalizeIdentifier(value) {
8374
8475
  if (typeof value !== "string") return null;
8375
8476
  const normalized = value.trim().toLowerCase();
8376
8477
  return normalized.length > 0 ? normalized : null;
8377
8478
  }
8378
- function readRequestedRuntimeKind(sessionMetadata) {
8379
- return normalizeRuntimeKind(sessionMetadata.runtime) ?? normalizeRuntimeKind(sessionMetadata.session_type) ?? normalizeRuntimeKind(sessionMetadata.sessionType) ?? null;
8479
+ function cloneRuntimeEntry(entry) {
8480
+ return {
8481
+ id: entry.id,
8482
+ label: entry.label,
8483
+ type: entry.type,
8484
+ enabled: entry.enabled !== false,
8485
+ config: entry.config ? { ...entry.config } : {}
8486
+ };
8487
+ }
8488
+ function readRequestedRuntimeEntryId(sessionMetadata) {
8489
+ return normalizeIdentifier(sessionMetadata.runtime) ?? normalizeIdentifier(sessionMetadata.session_type) ?? normalizeIdentifier(sessionMetadata.sessionType) ?? null;
8380
8490
  }
8381
8491
  var UiNcpRuntimeRegistry = class {
8382
- registrations = /* @__PURE__ */ new Map();
8383
- constructor(defaultKind = DEFAULT_UI_NCP_RUNTIME_KIND) {
8384
- this.defaultKind = defaultKind;
8385
- }
8492
+ providers = /* @__PURE__ */ new Map();
8493
+ entries = /* @__PURE__ */ new Map();
8494
+ defaultEntryId = DEFAULT_UI_NCP_RUNTIME_ENTRY_ID;
8386
8495
  register(registration) {
8387
- const normalizedKind = normalizeRuntimeKind(registration.kind);
8496
+ const normalizedKind = normalizeIdentifier(registration.kind);
8388
8497
  if (!normalizedKind) throw new Error("ui ncp runtime kind must be a non-empty string");
8389
8498
  const token = Symbol(normalizedKind);
8390
- this.registrations.set(normalizedKind, {
8499
+ this.providers.set(normalizedKind, {
8391
8500
  ...registration,
8392
8501
  kind: normalizedKind,
8393
8502
  token
8394
8503
  });
8395
8504
  return toDisposable(() => {
8396
- const current = this.registrations.get(normalizedKind);
8505
+ const current = this.providers.get(normalizedKind);
8397
8506
  if (!current || current.token !== token) return;
8398
- this.registrations.delete(normalizedKind);
8507
+ this.providers.delete(normalizedKind);
8399
8508
  });
8400
8509
  }
8510
+ applyEntries(params) {
8511
+ const nextEntries = /* @__PURE__ */ new Map();
8512
+ for (const rawEntry of params.entries) {
8513
+ const id = normalizeIdentifier(rawEntry.id);
8514
+ const type = normalizeIdentifier(rawEntry.type);
8515
+ const label = rawEntry.label?.trim() ?? "";
8516
+ if (!id || !type || !label) continue;
8517
+ nextEntries.set(id, cloneRuntimeEntry({
8518
+ ...rawEntry,
8519
+ id,
8520
+ type,
8521
+ label
8522
+ }));
8523
+ }
8524
+ this.entries = nextEntries;
8525
+ this.defaultEntryId = normalizeIdentifier(params.defaultEntryId) ?? (nextEntries.has("native") ? "native" : [...nextEntries.keys()][0] ?? "native");
8526
+ }
8527
+ listProviderKinds() {
8528
+ return [...this.providers.keys()];
8529
+ }
8401
8530
  createRuntime(params) {
8402
- const requestedKind = readRequestedRuntimeKind(params.sessionMetadata) ?? this.defaultKind;
8403
- const registration = this.registrations.get(requestedKind);
8404
- if (!registration) throw new Error(`ncp runtime unavailable: ${requestedKind}`);
8531
+ const requestedEntryId = readRequestedRuntimeEntryId(params.sessionMetadata) ?? this.defaultEntryId;
8532
+ const entry = this.entries.get(requestedEntryId);
8533
+ if (!entry || entry.enabled === false) throw new Error(`ncp runtime unavailable: ${requestedEntryId}`);
8534
+ const provider = this.providers.get(entry.type);
8535
+ if (!provider) throw new Error(`ncp runtime provider unavailable: ${entry.type}`);
8405
8536
  const nextSessionMetadata = {
8406
8537
  ...params.sessionMetadata,
8407
- session_type: registration.kind
8538
+ runtime: entry.id,
8539
+ session_type: entry.id,
8540
+ runtime_type: entry.type
8408
8541
  };
8409
8542
  params.setSessionMetadata(nextSessionMetadata);
8410
- return registration.createRuntime({
8543
+ if (provider.createRuntimeForEntry) return provider.createRuntimeForEntry({
8544
+ entry: cloneRuntimeEntry(entry),
8545
+ runtimeParams: {
8546
+ ...params,
8547
+ sessionMetadata: nextSessionMetadata
8548
+ }
8549
+ });
8550
+ return provider.createRuntime({
8411
8551
  ...params,
8412
8552
  sessionMetadata: nextSessionMetadata
8413
8553
  });
8414
8554
  }
8415
8555
  async listSessionTypes(params) {
8416
- const options = await Promise.all([...this.registrations.values()].map(async (registration) => {
8417
- const descriptor = await registration.describeSessionType?.(params);
8556
+ const options = await Promise.all([...this.entries.values()].filter((entry) => entry.enabled !== false).map(async (entry) => {
8557
+ const provider = this.providers.get(entry.type);
8558
+ if (!provider) return {
8559
+ value: entry.id,
8560
+ label: entry.label,
8561
+ ready: false,
8562
+ reason: "runtime_provider_unavailable",
8563
+ reasonMessage: `Runtime provider unavailable for type "${entry.type}".`,
8564
+ recommendedModel: null,
8565
+ cta: {
8566
+ kind: "settings",
8567
+ label: "Configure Runtime"
8568
+ }
8569
+ };
8570
+ const descriptor = provider.describeSessionTypeForEntry ? await provider.describeSessionTypeForEntry({
8571
+ entry: cloneRuntimeEntry(entry),
8572
+ describeParams: params
8573
+ }) : await provider.describeSessionType?.(params);
8418
8574
  return {
8419
- value: registration.kind,
8420
- label: registration.label,
8575
+ value: entry.id,
8576
+ label: entry.label,
8421
8577
  ready: descriptor?.ready ?? true,
8422
8578
  reason: descriptor?.reason ?? null,
8423
8579
  reasonMessage: descriptor?.reasonMessage ?? null,
@@ -8427,16 +8583,328 @@ var UiNcpRuntimeRegistry = class {
8427
8583
  };
8428
8584
  }));
8429
8585
  return {
8430
- defaultType: this.defaultKind,
8586
+ defaultType: this.defaultEntryId,
8431
8587
  options: options.sort((left, right) => {
8432
- if (left.value === this.defaultKind) return -1;
8433
- if (right.value === this.defaultKind) return 1;
8588
+ if (left.value === this.defaultEntryId) return -1;
8589
+ if (right.value === this.defaultEntryId) return 1;
8434
8590
  return left.value.localeCompare(right.value);
8435
8591
  })
8436
8592
  };
8437
8593
  }
8438
8594
  };
8439
8595
  //#endregion
8596
+ //#region src/cli/commands/ncp/ui-ncp-runtime-entry-resolver.ts
8597
+ function readString$2(value) {
8598
+ if (typeof value !== "string") return;
8599
+ return value.trim() || void 0;
8600
+ }
8601
+ function readRecord$1(value) {
8602
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
8603
+ return value;
8604
+ }
8605
+ function buildNativeRuntimeEntry(config) {
8606
+ const nativeRuntimeConfig = readRecord$1(config.ui?.ncp?.runtimes?.native) ?? {};
8607
+ return {
8608
+ id: "native",
8609
+ label: "Native",
8610
+ type: "native",
8611
+ enabled: nativeRuntimeConfig.enabled !== false,
8612
+ config: nativeRuntimeConfig
8613
+ };
8614
+ }
8615
+ function buildBuiltinRuntimeEntry(kind, label) {
8616
+ return {
8617
+ id: kind,
8618
+ label,
8619
+ type: kind,
8620
+ enabled: true,
8621
+ config: {}
8622
+ };
8623
+ }
8624
+ function resolveUiNcpRuntimeEntries(params) {
8625
+ const providerKindSet = new Set(params.providerKinds.map((kind) => kind.trim().toLowerCase()).filter(Boolean));
8626
+ const entries = /* @__PURE__ */ new Map();
8627
+ entries.set("native", buildNativeRuntimeEntry(params.config));
8628
+ if (providerKindSet.has("codex")) entries.set("codex", buildBuiltinRuntimeEntry("codex", "Codex"));
8629
+ if (providerKindSet.has("claude")) entries.set("claude", buildBuiltinRuntimeEntry("claude", "Claude"));
8630
+ for (const [rawId, rawEntry] of Object.entries(params.config.agents.runtimes.entries ?? {})) {
8631
+ const id = rawId.trim().toLowerCase();
8632
+ const type = readString$2(rawEntry.type)?.toLowerCase();
8633
+ if (!id || !type) continue;
8634
+ entries.set(id, {
8635
+ id,
8636
+ label: readString$2(rawEntry.label) ?? id,
8637
+ type,
8638
+ enabled: rawEntry.enabled !== false,
8639
+ config: rawEntry.config ? { ...rawEntry.config } : {}
8640
+ });
8641
+ }
8642
+ return {
8643
+ defaultEntryId: entries.has("native") ? "native" : [...entries.keys()][0] ?? "native",
8644
+ entries: [...entries.values()]
8645
+ };
8646
+ }
8647
+ //#endregion
8648
+ //#region src/cli/commands/ncp/builtin-narp-runtime-types.ts
8649
+ const NARP_HTTP_RUNTIME_KIND = "narp-http";
8650
+ const NARP_STDIO_RUNTIME_KIND = "narp-stdio";
8651
+ //#endregion
8652
+ //#region src/cli/commands/ncp/builtin-narp-runtime-registration.service.ts
8653
+ const NARP_API_MODE_HEADER = "x-nextclaw-narp-api-mode";
8654
+ function readRecord(value) {
8655
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
8656
+ return value;
8657
+ }
8658
+ function readString$1(value) {
8659
+ if (typeof value !== "string") return;
8660
+ return value.trim() || void 0;
8661
+ }
8662
+ function resolveRequestedModel(params) {
8663
+ const runMetadata = readRecord(params.input.metadata);
8664
+ return readString$1(runMetadata?.preferred_model) ?? readString$1(runMetadata?.preferredModel) ?? readString$1(runMetadata?.model) ?? readString$1(params.sessionMetadata.preferred_model) ?? readString$1(params.sessionMetadata.preferredModel) ?? readString$1(params.sessionMetadata.model) ?? params.configuredModel ?? params.defaultModel;
8665
+ }
8666
+ function resolveProviderApiMode(resolution) {
8667
+ const wireApi = resolution.provider?.wireApi?.trim().toLowerCase();
8668
+ if (wireApi === "responses") return "codex_responses";
8669
+ if (wireApi === "chat") return "chat_completions";
8670
+ const providerName = resolution.providerName?.trim().toLowerCase();
8671
+ const apiBase = resolution.apiBase?.trim().toLowerCase() ?? "";
8672
+ if (providerName === "anthropic" || apiBase.includes("anthropic.com")) return "anthropic_messages";
8673
+ return "chat_completions";
8674
+ }
8675
+ function buildProviderRoute(params) {
8676
+ const model = resolveRequestedModel(params);
8677
+ if (!model) return;
8678
+ const resolution = resolveProviderRuntime(params.config, model);
8679
+ if (!resolution.provider) return;
8680
+ return {
8681
+ model: resolution.providerLocalModel,
8682
+ apiKey: resolution.apiKey,
8683
+ apiBase: resolution.apiBase,
8684
+ headers: {
8685
+ ...resolution.provider.extraHeaders ?? {},
8686
+ [NARP_API_MODE_HEADER]: resolveProviderApiMode(resolution)
8687
+ }
8688
+ };
8689
+ }
8690
+ var BuiltinHttpRuntimeSessionTypeService = class {
8691
+ pendingDescribeByMode = /* @__PURE__ */ new Map();
8692
+ constructor(entry, defaultModel) {
8693
+ this.entry = entry;
8694
+ this.defaultModel = defaultModel;
8695
+ }
8696
+ describe = async (describeParams) => {
8697
+ const describeMode = describeParams?.describeMode === "probe" ? "probe" : "observation";
8698
+ const pending = this.pendingDescribeByMode.get(describeMode);
8699
+ if (pending) return await pending;
8700
+ const nextDescribe = this.describeInternal();
8701
+ this.pendingDescribeByMode.set(describeMode, nextDescribe);
8702
+ try {
8703
+ return await nextDescribe;
8704
+ } finally {
8705
+ this.pendingDescribeByMode.delete(describeMode);
8706
+ }
8707
+ };
8708
+ describeInternal = async () => {
8709
+ const config = new HttpRuntimeConfigResolver(this.entry.config ?? {}).resolve({ defaultModel: this.defaultModel });
8710
+ if (!config.baseUrl) return {
8711
+ ready: false,
8712
+ reason: "base_url_missing",
8713
+ reasonMessage: "Configure the runtime entry baseUrl before starting an HTTP runtime session.",
8714
+ recommendedModel: config.recommendedModel ?? null,
8715
+ cta: {
8716
+ kind: "settings",
8717
+ label: "Configure HTTP Runtime"
8718
+ }
8719
+ };
8720
+ if (!((config.capabilityProbe ?? true) && Boolean(config.healthcheckUrl))) return {
8721
+ ready: true,
8722
+ reason: null,
8723
+ reasonMessage: null,
8724
+ recommendedModel: config.recommendedModel ?? null,
8725
+ ...config.supportedModels ? { supportedModels: config.supportedModels } : {},
8726
+ cta: null
8727
+ };
8728
+ const controller = new AbortController();
8729
+ const timeoutMs = config.healthcheckTimeoutMs ?? 3e3;
8730
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
8731
+ try {
8732
+ const response = await fetch(config.healthcheckUrl ?? "", {
8733
+ method: "GET",
8734
+ headers: config.headers,
8735
+ signal: controller.signal
8736
+ });
8737
+ if (!response.ok) return {
8738
+ ready: false,
8739
+ reason: "healthcheck_failed",
8740
+ reasonMessage: `HTTP runtime healthcheck returned HTTP ${response.status}.`,
8741
+ recommendedModel: config.recommendedModel ?? null,
8742
+ ...config.supportedModels ? { supportedModels: config.supportedModels } : {},
8743
+ cta: null
8744
+ };
8745
+ return {
8746
+ ready: true,
8747
+ reason: null,
8748
+ reasonMessage: null,
8749
+ recommendedModel: config.recommendedModel ?? null,
8750
+ ...config.supportedModels ? { supportedModels: config.supportedModels } : {},
8751
+ cta: null
8752
+ };
8753
+ } catch (error) {
8754
+ return {
8755
+ ready: false,
8756
+ reason: "healthcheck_unreachable",
8757
+ reasonMessage: error instanceof Error ? error.message : "Failed to reach the configured HTTP runtime healthcheck.",
8758
+ recommendedModel: config.recommendedModel ?? null,
8759
+ ...config.supportedModels ? { supportedModels: config.supportedModels } : {},
8760
+ cta: null
8761
+ };
8762
+ } finally {
8763
+ clearTimeout(timeout);
8764
+ }
8765
+ };
8766
+ };
8767
+ var BuiltinStdioRuntimeSessionTypeService = class {
8768
+ pendingDescribeByMode = /* @__PURE__ */ new Map();
8769
+ constructor(entry) {
8770
+ this.entry = entry;
8771
+ }
8772
+ describe = async (describeParams) => {
8773
+ const describeMode = describeParams?.describeMode === "probe" ? "probe" : "observation";
8774
+ const pending = this.pendingDescribeByMode.get(describeMode);
8775
+ if (pending) return await pending;
8776
+ const nextDescribe = this.describeInternal(describeMode);
8777
+ this.pendingDescribeByMode.set(describeMode, nextDescribe);
8778
+ try {
8779
+ return await nextDescribe;
8780
+ } finally {
8781
+ this.pendingDescribeByMode.delete(describeMode);
8782
+ }
8783
+ };
8784
+ describeInternal = async (describeMode) => {
8785
+ const resolver = new StdioRuntimeConfigResolver(this.entry.config ?? {});
8786
+ try {
8787
+ const config = resolver.resolve();
8788
+ if (!await resolveExecutablePath(config.command)) return {
8789
+ ready: false,
8790
+ reason: "command_missing",
8791
+ reasonMessage: `Configured stdio command "${config.command}" is not available. Update the runtime entry command or install the required launcher first.`,
8792
+ cta: {
8793
+ kind: "settings",
8794
+ label: "Configure Stdio Runtime"
8795
+ }
8796
+ };
8797
+ if (describeMode === "probe") try {
8798
+ await probeStdioRuntime(config);
8799
+ } catch (error) {
8800
+ return {
8801
+ ready: false,
8802
+ reason: "probe_failed",
8803
+ reasonMessage: error instanceof Error ? error.message : "Configured stdio runtime could not complete the ACP probe.",
8804
+ cta: {
8805
+ kind: "settings",
8806
+ label: "Repair Stdio Runtime"
8807
+ }
8808
+ };
8809
+ }
8810
+ return {
8811
+ ready: true,
8812
+ reason: null,
8813
+ reasonMessage: null,
8814
+ cta: null
8815
+ };
8816
+ } catch (error) {
8817
+ return {
8818
+ ready: false,
8819
+ reason: "command_missing",
8820
+ reasonMessage: error instanceof Error ? error.message : "Configure a stdio command before starting this runtime.",
8821
+ cta: {
8822
+ kind: "settings",
8823
+ label: "Configure Stdio Runtime"
8824
+ }
8825
+ };
8826
+ }
8827
+ };
8828
+ };
8829
+ async function resolveExecutablePath(command) {
8830
+ if (command.includes("/") || command.includes("\\")) return await isExecutable(command) ? command : null;
8831
+ const searchPath = process.env.PATH ?? "";
8832
+ for (const directory of searchPath.split(delimiter)) {
8833
+ const trimmed = directory.trim();
8834
+ if (!trimmed) continue;
8835
+ const candidate = `${trimmed}/${command}`;
8836
+ if (await isExecutable(candidate)) return candidate;
8837
+ }
8838
+ return null;
8839
+ }
8840
+ async function isExecutable(filePath) {
8841
+ try {
8842
+ await access(filePath, constants.X_OK);
8843
+ return true;
8844
+ } catch {
8845
+ return false;
8846
+ }
8847
+ }
8848
+ var BuiltinNarpRuntimeRegistrationService = class {
8849
+ constructor(getConfig) {
8850
+ this.getConfig = getConfig;
8851
+ }
8852
+ registerInto = (runtimeRegistry) => {
8853
+ return [runtimeRegistry.register({
8854
+ kind: NARP_HTTP_RUNTIME_KIND,
8855
+ label: "NARP HTTP",
8856
+ createRuntime: this.createUnavailableRuntime,
8857
+ createRuntimeForEntry: ({ entry, runtimeParams }) => this.createHttpRuntime(entry, runtimeParams),
8858
+ describeSessionTypeForEntry: ({ entry, describeParams }) => new BuiltinHttpRuntimeSessionTypeService(entry, this.getConfig().agents.defaults.model).describe(describeParams)
8859
+ }), runtimeRegistry.register({
8860
+ kind: NARP_STDIO_RUNTIME_KIND,
8861
+ label: "NARP Stdio",
8862
+ createRuntime: this.createUnavailableRuntime,
8863
+ createRuntimeForEntry: ({ entry, runtimeParams }) => this.createStdioRuntime(entry, runtimeParams),
8864
+ describeSessionTypeForEntry: ({ entry, describeParams }) => new BuiltinStdioRuntimeSessionTypeService(entry).describe(describeParams)
8865
+ })];
8866
+ };
8867
+ createUnavailableRuntime = () => {
8868
+ throw new Error("[narp] runtime entry is required before creating this runtime");
8869
+ };
8870
+ createHttpRuntime = (entry, runtimeParams) => {
8871
+ const config = readRecord(entry.config) ?? {};
8872
+ const resolver = new HttpRuntimeConfigResolver(config);
8873
+ const resolvedConfig = resolver.resolve({ defaultModel: this.getConfig().agents.defaults.model });
8874
+ return new HttpRuntimeNcpAgentRuntime({
8875
+ baseUrl: resolver.requireBaseUrl(),
8876
+ ...resolvedConfig.basePath ? { basePath: resolvedConfig.basePath } : {},
8877
+ ...resolvedConfig.endpointId ? { endpointId: resolvedConfig.endpointId } : {},
8878
+ ...resolvedConfig.headers ? { headers: resolvedConfig.headers } : {},
8879
+ stateManager: runtimeParams.stateManager,
8880
+ resolveTools: runtimeParams.resolveTools,
8881
+ resolveProviderRoute: (input) => buildProviderRoute({
8882
+ config: this.getConfig(),
8883
+ input,
8884
+ sessionMetadata: runtimeParams.sessionMetadata,
8885
+ defaultModel: this.getConfig().agents.defaults.model,
8886
+ configuredModel: readString$1(config.model)
8887
+ })
8888
+ });
8889
+ };
8890
+ createStdioRuntime = (entry, runtimeParams) => {
8891
+ const config = readRecord(entry.config) ?? {};
8892
+ return new StdioRuntimeNcpAgentRuntime({
8893
+ ...new StdioRuntimeConfigResolver(config).resolve(),
8894
+ sessionId: runtimeParams.sessionId,
8895
+ stateManager: runtimeParams.stateManager,
8896
+ resolveTools: runtimeParams.resolveTools,
8897
+ resolveProviderRoute: (input) => buildProviderRoute({
8898
+ config: this.getConfig(),
8899
+ input,
8900
+ sessionMetadata: runtimeParams.sessionMetadata,
8901
+ defaultModel: this.getConfig().agents.defaults.model,
8902
+ configuredModel: readString$1(config.model)
8903
+ })
8904
+ });
8905
+ };
8906
+ };
8907
+ //#endregion
8440
8908
  //#region src/cli/commands/agent/agent-runtime.ts
8441
8909
  function createUnusedRuntime(_params) {
8442
8910
  throw new Error("runtime creation is not available during runtime listing");
@@ -8463,15 +8931,21 @@ async function listAvailableAgentRuntimes(params) {
8463
8931
  const pluginRegistry = loadRuntimeOnlyPluginRegistry(config, getWorkspacePath(config.agents.defaults.workspace));
8464
8932
  logPluginDiagnostics(pluginRegistry);
8465
8933
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
8466
- const runtimeRegistry = new UiNcpRuntimeRegistry(DEFAULT_UI_NCP_RUNTIME_KIND);
8934
+ const runtimeRegistry = new UiNcpRuntimeRegistry();
8467
8935
  const runtimeSourceByKind = /* @__PURE__ */ new Map();
8936
+ const runtimeSourceByEntryId = /* @__PURE__ */ new Map();
8468
8937
  runtimeRegistry.register({
8469
- kind: DEFAULT_UI_NCP_RUNTIME_KIND,
8938
+ kind: DEFAULT_UI_NCP_RUNTIME_ENTRY_ID,
8470
8939
  label: "Native",
8471
8940
  createRuntime: createUnusedRuntime
8472
8941
  });
8473
- runtimeSourceByKind.set(DEFAULT_UI_NCP_RUNTIME_KIND, { source: "builtin" });
8942
+ runtimeSourceByKind.set(DEFAULT_UI_NCP_RUNTIME_ENTRY_ID, { source: "builtin" });
8943
+ new BuiltinNarpRuntimeRegistrationService(() => config).registerInto(runtimeRegistry);
8944
+ runtimeSourceByKind.set("narp-http", { source: "builtin" });
8945
+ runtimeSourceByKind.set("narp-stdio", { source: "builtin" });
8474
8946
  for (const registration of extensionRegistry.ncpAgentRuntimes) {
8947
+ const normalizedKind = registration.kind.trim().toLowerCase();
8948
+ if (normalizedKind === "narp-http" || normalizedKind === "narp-stdio") continue;
8475
8949
  runtimeRegistry.register({
8476
8950
  kind: registration.kind,
8477
8951
  label: registration.label,
@@ -8483,11 +8957,20 @@ async function listAvailableAgentRuntimes(params) {
8483
8957
  pluginId: registration.pluginId
8484
8958
  });
8485
8959
  }
8960
+ const resolvedEntries = resolveUiNcpRuntimeEntries({
8961
+ config,
8962
+ providerKinds: runtimeRegistry.listProviderKinds()
8963
+ });
8964
+ runtimeRegistry.applyEntries(resolvedEntries);
8965
+ for (const entry of resolvedEntries.entries) {
8966
+ const source = runtimeSourceByKind.get(entry.type);
8967
+ runtimeSourceByEntryId.set(entry.id, source ?? { source: entry.id === "native" ? "builtin" : "plugin" });
8968
+ }
8486
8969
  const listed = await runtimeRegistry.listSessionTypes(params);
8487
8970
  return {
8488
8971
  defaultRuntime: listed.defaultType,
8489
8972
  runtimes: listed.options.map((runtime) => {
8490
- const source = runtimeSourceByKind.get(runtime.value);
8973
+ const source = runtimeSourceByEntryId.get(runtime.value);
8491
8974
  return {
8492
8975
  ...runtime,
8493
8976
  default: runtime.value === listed.defaultType,
@@ -10642,11 +11125,11 @@ function normalizeString(value) {
10642
11125
  const trimmed = value.trim();
10643
11126
  return trimmed.length > 0 ? trimmed : null;
10644
11127
  }
10645
- function isRecord$2(value) {
11128
+ function isRecord$3(value) {
10646
11129
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10647
11130
  }
10648
11131
  function cloneMetadata(value) {
10649
- return isRecord$2(value) ? structuredClone(value) : void 0;
11132
+ return isRecord$3(value) ? structuredClone(value) : void 0;
10650
11133
  }
10651
11134
  function readStringArray(value) {
10652
11135
  if (!Array.isArray(value)) return null;
@@ -11125,10 +11608,10 @@ var SessionSpawnTool = class extends Tool {
11125
11608
  //#endregion
11126
11609
  //#region src/cli/commands/ncp/nextclaw-ncp-tool-registry.ts
11127
11610
  function toToolParams(args) {
11128
- if (isRecord$2(args)) return args;
11611
+ if (isRecord$3(args)) return args;
11129
11612
  if (typeof args === "string") try {
11130
11613
  const parsed = JSON.parse(args);
11131
- return isRecord$2(parsed) ? parsed : {};
11614
+ return isRecord$3(parsed) ? parsed : {};
11132
11615
  } catch {
11133
11616
  return {};
11134
11617
  }
@@ -11171,13 +11654,14 @@ var NextclawNcpToolRegistry = class {
11171
11654
  this.options = options;
11172
11655
  }
11173
11656
  prepareForRun = (context) => {
11657
+ const { channel, chatId, config, restrictToWorkspace, sessionId, workspace } = context;
11174
11658
  this.currentExtensionToolContext = {
11175
- config: context.config,
11176
- workspaceDir: context.workspace,
11177
- sessionKey: context.sessionId,
11178
- channel: context.channel,
11179
- chatId: context.chatId,
11180
- sandboxed: context.restrictToWorkspace
11659
+ config,
11660
+ workspaceDir: workspace,
11661
+ sessionKey: sessionId,
11662
+ channel,
11663
+ chatId,
11664
+ sandboxed: restrictToWorkspace
11181
11665
  };
11182
11666
  this.registry = new ToolRegistry();
11183
11667
  this.tools.clear();
@@ -11305,14 +11789,14 @@ function readAccountIdForHints(metadata, sessionMetadata) {
11305
11789
  //#endregion
11306
11790
  //#region src/cli/commands/ncp/nextclaw-ncp-context-builder.ts
11307
11791
  const TIME_HINT_TRIGGER_PATTERNS = [/\b(now|right now|current time|what time|today|tonight|tomorrow|yesterday|this morning|this afternoon|this evening|date)\b/i, /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/];
11308
- function isRecord$1(value) {
11792
+ function isRecord$2(value) {
11309
11793
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11310
11794
  }
11311
11795
  function mergeInputMetadata(input) {
11312
- const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$1(message.metadata))?.metadata;
11796
+ const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$2(message.metadata))?.metadata;
11313
11797
  return {
11314
- ...isRecord$1(messageMetadata) ? structuredClone(messageMetadata) : {},
11315
- ...isRecord$1(input.metadata) ? structuredClone(input.metadata) : {}
11798
+ ...isRecord$2(messageMetadata) ? structuredClone(messageMetadata) : {},
11799
+ ...isRecord$2(input.metadata) ? structuredClone(input.metadata) : {}
11316
11800
  };
11317
11801
  }
11318
11802
  const REQUESTED_SKILLS_METADATA_READER = new RequestedSkillsMetadataReader();
@@ -11892,6 +12376,13 @@ function cloneInheritedMetadata(sourceMetadata) {
11892
12376
  }
11893
12377
  return nextMetadata;
11894
12378
  }
12379
+ function mergeMetadataOverrides(metadata, overrides) {
12380
+ if (!overrides || Object.keys(overrides).length === 0) return metadata;
12381
+ return {
12382
+ ...metadata,
12383
+ ...structuredClone(overrides)
12384
+ };
12385
+ }
11895
12386
  function buildSessionId() {
11896
12387
  return `ncp-${Date.now().toString(36)}-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
11897
12388
  }
@@ -11902,27 +12393,29 @@ function resolveSessionTitle(params) {
11902
12393
  return readOptionalString$2(params.title) ?? summarizeTask(params.task);
11903
12394
  }
11904
12395
  function resolveSessionType(params) {
11905
- return readOptionalString$2(params.runtime) ?? readOptionalString$2(params.metadata.runtime) ?? readOptionalString$2(params.sessionType) ?? readOptionalString$2(params.metadata.session_type) ?? DEFAULT_SESSION_TYPE;
12396
+ const { metadata, runtime, sessionType } = params;
12397
+ return readOptionalString$2(runtime) ?? readOptionalString$2(metadata.runtime) ?? readOptionalString$2(sessionType) ?? readOptionalString$2(metadata.session_type) ?? DEFAULT_SESSION_TYPE;
11906
12398
  }
11907
12399
  function applySessionOverrides(params) {
11908
- params.metadata.session_type = params.sessionType;
11909
- params.metadata.runtime = params.sessionType;
11910
- params.metadata[SESSION_METADATA_LABEL_KEY] = params.title;
11911
- params.metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = params.lifecycle;
11912
- if (params.parentSessionId) {
11913
- params.metadata[CHILD_SESSION_PARENT_METADATA_KEY] = params.parentSessionId;
11914
- params.metadata[CHILD_SESSION_PROMOTED_METADATA_KEY] = false;
11915
- }
11916
- if (params.requestId) params.metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = params.requestId;
11917
- if (readOptionalString$2(params.model)) {
11918
- params.metadata.model = params.model?.trim();
11919
- params.metadata.preferred_model = params.model?.trim();
11920
- }
11921
- if (readOptionalString$2(params.thinkingLevel)) {
11922
- params.metadata.thinking = params.thinkingLevel?.trim();
11923
- params.metadata.preferred_thinking = params.thinkingLevel?.trim();
11924
- }
11925
- if (readOptionalString$2(params.projectRoot)) params.metadata.project_root = params.projectRoot?.trim();
12400
+ const { lifecycle, metadata, model, parentSessionId, projectRoot, requestId, sessionType, thinkingLevel, title } = params;
12401
+ metadata.session_type = sessionType;
12402
+ metadata.runtime = sessionType;
12403
+ metadata[SESSION_METADATA_LABEL_KEY] = title;
12404
+ metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = lifecycle;
12405
+ if (parentSessionId) {
12406
+ metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
12407
+ metadata[CHILD_SESSION_PROMOTED_METADATA_KEY] = false;
12408
+ }
12409
+ if (requestId) metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
12410
+ if (readOptionalString$2(model)) {
12411
+ metadata.model = model?.trim();
12412
+ metadata.preferred_model = model?.trim();
12413
+ }
12414
+ if (readOptionalString$2(thinkingLevel)) {
12415
+ metadata.thinking = thinkingLevel?.trim();
12416
+ metadata.preferred_thinking = thinkingLevel?.trim();
12417
+ }
12418
+ if (readOptionalString$2(projectRoot)) metadata.project_root = projectRoot?.trim();
11926
12419
  }
11927
12420
  var SessionCreationService = class {
11928
12421
  constructor(sessionManager, getConfig, onSessionUpdated) {
@@ -11931,23 +12424,24 @@ var SessionCreationService = class {
11931
12424
  this.onSessionUpdated = onSessionUpdated;
11932
12425
  }
11933
12426
  createSession = (params) => {
12427
+ const { agentId, metadataOverrides, model, parentSessionId: rawParentSessionId, projectRoot, requestId: rawRequestId, runtime, sessionType: requestedSessionType, sourceSessionMetadata, task, thinkingLevel, title: requestedTitle } = params;
11934
12428
  const sessionId = buildSessionId();
11935
12429
  const now = (/* @__PURE__ */ new Date()).toISOString();
11936
12430
  const session = this.sessionManager.getOrCreate(sessionId);
11937
12431
  const resolvedAgentId = resolveSessionAgentId({
11938
- agentId: params.agentId,
12432
+ agentId,
11939
12433
  getConfig: this.getConfig
11940
12434
  });
11941
12435
  const title = resolveSessionTitle({
11942
- title: params.title,
11943
- task: params.task
12436
+ title: requestedTitle,
12437
+ task
11944
12438
  });
11945
- const metadata = cloneInheritedMetadata(params.sourceSessionMetadata);
11946
- const parentSessionId = readOptionalString$2(params.parentSessionId);
11947
- const requestId = readOptionalString$2(params.requestId);
12439
+ const metadata = cloneInheritedMetadata(sourceSessionMetadata);
12440
+ const parentSessionId = readOptionalString$2(rawParentSessionId);
12441
+ const requestId = readOptionalString$2(rawRequestId);
11948
12442
  const sessionType = resolveSessionType({
11949
- runtime: params.runtime,
11950
- sessionType: params.sessionType,
12443
+ runtime,
12444
+ sessionType: requestedSessionType,
11951
12445
  metadata
11952
12446
  });
11953
12447
  applySessionOverrides({
@@ -11957,12 +12451,13 @@ var SessionCreationService = class {
11957
12451
  lifecycle: DEFAULT_LIFECYCLE,
11958
12452
  parentSessionId,
11959
12453
  requestId,
11960
- model: params.model,
11961
- thinkingLevel: params.thinkingLevel,
11962
- projectRoot: params.projectRoot
12454
+ model,
12455
+ thinkingLevel,
12456
+ projectRoot
11963
12457
  });
12458
+ const nextMetadata = mergeMetadataOverrides(metadata, metadataOverrides);
11964
12459
  session.agentId = resolvedAgentId;
11965
- session.metadata = metadata;
12460
+ session.metadata = nextMetadata;
11966
12461
  session.updatedAt = new Date(now);
11967
12462
  this.sessionManager.save(session);
11968
12463
  this.onSessionUpdated?.(sessionId);
@@ -11975,7 +12470,7 @@ var SessionCreationService = class {
11975
12470
  ...requestId ? { spawnedByRequestId: requestId } : {},
11976
12471
  lifecycle: DEFAULT_LIFECYCLE,
11977
12472
  title,
11978
- metadata,
12473
+ metadata: nextMetadata,
11979
12474
  createdAt: session.createdAt.toISOString(),
11980
12475
  updatedAt: session.updatedAt.toISOString()
11981
12476
  };
@@ -12105,7 +12600,7 @@ function createFailedSessionRequest(params) {
12105
12600
  };
12106
12601
  }
12107
12602
  //#endregion
12108
- //#region src/cli/commands/ncp/session-request/session-request-broker.ts
12603
+ //#region src/cli/commands/ncp/session-request/session-request-broker.service.ts
12109
12604
  var SessionRequestBroker = class {
12110
12605
  constructor(sessionManager, sessionCreationService, deliveryService, resolveBackend, onSessionUpdated) {
12111
12606
  this.sessionManager = sessionManager;
@@ -12115,13 +12610,14 @@ var SessionRequestBroker = class {
12115
12610
  this.onSessionUpdated = onSessionUpdated;
12116
12611
  }
12117
12612
  spawnSessionAndRequest = async (params) => {
12118
- const { sourceSessionId, sourceToolCallId, sourceSessionMetadata, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId, parentSessionId, notify } = params;
12613
+ const { sourceSessionId, sourceToolCallId, sourceSessionMetadata, metadataOverrides, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId, parentSessionId, notify } = params;
12119
12614
  const requestId = randomUUID();
12120
12615
  const createdSession = this.sessionCreationService.createSession({
12121
12616
  ...parentSessionId ? { parentSessionId } : {},
12122
12617
  task,
12123
12618
  title,
12124
12619
  sourceSessionMetadata,
12620
+ ...metadataOverrides ? { metadataOverrides } : {},
12125
12621
  agentId,
12126
12622
  model,
12127
12623
  runtime,
@@ -12421,6 +12917,404 @@ var SessionRequestDeliveryService = class {
12421
12917
  };
12422
12918
  };
12423
12919
  //#endregion
12920
+ //#region src/cli/commands/ncp/session-search/session-search-index.manager.ts
12921
+ const AUTO_LABEL_MAX_LENGTH = 64;
12922
+ function normalizeWhitespace$1(value) {
12923
+ return value.replace(/\s+/gu, " ").trim();
12924
+ }
12925
+ function truncateLabel(value) {
12926
+ const characters = Array.from(value);
12927
+ if (characters.length <= AUTO_LABEL_MAX_LENGTH) return value;
12928
+ return `${characters.slice(0, AUTO_LABEL_MAX_LENGTH).join("")}…`;
12929
+ }
12930
+ function resolveSessionLabel(session) {
12931
+ const metadataLabel = normalizeString(session.metadata?.label) ?? normalizeString(session.metadata?.session_label);
12932
+ if (metadataLabel) return metadataLabel;
12933
+ for (const message of session.messages) {
12934
+ if (message.role !== "user") continue;
12935
+ const text = normalizeString(extractTextFromNcpMessage(message));
12936
+ if (text) return truncateLabel(text);
12937
+ }
12938
+ return "";
12939
+ }
12940
+ function buildSearchableContent(session) {
12941
+ const lines = [];
12942
+ for (const message of session.messages) {
12943
+ if (message.role !== "user" && message.role !== "assistant") continue;
12944
+ const text = normalizeString(extractTextFromNcpMessage(message));
12945
+ if (!text) continue;
12946
+ lines.push(`${message.role}: ${normalizeWhitespace$1(text)}`);
12947
+ }
12948
+ return lines.join("\n");
12949
+ }
12950
+ var SessionSearchIndexManager = class {
12951
+ constructor(store) {
12952
+ this.store = store;
12953
+ }
12954
+ indexSession = async (session) => {
12955
+ const document = this.buildDocument(session);
12956
+ if (!document) {
12957
+ await this.store.deleteDocument(session.sessionId);
12958
+ return;
12959
+ }
12960
+ await this.store.upsertDocument(document);
12961
+ };
12962
+ buildDocument(session) {
12963
+ const label = resolveSessionLabel(session);
12964
+ const content = buildSearchableContent(session);
12965
+ if (!label && !content) return null;
12966
+ return {
12967
+ sessionId: session.sessionId,
12968
+ label,
12969
+ content,
12970
+ updatedAt: session.updatedAt
12971
+ };
12972
+ }
12973
+ };
12974
+ //#endregion
12975
+ //#region src/cli/commands/ncp/session-search/session-search-query.service.ts
12976
+ const SNIPPET_RADIUS = 80;
12977
+ const MAX_FULL_SNIPPET_LENGTH = 180;
12978
+ function normalizeWhitespace(value) {
12979
+ return value.replace(/\s+/gu, " ").trim();
12980
+ }
12981
+ function sanitizeSearchToken(value) {
12982
+ return value.replace(/["*]/gu, " ").replace(/\s+/gu, " ").trim();
12983
+ }
12984
+ function tokenizeSearchTerms(query) {
12985
+ return normalizeWhitespace(query).split(" ").map(sanitizeSearchToken).filter((token) => token.length > 0);
12986
+ }
12987
+ function buildMatchExpression(query) {
12988
+ const terms = tokenizeSearchTerms(query);
12989
+ const normalizedTerms = (terms.length > 0 ? terms : [sanitizeSearchToken(query)]).filter((term) => term.length > 0);
12990
+ if (normalizedTerms.length === 0) throw new Error("query must contain searchable text.");
12991
+ return normalizedTerms.map((term) => `"${term.replace(/"/gu, "\"\"")}"*`).join(" AND ");
12992
+ }
12993
+ function findFirstMatchIndex(text, terms) {
12994
+ const lowerText = text.toLowerCase();
12995
+ let earliestIndex = -1;
12996
+ for (const term of terms) {
12997
+ const index = lowerText.indexOf(term.toLowerCase());
12998
+ if (index < 0) continue;
12999
+ if (earliestIndex < 0 || index < earliestIndex) earliestIndex = index;
13000
+ }
13001
+ return earliestIndex;
13002
+ }
13003
+ function buildSnippet(text, terms) {
13004
+ const normalizedText = normalizeWhitespace(text);
13005
+ if (normalizedText.length <= MAX_FULL_SNIPPET_LENGTH) return normalizedText;
13006
+ const matchIndex = findFirstMatchIndex(normalizedText, terms);
13007
+ if (matchIndex < 0) return `${normalizedText.slice(0, MAX_FULL_SNIPPET_LENGTH).trimEnd()}...`;
13008
+ const start = Math.max(0, matchIndex - SNIPPET_RADIUS);
13009
+ const end = Math.min(normalizedText.length, matchIndex + SNIPPET_RADIUS);
13010
+ const prefix = start > 0 ? "..." : "";
13011
+ const suffix = end < normalizedText.length ? "..." : "";
13012
+ return `${prefix}${normalizedText.slice(start, end).trim()}${suffix}`;
13013
+ }
13014
+ function resolveMatchSource(hit, terms) {
13015
+ return findFirstMatchIndex(hit.label, terms) >= 0 ? "label" : "content";
13016
+ }
13017
+ function normalizeLimit(limit) {
13018
+ if (typeof limit !== "number" || !Number.isFinite(limit)) return 5;
13019
+ const roundedLimit = Math.trunc(limit);
13020
+ if (roundedLimit <= 0) return 5;
13021
+ return Math.min(roundedLimit, 10);
13022
+ }
13023
+ var SessionSearchQueryService = class {
13024
+ constructor(store) {
13025
+ this.store = store;
13026
+ }
13027
+ search = async (request) => {
13028
+ const query = normalizeString(request.query);
13029
+ if (!query) throw new Error("query must be a non-empty string.");
13030
+ const terms = tokenizeSearchTerms(query);
13031
+ const matchExpression = buildMatchExpression(query);
13032
+ const excludeSessionId = request.includeCurrentSession === true ? void 0 : normalizeString(request.currentSessionId) ?? void 0;
13033
+ const result = await this.store.searchDocuments({
13034
+ matchExpression,
13035
+ limit: normalizeLimit(request.limit),
13036
+ excludeSessionId
13037
+ });
13038
+ return {
13039
+ query,
13040
+ totalHits: result.total,
13041
+ hits: result.hits.map((hit) => this.toSearchHit(hit, terms))
13042
+ };
13043
+ };
13044
+ toSearchHit(hit, terms) {
13045
+ const matchSource = resolveMatchSource(hit, terms);
13046
+ const label = normalizeWhitespace(hit.label) || hit.sessionId;
13047
+ const snippetSource = matchSource === "label" ? label : hit.content;
13048
+ return {
13049
+ sessionId: hit.sessionId,
13050
+ label,
13051
+ updatedAt: hit.updatedAt,
13052
+ snippet: buildSnippet(snippetSource, terms),
13053
+ matchSource,
13054
+ rank: hit.rank
13055
+ };
13056
+ }
13057
+ };
13058
+ //#endregion
13059
+ //#region src/cli/commands/ncp/session-search/session-search-store.service.ts
13060
+ const SESSION_SEARCH_TABLE = "session_search_index";
13061
+ var SessionSearchStoreService = class {
13062
+ database = null;
13063
+ constructor(databasePath) {
13064
+ this.databasePath = databasePath;
13065
+ }
13066
+ initialize = async () => {
13067
+ if (this.database) return;
13068
+ mkdirSync(dirname(this.databasePath), { recursive: true });
13069
+ this.database = new DatabaseSync(this.databasePath);
13070
+ this.database.exec(`
13071
+ PRAGMA journal_mode = WAL;
13072
+ CREATE VIRTUAL TABLE IF NOT EXISTS ${SESSION_SEARCH_TABLE}
13073
+ USING fts5(
13074
+ session_id UNINDEXED,
13075
+ label,
13076
+ content,
13077
+ updated_at UNINDEXED,
13078
+ tokenize = 'unicode61'
13079
+ );
13080
+ `);
13081
+ };
13082
+ listIndexedSessionIds = async () => {
13083
+ return this.requireDatabase().prepare(`
13084
+ SELECT session_id AS sessionId
13085
+ FROM ${SESSION_SEARCH_TABLE}
13086
+ `).all().map((row) => typeof row.sessionId === "string" ? row.sessionId : "").filter((sessionId) => sessionId.length > 0);
13087
+ };
13088
+ upsertDocument = async (document) => {
13089
+ const database = this.requireDatabase();
13090
+ this.withTransaction(() => {
13091
+ database.prepare(`
13092
+ DELETE FROM ${SESSION_SEARCH_TABLE}
13093
+ WHERE session_id = ?
13094
+ `).run(document.sessionId);
13095
+ database.prepare(`
13096
+ INSERT INTO ${SESSION_SEARCH_TABLE} (session_id, label, content, updated_at)
13097
+ VALUES (?, ?, ?, ?)
13098
+ `).run(document.sessionId, document.label, document.content, document.updatedAt);
13099
+ });
13100
+ };
13101
+ deleteDocument = async (sessionId) => {
13102
+ this.requireDatabase().prepare(`
13103
+ DELETE FROM ${SESSION_SEARCH_TABLE}
13104
+ WHERE session_id = ?
13105
+ `).run(sessionId);
13106
+ };
13107
+ searchDocuments = async (query) => {
13108
+ const database = this.requireDatabase();
13109
+ const countSql = this.buildSearchSql({
13110
+ includeLimit: false,
13111
+ excludeSessionId: Boolean(query.excludeSessionId)
13112
+ });
13113
+ const searchSql = this.buildSearchSql({
13114
+ includeLimit: true,
13115
+ excludeSessionId: Boolean(query.excludeSessionId)
13116
+ });
13117
+ const sharedParams = query.excludeSessionId ? [query.matchExpression, query.excludeSessionId] : [query.matchExpression];
13118
+ const countRow = database.prepare(countSql).get(...sharedParams);
13119
+ const rows = database.prepare(searchSql).all(...sharedParams, query.limit);
13120
+ return {
13121
+ total: typeof countRow?.total === "number" ? countRow.total : 0,
13122
+ hits: rows.map((row) => ({
13123
+ sessionId: typeof row.sessionId === "string" ? row.sessionId : "",
13124
+ label: typeof row.label === "string" ? row.label : "",
13125
+ content: typeof row.content === "string" ? row.content : "",
13126
+ updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : "",
13127
+ rank: typeof row.rank === "number" ? row.rank : 0
13128
+ }))
13129
+ };
13130
+ };
13131
+ close = async () => {
13132
+ this.database?.close();
13133
+ this.database = null;
13134
+ };
13135
+ buildSearchSql(params) {
13136
+ const extraFilter = params.excludeSessionId ? "AND session_id <> ?" : "";
13137
+ if (!params.includeLimit) return `
13138
+ SELECT COUNT(*) AS total
13139
+ FROM ${SESSION_SEARCH_TABLE}
13140
+ WHERE ${SESSION_SEARCH_TABLE} MATCH ?
13141
+ ${extraFilter}
13142
+ `;
13143
+ return `
13144
+ SELECT
13145
+ session_id AS sessionId,
13146
+ label,
13147
+ content,
13148
+ updated_at AS updatedAt,
13149
+ bm25(${SESSION_SEARCH_TABLE}) AS rank
13150
+ FROM ${SESSION_SEARCH_TABLE}
13151
+ WHERE ${SESSION_SEARCH_TABLE} MATCH ?
13152
+ ${extraFilter}
13153
+ ORDER BY rank, updated_at DESC
13154
+ LIMIT ?
13155
+ `;
13156
+ }
13157
+ withTransaction(work) {
13158
+ const database = this.requireDatabase();
13159
+ database.exec("BEGIN IMMEDIATE");
13160
+ try {
13161
+ work();
13162
+ database.exec("COMMIT");
13163
+ } catch (error) {
13164
+ database.exec("ROLLBACK");
13165
+ throw error;
13166
+ }
13167
+ }
13168
+ requireDatabase() {
13169
+ if (!this.database) throw new Error("Session search store has not been initialized.");
13170
+ return this.database;
13171
+ }
13172
+ };
13173
+ //#endregion
13174
+ //#region src/cli/commands/ncp/session-search/session-search-tool.service.ts
13175
+ function isRecord$1(value) {
13176
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13177
+ }
13178
+ function readOptionalInteger(value) {
13179
+ if (typeof value !== "number" || !Number.isFinite(value)) return;
13180
+ return Math.trunc(value);
13181
+ }
13182
+ function readOptionalBoolean(value) {
13183
+ return typeof value === "boolean" ? value : void 0;
13184
+ }
13185
+ var SessionSearchTool = class {
13186
+ name = "session_search";
13187
+ description = "Search prior sessions by keyword and return structured hits with snippets. Use it to recall earlier discussions before creating new plans or summaries.";
13188
+ parameters = {
13189
+ type: "object",
13190
+ properties: {
13191
+ query: {
13192
+ type: "string",
13193
+ description: "Keyword search query for prior session text or labels."
13194
+ },
13195
+ limit: {
13196
+ type: "integer",
13197
+ minimum: 1,
13198
+ maximum: 10,
13199
+ description: `Optional max hit count. Defaults to 5.`
13200
+ },
13201
+ includeCurrentSession: {
13202
+ type: "boolean",
13203
+ description: "Set true to include the current session in results. Defaults to false."
13204
+ }
13205
+ },
13206
+ required: ["query"],
13207
+ additionalProperties: false
13208
+ };
13209
+ constructor(queryService, context) {
13210
+ this.queryService = queryService;
13211
+ this.context = context;
13212
+ }
13213
+ validateArgs = (args) => {
13214
+ const issues = [];
13215
+ const query = normalizeString(args.query);
13216
+ const limit = readOptionalInteger(args.limit);
13217
+ const includeCurrentSession = readOptionalBoolean(args.includeCurrentSession);
13218
+ if (!query) issues.push("query must be a non-empty string.");
13219
+ if (typeof args.limit !== "undefined" && typeof limit === "undefined") issues.push("limit must be a finite integer.");
13220
+ if (typeof limit === "number" && (limit < 1 || limit > 10)) issues.push(`limit must be between 1 and 10.`);
13221
+ if (typeof args.includeCurrentSession !== "undefined" && typeof includeCurrentSession === "undefined") issues.push("includeCurrentSession must be a boolean.");
13222
+ return issues;
13223
+ };
13224
+ execute = async (args) => {
13225
+ if (!isRecord$1(args)) throw new Error("session_search requires an object argument.");
13226
+ const issues = this.validateArgs(args);
13227
+ if (issues.length > 0) throw new Error(issues.join(" "));
13228
+ return this.queryService.search({
13229
+ query: normalizeString(args.query) ?? "",
13230
+ limit: readOptionalInteger(args.limit),
13231
+ includeCurrentSession: readOptionalBoolean(args.includeCurrentSession),
13232
+ currentSessionId: this.context.currentSessionId
13233
+ });
13234
+ };
13235
+ };
13236
+ //#endregion
13237
+ //#region src/cli/commands/ncp/session-search/session-search-feature.service.ts
13238
+ var SessionSearchFeatureService = class {
13239
+ store;
13240
+ indexManager;
13241
+ queryService;
13242
+ pendingWork = Promise.resolve();
13243
+ initialized = false;
13244
+ disposed = false;
13245
+ constructor(options) {
13246
+ this.options = options;
13247
+ this.store = new SessionSearchStoreService(options.databasePath);
13248
+ this.indexManager = new SessionSearchIndexManager(this.store);
13249
+ this.queryService = new SessionSearchQueryService(this.store);
13250
+ }
13251
+ initialize = async () => {
13252
+ if (this.initialized) return;
13253
+ if (this.disposed) throw new Error("Session search feature has already been disposed.");
13254
+ await this.store.initialize();
13255
+ await this.enqueue(async () => {
13256
+ const sessions = await this.options.sessionStore.listSessions();
13257
+ const activeSessionIds = new Set(sessions.map((session) => session.sessionId));
13258
+ for (const session of sessions) await this.indexManager.indexSession(session);
13259
+ const indexedSessionIds = await this.store.listIndexedSessionIds();
13260
+ for (const sessionId of indexedSessionIds) if (!activeSessionIds.has(sessionId)) await this.store.deleteDocument(sessionId);
13261
+ });
13262
+ this.initialized = true;
13263
+ };
13264
+ createTool = (params) => new SessionSearchTool(this.queryService, params);
13265
+ handleSessionUpdated = async (sessionId) => {
13266
+ if (this.disposed || !this.initialized) return;
13267
+ await this.enqueue(async () => {
13268
+ const session = await this.options.sessionStore.getSession(sessionId);
13269
+ if (!session) {
13270
+ await this.store.deleteDocument(sessionId);
13271
+ return;
13272
+ }
13273
+ await this.indexManager.indexSession(session);
13274
+ });
13275
+ };
13276
+ dispose = async () => {
13277
+ if (this.disposed) return;
13278
+ this.disposed = true;
13279
+ await this.pendingWork;
13280
+ await this.store.close();
13281
+ };
13282
+ enqueue(work) {
13283
+ const next = this.pendingWork.then(work);
13284
+ this.pendingWork = next.catch(() => void 0);
13285
+ return next;
13286
+ }
13287
+ };
13288
+ //#endregion
13289
+ //#region src/cli/commands/ncp/session-search/session-search-runtime.service.ts
13290
+ function formatErrorMessage(error) {
13291
+ return error instanceof Error ? error.message : String(error);
13292
+ }
13293
+ var SessionSearchRuntimeSupport = class {
13294
+ feature;
13295
+ constructor(params) {
13296
+ this.onSessionUpdated = params.onSessionUpdated;
13297
+ this.feature = new SessionSearchFeatureService({
13298
+ sessionStore: new NextclawAgentSessionStore(params.sessionManager),
13299
+ databasePath: params.databasePath
13300
+ });
13301
+ }
13302
+ onSessionUpdated;
13303
+ initialize = async () => {
13304
+ await this.feature.initialize();
13305
+ };
13306
+ createTool = (params) => this.feature.createTool(params);
13307
+ handleSessionUpdated = (sessionKey) => {
13308
+ this.onSessionUpdated?.(sessionKey);
13309
+ this.feature.handleSessionUpdated(sessionKey).catch((error) => {
13310
+ console.warn(`[session-search] Failed to update ${sessionKey}: ${formatErrorMessage(error)}`);
13311
+ });
13312
+ };
13313
+ dispose = async () => {
13314
+ await this.feature.dispose();
13315
+ };
13316
+ };
13317
+ //#endregion
12424
13318
  //#region src/cli/commands/shared/llm-usage-observer.ts
12425
13319
  var LlmUsageObserver = class {
12426
13320
  constructor(recorder, source) {
@@ -12471,7 +13365,7 @@ var ObservedProviderManager = class extends ProviderManager {
12471
13365
  //#endregion
12472
13366
  //#region src/cli/commands/ncp/runtime/ui-ncp-agent-handle.ts
12473
13367
  function createUiNcpAgentHandle(params) {
12474
- const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, applyExtensionRegistry, applyMcpConfig, dispose, assetStore } = params;
13368
+ const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, refreshConfiguredRuntimeEntries, applyExtensionRegistry, applyMcpConfig, dispose, assetStore } = params;
12475
13369
  return {
12476
13370
  basePath: "/api/ncp/agent",
12477
13371
  agentClientEndpoint: createAgentClientFromServer(backend),
@@ -12480,6 +13374,7 @@ function createUiNcpAgentHandle(params) {
12480
13374
  sessionApi: backend,
12481
13375
  listSessionTypes: (describeParams) => {
12482
13376
  refreshPluginRuntimeRegistrations();
13377
+ refreshConfiguredRuntimeEntries();
12483
13378
  return runtimeRegistry.listSessionTypes(describeParams);
12484
13379
  },
12485
13380
  assetApi: {
@@ -12498,7 +13393,357 @@ function createUiNcpAgentHandle(params) {
12498
13393
  };
12499
13394
  }
12500
13395
  //#endregion
12501
- //#region src/cli/runtime-state/llm-usage-record.ts
13396
+ //#region src/cli/commands/learning-loop/learning-loop.config.ts
13397
+ const LEARNING_LOOP_DISABLED_METADATA_KEY = "learning_loop_disabled";
13398
+ const LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY = "learning_loop_last_tool_call_count";
13399
+ const LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY = "learning_loop_last_requested_at";
13400
+ const LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY = "learning_loop_last_review_session_id";
13401
+ const LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY = "learning_loop_source_session_id";
13402
+ const LEARNING_LOOP_REQUESTED_SKILLS = ["skill-creator"];
13403
+ function readLearningLoopRuntimeConfig(config) {
13404
+ return {
13405
+ enabled: config.agents.learningLoop.enabled,
13406
+ toolCallThreshold: config.agents.learningLoop.toolCallThreshold ?? 15
13407
+ };
13408
+ }
13409
+ //#endregion
13410
+ //#region src/cli/commands/learning-loop/learning-loop-prompt.utils.ts
13411
+ function pluralizeToolCalls(value) {
13412
+ return value === 1 ? "1 tool call" : `${value} tool calls`;
13413
+ }
13414
+ function buildLearningLoopTask(params) {
13415
+ return [
13416
+ "Run a background learning-loop review for the parent session.",
13417
+ `Parent session id: ${params.sessionId}.`,
13418
+ `There have been ${pluralizeToolCalls(params.toolCallsSinceReview)} since the last learning-loop review (${params.currentToolCallCount} total so far).`,
13419
+ "",
13420
+ "Your job is to extract only the reusable lesson.",
13421
+ "- Review the parent session history and, if helpful, use session_search to compare similar historical sessions.",
13422
+ "- Decide exactly one outcome: no_skill_change, patch_existing_skill, or create_new_skill.",
13423
+ "- Prefer patching an existing skill when the lesson extends an existing workflow.",
13424
+ "- Only create a new skill when the lesson is clearly reusable, repeatable, and likely to recur.",
13425
+ "- Do not create or patch a skill for one-off quirks, noisy transcripts, or narrow local accidents.",
13426
+ "",
13427
+ "If you decide to patch or create a skill:",
13428
+ "- Use the available file tools.",
13429
+ "- Keep the change minimal and maintainable.",
13430
+ "- Write the skill into the current workspace/project skills directory under skills/<slug>/SKILL.md.",
13431
+ "- Follow the built-in skill-creator guidance when it helps.",
13432
+ "",
13433
+ "If nothing reusable should be saved, stop without writing files."
13434
+ ].join("\n");
13435
+ }
13436
+ //#endregion
13437
+ //#region src/cli/commands/ncp/lifecycle-events/ncp-lifecycle-event.config.ts
13438
+ const agentRunStartedLifecycleEventKey = createTypedEventKey("agent.run.started");
13439
+ const agentRunFinishedLifecycleEventKey = createTypedEventKey("agent.run.finished");
13440
+ const agentMessageSentLifecycleEventKey = createTypedEventKey("agent.message.sent");
13441
+ const agentSessionUpdatedLifecycleEventKey = createTypedEventKey("agent.session.updated");
13442
+ //#endregion
13443
+ //#region src/cli/commands/learning-loop/learning-loop-feature.service.ts
13444
+ function countToolCallsFromMessage(message) {
13445
+ if (Array.isArray(message.tool_calls)) return message.tool_calls.filter((toolCall) => Boolean(toolCall) && typeof toolCall === "object" && !Array.isArray(toolCall)).length;
13446
+ if (!Array.isArray(message.ncp_parts)) return 0;
13447
+ const seenIds = /* @__PURE__ */ new Set();
13448
+ let anonymousCount = 0;
13449
+ for (const part of message.ncp_parts) {
13450
+ if (!part || typeof part !== "object" || Array.isArray(part)) continue;
13451
+ const candidate = part;
13452
+ if (candidate.type !== "tool-invocation") continue;
13453
+ if (typeof candidate.toolCallId === "string" && candidate.toolCallId.trim()) {
13454
+ seenIds.add(candidate.toolCallId.trim());
13455
+ continue;
13456
+ }
13457
+ anonymousCount += 1;
13458
+ }
13459
+ return seenIds.size + anonymousCount;
13460
+ }
13461
+ function countSessionToolCalls(session) {
13462
+ return session.messages.reduce((count, message) => count + countToolCallsFromMessage(message), 0);
13463
+ }
13464
+ function readSessionLabel(metadata) {
13465
+ const label = metadata.label;
13466
+ return typeof label === "string" && label.trim().length > 0 ? label.trim() : void 0;
13467
+ }
13468
+ function isLearningLoopDisabled(metadata) {
13469
+ return metadata[LEARNING_LOOP_DISABLED_METADATA_KEY] === true;
13470
+ }
13471
+ function readLearningLoopLastToolCallCount(metadata) {
13472
+ const value = metadata[LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY];
13473
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
13474
+ }
13475
+ var LearningLoopFeature = class {
13476
+ inFlightSessionIds = /* @__PURE__ */ new Set();
13477
+ unsubscribe = null;
13478
+ constructor(config) {
13479
+ this.config = config;
13480
+ }
13481
+ start = () => {
13482
+ if (this.unsubscribe) return;
13483
+ this.unsubscribe = this.config.eventBus.on(agentRunFinishedLifecycleEventKey, this.handleRunFinished);
13484
+ };
13485
+ dispose = () => {
13486
+ this.unsubscribe?.();
13487
+ this.unsubscribe = null;
13488
+ };
13489
+ handleRunFinished = (event) => {
13490
+ this.handleRunFinishedInBackground(event).catch((error) => {
13491
+ console.warn(`[learning-loop] Failed for ${event.sessionId}: ${error instanceof Error ? error.message : String(error)}`);
13492
+ });
13493
+ };
13494
+ handleRunFinishedInBackground = async (event) => {
13495
+ if (event.isChildSession || this.inFlightSessionIds.has(event.sessionId)) return;
13496
+ const session = this.config.sessionStore.getIfExists(event.sessionId);
13497
+ if (!session) return;
13498
+ const runtimeConfig = this.readRuntimeConfig();
13499
+ if (!runtimeConfig.enabled) return;
13500
+ if (isLearningLoopDisabled(session.metadata)) return;
13501
+ const totalToolCalls = countSessionToolCalls(session);
13502
+ const toolCallsSinceReview = totalToolCalls - readLearningLoopLastToolCallCount(session.metadata);
13503
+ if (toolCallsSinceReview < runtimeConfig.toolCallThreshold) return;
13504
+ this.inFlightSessionIds.add(event.sessionId);
13505
+ try {
13506
+ const reviewSession = await this.config.sessionRequester.spawnSessionAndRequest({
13507
+ sourceSessionId: event.sessionId,
13508
+ sourceSessionMetadata: session.metadata,
13509
+ metadataOverrides: {
13510
+ requested_skills: LEARNING_LOOP_REQUESTED_SKILLS,
13511
+ [LEARNING_LOOP_DISABLED_METADATA_KEY]: true,
13512
+ [LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY]: event.sessionId
13513
+ },
13514
+ parentSessionId: event.sessionId,
13515
+ notify: "none",
13516
+ title: this.buildReviewTitle(session.metadata),
13517
+ task: buildLearningLoopTask({
13518
+ sessionId: event.sessionId,
13519
+ toolCallsSinceReview,
13520
+ currentToolCallCount: totalToolCalls
13521
+ })
13522
+ });
13523
+ session.metadata = {
13524
+ ...session.metadata,
13525
+ [LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY]: totalToolCalls,
13526
+ [LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY]: (/* @__PURE__ */ new Date()).toISOString(),
13527
+ [LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY]: reviewSession.sessionId
13528
+ };
13529
+ this.config.sessionStore.save(session);
13530
+ } finally {
13531
+ this.inFlightSessionIds.delete(event.sessionId);
13532
+ }
13533
+ };
13534
+ readRuntimeConfig = () => {
13535
+ if (this.config.resolveRuntimeConfig) return this.config.resolveRuntimeConfig();
13536
+ return {
13537
+ enabled: true,
13538
+ toolCallThreshold: this.config.toolCallThreshold ?? 15
13539
+ };
13540
+ };
13541
+ buildReviewTitle = (metadata) => {
13542
+ const label = readSessionLabel(metadata);
13543
+ return label ? `Learning loop: ${label}` : "Learning loop";
13544
+ };
13545
+ };
13546
+ //#endregion
13547
+ //#region src/cli/commands/ncp/lifecycle-events/ncp-lifecycle-event-bridge.service.ts
13548
+ function readSessionType(metadata) {
13549
+ const sessionType = metadata?.session_type;
13550
+ return typeof sessionType === "string" && sessionType.trim().length > 0 ? sessionType.trim() : void 0;
13551
+ }
13552
+ var NcpLifecycleEventBridge = class {
13553
+ constructor(sessionManager, eventBus) {
13554
+ this.sessionManager = sessionManager;
13555
+ this.eventBus = eventBus;
13556
+ }
13557
+ publishSessionUpdated = (sessionId) => {
13558
+ const context = this.buildSessionContext(sessionId);
13559
+ if (!context) return;
13560
+ const payload = { ...context };
13561
+ this.eventBus.emit(agentSessionUpdatedLifecycleEventKey, payload);
13562
+ };
13563
+ handleEndpointEvent = (event) => {
13564
+ switch (event.type) {
13565
+ case NcpEventType.RunStarted:
13566
+ this.publishRunStarted(event);
13567
+ return;
13568
+ case NcpEventType.RunFinished:
13569
+ this.publishRunFinished(event);
13570
+ return;
13571
+ case NcpEventType.MessageSent:
13572
+ this.publishMessageSent(event);
13573
+ return;
13574
+ default: return;
13575
+ }
13576
+ };
13577
+ publishRunStarted = (event) => {
13578
+ const sessionId = event.payload.sessionId?.trim();
13579
+ if (!sessionId) return;
13580
+ const context = this.buildSessionContext(sessionId);
13581
+ if (!context) return;
13582
+ const payload = {
13583
+ ...context,
13584
+ ...event.payload.runId ? { runId: event.payload.runId } : {},
13585
+ ...event.payload.messageId ? { messageId: event.payload.messageId } : {}
13586
+ };
13587
+ this.eventBus.emit(agentRunStartedLifecycleEventKey, payload);
13588
+ };
13589
+ publishRunFinished = (event) => {
13590
+ const sessionId = event.payload.sessionId?.trim();
13591
+ if (!sessionId) return;
13592
+ const context = this.buildSessionContext(sessionId);
13593
+ if (!context) return;
13594
+ const payload = {
13595
+ ...context,
13596
+ ...event.payload.runId ? { runId: event.payload.runId } : {},
13597
+ ...event.payload.messageId ? { messageId: event.payload.messageId } : {}
13598
+ };
13599
+ this.eventBus.emit(agentRunFinishedLifecycleEventKey, payload);
13600
+ };
13601
+ publishMessageSent = (event) => {
13602
+ const sessionId = event.payload.sessionId.trim();
13603
+ const context = this.buildSessionContext(sessionId);
13604
+ if (!context) return;
13605
+ const payload = {
13606
+ ...context,
13607
+ messageId: event.payload.message.id,
13608
+ role: event.payload.message.role
13609
+ };
13610
+ this.eventBus.emit(agentMessageSentLifecycleEventKey, payload);
13611
+ };
13612
+ buildSessionContext = (sessionId) => {
13613
+ const metadata = this.sessionManager.getIfExists(sessionId)?.metadata;
13614
+ const sessionType = readSessionType(metadata);
13615
+ const parentSessionId = metadata ? readParentSessionId(metadata) : void 0;
13616
+ return {
13617
+ sessionId,
13618
+ ...sessionType ? { sessionType } : {},
13619
+ ...parentSessionId ? { parentSessionId } : {},
13620
+ isChildSession: Boolean(parentSessionId),
13621
+ emittedAt: (/* @__PURE__ */ new Date()).toISOString()
13622
+ };
13623
+ };
13624
+ };
13625
+ //#endregion
13626
+ //#region src/cli/commands/learning-loop/learning-loop-runtime.service.ts
13627
+ var LearningLoopRuntimeService = class {
13628
+ globalEventBus;
13629
+ lifecycleEventBridge;
13630
+ learningLoopFeature;
13631
+ unsubscribeEndpointEvents = null;
13632
+ constructor(params) {
13633
+ const { sessionManager, sessionRequestBroker, onSessionUpdated, globalEventBus, resolveLearningLoopConfig } = params;
13634
+ this.globalEventBus = globalEventBus ?? createGlobalTypedEventBus({ onListenerError: ({ key, error }) => {
13635
+ console.warn(`[global-event-bus] listener failed for ${key}: ${error instanceof Error ? error.message : String(error)}`);
13636
+ } });
13637
+ this.lifecycleEventBridge = new NcpLifecycleEventBridge(sessionManager, this.globalEventBus);
13638
+ this.learningLoopFeature = new LearningLoopFeature({
13639
+ eventBus: this.globalEventBus,
13640
+ sessionStore: sessionManager,
13641
+ sessionRequester: sessionRequestBroker,
13642
+ resolveRuntimeConfig: resolveLearningLoopConfig
13643
+ });
13644
+ this.onSessionUpdated = onSessionUpdated;
13645
+ }
13646
+ onSessionUpdated;
13647
+ handleSessionUpdated = (sessionKey) => {
13648
+ this.onSessionUpdated?.(sessionKey);
13649
+ this.lifecycleEventBridge.publishSessionUpdated(sessionKey);
13650
+ };
13651
+ attachBackend = (backend) => {
13652
+ this.unsubscribeEndpointEvents?.();
13653
+ this.unsubscribeEndpointEvents = backend.subscribe(this.lifecycleEventBridge.handleEndpointEvent);
13654
+ this.learningLoopFeature.start();
13655
+ };
13656
+ dispose = () => {
13657
+ this.learningLoopFeature.dispose();
13658
+ this.unsubscribeEndpointEvents?.();
13659
+ this.unsubscribeEndpointEvents = null;
13660
+ };
13661
+ };
13662
+ //#endregion
13663
+ //#region src/cli/commands/ncp/plugin-runtime-registration.controller.ts
13664
+ const RESERVED_BUILTIN_RUNTIME_KINDS = new Set([NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND]);
13665
+ function buildPluginRuntimeSnapshotKey(extensionRegistry) {
13666
+ return (extensionRegistry?.ncpAgentRuntimes ?? []).map((registration) => [
13667
+ registration.pluginId,
13668
+ registration.kind,
13669
+ registration.label,
13670
+ registration.source
13671
+ ].join(":")).join("|");
13672
+ }
13673
+ function createRuntimeFactory(registration) {
13674
+ if (registration.kind !== "codex") return registration.createRuntime;
13675
+ return (runtimeParams) => registration.createRuntime({
13676
+ ...runtimeParams,
13677
+ sessionMetadata: {
13678
+ ...runtimeParams.sessionMetadata,
13679
+ session_type: "codex",
13680
+ codex_runtime_backend: "codex-sdk"
13681
+ },
13682
+ setSessionMetadata: (nextMetadata) => {
13683
+ runtimeParams.setSessionMetadata({
13684
+ ...nextMetadata,
13685
+ session_type: "codex",
13686
+ codex_runtime_backend: "codex-sdk"
13687
+ });
13688
+ }
13689
+ });
13690
+ }
13691
+ var PluginRuntimeRegistrationController = class {
13692
+ pluginRuntimeScopes = /* @__PURE__ */ new Map();
13693
+ pluginRuntimeSnapshotKey = "";
13694
+ activeExtensionRegistry;
13695
+ constructor(runtimeRegistry, getExtensionRegistry) {
13696
+ this.runtimeRegistry = runtimeRegistry;
13697
+ this.getExtensionRegistry = getExtensionRegistry;
13698
+ }
13699
+ refreshPluginRuntimeRegistrations = () => {
13700
+ this.syncPluginRuntimeRegistrations(this.resolveActiveExtensionRegistry());
13701
+ };
13702
+ applyExtensionRegistry = (extensionRegistry) => {
13703
+ this.activeExtensionRegistry = extensionRegistry;
13704
+ this.syncPluginRuntimeRegistrations(extensionRegistry);
13705
+ };
13706
+ dispose = () => {
13707
+ for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
13708
+ this.pluginRuntimeScopes.clear();
13709
+ this.activeExtensionRegistry = void 0;
13710
+ this.pluginRuntimeSnapshotKey = "";
13711
+ };
13712
+ syncPluginRuntimeRegistrations = (extensionRegistry) => {
13713
+ const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
13714
+ if (nextSnapshotKey === this.pluginRuntimeSnapshotKey) return;
13715
+ this.pluginRuntimeSnapshotKey = nextSnapshotKey;
13716
+ for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
13717
+ this.pluginRuntimeScopes.clear();
13718
+ for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
13719
+ if (RESERVED_BUILTIN_RUNTIME_KINDS.has(registration.kind.trim().toLowerCase())) continue;
13720
+ const pluginId = registration.pluginId.trim() || registration.kind;
13721
+ const scope = this.pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
13722
+ this.pluginRuntimeScopes.set(pluginId, scope);
13723
+ const createRuntimeForEntry = registration.createRuntimeForEntry;
13724
+ scope.add(this.runtimeRegistry.register({
13725
+ kind: registration.kind,
13726
+ label: registration.label,
13727
+ createRuntime: createRuntimeFactory(registration),
13728
+ createRuntimeForEntry: createRuntimeForEntry ? ({ entry, runtimeParams }) => createRuntimeForEntry({
13729
+ entry,
13730
+ runtimeParams: {
13731
+ ...runtimeParams,
13732
+ sessionMetadata: {
13733
+ ...runtimeParams.sessionMetadata,
13734
+ runtime_type: entry.type
13735
+ }
13736
+ }
13737
+ }) : void 0,
13738
+ describeSessionType: registration.describeSessionType,
13739
+ describeSessionTypeForEntry: registration.describeSessionTypeForEntry
13740
+ }));
13741
+ }
13742
+ };
13743
+ resolveActiveExtensionRegistry = () => this.activeExtensionRegistry ?? this.getExtensionRegistry?.();
13744
+ };
13745
+ //#endregion
13746
+ //#region src/cli/runtime-state/llm-usage-record.ts
12502
13747
  var LlmUsageRecordFactory = class {
12503
13748
  create = (params) => {
12504
13749
  const { observedAt, source, model, usage: rawUsage } = params;
@@ -12520,6 +13765,9 @@ var LlmUsageRecordFactory = class {
12520
13765
  }
12521
13766
  return next;
12522
13767
  };
13768
+ hasTelemetry = (usage) => {
13769
+ return Object.keys(usage).length > 0;
13770
+ };
12523
13771
  buildSummary = (usage) => {
12524
13772
  const promptTokens = usage.prompt_tokens ?? usage.input_tokens ?? 0;
12525
13773
  const completionTokens = usage.completion_tokens ?? usage.output_tokens ?? 0;
@@ -12550,6 +13798,7 @@ var LlmUsageRecorder = class {
12550
13798
  }
12551
13799
  record = (params) => {
12552
13800
  const record = this.recordFactory.create(params);
13801
+ if (!this.recordFactory.hasTelemetry(record.usage)) return null;
12553
13802
  this.snapshotStore.write(record);
12554
13803
  this.historyStore.append(record);
12555
13804
  return record;
@@ -12566,45 +13815,15 @@ var LlmUsageRecorder = class {
12566
13815
  };
12567
13816
  const llmUsageRecorder = new LlmUsageRecorder();
12568
13817
  //#endregion
12569
- //#region src/cli/commands/ncp/create-ui-ncp-agent.ts
12570
- const CODEX_RUNTIME_KIND = "codex";
12571
- const CODEX_DIRECT_RUNTIME_BACKEND = "codex-sdk";
13818
+ //#region src/cli/commands/ncp/create-ui-ncp-agent.service.ts
12572
13819
  function isRecord(value) {
12573
13820
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
12574
13821
  }
12575
- function decorateCodexRuntimeFactoryParams(runtimeParams, backend) {
12576
- const nextSessionMetadata = {
12577
- ...runtimeParams.sessionMetadata,
12578
- session_type: CODEX_RUNTIME_KIND,
12579
- codex_runtime_backend: backend
12580
- };
12581
- const setSessionMetadata = (nextMetadata) => {
12582
- runtimeParams.setSessionMetadata({
12583
- ...nextMetadata,
12584
- session_type: CODEX_RUNTIME_KIND,
12585
- codex_runtime_backend: backend
12586
- });
12587
- };
12588
- setSessionMetadata(nextSessionMetadata);
12589
- return {
12590
- ...runtimeParams,
12591
- sessionMetadata: nextSessionMetadata,
12592
- setSessionMetadata
12593
- };
12594
- }
12595
13822
  function resolveNativeReasoningNormalizationMode(params) {
12596
- const runtimeEntry = params.config.ui.ncp.runtimes.native;
13823
+ const runtimeEntry = params.config.agents.runtimes.entries.native?.config ?? params.config.ui.ncp.runtimes.native;
12597
13824
  const runtimeMetadata = isRecord(runtimeEntry) ? runtimeEntry : {};
12598
13825
  return readAssistantReasoningNormalizationModeFromMetadata(params.sessionMetadata) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalizationMode) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization_mode) ?? "think-tags";
12599
13826
  }
12600
- function buildPluginRuntimeSnapshotKey(extensionRegistry) {
12601
- return (extensionRegistry?.ncpAgentRuntimes ?? []).map((registration) => [
12602
- registration.pluginId,
12603
- registration.kind,
12604
- registration.label,
12605
- registration.source
12606
- ].join(":")).join("|");
12607
- }
12608
13827
  async function createMcpRuntimeSupport(getConfig) {
12609
13828
  let currentMcpConfig = getConfig();
12610
13829
  const mcpRegistryService = new McpRegistryService({
@@ -12629,7 +13848,7 @@ async function createMcpRuntimeSupport(getConfig) {
12629
13848
  }
12630
13849
  };
12631
13850
  }
12632
- function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker) {
13851
+ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker, sessionSearchRuntimeSupport) {
12633
13852
  const observedProviderManager = new ObservedProviderManager(params.providerManager, new LlmUsageObserver(llmUsageRecorder, "ui-ncp"));
12634
13853
  return ({ stateManager, sessionMetadata, setSessionMetadata }) => {
12635
13854
  const reasoningNormalizationMode = resolveNativeReasoningNormalizationMode({
@@ -12647,7 +13866,11 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
12647
13866
  getExtensionRegistry: params.getExtensionRegistry,
12648
13867
  sessionCreationService,
12649
13868
  sessionRequestBroker,
12650
- getAdditionalTools: (context) => [...createAssetTools({ assetStore }), ...mcpToolRegistryAdapter.listToolsForRun({ agentId: context.agentId })]
13869
+ getAdditionalTools: (context) => [
13870
+ ...createAssetTools({ assetStore }),
13871
+ ...mcpToolRegistryAdapter.listToolsForRun({ agentId: context.agentId }),
13872
+ sessionSearchRuntimeSupport.createTool({ currentSessionId: context.sessionId })
13873
+ ]
12651
13874
  });
12652
13875
  return new DefaultNcpAgentRuntime({
12653
13876
  contextBuilder: new NextclawNcpContextBuilder({
@@ -12664,93 +13887,121 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
12664
13887
  });
12665
13888
  };
12666
13889
  }
12667
- function createCodexAwareRuntimeFactory(params) {
12668
- return (runtimeParams) => {
12669
- const decoratedRuntimeParams = decorateCodexRuntimeFactoryParams(runtimeParams, CODEX_DIRECT_RUNTIME_BACKEND);
12670
- return params.registration.createRuntime(decoratedRuntimeParams);
12671
- };
12672
- }
12673
- function resolveRegisteredRuntimeFactory(params) {
12674
- return params.registration.kind === CODEX_RUNTIME_KIND ? createCodexAwareRuntimeFactory(params) : params.registration.createRuntime;
13890
+ function createResolveOpenAiToolsForRuntime(params) {
13891
+ const toolRegistry = new NextclawNcpToolRegistry({
13892
+ bus: params.bus,
13893
+ providerManager: params.providerManager,
13894
+ sessionManager: params.sessionManager,
13895
+ cronService: params.cronService,
13896
+ gatewayController: params.gatewayController,
13897
+ getConfig: params.getConfig,
13898
+ getExtensionRegistry: params.getExtensionRegistry,
13899
+ sessionCreationService: params.sessionCreationService,
13900
+ sessionRequestBroker: params.sessionRequestBroker,
13901
+ getAdditionalTools: (context) => [
13902
+ ...createAssetTools({ assetStore: params.assetStore }),
13903
+ ...params.toolRegistryAdapter.listToolsForRun({ agentId: context.agentId }),
13904
+ params.sessionSearchRuntimeSupport.createTool({ currentSessionId: context.sessionId })
13905
+ ]
13906
+ });
13907
+ const contextBuilder = new NextclawNcpContextBuilder({
13908
+ sessionManager: params.sessionManager,
13909
+ toolRegistry,
13910
+ getConfig: params.getConfig,
13911
+ resolveMessageToolHints: params.resolveMessageToolHints,
13912
+ assetStore: params.assetStore
13913
+ });
13914
+ return (input) => contextBuilder.prepare(input).tools;
12675
13915
  }
12676
- var PluginRuntimeRegistrationController = class {
12677
- pluginRuntimeScopes = /* @__PURE__ */ new Map();
12678
- pluginRuntimeSnapshotKey = "";
12679
- activeExtensionRegistry;
12680
- constructor(runtimeRegistry, getExtensionRegistry) {
12681
- this.runtimeRegistry = runtimeRegistry;
12682
- this.getExtensionRegistry = getExtensionRegistry;
12683
- }
12684
- syncPluginRuntimeRegistrations = (extensionRegistry) => {
12685
- const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
12686
- if (nextSnapshotKey === this.pluginRuntimeSnapshotKey) return;
12687
- this.pluginRuntimeSnapshotKey = nextSnapshotKey;
12688
- for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
12689
- this.pluginRuntimeScopes.clear();
12690
- for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
12691
- const pluginId = registration.pluginId.trim() || registration.kind;
12692
- const scope = this.pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
12693
- this.pluginRuntimeScopes.set(pluginId, scope);
12694
- scope.add(this.runtimeRegistry.register({
12695
- kind: registration.kind,
12696
- label: registration.label,
12697
- createRuntime: resolveRegisteredRuntimeFactory({ registration }),
12698
- describeSessionType: registration.describeSessionType
12699
- }));
12700
- }
12701
- };
12702
- resolveActiveExtensionRegistry = () => this.activeExtensionRegistry ?? this.getExtensionRegistry?.();
12703
- refreshPluginRuntimeRegistrations = () => {
12704
- this.syncPluginRuntimeRegistrations(this.resolveActiveExtensionRegistry());
12705
- };
12706
- applyExtensionRegistry = (extensionRegistry) => {
12707
- this.activeExtensionRegistry = extensionRegistry;
12708
- this.syncPluginRuntimeRegistrations(extensionRegistry);
12709
- };
12710
- dispose = () => {
12711
- for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
12712
- this.pluginRuntimeScopes.clear();
12713
- this.activeExtensionRegistry = void 0;
12714
- this.pluginRuntimeSnapshotKey = "";
12715
- };
12716
- };
12717
13916
  async function createUiNcpAgent(params) {
12718
- const sessionStore = new NextclawAgentSessionStore(params.sessionManager, { onSessionUpdated: params.onSessionUpdated });
13917
+ const { getConfig, getExtensionRegistry, globalEventBus, onSessionRunStatusChanged, onSessionUpdated, providerManager, resolveMessageToolHints, sessionManager } = params;
13918
+ const sessionSearchRuntimeSupport = new SessionSearchRuntimeSupport({
13919
+ sessionManager,
13920
+ onSessionUpdated,
13921
+ databasePath: join(getDataDir(), "session-search.db")
13922
+ });
13923
+ const sessionStore = new NextclawAgentSessionStore(sessionManager, { onSessionUpdated: sessionSearchRuntimeSupport.handleSessionUpdated });
12719
13924
  const runtimeRegistry = new UiNcpRuntimeRegistry();
12720
- const { toolRegistryAdapter, applyMcpConfig, dispose: disposeMcpRuntimeSupport } = await createMcpRuntimeSupport(params.getConfig);
13925
+ const { toolRegistryAdapter, applyMcpConfig, dispose: disposeMcpRuntimeSupport } = await createMcpRuntimeSupport(getConfig);
12721
13926
  const assetStore = new LocalAssetStore({ rootDir: join(getDataDir(), "assets") });
12722
13927
  let backend = null;
12723
- const sessionCreationService = new SessionCreationService(params.sessionManager, params.getConfig, params.onSessionUpdated);
12724
- const createNativeRuntime = createNativeRuntimeFactory(params, toolRegistryAdapter, assetStore, sessionCreationService, new SessionRequestBroker(params.sessionManager, sessionCreationService, new SessionRequestDeliveryService(() => backend), () => backend, params.onSessionUpdated));
13928
+ const sessionCreationService = new SessionCreationService(sessionManager, getConfig, sessionSearchRuntimeSupport.handleSessionUpdated);
13929
+ const sessionRequestBroker = new SessionRequestBroker(sessionManager, sessionCreationService, new SessionRequestDeliveryService(() => backend), () => backend, sessionSearchRuntimeSupport.handleSessionUpdated);
13930
+ const learningLoopRuntime = new LearningLoopRuntimeService({
13931
+ sessionManager,
13932
+ sessionRequestBroker,
13933
+ onSessionUpdated: sessionSearchRuntimeSupport.handleSessionUpdated,
13934
+ globalEventBus,
13935
+ resolveLearningLoopConfig: () => readLearningLoopRuntimeConfig(getConfig())
13936
+ });
13937
+ const createNativeRuntime = createNativeRuntimeFactory({
13938
+ ...params,
13939
+ getConfig,
13940
+ getExtensionRegistry,
13941
+ onSessionUpdated,
13942
+ providerManager,
13943
+ resolveMessageToolHints,
13944
+ sessionManager
13945
+ }, toolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker, sessionSearchRuntimeSupport);
12725
13946
  runtimeRegistry.register({
12726
13947
  kind: "native",
12727
13948
  label: "Native",
12728
13949
  createRuntime: createNativeRuntime
12729
13950
  });
12730
- const pluginRuntimeRegistrationController = new PluginRuntimeRegistrationController(runtimeRegistry, params.getExtensionRegistry);
13951
+ const builtinNarpRegistrations = new BuiltinNarpRuntimeRegistrationService(getConfig).registerInto(runtimeRegistry);
13952
+ const pluginRuntimeRegistrationController = new PluginRuntimeRegistrationController(runtimeRegistry, getExtensionRegistry);
13953
+ const refreshConfiguredRuntimeEntries = () => {
13954
+ runtimeRegistry.applyEntries(resolveUiNcpRuntimeEntries({
13955
+ config: getConfig(),
13956
+ providerKinds: runtimeRegistry.listProviderKinds()
13957
+ }));
13958
+ };
12731
13959
  pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
13960
+ refreshConfiguredRuntimeEntries();
13961
+ await sessionSearchRuntimeSupport.initialize();
12732
13962
  backend = new DefaultNcpAgentBackend({
12733
13963
  endpointId: "nextclaw-ui-agent",
12734
13964
  sessionStore,
12735
- onSessionRunStatusChanged: params.onSessionRunStatusChanged,
13965
+ onSessionRunStatusChanged,
12736
13966
  createRuntime: (runtimeParams) => {
12737
13967
  pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
13968
+ refreshConfiguredRuntimeEntries();
12738
13969
  return runtimeRegistry.createRuntime({
12739
13970
  ...runtimeParams,
12740
- resolveAssetContentPath: (assetUri) => assetStore.resolveContentPath(assetUri)
13971
+ resolveAssetContentPath: (assetUri) => assetStore.resolveContentPath(assetUri),
13972
+ resolveTools: createResolveOpenAiToolsForRuntime({
13973
+ bus: params.bus,
13974
+ providerManager,
13975
+ sessionManager,
13976
+ cronService: params.cronService,
13977
+ gatewayController: params.gatewayController,
13978
+ getConfig,
13979
+ getExtensionRegistry,
13980
+ resolveMessageToolHints,
13981
+ assetStore,
13982
+ toolRegistryAdapter,
13983
+ sessionCreationService,
13984
+ sessionRequestBroker,
13985
+ sessionSearchRuntimeSupport
13986
+ })
12741
13987
  });
12742
13988
  }
12743
13989
  });
12744
13990
  await backend.start();
13991
+ learningLoopRuntime.attachBackend(backend);
12745
13992
  return createUiNcpAgentHandle({
12746
13993
  backend,
12747
13994
  runtimeRegistry,
12748
13995
  refreshPluginRuntimeRegistrations: pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations,
13996
+ refreshConfiguredRuntimeEntries,
12749
13997
  applyExtensionRegistry: pluginRuntimeRegistrationController.applyExtensionRegistry,
12750
13998
  applyMcpConfig,
12751
13999
  dispose: async () => {
14000
+ learningLoopRuntime.dispose();
14001
+ for (const registration of builtinNarpRegistrations) registration.dispose();
12752
14002
  pluginRuntimeRegistrationController.dispose();
12753
14003
  await backend?.stop();
14004
+ await sessionSearchRuntimeSupport.dispose();
12754
14005
  await disposeMcpRuntimeSupport();
12755
14006
  },
12756
14007
  assetStore
@@ -13589,6 +14840,66 @@ async function runPromptOverNcp(params) {
13589
14840
  };
13590
14841
  }
13591
14842
  //#endregion
14843
+ //#region src/cli/commands/ncp/runtime/runner/ncp-event-stream.ts
14844
+ async function* streamPromptOverNcp(params) {
14845
+ const { abortSignal, agent, attachments, content, metadata, onEvent, sessionId } = params;
14846
+ const message = await buildNcpUserMessage({
14847
+ sessionId,
14848
+ content,
14849
+ attachments,
14850
+ metadata,
14851
+ assetApi: agent.assetApi
14852
+ });
14853
+ for await (const event of agent.runApi.send({
14854
+ sessionId,
14855
+ message,
14856
+ metadata
14857
+ }, { ...abortSignal ? { signal: abortSignal } : {} })) {
14858
+ onEvent?.(event);
14859
+ yield event;
14860
+ }
14861
+ }
14862
+ //#endregion
14863
+ //#region src/cli/commands/ncp/runtime/runner/channel-reply.ts
14864
+ function readString(value) {
14865
+ if (typeof value !== "string") return;
14866
+ return value.trim() || void 0;
14867
+ }
14868
+ function isReplyCapableChannel(channel) {
14869
+ return Boolean(channel) && typeof channel.consumeNcpReply === "function";
14870
+ }
14871
+ function resolveChannelReplyRoute(params) {
14872
+ if (!isReplyCapableChannel(params.channel)) return null;
14873
+ const metadata = structuredClone(params.message.metadata ?? {});
14874
+ const accountId = readString(params.route.accountId) ?? readString(metadata.accountId) ?? readString(metadata.account_id);
14875
+ return {
14876
+ target: {
14877
+ conversationId: params.message.chatId,
14878
+ ...accountId ? { accountId } : {},
14879
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
14880
+ },
14881
+ channel: params.channel
14882
+ };
14883
+ }
14884
+ async function dispatchChannelReplyRoute(params) {
14885
+ const input = {
14886
+ target: {
14887
+ ...params.route.target,
14888
+ ...params.agent.assetApi?.resolveContentPath ? { resolveAssetContentPath: params.agent.assetApi.resolveContentPath } : {}
14889
+ },
14890
+ eventStream: streamPromptOverNcp({
14891
+ agent: params.agent,
14892
+ sessionId: params.sessionId,
14893
+ content: params.content,
14894
+ attachments: params.attachments,
14895
+ metadata: params.metadata,
14896
+ abortSignal: params.abortSignal,
14897
+ onEvent: params.onEvent
14898
+ })
14899
+ };
14900
+ await params.route.channel.consumeNcpReply(input);
14901
+ }
14902
+ //#endregion
13592
14903
  //#region src/cli/commands/ncp/runtime/nextclaw-ncp-dispatch.ts
13593
14904
  function normalizeOptionalString(value) {
13594
14905
  if (typeof value !== "string") return;
@@ -13627,67 +14938,9 @@ function buildRunMetadata(params) {
13627
14938
  sender_id: params.message.senderId
13628
14939
  };
13629
14940
  }
13630
- function parseCommandOptionValue(type, rawValue) {
13631
- const value = rawValue.trim();
13632
- if (!value) return;
13633
- if (type === "number") {
13634
- const parsed = Number(value);
13635
- return Number.isFinite(parsed) ? parsed : void 0;
13636
- }
13637
- if (type === "boolean") {
13638
- const lowered = value.toLowerCase();
13639
- if ([
13640
- "1",
13641
- "true",
13642
- "yes",
13643
- "on"
13644
- ].includes(lowered)) return true;
13645
- if ([
13646
- "0",
13647
- "false",
13648
- "no",
13649
- "off"
13650
- ].includes(lowered)) return false;
13651
- return;
13652
- }
13653
- return value;
13654
- }
13655
- function parseCommandArgsFromText(commandName, rawTail, specs) {
13656
- if (!rawTail) return {};
13657
- const options = specs.find((item) => item.name.trim().toLowerCase() === commandName)?.options;
13658
- if (!options || options.length === 0) return {};
13659
- const tokens = rawTail.split(/\s+/).filter(Boolean);
13660
- const args = {};
13661
- let cursor = 0;
13662
- for (let index = 0; index < options.length; index += 1) {
13663
- if (cursor >= tokens.length) break;
13664
- const option = options[index];
13665
- const isLastOption = index === options.length - 1;
13666
- const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
13667
- cursor += isLastOption ? tokens.length - cursor : 1;
13668
- const parsedValue = parseCommandOptionValue(option.type, rawValue);
13669
- if (parsedValue !== void 0) args[option.name] = parsedValue;
13670
- }
13671
- return args;
13672
- }
13673
14941
  async function executeSlashCommandMaybe(params) {
13674
- const trimmed = params.rawContent.trim();
13675
- if (!trimmed.startsWith("/")) return null;
13676
- const registry = new CommandRegistry(params.config, params.sessionManager);
13677
- const executeText = registry.executeText;
13678
- if (typeof executeText === "function") return (await executeText.call(registry, params.rawContent, {
13679
- channel: params.channel,
13680
- chatId: params.chatId,
13681
- senderId: "user",
13682
- sessionKey: params.sessionKey
13683
- }))?.content ?? null;
13684
- const commandRaw = trimmed.slice(1).trim();
13685
- if (!commandRaw) return null;
13686
- const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
13687
- const commandName = nameToken.trim().toLowerCase();
13688
- if (!commandName) return null;
13689
- const args = parseCommandArgsFromText(commandName, restTokens.join(" ").trim(), registry.listSlashCommands());
13690
- return (await registry.execute(commandName, args, {
14942
+ if (!params.rawContent.trim().startsWith("/")) return null;
14943
+ return (await new CommandRegistry(params.config, params.sessionManager).executeText(params.rawContent, {
13691
14944
  channel: params.channel,
13692
14945
  chatId: params.chatId,
13693
14946
  senderId: "user",
@@ -13712,6 +14965,48 @@ function formatUserFacingError(error, maxChars = 320) {
13712
14965
  if (normalized.length <= maxChars) return normalized;
13713
14966
  return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
13714
14967
  }
14968
+ async function dispatchChannelReplyRouteMaybe(params, context) {
14969
+ if (context.message.channel === "system" || !params.getChannels) return false;
14970
+ const replyRoute = resolveChannelReplyRoute({
14971
+ channel: params.getChannels().getChannel(context.message.channel),
14972
+ message: context.message,
14973
+ route: context.route
14974
+ });
14975
+ if (!replyRoute) return false;
14976
+ await dispatchChannelReplyRoute({
14977
+ agent: context.agent,
14978
+ route: replyRoute,
14979
+ sessionId: context.route.sessionKey,
14980
+ content: context.message.content,
14981
+ attachments: context.message.attachments,
14982
+ metadata: context.runMetadata
14983
+ });
14984
+ return true;
14985
+ }
14986
+ async function publishLegacyReply(params, context, result) {
14987
+ if (context.message.channel === "system") {
14988
+ params.onSystemSessionUpdated?.({
14989
+ sessionKey: context.route.sessionKey,
14990
+ message: context.message
14991
+ });
14992
+ return;
14993
+ }
14994
+ if (!result.text.trim()) {
14995
+ await params.bus.publishOutbound(createTypingStopControlMessage(context.message));
14996
+ return;
14997
+ }
14998
+ await params.bus.publishOutbound({
14999
+ channel: context.message.channel,
15000
+ chatId: context.message.chatId,
15001
+ content: result.text,
15002
+ media: [],
15003
+ metadata: buildRunMetadata({
15004
+ message: context.message,
15005
+ route: context.route,
15006
+ metadata: result.completedMessage.metadata
15007
+ })
15008
+ });
15009
+ }
13715
15010
  async function dispatchPromptOverNcp(params) {
13716
15011
  const { message, route } = resolveDirectRoute({
13717
15012
  config: params.config,
@@ -13750,6 +15045,7 @@ async function dispatchPromptOverNcp(params) {
13750
15045
  async function runGatewayInboundLoop(params) {
13751
15046
  while (true) {
13752
15047
  const message = await params.bus.consumeInbound();
15048
+ let usedChannelReplyRoute = false;
13753
15049
  try {
13754
15050
  const explicitSessionKey = normalizeOptionalString(message.metadata.session_key_override);
13755
15051
  const forcedAgentId = normalizeOptionalString(message.metadata.target_agent_id);
@@ -13759,46 +15055,36 @@ async function runGatewayInboundLoop(params) {
13759
15055
  sessionKeyOverride: explicitSessionKey
13760
15056
  });
13761
15057
  const agent = requireNcpAgent(params.resolveNcpAgent, "gateway dispatch");
15058
+ const runMetadata = buildRunMetadata({
15059
+ message,
15060
+ route
15061
+ });
15062
+ const context = {
15063
+ agent,
15064
+ message,
15065
+ route,
15066
+ runMetadata
15067
+ };
15068
+ if (await dispatchChannelReplyRouteMaybe(params, context)) {
15069
+ usedChannelReplyRoute = true;
15070
+ continue;
15071
+ }
13762
15072
  if (message.channel !== "system") await params.bus.publishOutbound(createAssistantStreamResetControlMessage(message));
13763
- const result = await runPromptOverNcp({
15073
+ await publishLegacyReply(params, context, await runPromptOverNcp({
13764
15074
  agent,
13765
15075
  sessionId: route.sessionKey,
13766
15076
  content: message.content,
13767
15077
  attachments: message.attachments,
13768
- metadata: buildRunMetadata({
13769
- message,
13770
- route
13771
- }),
15078
+ metadata: runMetadata,
13772
15079
  onAssistantDelta: message.channel !== "system" ? (delta) => {
13773
15080
  if (!delta) return;
13774
15081
  params.bus.publishOutbound(createAssistantStreamDeltaControlMessage(message, delta));
13775
15082
  } : void 0,
13776
15083
  missingCompletedMessageError: `session "${route.sessionKey}" completed without a final assistant message`,
13777
15084
  runErrorMessage: `session "${route.sessionKey}" failed`
13778
- });
13779
- if (message.channel === "system") {
13780
- params.onSystemSessionUpdated?.({
13781
- sessionKey: route.sessionKey,
13782
- message
13783
- });
13784
- continue;
13785
- }
13786
- if (!result.text.trim()) {
13787
- await params.bus.publishOutbound(createTypingStopControlMessage(message));
13788
- continue;
13789
- }
13790
- await params.bus.publishOutbound({
13791
- channel: message.channel,
13792
- chatId: message.chatId,
13793
- content: result.text,
13794
- media: [],
13795
- metadata: buildRunMetadata({
13796
- message,
13797
- route,
13798
- metadata: result.completedMessage.metadata
13799
- })
13800
- });
15085
+ }));
13801
15086
  } catch (error) {
15087
+ if (usedChannelReplyRoute) continue;
13802
15088
  await params.bus.publishOutbound({
13803
15089
  channel: message.channel,
13804
15090
  chatId: message.chatId,
@@ -13945,6 +15231,7 @@ async function startUiShell(params) {
13945
15231
  async function startDeferredGatewayStartup(params) {
13946
15232
  const { uiStartup, deferredNcpSessionService, bus, sessionManager, providerManager, cronService, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints, hydrateCapabilities, startPluginGateways, startChannels, wakeFromRestartSentinel, onNcpAgentReady, publishSessionChange } = params;
13947
15233
  logStartupTrace("service.deferred_startup.begin");
15234
+ if (hydrateCapabilities) await measureStartupAsync("service.deferred_startup.hydrate_capabilities", hydrateCapabilities);
13948
15235
  try {
13949
15236
  const ncpAgent = await measureStartupAsync("service.deferred_startup.create_ui_ncp_agent", async () => await createUiNcpAgent({
13950
15237
  bus,
@@ -13975,7 +15262,6 @@ async function startDeferredGatewayStartup(params) {
13975
15262
  } catch (error) {
13976
15263
  console.error(`UI NCP agent startup failed: ${error instanceof Error ? error.message : String(error)}`);
13977
15264
  }
13978
- if (hydrateCapabilities) await measureStartupAsync("service.deferred_startup.hydrate_capabilities", hydrateCapabilities);
13979
15265
  await measureStartupAsync("service.deferred_startup.start_plugin_gateways", startPluginGateways);
13980
15266
  await measureStartupAsync("service.deferred_startup.start_channels", startChannels);
13981
15267
  await measureStartupAsync("service.deferred_startup.wake_restart_sentinel", wakeFromRestartSentinel);
@@ -14003,6 +15289,7 @@ async function runConfiguredGatewayRuntime(params) {
14003
15289
  sessionManager: params.gateway.sessionManager,
14004
15290
  getConfig: params.getConfig,
14005
15291
  resolveNcpAgent: params.getLiveUiNcpAgent,
15292
+ getChannels: () => params.gateway.reloader.getChannels(),
14006
15293
  onSystemSessionUpdated: ({ sessionKey }) => onSystemSessionUpdated({ sessionKey })
14007
15294
  }),
14008
15295
  startDeferredStartup: () => startDeferredGatewayStartup({
@@ -16149,6 +17436,42 @@ function registerAgentsCommands(program, runtime) {
16149
17436
  agents.command("remove <agentId>").description("Remove an agent").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime.agentsRemove(agentId, opts));
16150
17437
  }
16151
17438
  //#endregion
17439
+ //#region src/cli/commands/learning-loop/register-learning-loop-commands.ts
17440
+ function readLearningLoopThresholdOrExit(value) {
17441
+ const threshold = Number.parseInt(value, 10);
17442
+ if (!Number.isInteger(threshold) || threshold < 1) {
17443
+ console.error(`Invalid learning loop threshold: ${value}. Expected an integer >= 1.`);
17444
+ process.exit(1);
17445
+ }
17446
+ return threshold;
17447
+ }
17448
+ function registerLearningLoopCommands(program, runtime) {
17449
+ const learningLoop = program.command("learning-loop").description("Manage the learning loop");
17450
+ learningLoop.command("status").description("Show current learning loop settings").option("--json", "Output JSON", false).action((opts) => {
17451
+ const status = readLearningLoopRuntimeConfig(loadConfig());
17452
+ if (opts.json) {
17453
+ console.log(JSON.stringify(status, null, 2));
17454
+ return;
17455
+ }
17456
+ console.log("Learning loop");
17457
+ console.log(` enabled: ${status.enabled}`);
17458
+ console.log(` toolCallThreshold: ${status.toolCallThreshold}`);
17459
+ });
17460
+ learningLoop.command("enable").description("Enable the learning loop").action(async () => {
17461
+ await runtime.configSet("agents.learningLoop.enabled", "true", { json: true });
17462
+ console.log("✓ Enabled learning loop.");
17463
+ });
17464
+ learningLoop.command("disable").description("Disable the learning loop").action(async () => {
17465
+ await runtime.configSet("agents.learningLoop.enabled", "false", { json: true });
17466
+ console.log("✓ Disabled learning loop.");
17467
+ });
17468
+ learningLoop.command("threshold <count>").description("Set the tool-call threshold that triggers a learning-loop review").action(async (count) => {
17469
+ const threshold = readLearningLoopThresholdOrExit(count);
17470
+ await runtime.configSet("agents.learningLoop.toolCallThreshold", String(threshold), { json: true });
17471
+ console.log(`✓ Updated learning loop tool-call threshold to ${threshold}.`);
17472
+ });
17473
+ }
17474
+ //#endregion
16152
17475
  //#region src/cli/index.ts
16153
17476
  logStartupTrace("cli.index.module_loaded");
16154
17477
  const program = new Command();
@@ -16157,7 +17480,7 @@ const llmUsageCommands = new LlmUsageCommands();
16157
17480
  program.name(APP_NAME).description(`${LOGO} ${APP_NAME} - ${APP_TAGLINE}`).version(getPackageVersion$1(), "-v, --version", "show version");
16158
17481
  program.command("onboard").description(`Initialize ${APP_NAME} configuration and workspace`).action(async () => runtime.onboard());
16159
17482
  program.command("init").description(`Initialize ${APP_NAME} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
16160
- program.command("login").description("Login to NextClaw platform and save token into providers.nextclaw.apiKey").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email").option("--password <password>", "Login password").action(async (opts) => runtime.login(opts));
17483
+ program.command("login").description("Sign in to NextClaw Platform and save the platform token locally (browser flow by default)").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email for direct password sign-in").option("--password <password>", "Login password for direct password sign-in").option("--no-open", "Do not open the browser automatically").action(async (opts) => runtime.login(opts));
16161
17484
  registerRemoteCommands(program, runtime.remote);
16162
17485
  program.command("gateway").description(`Start the ${APP_NAME} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
16163
17486
  program.command("ui").description(`Start the ${APP_NAME} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
@@ -16197,6 +17520,7 @@ const config = program.command("config").description("Manage config values");
16197
17520
  config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
16198
17521
  config.command("set <path> <value>").description("Set a config value by dot path").option("--json", "Parse value as JSON", false).action((path, value, opts) => runtime.configSet(path, value, opts));
16199
17522
  config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
17523
+ registerLearningLoopCommands(program, runtime);
16200
17524
  const mcp = program.command("mcp").description("Manage MCP servers");
16201
17525
  mcp.command("list").description("List configured MCP servers").option("--json", "Output JSON", false).action((opts) => runtime.mcpList(opts));
16202
17526
  mcp.command("add <name> [command...]").description("Add an MCP server (stdio by default, or use --transport http|sse)").allowUnknownOption(true).option("--transport <type>", "Transport type: stdio|http|sse", "stdio").option("--url <url>", "HTTP/SSE endpoint URL").option("--header <key=value>", "Transport header (repeatable)", withRepeatableTag, []).option("--env <key=value>", "stdio env var (repeatable)", withRepeatableTag, []).option("--cwd <dir>", "stdio working directory").option("--timeout-ms <ms>", "HTTP/SSE timeout in milliseconds").option("--stderr <mode>", "stdio stderr handling: inherit|pipe|ignore", "pipe").option("--disabled", "Create the server in disabled state", false).option("--all-agents", "Expose this server to all agents", false).option("--agent <id>", "Expose to an agent id (repeatable)", withRepeatableTag, []).option("--insecure", "Disable TLS verification for HTTP/SSE", false).action(async (name, command, opts) => runtime.mcpAdd(name, command ?? [], opts));