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