nextclaw 0.17.10 → 0.17.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1760 -346
- package/package.json +14 -12
- package/resources/USAGE.md +2 -1
- package/ui-dist/assets/ChannelsList-Ita2Zm1_.js +8 -0
- package/ui-dist/assets/{DocBrowser-Cse_F8Nn.js → DocBrowser-6ReNjvzF.js} +1 -1
- package/ui-dist/assets/DocBrowser-BNwbPHf4.js +1 -0
- package/ui-dist/assets/{DocBrowserContext-Bai1WU2H.js → DocBrowserContext-B6SpA7Qs.js} +1 -1
- package/ui-dist/assets/{LogoBadge-BdxMPc9v.js → LogoBadge-ByNLYg65.js} +1 -1
- package/ui-dist/assets/MarketplacePage-CjX2MWww.js +1 -0
- package/ui-dist/assets/{MarketplacePage-BbpAkllU.js → MarketplacePage-D0sDlYX4.js} +1 -1
- package/ui-dist/assets/McpMarketplacePage-BGKJm1sJ.js +40 -0
- package/ui-dist/assets/{ModelConfig-3GLqQ5GY.js → ModelConfig-BzZenCH-.js} +1 -1
- package/ui-dist/assets/{ProviderScopedModelInput-BYNouw-i.js → ProviderScopedModelInput-Da7khnBA.js} +1 -1
- package/ui-dist/assets/{ProvidersList-BR1gJ4Dm.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-DTeJvp8m.js → SearchConfig-BGkzXQP-.js} +1 -1
- package/ui-dist/assets/{SecretsConfig-CCYO6NcV.js → SecretsConfig-D281Rotl.js} +2 -2
- package/ui-dist/assets/{SessionsConfig-Du39vDgt.js → SessionsConfig-ChHQ7M5c.js} +2 -2
- package/ui-dist/assets/{app-query-client-Dr5d-K8d.js → app-query-client-VnFElj4E.js} +1 -1
- package/ui-dist/assets/{book-open-Da4OEPqB.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-CoFVxHXV.js → chunk-JZWAC4HX-DK5HPmIK.js} +1 -1
- package/ui-dist/assets/{client-CSk58DcF.js → client-_i4MU2bB.js} +1 -1
- package/ui-dist/assets/{config-D8KzikVB.js → config-DtIQwrHF.js} +1 -1
- package/ui-dist/assets/{createLucideIcon-83gaZMtv.js → createLucideIcon-BSeTgkZW.js} +1 -1
- package/ui-dist/assets/desktop-update-config-Dpcf4BKG.js +1 -0
- package/ui-dist/assets/{dist-toEYs-MZ.js → dist-6TrrnPCR.js} +1 -1
- package/ui-dist/assets/{dist-aTmhMDVh.js → dist-ccBFUi-o.js} +1 -1
- package/ui-dist/assets/download-BhDxnyvU.js +1 -0
- package/ui-dist/assets/{external-link-QQ0TC6X4.js → external-link-BgErLCNT.js} +1 -1
- package/ui-dist/assets/{hash-DaFBEkmi.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-BmHX_ayZ.js → infiniteQueryBehavior-ZDS92Qpp.js} +1 -1
- package/ui-dist/assets/loader-circle-ACM1s51e.js +1 -0
- package/ui-dist/assets/{logos-Dzlz30M3.js → logos-x89HbrZ4.js} +1 -1
- package/ui-dist/assets/{page-layout-D2eRufRQ.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-BSXxm5bj.js → popover-Bg1VoTZ6.js} +1 -1
- package/ui-dist/assets/{refresh-ccw-B3zMtN-_.js → refresh-ccw-DT98i__E.js} +1 -1
- package/ui-dist/assets/{refresh-cw-DlZkIHnJ.js → refresh-cw-C47QSEwg.js} +1 -1
- package/ui-dist/assets/rotate-cw-JtFzpNn6.js +1 -0
- package/ui-dist/assets/{save-Us9fg4Sj.js → save-3S6-H3Xw.js} +1 -1
- package/ui-dist/assets/search-3kFR_zh9.js +1 -0
- package/ui-dist/assets/{security-config-BGWYwxNr.js → security-config-BWaiARNk.js} +1 -1
- package/ui-dist/assets/{select-DLYqySQK.js → select-DJ2MUjBB.js} +1 -1
- package/ui-dist/assets/skeleton-ByQepn0M.js +1 -0
- package/ui-dist/assets/{status-dot-DGayudyB.js → status-dot-vbanNPFU.js} +1 -1
- package/ui-dist/assets/{switch-Dz2ScsKx.js → switch-BsLtHOH-.js} +1 -1
- package/ui-dist/assets/{tabs-custom-CdKyjiGk.js → tabs-custom-D3HYMt6k.js} +1 -1
- package/ui-dist/assets/{trash-2-Db-mZOZs.js → trash-2-G48scll7.js} +1 -1
- package/ui-dist/assets/{use-infinite-scroll-loader-DBJX5hj0.js → use-infinite-scroll-loader-DkNhD-42.js} +1 -1
- package/ui-dist/assets/{useConfirmDialog-DL0a-oGC.js → useConfirmDialog-BkvTN-vd.js} +1 -1
- package/ui-dist/assets/{useMutation-BdZm-9PL.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-D8p4OlM6.js +0 -8
- package/ui-dist/assets/ChatPage-A45t1Rmf.js +0 -58
- package/ui-dist/assets/DocBrowser-B2MpsnU9.js +0 -1
- package/ui-dist/assets/MarketplacePage-BNZ3Jx5d.js +0 -1
- package/ui-dist/assets/McpMarketplacePage-CxPFOgxv.js +0 -40
- package/ui-dist/assets/RemoteAccessPage-DyYVWsyK.js +0 -1
- package/ui-dist/assets/RuntimeConfig-ChdfK4Y_.js +0 -1
- package/ui-dist/assets/chat-session-display-CAlPrnlV.js +0 -1
- package/ui-dist/assets/desktop-update-config-CfoVwf-w.js +0 -1
- package/ui-dist/assets/i18n-C3jb83S6.js +0 -1
- package/ui-dist/assets/index-CE4N7ItL.css +0 -1
- package/ui-dist/assets/index-riX7Sg0_.js +0 -6
- package/ui-dist/assets/loader-circle-BjMg63eu.js +0 -1
- package/ui-dist/assets/plus-CIXME2pD.js +0 -1
- package/ui-dist/assets/search-B_Qr0f6C.js +0 -1
- package/ui-dist/assets/skeleton-CYQJazv6.js +0 -1
- package/ui-dist/assets/x-B8Tho_xC.js +0 -1
- /package/ui-dist/assets/{config-hints-GSUMvmSo.js → config-hints-BhTmc9P1.js} +0 -0
- /package/ui-dist/assets/{config-layout-CgBMG7OL.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, SessionManager, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createExternalCommandEnv, createTypingStopControlMessage, diffConfigPaths, expandHome, findEffectiveAgentProfile, getAppLogger, getConfigPath, getDataDir, getLoggingRuntime, getLogsPath, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveAppLogPath, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveLocalUiBaseUrl, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
|
|
4
|
+
import { APP_NAME, APP_TAGLINE, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, FileLogSink, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderManager, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createExternalCommandEnv, createGlobalTypedEventBus, createTypedEventKey, createTypingStopControlMessage, diffConfigPaths, expandHome, findEffectiveAgentProfile, getAppLogger, getConfigPath, getDataDir, getLoggingRuntime, getLogsPath, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveAppLogPath, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveLocalUiBaseUrl, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import fs, { appendFileSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
-
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import fs, { appendFileSync, closeSync, constants, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import path, { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
8
|
import { hostname, platform } from "node:os";
|
|
9
9
|
import { ensureUiBridgeSecret, startUiServer } from "@nextclaw/server";
|
|
10
10
|
import { addPluginLoadPath, buildPluginStatusReport, disablePluginInConfig, discoverPluginStatusReport, enablePluginInConfig, getPackageManifestExtensions, getPluginChannelBindings, getPluginUiMetadataFromRegistry, installPluginFromNpmSpec, installPluginFromPath, loadOpenClawPlugins, loadOpenClawPluginsProgressively, loadPluginManifest, mergePluginConfigView, recordPluginInstall, resolvePluginChannelMessageToolHints, resolveUninstallDirectoryTargets, setPluginRuntimeBridge, startPluginChannelGateways, stopPluginChannelGateways, toPluginConfigView, toPluginConfigView as toPluginConfigView$1, uninstallPlugin } from "@nextclaw/openclaw-compat";
|
|
@@ -15,6 +15,9 @@ import { createInterface } from "node:readline";
|
|
|
15
15
|
import { createServer, isIP } from "node:net";
|
|
16
16
|
import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds, listBuiltinProviders } from "@nextclaw/runtime";
|
|
17
17
|
import { McpDoctorFacade, McpMutationService, McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
|
|
18
|
+
import { access, readFile } from "node:fs/promises";
|
|
19
|
+
import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
|
|
20
|
+
import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRuntime } from "@nextclaw/nextclaw-ncp-runtime-stdio-client";
|
|
18
21
|
import { request } from "node:http";
|
|
19
22
|
import { request as request$1 } from "node:https";
|
|
20
23
|
import { setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -24,7 +27,7 @@ import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
|
|
|
24
27
|
import { NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
|
|
25
28
|
import { DefaultNcpAgentBackend, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
|
|
26
29
|
import { createHash, randomUUID } from "node:crypto";
|
|
27
|
-
import {
|
|
30
|
+
import { DatabaseSync } from "node:sqlite";
|
|
28
31
|
//#region \0rolldown/runtime.js
|
|
29
32
|
var __create = Object.create;
|
|
30
33
|
var __defProp = Object.defineProperty;
|
|
@@ -4711,17 +4714,24 @@ var LlmUsageQueryService = class {
|
|
|
4711
4714
|
let totalTokens = 0;
|
|
4712
4715
|
let totalCachedTokens = 0;
|
|
4713
4716
|
let cacheHitRecords = 0;
|
|
4717
|
+
let usageRecordCount = 0;
|
|
4718
|
+
let promptTokenRecordCount = 0;
|
|
4714
4719
|
for (const record of records) {
|
|
4720
|
+
if (Object.keys(record.usage).length > 0) usageRecordCount += 1;
|
|
4715
4721
|
totalPromptTokens += record.summary.promptTokens;
|
|
4716
4722
|
totalCompletionTokens += record.summary.completionTokens;
|
|
4717
4723
|
totalTokens += record.summary.totalTokens;
|
|
4718
4724
|
totalCachedTokens += record.summary.cachedTokens;
|
|
4725
|
+
if (record.summary.promptTokens > 0) promptTokenRecordCount += 1;
|
|
4719
4726
|
if (record.summary.cacheHit) cacheHitRecords += 1;
|
|
4720
4727
|
this.bumpCounter(sources, record.source);
|
|
4721
4728
|
this.bumpCounter(models, record.model ?? "unknown");
|
|
4722
4729
|
}
|
|
4723
4730
|
return {
|
|
4724
4731
|
totalRecords: records.length,
|
|
4732
|
+
usageRecordCount,
|
|
4733
|
+
emptyUsageRecordCount: records.length - usageRecordCount,
|
|
4734
|
+
promptTokenRecordCount,
|
|
4725
4735
|
oldestObservedAt: records[0]?.observedAt ?? null,
|
|
4726
4736
|
latestObservedAt: records.at(-1)?.observedAt ?? null,
|
|
4727
4737
|
totalPromptTokens,
|
|
@@ -4729,7 +4739,8 @@ var LlmUsageQueryService = class {
|
|
|
4729
4739
|
totalTokens,
|
|
4730
4740
|
totalCachedTokens,
|
|
4731
4741
|
cacheHitRecords,
|
|
4732
|
-
cacheHitRate:
|
|
4742
|
+
cacheHitRate: promptTokenRecordCount > 0 ? cacheHitRecords / promptTokenRecordCount : 0,
|
|
4743
|
+
tokenCacheRate: totalPromptTokens > 0 ? totalCachedTokens / totalPromptTokens : 0,
|
|
4733
4744
|
sources: this.toSortedCounts(sources),
|
|
4734
4745
|
models: this.toSortedCounts(models)
|
|
4735
4746
|
};
|
|
@@ -4892,13 +4903,17 @@ var LlmUsageCommands = class {
|
|
|
4892
4903
|
"LLM usage history stats",
|
|
4893
4904
|
`History path: ${this.queryService.historyPath}`,
|
|
4894
4905
|
`Records: ${stats.totalRecords}`,
|
|
4906
|
+
`Usage records: ${stats.usageRecordCount}`,
|
|
4907
|
+
`Empty usage records: ${stats.emptyUsageRecordCount}`,
|
|
4908
|
+
`Prompt-bearing records: ${stats.promptTokenRecordCount}`,
|
|
4895
4909
|
`Oldest observed at: ${stats.oldestObservedAt ?? "n/a"}`,
|
|
4896
4910
|
`Latest observed at: ${stats.latestObservedAt ?? "n/a"}`,
|
|
4897
4911
|
`Prompt tokens: ${stats.totalPromptTokens}`,
|
|
4898
4912
|
`Completion tokens: ${stats.totalCompletionTokens}`,
|
|
4899
4913
|
`Total tokens: ${stats.totalTokens}`,
|
|
4900
4914
|
`Cached tokens: ${stats.totalCachedTokens}`,
|
|
4901
|
-
`Cache
|
|
4915
|
+
`Cache hit records: ${stats.cacheHitRecords}/${stats.promptTokenRecordCount} (${this.toPercent(stats.cacheHitRate)})`,
|
|
4916
|
+
`Cache token rate: ${this.toPercent(stats.tokenCacheRate)}`
|
|
4902
4917
|
];
|
|
4903
4918
|
if (stats.sources.length > 0) lines.push(`Sources: ${stats.sources.map((item) => `${item.value}=${item.count}`).join(", ")}`);
|
|
4904
4919
|
if (stats.models.length > 0) lines.push(`Models: ${stats.models.map((item) => `${item.value}=${item.count}`).join(", ")}`);
|
|
@@ -5145,7 +5160,7 @@ function parseSkillFrontmatter(raw) {
|
|
|
5145
5160
|
const message = error instanceof Error ? error.message : String(error);
|
|
5146
5161
|
throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
|
|
5147
5162
|
}
|
|
5148
|
-
if (!isRecord$
|
|
5163
|
+
if (!isRecord$6(parsed)) return {};
|
|
5149
5164
|
const summaryI18n = readLocalizedTextMapField(parsed, [["summaryi18n"], ["summary_i18n"]]);
|
|
5150
5165
|
const descriptionI18n = readLocalizedTextMapField(parsed, [["descriptioni18n"], ["description_i18n"]]);
|
|
5151
5166
|
const summaryZh = readFrontmatterStringField(parsed, [["summaryzh"], ["summary_zh"]]);
|
|
@@ -5176,7 +5191,7 @@ function readMarketplaceMetadataFile(skillDir, explicitMetaFile) {
|
|
|
5176
5191
|
const message = error instanceof Error ? error.message : String(error);
|
|
5177
5192
|
throw new Error(`Invalid marketplace metadata file: ${metadataPath} (${message})`);
|
|
5178
5193
|
}
|
|
5179
|
-
if (!isRecord$
|
|
5194
|
+
if (!isRecord$6(parsed)) throw new Error(`Invalid marketplace metadata file: ${metadataPath} (root must be an object)`);
|
|
5180
5195
|
return {
|
|
5181
5196
|
slug: readMetadataString(parsed, "slug"),
|
|
5182
5197
|
name: readMetadataString(parsed, "name"),
|
|
@@ -5224,7 +5239,7 @@ function readMetadataStringArray(record, fieldName) {
|
|
|
5224
5239
|
function readMetadataLocalizedTextMap(record, fieldName) {
|
|
5225
5240
|
const value = record[fieldName];
|
|
5226
5241
|
if (value == null) return;
|
|
5227
|
-
if (!isRecord$
|
|
5242
|
+
if (!isRecord$6(value)) throw new Error(`Invalid marketplace metadata field: ${fieldName} must be an object`);
|
|
5228
5243
|
const localized = {};
|
|
5229
5244
|
for (const [locale, text] of Object.entries(value)) {
|
|
5230
5245
|
if (typeof text !== "string") throw new Error(`Invalid marketplace metadata field: ${fieldName}.${locale} must be a string`);
|
|
@@ -5245,7 +5260,7 @@ function readFrontmatterStringField(record, keyPaths) {
|
|
|
5245
5260
|
function readLocalizedTextMapField(record, keyPaths) {
|
|
5246
5261
|
for (const keyPath of keyPaths) {
|
|
5247
5262
|
const value = readNestedFrontmatterValue(record, keyPath);
|
|
5248
|
-
if (!isRecord$
|
|
5263
|
+
if (!isRecord$6(value)) continue;
|
|
5249
5264
|
const normalized = {};
|
|
5250
5265
|
for (const [locale, text] of Object.entries(value)) {
|
|
5251
5266
|
if (typeof text !== "string") continue;
|
|
@@ -5269,7 +5284,7 @@ function readFrontmatterTags(record) {
|
|
|
5269
5284
|
function readNestedFrontmatterValue(record, keyPath) {
|
|
5270
5285
|
let current = record;
|
|
5271
5286
|
for (const rawKey of keyPath) {
|
|
5272
|
-
if (!isRecord$
|
|
5287
|
+
if (!isRecord$6(current)) return;
|
|
5273
5288
|
const normalizedKey = normalizeFrontmatterKey(rawKey);
|
|
5274
5289
|
const matchingKey = Object.keys(current).find((candidate) => normalizeFrontmatterKey(candidate) === normalizedKey);
|
|
5275
5290
|
if (!matchingKey) return;
|
|
@@ -5283,7 +5298,7 @@ function normalizeFrontmatterKey(raw) {
|
|
|
5283
5298
|
function normalizeLocaleTag(raw) {
|
|
5284
5299
|
return raw.trim().toLowerCase();
|
|
5285
5300
|
}
|
|
5286
|
-
function isRecord$
|
|
5301
|
+
function isRecord$6(value) {
|
|
5287
5302
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5288
5303
|
}
|
|
5289
5304
|
//#endregion
|
|
@@ -5503,11 +5518,17 @@ function openBrowser(url) {
|
|
|
5503
5518
|
command = "xdg-open";
|
|
5504
5519
|
args = [url];
|
|
5505
5520
|
}
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5521
|
+
if (!which(command)) return false;
|
|
5522
|
+
try {
|
|
5523
|
+
spawn(command, args, {
|
|
5524
|
+
stdio: "ignore",
|
|
5525
|
+
detached: true,
|
|
5526
|
+
env: createExternalCommandEnv(process.env)
|
|
5527
|
+
}).unref();
|
|
5528
|
+
return true;
|
|
5529
|
+
} catch {
|
|
5530
|
+
return false;
|
|
5531
|
+
}
|
|
5511
5532
|
}
|
|
5512
5533
|
function normalizePathEntries(rawPath, platform) {
|
|
5513
5534
|
const delimiter = platform === "win32" ? ";" : ":";
|
|
@@ -5558,9 +5579,9 @@ function resolveVersionFromPackageTree(startDir, expectedName) {
|
|
|
5558
5579
|
if (existsSync(pkgPath)) try {
|
|
5559
5580
|
const raw = readFileSync(pkgPath, "utf-8");
|
|
5560
5581
|
const parsed = JSON.parse(raw);
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5582
|
+
const version = typeof parsed.version === "string" ? parsed.version : null;
|
|
5583
|
+
const matchesExpectedName = !expectedName || parsed.name === expectedName;
|
|
5584
|
+
if (version && matchesExpectedName) return version;
|
|
5564
5585
|
} catch {}
|
|
5565
5586
|
const parent = resolve(current, "..");
|
|
5566
5587
|
if (parent === current) break;
|
|
@@ -5642,6 +5663,71 @@ function persistPlatformToken(params) {
|
|
|
5642
5663
|
saveConfig(config, configPath);
|
|
5643
5664
|
}
|
|
5644
5665
|
var PlatformAuthCommands = class {
|
|
5666
|
+
shouldUseBrowserLogin = (opts) => {
|
|
5667
|
+
const email = typeof opts.email === "string" ? opts.email.trim() : "";
|
|
5668
|
+
const password = typeof opts.password === "string" ? opts.password : "";
|
|
5669
|
+
return !email && !password;
|
|
5670
|
+
};
|
|
5671
|
+
waitFor = async (delayMs) => {
|
|
5672
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
5673
|
+
};
|
|
5674
|
+
formatLoginDeadline = (expiresAt) => {
|
|
5675
|
+
const value = Date.parse(expiresAt);
|
|
5676
|
+
return Number.isNaN(value) ? expiresAt : new Date(value).toLocaleString();
|
|
5677
|
+
};
|
|
5678
|
+
printLoginSuccess = (result) => {
|
|
5679
|
+
console.log(`✓ Logged in to NextClaw platform (${result.platformBase})`);
|
|
5680
|
+
console.log(`✓ Account: ${result.email} (${result.role})`);
|
|
5681
|
+
console.log("✓ Token saved into providers.nextclaw.apiKey");
|
|
5682
|
+
};
|
|
5683
|
+
printBrowserLoginIntro = (params) => {
|
|
5684
|
+
const { expiresAt, open, openedBrowser, verificationUri } = params;
|
|
5685
|
+
const expiresText = this.formatLoginDeadline(expiresAt);
|
|
5686
|
+
console.log("NextClaw browser sign-in");
|
|
5687
|
+
console.log(`Open this link to continue: ${verificationUri}`);
|
|
5688
|
+
if (open) if (openedBrowser) console.log("✓ Opened the default browser. Finish sign-in there and this terminal will complete automatically.");
|
|
5689
|
+
else console.log("Browser did not open automatically. Open the link above in any browser to continue.");
|
|
5690
|
+
else console.log("Automatic browser opening is disabled. Open the link above in any browser to continue.");
|
|
5691
|
+
console.log("This link can be opened on this machine or on another device if your CLI is running remotely.");
|
|
5692
|
+
console.log(`Waiting for authorization until ${expiresText}...`);
|
|
5693
|
+
};
|
|
5694
|
+
waitForBrowserLoginResult = async (params) => {
|
|
5695
|
+
while (true) {
|
|
5696
|
+
const result = await this.pollBrowserAuth({
|
|
5697
|
+
apiBase: params.apiBase,
|
|
5698
|
+
sessionId: params.sessionId
|
|
5699
|
+
});
|
|
5700
|
+
if (result.status === "pending") {
|
|
5701
|
+
await this.waitFor(result.nextPollMs);
|
|
5702
|
+
continue;
|
|
5703
|
+
}
|
|
5704
|
+
if (result.status === "expired") throw new Error(`${result.message} Run \`nextclaw login\` again to generate a new sign-in link.`);
|
|
5705
|
+
return {
|
|
5706
|
+
token: result.token,
|
|
5707
|
+
role: result.role,
|
|
5708
|
+
email: result.email,
|
|
5709
|
+
platformBase: result.platformBase,
|
|
5710
|
+
v1Base: result.v1Base
|
|
5711
|
+
};
|
|
5712
|
+
}
|
|
5713
|
+
};
|
|
5714
|
+
loginWithBrowserResult = async (opts = {}) => {
|
|
5715
|
+
const startResult = await this.startBrowserAuth({ apiBase: opts.apiBase });
|
|
5716
|
+
const shouldOpenBrowser = opts.open !== false;
|
|
5717
|
+
const openedBrowser = shouldOpenBrowser ? openBrowser(startResult.verificationUri) : false;
|
|
5718
|
+
this.printBrowserLoginIntro({
|
|
5719
|
+
verificationUri: startResult.verificationUri,
|
|
5720
|
+
expiresAt: startResult.expiresAt,
|
|
5721
|
+
open: shouldOpenBrowser,
|
|
5722
|
+
openedBrowser
|
|
5723
|
+
});
|
|
5724
|
+
const result = await this.waitForBrowserLoginResult({
|
|
5725
|
+
apiBase: opts.apiBase,
|
|
5726
|
+
sessionId: startResult.sessionId
|
|
5727
|
+
});
|
|
5728
|
+
console.log("✓ Browser authorization completed.");
|
|
5729
|
+
return result;
|
|
5730
|
+
};
|
|
5645
5731
|
readStoredToken = (params = {}) => {
|
|
5646
5732
|
const resolved = resolveProviderConfig({ apiBase: params.apiBase });
|
|
5647
5733
|
const token = resolved.nextclawProvider.apiKey?.trim() ?? "";
|
|
@@ -5732,10 +5818,8 @@ var PlatformAuthCommands = class {
|
|
|
5732
5818
|
};
|
|
5733
5819
|
};
|
|
5734
5820
|
login = async (opts = {}) => {
|
|
5735
|
-
const result = await this.loginResult(opts);
|
|
5736
|
-
|
|
5737
|
-
console.log(`✓ Account: ${result.email} (${result.role})`);
|
|
5738
|
-
console.log(`✓ Token saved into providers.nextclaw.apiKey`);
|
|
5821
|
+
const result = this.shouldUseBrowserLogin(opts) ? await this.loginWithBrowserResult(opts) : await this.loginResult(opts);
|
|
5822
|
+
this.printLoginSuccess(result);
|
|
5739
5823
|
};
|
|
5740
5824
|
me = async (params = {}) => {
|
|
5741
5825
|
const { platformBase, v1Base, inputApiBase, token } = this.readStoredToken(params);
|
|
@@ -5852,9 +5936,9 @@ async function fetchMarketplaceSkillFiles(apiBase, slug) {
|
|
|
5852
5936
|
const message = payload.error?.message || `marketplace skill file fetch failed: ${response.status}`;
|
|
5853
5937
|
throw new Error(message);
|
|
5854
5938
|
}
|
|
5855
|
-
if (!isRecord$
|
|
5939
|
+
if (!isRecord$5(payload.data) || !Array.isArray(payload.data.files)) throw new Error("Invalid marketplace skill file manifest response");
|
|
5856
5940
|
return { files: payload.data.files.map((entry, index) => {
|
|
5857
|
-
if (!isRecord$
|
|
5941
|
+
if (!isRecord$5(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) throw new Error(`Invalid marketplace skill file manifest at index ${index}`);
|
|
5858
5942
|
const normalized = { path: entry.path.trim() };
|
|
5859
5943
|
if (typeof entry.downloadPath === "string" && entry.downloadPath.trim().length > 0) normalized.downloadPath = entry.downloadPath.trim();
|
|
5860
5944
|
if (typeof entry.contentBase64 === "string" && entry.contentBase64.trim().length > 0) normalized.contentBase64 = entry.contentBase64.trim();
|
|
@@ -5881,7 +5965,7 @@ async function readMarketplaceEnvelope(response) {
|
|
|
5881
5965
|
} catch {
|
|
5882
5966
|
throw new Error(`Invalid marketplace response: ${response.status}`);
|
|
5883
5967
|
}
|
|
5884
|
-
if (!isRecord$
|
|
5968
|
+
if (!isRecord$5(payload) || typeof payload.ok !== "boolean") throw new Error(`Invalid marketplace response shape: ${response.status}`);
|
|
5885
5969
|
return payload;
|
|
5886
5970
|
}
|
|
5887
5971
|
function resolveSkillFileDownloadUrl(apiBase, slug, file) {
|
|
@@ -5896,7 +5980,7 @@ function extractMarketplaceErrorMessage(raw, fallbackStatus) {
|
|
|
5896
5980
|
return raw || `Request failed (${fallbackStatus})`;
|
|
5897
5981
|
}
|
|
5898
5982
|
}
|
|
5899
|
-
function isRecord$
|
|
5983
|
+
function isRecord$5(value) {
|
|
5900
5984
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5901
5985
|
}
|
|
5902
5986
|
//#endregion
|
|
@@ -6433,10 +6517,14 @@ const readJsonFile = (filePath) => {
|
|
|
6433
6517
|
return null;
|
|
6434
6518
|
}
|
|
6435
6519
|
};
|
|
6436
|
-
const readString = (value) => {
|
|
6520
|
+
const readString$3 = (value) => {
|
|
6437
6521
|
if (typeof value !== "string") return;
|
|
6438
6522
|
return value.trim() || void 0;
|
|
6439
6523
|
};
|
|
6524
|
+
const normalizeInstallRecordPath = (value) => {
|
|
6525
|
+
const filePath = readString$3(value);
|
|
6526
|
+
return filePath ? path.resolve(filePath) : void 0;
|
|
6527
|
+
};
|
|
6440
6528
|
const resolveDevFirstPartyPluginDir = (explicitDir, moduleDir = path.dirname(fileURLToPath(import.meta.url))) => {
|
|
6441
6529
|
const configured = explicitDir?.trim();
|
|
6442
6530
|
if (configured) return configured;
|
|
@@ -6478,7 +6566,7 @@ const readWorkspacePluginPackages = (workspaceExtensionsDir) => {
|
|
|
6478
6566
|
const packageDir = path.join(workspaceExtensionsDir, entry.name);
|
|
6479
6567
|
const pkg = readJsonFile(path.join(packageDir, "package.json"));
|
|
6480
6568
|
if (!pkg || !hasOpenClawExtensions(pkg)) continue;
|
|
6481
|
-
const packageName = readString(pkg.name);
|
|
6569
|
+
const packageName = readString$3(pkg.name);
|
|
6482
6570
|
if (!packageName?.startsWith("@nextclaw/")) continue;
|
|
6483
6571
|
packages.push({
|
|
6484
6572
|
packageName,
|
|
@@ -6493,14 +6581,22 @@ const mergeLoadPaths$1 = (existingLoadPaths, devLoadPaths) => {
|
|
|
6493
6581
|
for (const entry of existingLoadPaths) if (!mergedLoadPaths.includes(entry)) mergedLoadPaths.push(entry);
|
|
6494
6582
|
return mergedLoadPaths;
|
|
6495
6583
|
};
|
|
6584
|
+
const findWorkspacePackageForInstallRecord = (installRecord, workspacePackages, packageByName) => {
|
|
6585
|
+
const packageName = normalizePackageSpec(readString$3(installRecord.spec) ?? "");
|
|
6586
|
+
if (packageName) {
|
|
6587
|
+
const matchedByPackageName = packageByName.get(packageName);
|
|
6588
|
+
if (matchedByPackageName) return matchedByPackageName;
|
|
6589
|
+
}
|
|
6590
|
+
const installPathCandidates = new Set([installRecord.sourcePath, installRecord.installPath].map(normalizeInstallRecordPath).filter((entry) => Boolean(entry)));
|
|
6591
|
+
if (installPathCandidates.size === 0) return;
|
|
6592
|
+
return workspacePackages.find((workspacePackage) => installPathCandidates.has(path.resolve(workspacePackage.dir)));
|
|
6593
|
+
};
|
|
6496
6594
|
const buildDevelopmentSourceEntryDefaults = (config, workspacePackages) => {
|
|
6497
6595
|
const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
|
|
6498
6596
|
const nextEntries = { ...config.plugins.entries ?? {} };
|
|
6499
6597
|
let didDefaultDevelopmentSource = false;
|
|
6500
6598
|
for (const [pluginId, installRecord] of Object.entries(config.plugins.installs ?? {})) {
|
|
6501
|
-
|
|
6502
|
-
if (!packageName) continue;
|
|
6503
|
-
if (!packageByName.get(packageName)?.supportsDevelopmentSource) continue;
|
|
6599
|
+
if (!findWorkspacePackageForInstallRecord(installRecord, workspacePackages, packageByName)?.supportsDevelopmentSource) continue;
|
|
6504
6600
|
const existingEntry = nextEntries[pluginId];
|
|
6505
6601
|
if (existingEntry?.source) continue;
|
|
6506
6602
|
nextEntries[pluginId] = {
|
|
@@ -6520,12 +6616,12 @@ const resolveDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) =>
|
|
|
6520
6616
|
const workspacePackages = readWorkspacePluginPackages(rootDir);
|
|
6521
6617
|
if (workspacePackages.length === 0) return [];
|
|
6522
6618
|
const packageDirByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry.dir]));
|
|
6619
|
+
const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
|
|
6523
6620
|
const loadPaths = [];
|
|
6524
6621
|
const installs = config.plugins.installs ?? {};
|
|
6525
6622
|
for (const installRecord of Object.values(installs)) {
|
|
6526
|
-
const
|
|
6527
|
-
|
|
6528
|
-
const packageDir = packageDirByName.get(packageName);
|
|
6623
|
+
const matchedWorkspacePackage = findWorkspacePackageForInstallRecord(installRecord, workspacePackages, packageByName);
|
|
6624
|
+
const packageDir = matchedWorkspacePackage ? matchedWorkspacePackage.dir : packageDirByName.get(normalizePackageSpec(readString$3(installRecord.spec) ?? "") ?? "");
|
|
6529
6625
|
if (!packageDir || loadPaths.includes(packageDir)) continue;
|
|
6530
6626
|
loadPaths.push(packageDir);
|
|
6531
6627
|
}
|
|
@@ -6537,12 +6633,15 @@ const resolveDevFirstPartyPluginInstallRoots = (config, workspaceExtensionsDir)
|
|
|
6537
6633
|
const workspacePackages = readWorkspacePluginPackages(rootDir);
|
|
6538
6634
|
if (workspacePackages.length === 0) return [];
|
|
6539
6635
|
const packageNames = new Set(workspacePackages.map((entry) => entry.packageName));
|
|
6636
|
+
const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
|
|
6540
6637
|
const installRoots = [];
|
|
6541
6638
|
for (const installRecord of Object.values(config.plugins.installs ?? {})) {
|
|
6542
|
-
const
|
|
6543
|
-
|
|
6544
|
-
|
|
6639
|
+
const workspacePackage = findWorkspacePackageForInstallRecord(installRecord, workspacePackages, packageByName);
|
|
6640
|
+
const packageName = normalizePackageSpec(readString$3(installRecord.spec) ?? "");
|
|
6641
|
+
if (!workspacePackage && (!packageName || !packageNames.has(packageName))) continue;
|
|
6642
|
+
const installPath = readString$3(installRecord.installPath);
|
|
6545
6643
|
if (!installPath || installRoots.includes(installPath)) continue;
|
|
6644
|
+
if (workspacePackage && path.resolve(installPath) === path.resolve(workspacePackage.dir)) continue;
|
|
6546
6645
|
installRoots.push(installPath);
|
|
6547
6646
|
}
|
|
6548
6647
|
return installRoots;
|
|
@@ -6571,7 +6670,7 @@ const applyDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
|
6571
6670
|
//#endregion
|
|
6572
6671
|
//#region src/cli/commands/plugin/development-source/dev-plugin-overrides.utils.ts
|
|
6573
6672
|
const DEV_PLUGIN_OVERRIDES_ENV = "NEXTCLAW_DEV_PLUGIN_OVERRIDES";
|
|
6574
|
-
function isRecord$
|
|
6673
|
+
function isRecord$4(value) {
|
|
6575
6674
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6576
6675
|
}
|
|
6577
6676
|
function readOptionalString$6(value) {
|
|
@@ -6600,7 +6699,7 @@ function assertOverridePluginReadable(override) {
|
|
|
6600
6699
|
}
|
|
6601
6700
|
}
|
|
6602
6701
|
function readOverrideRecord(value, index) {
|
|
6603
|
-
if (!isRecord$
|
|
6702
|
+
if (!isRecord$4(value)) throw new Error(`[dev-plugin-override] override[${index}] must be an object`);
|
|
6604
6703
|
const pluginId = readOptionalString$6(value.pluginId);
|
|
6605
6704
|
const pluginPath = readOptionalString$6(value.pluginPath);
|
|
6606
6705
|
const source = value.source === "development" ? "development" : "production";
|
|
@@ -6851,7 +6950,9 @@ function toExtensionRegistry(pluginRegistry) {
|
|
|
6851
6950
|
kind: runtime.kind,
|
|
6852
6951
|
label: runtime.label,
|
|
6853
6952
|
createRuntime: runtime.createRuntime,
|
|
6953
|
+
createRuntimeForEntry: runtime.createRuntimeForEntry,
|
|
6854
6954
|
describeSessionType: runtime.describeSessionType,
|
|
6955
|
+
describeSessionTypeForEntry: runtime.describeSessionTypeForEntry,
|
|
6855
6956
|
source: runtime.source
|
|
6856
6957
|
})),
|
|
6857
6958
|
diagnostics: pluginRegistry.diagnostics.map((diag) => ({
|
|
@@ -8369,55 +8470,110 @@ var CronCommands = class {
|
|
|
8369
8470
|
};
|
|
8370
8471
|
//#endregion
|
|
8371
8472
|
//#region src/cli/commands/ncp/ui-ncp-runtime-registry.ts
|
|
8372
|
-
const
|
|
8373
|
-
function
|
|
8473
|
+
const DEFAULT_UI_NCP_RUNTIME_ENTRY_ID = "native";
|
|
8474
|
+
function normalizeIdentifier(value) {
|
|
8374
8475
|
if (typeof value !== "string") return null;
|
|
8375
8476
|
const normalized = value.trim().toLowerCase();
|
|
8376
8477
|
return normalized.length > 0 ? normalized : null;
|
|
8377
8478
|
}
|
|
8378
|
-
function
|
|
8379
|
-
return
|
|
8479
|
+
function cloneRuntimeEntry(entry) {
|
|
8480
|
+
return {
|
|
8481
|
+
id: entry.id,
|
|
8482
|
+
label: entry.label,
|
|
8483
|
+
type: entry.type,
|
|
8484
|
+
enabled: entry.enabled !== false,
|
|
8485
|
+
config: entry.config ? { ...entry.config } : {}
|
|
8486
|
+
};
|
|
8487
|
+
}
|
|
8488
|
+
function readRequestedRuntimeEntryId(sessionMetadata) {
|
|
8489
|
+
return normalizeIdentifier(sessionMetadata.runtime) ?? normalizeIdentifier(sessionMetadata.session_type) ?? normalizeIdentifier(sessionMetadata.sessionType) ?? null;
|
|
8380
8490
|
}
|
|
8381
8491
|
var UiNcpRuntimeRegistry = class {
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
}
|
|
8492
|
+
providers = /* @__PURE__ */ new Map();
|
|
8493
|
+
entries = /* @__PURE__ */ new Map();
|
|
8494
|
+
defaultEntryId = DEFAULT_UI_NCP_RUNTIME_ENTRY_ID;
|
|
8386
8495
|
register(registration) {
|
|
8387
|
-
const normalizedKind =
|
|
8496
|
+
const normalizedKind = normalizeIdentifier(registration.kind);
|
|
8388
8497
|
if (!normalizedKind) throw new Error("ui ncp runtime kind must be a non-empty string");
|
|
8389
8498
|
const token = Symbol(normalizedKind);
|
|
8390
|
-
this.
|
|
8499
|
+
this.providers.set(normalizedKind, {
|
|
8391
8500
|
...registration,
|
|
8392
8501
|
kind: normalizedKind,
|
|
8393
8502
|
token
|
|
8394
8503
|
});
|
|
8395
8504
|
return toDisposable(() => {
|
|
8396
|
-
const current = this.
|
|
8505
|
+
const current = this.providers.get(normalizedKind);
|
|
8397
8506
|
if (!current || current.token !== token) return;
|
|
8398
|
-
this.
|
|
8507
|
+
this.providers.delete(normalizedKind);
|
|
8399
8508
|
});
|
|
8400
8509
|
}
|
|
8510
|
+
applyEntries(params) {
|
|
8511
|
+
const nextEntries = /* @__PURE__ */ new Map();
|
|
8512
|
+
for (const rawEntry of params.entries) {
|
|
8513
|
+
const id = normalizeIdentifier(rawEntry.id);
|
|
8514
|
+
const type = normalizeIdentifier(rawEntry.type);
|
|
8515
|
+
const label = rawEntry.label?.trim() ?? "";
|
|
8516
|
+
if (!id || !type || !label) continue;
|
|
8517
|
+
nextEntries.set(id, cloneRuntimeEntry({
|
|
8518
|
+
...rawEntry,
|
|
8519
|
+
id,
|
|
8520
|
+
type,
|
|
8521
|
+
label
|
|
8522
|
+
}));
|
|
8523
|
+
}
|
|
8524
|
+
this.entries = nextEntries;
|
|
8525
|
+
this.defaultEntryId = normalizeIdentifier(params.defaultEntryId) ?? (nextEntries.has("native") ? "native" : [...nextEntries.keys()][0] ?? "native");
|
|
8526
|
+
}
|
|
8527
|
+
listProviderKinds() {
|
|
8528
|
+
return [...this.providers.keys()];
|
|
8529
|
+
}
|
|
8401
8530
|
createRuntime(params) {
|
|
8402
|
-
const
|
|
8403
|
-
const
|
|
8404
|
-
if (!
|
|
8531
|
+
const requestedEntryId = readRequestedRuntimeEntryId(params.sessionMetadata) ?? this.defaultEntryId;
|
|
8532
|
+
const entry = this.entries.get(requestedEntryId);
|
|
8533
|
+
if (!entry || entry.enabled === false) throw new Error(`ncp runtime unavailable: ${requestedEntryId}`);
|
|
8534
|
+
const provider = this.providers.get(entry.type);
|
|
8535
|
+
if (!provider) throw new Error(`ncp runtime provider unavailable: ${entry.type}`);
|
|
8405
8536
|
const nextSessionMetadata = {
|
|
8406
8537
|
...params.sessionMetadata,
|
|
8407
|
-
|
|
8538
|
+
runtime: entry.id,
|
|
8539
|
+
session_type: entry.id,
|
|
8540
|
+
runtime_type: entry.type
|
|
8408
8541
|
};
|
|
8409
8542
|
params.setSessionMetadata(nextSessionMetadata);
|
|
8410
|
-
return
|
|
8543
|
+
if (provider.createRuntimeForEntry) return provider.createRuntimeForEntry({
|
|
8544
|
+
entry: cloneRuntimeEntry(entry),
|
|
8545
|
+
runtimeParams: {
|
|
8546
|
+
...params,
|
|
8547
|
+
sessionMetadata: nextSessionMetadata
|
|
8548
|
+
}
|
|
8549
|
+
});
|
|
8550
|
+
return provider.createRuntime({
|
|
8411
8551
|
...params,
|
|
8412
8552
|
sessionMetadata: nextSessionMetadata
|
|
8413
8553
|
});
|
|
8414
8554
|
}
|
|
8415
8555
|
async listSessionTypes(params) {
|
|
8416
|
-
const options = await Promise.all([...this.
|
|
8417
|
-
const
|
|
8556
|
+
const options = await Promise.all([...this.entries.values()].filter((entry) => entry.enabled !== false).map(async (entry) => {
|
|
8557
|
+
const provider = this.providers.get(entry.type);
|
|
8558
|
+
if (!provider) return {
|
|
8559
|
+
value: entry.id,
|
|
8560
|
+
label: entry.label,
|
|
8561
|
+
ready: false,
|
|
8562
|
+
reason: "runtime_provider_unavailable",
|
|
8563
|
+
reasonMessage: `Runtime provider unavailable for type "${entry.type}".`,
|
|
8564
|
+
recommendedModel: null,
|
|
8565
|
+
cta: {
|
|
8566
|
+
kind: "settings",
|
|
8567
|
+
label: "Configure Runtime"
|
|
8568
|
+
}
|
|
8569
|
+
};
|
|
8570
|
+
const descriptor = provider.describeSessionTypeForEntry ? await provider.describeSessionTypeForEntry({
|
|
8571
|
+
entry: cloneRuntimeEntry(entry),
|
|
8572
|
+
describeParams: params
|
|
8573
|
+
}) : await provider.describeSessionType?.(params);
|
|
8418
8574
|
return {
|
|
8419
|
-
value:
|
|
8420
|
-
label:
|
|
8575
|
+
value: entry.id,
|
|
8576
|
+
label: entry.label,
|
|
8421
8577
|
ready: descriptor?.ready ?? true,
|
|
8422
8578
|
reason: descriptor?.reason ?? null,
|
|
8423
8579
|
reasonMessage: descriptor?.reasonMessage ?? null,
|
|
@@ -8427,16 +8583,328 @@ var UiNcpRuntimeRegistry = class {
|
|
|
8427
8583
|
};
|
|
8428
8584
|
}));
|
|
8429
8585
|
return {
|
|
8430
|
-
defaultType: this.
|
|
8586
|
+
defaultType: this.defaultEntryId,
|
|
8431
8587
|
options: options.sort((left, right) => {
|
|
8432
|
-
if (left.value === this.
|
|
8433
|
-
if (right.value === this.
|
|
8588
|
+
if (left.value === this.defaultEntryId) return -1;
|
|
8589
|
+
if (right.value === this.defaultEntryId) return 1;
|
|
8434
8590
|
return left.value.localeCompare(right.value);
|
|
8435
8591
|
})
|
|
8436
8592
|
};
|
|
8437
8593
|
}
|
|
8438
8594
|
};
|
|
8439
8595
|
//#endregion
|
|
8596
|
+
//#region src/cli/commands/ncp/ui-ncp-runtime-entry-resolver.ts
|
|
8597
|
+
function readString$2(value) {
|
|
8598
|
+
if (typeof value !== "string") return;
|
|
8599
|
+
return value.trim() || void 0;
|
|
8600
|
+
}
|
|
8601
|
+
function readRecord$1(value) {
|
|
8602
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
8603
|
+
return value;
|
|
8604
|
+
}
|
|
8605
|
+
function buildNativeRuntimeEntry(config) {
|
|
8606
|
+
const nativeRuntimeConfig = readRecord$1(config.ui?.ncp?.runtimes?.native) ?? {};
|
|
8607
|
+
return {
|
|
8608
|
+
id: "native",
|
|
8609
|
+
label: "Native",
|
|
8610
|
+
type: "native",
|
|
8611
|
+
enabled: nativeRuntimeConfig.enabled !== false,
|
|
8612
|
+
config: nativeRuntimeConfig
|
|
8613
|
+
};
|
|
8614
|
+
}
|
|
8615
|
+
function buildBuiltinRuntimeEntry(kind, label) {
|
|
8616
|
+
return {
|
|
8617
|
+
id: kind,
|
|
8618
|
+
label,
|
|
8619
|
+
type: kind,
|
|
8620
|
+
enabled: true,
|
|
8621
|
+
config: {}
|
|
8622
|
+
};
|
|
8623
|
+
}
|
|
8624
|
+
function resolveUiNcpRuntimeEntries(params) {
|
|
8625
|
+
const providerKindSet = new Set(params.providerKinds.map((kind) => kind.trim().toLowerCase()).filter(Boolean));
|
|
8626
|
+
const entries = /* @__PURE__ */ new Map();
|
|
8627
|
+
entries.set("native", buildNativeRuntimeEntry(params.config));
|
|
8628
|
+
if (providerKindSet.has("codex")) entries.set("codex", buildBuiltinRuntimeEntry("codex", "Codex"));
|
|
8629
|
+
if (providerKindSet.has("claude")) entries.set("claude", buildBuiltinRuntimeEntry("claude", "Claude"));
|
|
8630
|
+
for (const [rawId, rawEntry] of Object.entries(params.config.agents.runtimes.entries ?? {})) {
|
|
8631
|
+
const id = rawId.trim().toLowerCase();
|
|
8632
|
+
const type = readString$2(rawEntry.type)?.toLowerCase();
|
|
8633
|
+
if (!id || !type) continue;
|
|
8634
|
+
entries.set(id, {
|
|
8635
|
+
id,
|
|
8636
|
+
label: readString$2(rawEntry.label) ?? id,
|
|
8637
|
+
type,
|
|
8638
|
+
enabled: rawEntry.enabled !== false,
|
|
8639
|
+
config: rawEntry.config ? { ...rawEntry.config } : {}
|
|
8640
|
+
});
|
|
8641
|
+
}
|
|
8642
|
+
return {
|
|
8643
|
+
defaultEntryId: entries.has("native") ? "native" : [...entries.keys()][0] ?? "native",
|
|
8644
|
+
entries: [...entries.values()]
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
//#endregion
|
|
8648
|
+
//#region src/cli/commands/ncp/builtin-narp-runtime-types.ts
|
|
8649
|
+
const NARP_HTTP_RUNTIME_KIND = "narp-http";
|
|
8650
|
+
const NARP_STDIO_RUNTIME_KIND = "narp-stdio";
|
|
8651
|
+
//#endregion
|
|
8652
|
+
//#region src/cli/commands/ncp/builtin-narp-runtime-registration.service.ts
|
|
8653
|
+
const NARP_API_MODE_HEADER = "x-nextclaw-narp-api-mode";
|
|
8654
|
+
function readRecord(value) {
|
|
8655
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
8656
|
+
return value;
|
|
8657
|
+
}
|
|
8658
|
+
function readString$1(value) {
|
|
8659
|
+
if (typeof value !== "string") return;
|
|
8660
|
+
return value.trim() || void 0;
|
|
8661
|
+
}
|
|
8662
|
+
function resolveRequestedModel(params) {
|
|
8663
|
+
const runMetadata = readRecord(params.input.metadata);
|
|
8664
|
+
return readString$1(runMetadata?.preferred_model) ?? readString$1(runMetadata?.preferredModel) ?? readString$1(runMetadata?.model) ?? readString$1(params.sessionMetadata.preferred_model) ?? readString$1(params.sessionMetadata.preferredModel) ?? readString$1(params.sessionMetadata.model) ?? params.configuredModel ?? params.defaultModel;
|
|
8665
|
+
}
|
|
8666
|
+
function resolveProviderApiMode(resolution) {
|
|
8667
|
+
const wireApi = resolution.provider?.wireApi?.trim().toLowerCase();
|
|
8668
|
+
if (wireApi === "responses") return "codex_responses";
|
|
8669
|
+
if (wireApi === "chat") return "chat_completions";
|
|
8670
|
+
const providerName = resolution.providerName?.trim().toLowerCase();
|
|
8671
|
+
const apiBase = resolution.apiBase?.trim().toLowerCase() ?? "";
|
|
8672
|
+
if (providerName === "anthropic" || apiBase.includes("anthropic.com")) return "anthropic_messages";
|
|
8673
|
+
return "chat_completions";
|
|
8674
|
+
}
|
|
8675
|
+
function buildProviderRoute(params) {
|
|
8676
|
+
const model = resolveRequestedModel(params);
|
|
8677
|
+
if (!model) return;
|
|
8678
|
+
const resolution = resolveProviderRuntime(params.config, model);
|
|
8679
|
+
if (!resolution.provider) return;
|
|
8680
|
+
return {
|
|
8681
|
+
model: resolution.providerLocalModel,
|
|
8682
|
+
apiKey: resolution.apiKey,
|
|
8683
|
+
apiBase: resolution.apiBase,
|
|
8684
|
+
headers: {
|
|
8685
|
+
...resolution.provider.extraHeaders ?? {},
|
|
8686
|
+
[NARP_API_MODE_HEADER]: resolveProviderApiMode(resolution)
|
|
8687
|
+
}
|
|
8688
|
+
};
|
|
8689
|
+
}
|
|
8690
|
+
var BuiltinHttpRuntimeSessionTypeService = class {
|
|
8691
|
+
pendingDescribeByMode = /* @__PURE__ */ new Map();
|
|
8692
|
+
constructor(entry, defaultModel) {
|
|
8693
|
+
this.entry = entry;
|
|
8694
|
+
this.defaultModel = defaultModel;
|
|
8695
|
+
}
|
|
8696
|
+
describe = async (describeParams) => {
|
|
8697
|
+
const describeMode = describeParams?.describeMode === "probe" ? "probe" : "observation";
|
|
8698
|
+
const pending = this.pendingDescribeByMode.get(describeMode);
|
|
8699
|
+
if (pending) return await pending;
|
|
8700
|
+
const nextDescribe = this.describeInternal();
|
|
8701
|
+
this.pendingDescribeByMode.set(describeMode, nextDescribe);
|
|
8702
|
+
try {
|
|
8703
|
+
return await nextDescribe;
|
|
8704
|
+
} finally {
|
|
8705
|
+
this.pendingDescribeByMode.delete(describeMode);
|
|
8706
|
+
}
|
|
8707
|
+
};
|
|
8708
|
+
describeInternal = async () => {
|
|
8709
|
+
const config = new HttpRuntimeConfigResolver(this.entry.config ?? {}).resolve({ defaultModel: this.defaultModel });
|
|
8710
|
+
if (!config.baseUrl) return {
|
|
8711
|
+
ready: false,
|
|
8712
|
+
reason: "base_url_missing",
|
|
8713
|
+
reasonMessage: "Configure the runtime entry baseUrl before starting an HTTP runtime session.",
|
|
8714
|
+
recommendedModel: config.recommendedModel ?? null,
|
|
8715
|
+
cta: {
|
|
8716
|
+
kind: "settings",
|
|
8717
|
+
label: "Configure HTTP Runtime"
|
|
8718
|
+
}
|
|
8719
|
+
};
|
|
8720
|
+
if (!((config.capabilityProbe ?? true) && Boolean(config.healthcheckUrl))) return {
|
|
8721
|
+
ready: true,
|
|
8722
|
+
reason: null,
|
|
8723
|
+
reasonMessage: null,
|
|
8724
|
+
recommendedModel: config.recommendedModel ?? null,
|
|
8725
|
+
...config.supportedModels ? { supportedModels: config.supportedModels } : {},
|
|
8726
|
+
cta: null
|
|
8727
|
+
};
|
|
8728
|
+
const controller = new AbortController();
|
|
8729
|
+
const timeoutMs = config.healthcheckTimeoutMs ?? 3e3;
|
|
8730
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
8731
|
+
try {
|
|
8732
|
+
const response = await fetch(config.healthcheckUrl ?? "", {
|
|
8733
|
+
method: "GET",
|
|
8734
|
+
headers: config.headers,
|
|
8735
|
+
signal: controller.signal
|
|
8736
|
+
});
|
|
8737
|
+
if (!response.ok) return {
|
|
8738
|
+
ready: false,
|
|
8739
|
+
reason: "healthcheck_failed",
|
|
8740
|
+
reasonMessage: `HTTP runtime healthcheck returned HTTP ${response.status}.`,
|
|
8741
|
+
recommendedModel: config.recommendedModel ?? null,
|
|
8742
|
+
...config.supportedModels ? { supportedModels: config.supportedModels } : {},
|
|
8743
|
+
cta: null
|
|
8744
|
+
};
|
|
8745
|
+
return {
|
|
8746
|
+
ready: true,
|
|
8747
|
+
reason: null,
|
|
8748
|
+
reasonMessage: null,
|
|
8749
|
+
recommendedModel: config.recommendedModel ?? null,
|
|
8750
|
+
...config.supportedModels ? { supportedModels: config.supportedModels } : {},
|
|
8751
|
+
cta: null
|
|
8752
|
+
};
|
|
8753
|
+
} catch (error) {
|
|
8754
|
+
return {
|
|
8755
|
+
ready: false,
|
|
8756
|
+
reason: "healthcheck_unreachable",
|
|
8757
|
+
reasonMessage: error instanceof Error ? error.message : "Failed to reach the configured HTTP runtime healthcheck.",
|
|
8758
|
+
recommendedModel: config.recommendedModel ?? null,
|
|
8759
|
+
...config.supportedModels ? { supportedModels: config.supportedModels } : {},
|
|
8760
|
+
cta: null
|
|
8761
|
+
};
|
|
8762
|
+
} finally {
|
|
8763
|
+
clearTimeout(timeout);
|
|
8764
|
+
}
|
|
8765
|
+
};
|
|
8766
|
+
};
|
|
8767
|
+
var BuiltinStdioRuntimeSessionTypeService = class {
|
|
8768
|
+
pendingDescribeByMode = /* @__PURE__ */ new Map();
|
|
8769
|
+
constructor(entry) {
|
|
8770
|
+
this.entry = entry;
|
|
8771
|
+
}
|
|
8772
|
+
describe = async (describeParams) => {
|
|
8773
|
+
const describeMode = describeParams?.describeMode === "probe" ? "probe" : "observation";
|
|
8774
|
+
const pending = this.pendingDescribeByMode.get(describeMode);
|
|
8775
|
+
if (pending) return await pending;
|
|
8776
|
+
const nextDescribe = this.describeInternal(describeMode);
|
|
8777
|
+
this.pendingDescribeByMode.set(describeMode, nextDescribe);
|
|
8778
|
+
try {
|
|
8779
|
+
return await nextDescribe;
|
|
8780
|
+
} finally {
|
|
8781
|
+
this.pendingDescribeByMode.delete(describeMode);
|
|
8782
|
+
}
|
|
8783
|
+
};
|
|
8784
|
+
describeInternal = async (describeMode) => {
|
|
8785
|
+
const resolver = new StdioRuntimeConfigResolver(this.entry.config ?? {});
|
|
8786
|
+
try {
|
|
8787
|
+
const config = resolver.resolve();
|
|
8788
|
+
if (!await resolveExecutablePath(config.command)) return {
|
|
8789
|
+
ready: false,
|
|
8790
|
+
reason: "command_missing",
|
|
8791
|
+
reasonMessage: `Configured stdio command "${config.command}" is not available. Update the runtime entry command or install the required launcher first.`,
|
|
8792
|
+
cta: {
|
|
8793
|
+
kind: "settings",
|
|
8794
|
+
label: "Configure Stdio Runtime"
|
|
8795
|
+
}
|
|
8796
|
+
};
|
|
8797
|
+
if (describeMode === "probe") try {
|
|
8798
|
+
await probeStdioRuntime(config);
|
|
8799
|
+
} catch (error) {
|
|
8800
|
+
return {
|
|
8801
|
+
ready: false,
|
|
8802
|
+
reason: "probe_failed",
|
|
8803
|
+
reasonMessage: error instanceof Error ? error.message : "Configured stdio runtime could not complete the ACP probe.",
|
|
8804
|
+
cta: {
|
|
8805
|
+
kind: "settings",
|
|
8806
|
+
label: "Repair Stdio Runtime"
|
|
8807
|
+
}
|
|
8808
|
+
};
|
|
8809
|
+
}
|
|
8810
|
+
return {
|
|
8811
|
+
ready: true,
|
|
8812
|
+
reason: null,
|
|
8813
|
+
reasonMessage: null,
|
|
8814
|
+
cta: null
|
|
8815
|
+
};
|
|
8816
|
+
} catch (error) {
|
|
8817
|
+
return {
|
|
8818
|
+
ready: false,
|
|
8819
|
+
reason: "command_missing",
|
|
8820
|
+
reasonMessage: error instanceof Error ? error.message : "Configure a stdio command before starting this runtime.",
|
|
8821
|
+
cta: {
|
|
8822
|
+
kind: "settings",
|
|
8823
|
+
label: "Configure Stdio Runtime"
|
|
8824
|
+
}
|
|
8825
|
+
};
|
|
8826
|
+
}
|
|
8827
|
+
};
|
|
8828
|
+
};
|
|
8829
|
+
async function resolveExecutablePath(command) {
|
|
8830
|
+
if (command.includes("/") || command.includes("\\")) return await isExecutable(command) ? command : null;
|
|
8831
|
+
const searchPath = process.env.PATH ?? "";
|
|
8832
|
+
for (const directory of searchPath.split(delimiter)) {
|
|
8833
|
+
const trimmed = directory.trim();
|
|
8834
|
+
if (!trimmed) continue;
|
|
8835
|
+
const candidate = `${trimmed}/${command}`;
|
|
8836
|
+
if (await isExecutable(candidate)) return candidate;
|
|
8837
|
+
}
|
|
8838
|
+
return null;
|
|
8839
|
+
}
|
|
8840
|
+
async function isExecutable(filePath) {
|
|
8841
|
+
try {
|
|
8842
|
+
await access(filePath, constants.X_OK);
|
|
8843
|
+
return true;
|
|
8844
|
+
} catch {
|
|
8845
|
+
return false;
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
var BuiltinNarpRuntimeRegistrationService = class {
|
|
8849
|
+
constructor(getConfig) {
|
|
8850
|
+
this.getConfig = getConfig;
|
|
8851
|
+
}
|
|
8852
|
+
registerInto = (runtimeRegistry) => {
|
|
8853
|
+
return [runtimeRegistry.register({
|
|
8854
|
+
kind: NARP_HTTP_RUNTIME_KIND,
|
|
8855
|
+
label: "NARP HTTP",
|
|
8856
|
+
createRuntime: this.createUnavailableRuntime,
|
|
8857
|
+
createRuntimeForEntry: ({ entry, runtimeParams }) => this.createHttpRuntime(entry, runtimeParams),
|
|
8858
|
+
describeSessionTypeForEntry: ({ entry, describeParams }) => new BuiltinHttpRuntimeSessionTypeService(entry, this.getConfig().agents.defaults.model).describe(describeParams)
|
|
8859
|
+
}), runtimeRegistry.register({
|
|
8860
|
+
kind: NARP_STDIO_RUNTIME_KIND,
|
|
8861
|
+
label: "NARP Stdio",
|
|
8862
|
+
createRuntime: this.createUnavailableRuntime,
|
|
8863
|
+
createRuntimeForEntry: ({ entry, runtimeParams }) => this.createStdioRuntime(entry, runtimeParams),
|
|
8864
|
+
describeSessionTypeForEntry: ({ entry, describeParams }) => new BuiltinStdioRuntimeSessionTypeService(entry).describe(describeParams)
|
|
8865
|
+
})];
|
|
8866
|
+
};
|
|
8867
|
+
createUnavailableRuntime = () => {
|
|
8868
|
+
throw new Error("[narp] runtime entry is required before creating this runtime");
|
|
8869
|
+
};
|
|
8870
|
+
createHttpRuntime = (entry, runtimeParams) => {
|
|
8871
|
+
const config = readRecord(entry.config) ?? {};
|
|
8872
|
+
const resolver = new HttpRuntimeConfigResolver(config);
|
|
8873
|
+
const resolvedConfig = resolver.resolve({ defaultModel: this.getConfig().agents.defaults.model });
|
|
8874
|
+
return new HttpRuntimeNcpAgentRuntime({
|
|
8875
|
+
baseUrl: resolver.requireBaseUrl(),
|
|
8876
|
+
...resolvedConfig.basePath ? { basePath: resolvedConfig.basePath } : {},
|
|
8877
|
+
...resolvedConfig.endpointId ? { endpointId: resolvedConfig.endpointId } : {},
|
|
8878
|
+
...resolvedConfig.headers ? { headers: resolvedConfig.headers } : {},
|
|
8879
|
+
stateManager: runtimeParams.stateManager,
|
|
8880
|
+
resolveTools: runtimeParams.resolveTools,
|
|
8881
|
+
resolveProviderRoute: (input) => buildProviderRoute({
|
|
8882
|
+
config: this.getConfig(),
|
|
8883
|
+
input,
|
|
8884
|
+
sessionMetadata: runtimeParams.sessionMetadata,
|
|
8885
|
+
defaultModel: this.getConfig().agents.defaults.model,
|
|
8886
|
+
configuredModel: readString$1(config.model)
|
|
8887
|
+
})
|
|
8888
|
+
});
|
|
8889
|
+
};
|
|
8890
|
+
createStdioRuntime = (entry, runtimeParams) => {
|
|
8891
|
+
const config = readRecord(entry.config) ?? {};
|
|
8892
|
+
return new StdioRuntimeNcpAgentRuntime({
|
|
8893
|
+
...new StdioRuntimeConfigResolver(config).resolve(),
|
|
8894
|
+
sessionId: runtimeParams.sessionId,
|
|
8895
|
+
stateManager: runtimeParams.stateManager,
|
|
8896
|
+
resolveTools: runtimeParams.resolveTools,
|
|
8897
|
+
resolveProviderRoute: (input) => buildProviderRoute({
|
|
8898
|
+
config: this.getConfig(),
|
|
8899
|
+
input,
|
|
8900
|
+
sessionMetadata: runtimeParams.sessionMetadata,
|
|
8901
|
+
defaultModel: this.getConfig().agents.defaults.model,
|
|
8902
|
+
configuredModel: readString$1(config.model)
|
|
8903
|
+
})
|
|
8904
|
+
});
|
|
8905
|
+
};
|
|
8906
|
+
};
|
|
8907
|
+
//#endregion
|
|
8440
8908
|
//#region src/cli/commands/agent/agent-runtime.ts
|
|
8441
8909
|
function createUnusedRuntime(_params) {
|
|
8442
8910
|
throw new Error("runtime creation is not available during runtime listing");
|
|
@@ -8463,15 +8931,21 @@ async function listAvailableAgentRuntimes(params) {
|
|
|
8463
8931
|
const pluginRegistry = loadRuntimeOnlyPluginRegistry(config, getWorkspacePath(config.agents.defaults.workspace));
|
|
8464
8932
|
logPluginDiagnostics(pluginRegistry);
|
|
8465
8933
|
const extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
8466
|
-
const runtimeRegistry = new UiNcpRuntimeRegistry(
|
|
8934
|
+
const runtimeRegistry = new UiNcpRuntimeRegistry();
|
|
8467
8935
|
const runtimeSourceByKind = /* @__PURE__ */ new Map();
|
|
8936
|
+
const runtimeSourceByEntryId = /* @__PURE__ */ new Map();
|
|
8468
8937
|
runtimeRegistry.register({
|
|
8469
|
-
kind:
|
|
8938
|
+
kind: DEFAULT_UI_NCP_RUNTIME_ENTRY_ID,
|
|
8470
8939
|
label: "Native",
|
|
8471
8940
|
createRuntime: createUnusedRuntime
|
|
8472
8941
|
});
|
|
8473
|
-
runtimeSourceByKind.set(
|
|
8942
|
+
runtimeSourceByKind.set(DEFAULT_UI_NCP_RUNTIME_ENTRY_ID, { source: "builtin" });
|
|
8943
|
+
new BuiltinNarpRuntimeRegistrationService(() => config).registerInto(runtimeRegistry);
|
|
8944
|
+
runtimeSourceByKind.set("narp-http", { source: "builtin" });
|
|
8945
|
+
runtimeSourceByKind.set("narp-stdio", { source: "builtin" });
|
|
8474
8946
|
for (const registration of extensionRegistry.ncpAgentRuntimes) {
|
|
8947
|
+
const normalizedKind = registration.kind.trim().toLowerCase();
|
|
8948
|
+
if (normalizedKind === "narp-http" || normalizedKind === "narp-stdio") continue;
|
|
8475
8949
|
runtimeRegistry.register({
|
|
8476
8950
|
kind: registration.kind,
|
|
8477
8951
|
label: registration.label,
|
|
@@ -8483,11 +8957,20 @@ async function listAvailableAgentRuntimes(params) {
|
|
|
8483
8957
|
pluginId: registration.pluginId
|
|
8484
8958
|
});
|
|
8485
8959
|
}
|
|
8960
|
+
const resolvedEntries = resolveUiNcpRuntimeEntries({
|
|
8961
|
+
config,
|
|
8962
|
+
providerKinds: runtimeRegistry.listProviderKinds()
|
|
8963
|
+
});
|
|
8964
|
+
runtimeRegistry.applyEntries(resolvedEntries);
|
|
8965
|
+
for (const entry of resolvedEntries.entries) {
|
|
8966
|
+
const source = runtimeSourceByKind.get(entry.type);
|
|
8967
|
+
runtimeSourceByEntryId.set(entry.id, source ?? { source: entry.id === "native" ? "builtin" : "plugin" });
|
|
8968
|
+
}
|
|
8486
8969
|
const listed = await runtimeRegistry.listSessionTypes(params);
|
|
8487
8970
|
return {
|
|
8488
8971
|
defaultRuntime: listed.defaultType,
|
|
8489
8972
|
runtimes: listed.options.map((runtime) => {
|
|
8490
|
-
const source =
|
|
8973
|
+
const source = runtimeSourceByEntryId.get(runtime.value);
|
|
8491
8974
|
return {
|
|
8492
8975
|
...runtime,
|
|
8493
8976
|
default: runtime.value === listed.defaultType,
|
|
@@ -10343,7 +10826,7 @@ var RemoteAccessHost = class {
|
|
|
10343
10826
|
};
|
|
10344
10827
|
};
|
|
10345
10828
|
//#endregion
|
|
10346
|
-
//#region src/cli/commands/service-support/
|
|
10829
|
+
//#region src/cli/commands/service-support/ui/service-remote-access.service.ts
|
|
10347
10830
|
function requestManagedServiceRestart(requestRestart, options = {}) {
|
|
10348
10831
|
const uiPort = typeof options.uiPort === "number" && Number.isFinite(options.uiPort) ? Math.floor(options.uiPort) : void 0;
|
|
10349
10832
|
return requestRestart({
|
|
@@ -10355,27 +10838,122 @@ function requestManagedServiceRestart(requestRestart, options = {}) {
|
|
|
10355
10838
|
});
|
|
10356
10839
|
}
|
|
10357
10840
|
function createRemoteAccessHost(params) {
|
|
10358
|
-
const
|
|
10841
|
+
const { remoteModule, requestRestart, serviceCommands, uiConfig } = params;
|
|
10359
10842
|
return new RemoteAccessHost({
|
|
10360
|
-
serviceCommands
|
|
10361
|
-
requestManagedServiceRestart: (options) => requestManagedServiceRestart(
|
|
10362
|
-
remoteCommands: new RemoteCommands({ currentLocalOrigin }),
|
|
10843
|
+
serviceCommands,
|
|
10844
|
+
requestManagedServiceRestart: (options) => requestManagedServiceRestart(requestRestart, options),
|
|
10845
|
+
remoteCommands: new RemoteCommands({ currentLocalOrigin: `http://127.0.0.1:${uiConfig.port}` }),
|
|
10363
10846
|
platformAuthCommands: new PlatformAuthCommands(),
|
|
10364
|
-
currentUi:
|
|
10365
|
-
remoteRuntimeController:
|
|
10847
|
+
currentUi: uiConfig,
|
|
10848
|
+
remoteRuntimeController: remoteModule ? {
|
|
10366
10849
|
start: async () => {
|
|
10367
|
-
|
|
10850
|
+
remoteModule?.start();
|
|
10368
10851
|
},
|
|
10369
10852
|
stop: async () => {
|
|
10370
|
-
await
|
|
10853
|
+
await remoteModule?.stop();
|
|
10371
10854
|
},
|
|
10372
10855
|
restart: async () => {
|
|
10373
|
-
await
|
|
10856
|
+
await remoteModule?.restart();
|
|
10374
10857
|
}
|
|
10375
10858
|
} : null
|
|
10376
10859
|
});
|
|
10377
10860
|
}
|
|
10378
10861
|
//#endregion
|
|
10862
|
+
//#region src/cli/commands/service-support/ui/runtime-control-host.service.ts
|
|
10863
|
+
const MANAGED_SERVICE_OWNER_LABEL = "Managed local service";
|
|
10864
|
+
const DESKTOP_APP_ONLY_REASON = "App restart is only available in the desktop shell.";
|
|
10865
|
+
const RUNNING_PAGE_START_REASON = "This page is already hosted by the running local service.";
|
|
10866
|
+
var RuntimeControlHost = class {
|
|
10867
|
+
constructor(deps) {
|
|
10868
|
+
this.deps = deps;
|
|
10869
|
+
}
|
|
10870
|
+
getControl = () => {
|
|
10871
|
+
const service = resolveRemoteServiceView(this.deps.uiConfig);
|
|
10872
|
+
const serviceRunning = service.running;
|
|
10873
|
+
return {
|
|
10874
|
+
environment: "managed-local-service",
|
|
10875
|
+
lifecycle: serviceRunning ? "healthy" : "unavailable",
|
|
10876
|
+
serviceState: serviceRunning ? "running" : "stopped",
|
|
10877
|
+
canStartService: {
|
|
10878
|
+
available: !serviceRunning,
|
|
10879
|
+
requiresConfirmation: false,
|
|
10880
|
+
impact: "brief-ui-disconnect",
|
|
10881
|
+
...serviceRunning ? { reasonIfUnavailable: RUNNING_PAGE_START_REASON } : {}
|
|
10882
|
+
},
|
|
10883
|
+
canRestartService: {
|
|
10884
|
+
available: serviceRunning,
|
|
10885
|
+
requiresConfirmation: false,
|
|
10886
|
+
impact: "brief-ui-disconnect",
|
|
10887
|
+
...!serviceRunning ? { reasonIfUnavailable: "The local service is not running." } : {}
|
|
10888
|
+
},
|
|
10889
|
+
canStopService: {
|
|
10890
|
+
available: serviceRunning,
|
|
10891
|
+
requiresConfirmation: true,
|
|
10892
|
+
impact: "brief-ui-disconnect",
|
|
10893
|
+
...!serviceRunning ? { reasonIfUnavailable: "The local service is already stopped." } : {}
|
|
10894
|
+
},
|
|
10895
|
+
canRestartApp: {
|
|
10896
|
+
available: false,
|
|
10897
|
+
requiresConfirmation: true,
|
|
10898
|
+
impact: "full-app-relaunch",
|
|
10899
|
+
reasonIfUnavailable: DESKTOP_APP_ONLY_REASON
|
|
10900
|
+
},
|
|
10901
|
+
ownerLabel: MANAGED_SERVICE_OWNER_LABEL,
|
|
10902
|
+
managementHint: service.currentProcess ? "This page is served by the running local service. Closing the browser does not stop it." : "Manage the local NextClaw service from this page without tying service lifecycle to the browser tab.",
|
|
10903
|
+
message: "Use this page to manage the local NextClaw service. Closing the browser does not stop the service."
|
|
10904
|
+
};
|
|
10905
|
+
};
|
|
10906
|
+
startService = async () => {
|
|
10907
|
+
const result = await controlRemoteService("start", this.createServiceControlDeps());
|
|
10908
|
+
return {
|
|
10909
|
+
accepted: result.accepted,
|
|
10910
|
+
action: "start-service",
|
|
10911
|
+
lifecycle: "starting-service",
|
|
10912
|
+
message: result.message
|
|
10913
|
+
};
|
|
10914
|
+
};
|
|
10915
|
+
restartService = async () => {
|
|
10916
|
+
const result = await controlRemoteService("restart", this.createServiceControlDeps());
|
|
10917
|
+
return {
|
|
10918
|
+
accepted: result.accepted,
|
|
10919
|
+
action: "restart-service",
|
|
10920
|
+
lifecycle: "restarting-service",
|
|
10921
|
+
message: result.message
|
|
10922
|
+
};
|
|
10923
|
+
};
|
|
10924
|
+
stopService = async () => {
|
|
10925
|
+
const result = await controlRemoteService("stop", this.createServiceControlDeps());
|
|
10926
|
+
return {
|
|
10927
|
+
accepted: result.accepted,
|
|
10928
|
+
action: "stop-service",
|
|
10929
|
+
lifecycle: "stopping-service",
|
|
10930
|
+
message: result.message
|
|
10931
|
+
};
|
|
10932
|
+
};
|
|
10933
|
+
createServiceControlDeps = () => {
|
|
10934
|
+
return {
|
|
10935
|
+
serviceCommands: this.deps.serviceCommands,
|
|
10936
|
+
requestManagedServiceRestart: () => requestManagedServiceRestart(this.deps.requestRestart, { uiPort: this.deps.uiConfig.port }),
|
|
10937
|
+
currentUi: this.deps.uiConfig
|
|
10938
|
+
};
|
|
10939
|
+
};
|
|
10940
|
+
};
|
|
10941
|
+
function createRuntimeControlHost(params) {
|
|
10942
|
+
return new RuntimeControlHost(params);
|
|
10943
|
+
}
|
|
10944
|
+
//#endregion
|
|
10945
|
+
//#region src/cli/commands/service-support/ui/service-ui-hosts.service.ts
|
|
10946
|
+
function createServiceUiHosts(params) {
|
|
10947
|
+
return {
|
|
10948
|
+
remoteAccess: createRemoteAccessHost(params),
|
|
10949
|
+
runtimeControl: createRuntimeControlHost({
|
|
10950
|
+
serviceCommands: params.serviceCommands,
|
|
10951
|
+
requestRestart: params.requestRestart,
|
|
10952
|
+
uiConfig: params.uiConfig
|
|
10953
|
+
})
|
|
10954
|
+
};
|
|
10955
|
+
}
|
|
10956
|
+
//#endregion
|
|
10379
10957
|
//#region src/cli/commands/ncp/runtime/ncp-asset-tools.ts
|
|
10380
10958
|
function readOptionalString$5(value) {
|
|
10381
10959
|
if (typeof value !== "string") return null;
|
|
@@ -10547,11 +11125,11 @@ function normalizeString(value) {
|
|
|
10547
11125
|
const trimmed = value.trim();
|
|
10548
11126
|
return trimmed.length > 0 ? trimmed : null;
|
|
10549
11127
|
}
|
|
10550
|
-
function isRecord$
|
|
11128
|
+
function isRecord$3(value) {
|
|
10551
11129
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
10552
11130
|
}
|
|
10553
11131
|
function cloneMetadata(value) {
|
|
10554
|
-
return isRecord$
|
|
11132
|
+
return isRecord$3(value) ? structuredClone(value) : void 0;
|
|
10555
11133
|
}
|
|
10556
11134
|
function readStringArray(value) {
|
|
10557
11135
|
if (!Array.isArray(value)) return null;
|
|
@@ -11030,10 +11608,10 @@ var SessionSpawnTool = class extends Tool {
|
|
|
11030
11608
|
//#endregion
|
|
11031
11609
|
//#region src/cli/commands/ncp/nextclaw-ncp-tool-registry.ts
|
|
11032
11610
|
function toToolParams(args) {
|
|
11033
|
-
if (isRecord$
|
|
11611
|
+
if (isRecord$3(args)) return args;
|
|
11034
11612
|
if (typeof args === "string") try {
|
|
11035
11613
|
const parsed = JSON.parse(args);
|
|
11036
|
-
return isRecord$
|
|
11614
|
+
return isRecord$3(parsed) ? parsed : {};
|
|
11037
11615
|
} catch {
|
|
11038
11616
|
return {};
|
|
11039
11617
|
}
|
|
@@ -11076,13 +11654,14 @@ var NextclawNcpToolRegistry = class {
|
|
|
11076
11654
|
this.options = options;
|
|
11077
11655
|
}
|
|
11078
11656
|
prepareForRun = (context) => {
|
|
11657
|
+
const { channel, chatId, config, restrictToWorkspace, sessionId, workspace } = context;
|
|
11079
11658
|
this.currentExtensionToolContext = {
|
|
11080
|
-
config
|
|
11081
|
-
workspaceDir:
|
|
11082
|
-
sessionKey:
|
|
11083
|
-
channel
|
|
11084
|
-
chatId
|
|
11085
|
-
sandboxed:
|
|
11659
|
+
config,
|
|
11660
|
+
workspaceDir: workspace,
|
|
11661
|
+
sessionKey: sessionId,
|
|
11662
|
+
channel,
|
|
11663
|
+
chatId,
|
|
11664
|
+
sandboxed: restrictToWorkspace
|
|
11086
11665
|
};
|
|
11087
11666
|
this.registry = new ToolRegistry();
|
|
11088
11667
|
this.tools.clear();
|
|
@@ -11210,26 +11789,17 @@ function readAccountIdForHints(metadata, sessionMetadata) {
|
|
|
11210
11789
|
//#endregion
|
|
11211
11790
|
//#region src/cli/commands/ncp/nextclaw-ncp-context-builder.ts
|
|
11212
11791
|
const TIME_HINT_TRIGGER_PATTERNS = [/\b(now|right now|current time|what time|today|tonight|tomorrow|yesterday|this morning|this afternoon|this evening|date)\b/i, /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/];
|
|
11213
|
-
function isRecord$
|
|
11792
|
+
function isRecord$2(value) {
|
|
11214
11793
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11215
11794
|
}
|
|
11216
11795
|
function mergeInputMetadata(input) {
|
|
11217
|
-
const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$
|
|
11796
|
+
const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$2(message.metadata))?.metadata;
|
|
11218
11797
|
return {
|
|
11219
|
-
...isRecord$
|
|
11220
|
-
...isRecord$
|
|
11798
|
+
...isRecord$2(messageMetadata) ? structuredClone(messageMetadata) : {},
|
|
11799
|
+
...isRecord$2(input.metadata) ? structuredClone(input.metadata) : {}
|
|
11221
11800
|
};
|
|
11222
11801
|
}
|
|
11223
|
-
|
|
11224
|
-
const rawValue = metadata.requested_skills ?? metadata.requestedSkills;
|
|
11225
|
-
const values = [];
|
|
11226
|
-
if (Array.isArray(rawValue)) for (const item of rawValue) {
|
|
11227
|
-
const normalized = normalizeString(item);
|
|
11228
|
-
if (normalized) values.push(normalized);
|
|
11229
|
-
}
|
|
11230
|
-
else if (typeof rawValue === "string") values.push(...rawValue.split(/[,\s]+/g).map((item) => item.trim()).filter(Boolean));
|
|
11231
|
-
return Array.from(new Set(values)).slice(0, 8);
|
|
11232
|
-
}
|
|
11802
|
+
const REQUESTED_SKILLS_METADATA_READER = new RequestedSkillsMetadataReader();
|
|
11233
11803
|
function resolveRequestedToolNames(metadata) {
|
|
11234
11804
|
const rawValue = metadata.requested_tools ?? metadata.requestedTools;
|
|
11235
11805
|
if (!Array.isArray(rawValue)) return [];
|
|
@@ -11275,9 +11845,9 @@ function appendTimeHintForPrompt(content, timestamp) {
|
|
|
11275
11845
|
if (!shouldAppendTimeHint(content)) return content;
|
|
11276
11846
|
return `${content}\n\n[time_hint_local_minute] ${buildMinutePrecisionTimeHint(Number.isNaN(timestamp.getTime()) ? /* @__PURE__ */ new Date() : timestamp)}`;
|
|
11277
11847
|
}
|
|
11278
|
-
function prependRequestedSkills(content,
|
|
11279
|
-
if (
|
|
11280
|
-
return `[Requested skills for this turn: ${
|
|
11848
|
+
function prependRequestedSkills(content, requestedSkillSelectors) {
|
|
11849
|
+
if (requestedSkillSelectors.length === 0) return content;
|
|
11850
|
+
return `[Requested skills for this turn: ${requestedSkillSelectors.join(", ")}]\n\n${content}`;
|
|
11281
11851
|
}
|
|
11282
11852
|
function buildSessionOrchestrationSection() {
|
|
11283
11853
|
return [
|
|
@@ -11333,12 +11903,12 @@ var NextclawNcpContextBuilder = class {
|
|
|
11333
11903
|
session,
|
|
11334
11904
|
requestMetadata
|
|
11335
11905
|
});
|
|
11336
|
-
const
|
|
11906
|
+
const requestedSkills = REQUESTED_SKILLS_METADATA_READER.readSelection(requestMetadata);
|
|
11337
11907
|
const requestedToolNames = resolveRequestedToolNames(requestMetadata);
|
|
11338
11908
|
const currentTurn = buildCurrentTurnState({
|
|
11339
11909
|
input,
|
|
11340
11910
|
currentModel: effectiveModel,
|
|
11341
|
-
formatPrompt: ({ text, timestamp }) => appendTimeHintForPrompt(prependRequestedSkills(text,
|
|
11911
|
+
formatPrompt: ({ text, timestamp }) => appendTimeHintForPrompt(prependRequestedSkills(text, requestedSkills.selectors), timestamp),
|
|
11342
11912
|
assetStore: this.options.assetStore
|
|
11343
11913
|
});
|
|
11344
11914
|
effectiveModel = currentTurn.effectiveModel;
|
|
@@ -11386,7 +11956,7 @@ var NextclawNcpContextBuilder = class {
|
|
|
11386
11956
|
chatId,
|
|
11387
11957
|
sessionKey: input.sessionId,
|
|
11388
11958
|
thinkingLevel: runtimeThinking,
|
|
11389
|
-
skillNames:
|
|
11959
|
+
skillNames: requestedSkills.selectors,
|
|
11390
11960
|
messageToolHints,
|
|
11391
11961
|
availableTools: buildToolCatalogEntries(toolDefinitions),
|
|
11392
11962
|
additionalSystemSections
|
|
@@ -11806,6 +12376,13 @@ function cloneInheritedMetadata(sourceMetadata) {
|
|
|
11806
12376
|
}
|
|
11807
12377
|
return nextMetadata;
|
|
11808
12378
|
}
|
|
12379
|
+
function mergeMetadataOverrides(metadata, overrides) {
|
|
12380
|
+
if (!overrides || Object.keys(overrides).length === 0) return metadata;
|
|
12381
|
+
return {
|
|
12382
|
+
...metadata,
|
|
12383
|
+
...structuredClone(overrides)
|
|
12384
|
+
};
|
|
12385
|
+
}
|
|
11809
12386
|
function buildSessionId() {
|
|
11810
12387
|
return `ncp-${Date.now().toString(36)}-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
|
|
11811
12388
|
}
|
|
@@ -11816,27 +12393,29 @@ function resolveSessionTitle(params) {
|
|
|
11816
12393
|
return readOptionalString$2(params.title) ?? summarizeTask(params.task);
|
|
11817
12394
|
}
|
|
11818
12395
|
function resolveSessionType(params) {
|
|
11819
|
-
|
|
12396
|
+
const { metadata, runtime, sessionType } = params;
|
|
12397
|
+
return readOptionalString$2(runtime) ?? readOptionalString$2(metadata.runtime) ?? readOptionalString$2(sessionType) ?? readOptionalString$2(metadata.session_type) ?? DEFAULT_SESSION_TYPE;
|
|
11820
12398
|
}
|
|
11821
12399
|
function applySessionOverrides(params) {
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
if (
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
12400
|
+
const { lifecycle, metadata, model, parentSessionId, projectRoot, requestId, sessionType, thinkingLevel, title } = params;
|
|
12401
|
+
metadata.session_type = sessionType;
|
|
12402
|
+
metadata.runtime = sessionType;
|
|
12403
|
+
metadata[SESSION_METADATA_LABEL_KEY] = title;
|
|
12404
|
+
metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = lifecycle;
|
|
12405
|
+
if (parentSessionId) {
|
|
12406
|
+
metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
|
|
12407
|
+
metadata[CHILD_SESSION_PROMOTED_METADATA_KEY] = false;
|
|
12408
|
+
}
|
|
12409
|
+
if (requestId) metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
|
|
12410
|
+
if (readOptionalString$2(model)) {
|
|
12411
|
+
metadata.model = model?.trim();
|
|
12412
|
+
metadata.preferred_model = model?.trim();
|
|
12413
|
+
}
|
|
12414
|
+
if (readOptionalString$2(thinkingLevel)) {
|
|
12415
|
+
metadata.thinking = thinkingLevel?.trim();
|
|
12416
|
+
metadata.preferred_thinking = thinkingLevel?.trim();
|
|
12417
|
+
}
|
|
12418
|
+
if (readOptionalString$2(projectRoot)) metadata.project_root = projectRoot?.trim();
|
|
11840
12419
|
}
|
|
11841
12420
|
var SessionCreationService = class {
|
|
11842
12421
|
constructor(sessionManager, getConfig, onSessionUpdated) {
|
|
@@ -11845,23 +12424,24 @@ var SessionCreationService = class {
|
|
|
11845
12424
|
this.onSessionUpdated = onSessionUpdated;
|
|
11846
12425
|
}
|
|
11847
12426
|
createSession = (params) => {
|
|
12427
|
+
const { agentId, metadataOverrides, model, parentSessionId: rawParentSessionId, projectRoot, requestId: rawRequestId, runtime, sessionType: requestedSessionType, sourceSessionMetadata, task, thinkingLevel, title: requestedTitle } = params;
|
|
11848
12428
|
const sessionId = buildSessionId();
|
|
11849
12429
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11850
12430
|
const session = this.sessionManager.getOrCreate(sessionId);
|
|
11851
12431
|
const resolvedAgentId = resolveSessionAgentId({
|
|
11852
|
-
agentId
|
|
12432
|
+
agentId,
|
|
11853
12433
|
getConfig: this.getConfig
|
|
11854
12434
|
});
|
|
11855
12435
|
const title = resolveSessionTitle({
|
|
11856
|
-
title:
|
|
11857
|
-
task
|
|
12436
|
+
title: requestedTitle,
|
|
12437
|
+
task
|
|
11858
12438
|
});
|
|
11859
|
-
const metadata = cloneInheritedMetadata(
|
|
11860
|
-
const parentSessionId = readOptionalString$2(
|
|
11861
|
-
const requestId = readOptionalString$2(
|
|
12439
|
+
const metadata = cloneInheritedMetadata(sourceSessionMetadata);
|
|
12440
|
+
const parentSessionId = readOptionalString$2(rawParentSessionId);
|
|
12441
|
+
const requestId = readOptionalString$2(rawRequestId);
|
|
11862
12442
|
const sessionType = resolveSessionType({
|
|
11863
|
-
runtime
|
|
11864
|
-
sessionType:
|
|
12443
|
+
runtime,
|
|
12444
|
+
sessionType: requestedSessionType,
|
|
11865
12445
|
metadata
|
|
11866
12446
|
});
|
|
11867
12447
|
applySessionOverrides({
|
|
@@ -11871,12 +12451,13 @@ var SessionCreationService = class {
|
|
|
11871
12451
|
lifecycle: DEFAULT_LIFECYCLE,
|
|
11872
12452
|
parentSessionId,
|
|
11873
12453
|
requestId,
|
|
11874
|
-
model
|
|
11875
|
-
thinkingLevel
|
|
11876
|
-
projectRoot
|
|
12454
|
+
model,
|
|
12455
|
+
thinkingLevel,
|
|
12456
|
+
projectRoot
|
|
11877
12457
|
});
|
|
12458
|
+
const nextMetadata = mergeMetadataOverrides(metadata, metadataOverrides);
|
|
11878
12459
|
session.agentId = resolvedAgentId;
|
|
11879
|
-
session.metadata =
|
|
12460
|
+
session.metadata = nextMetadata;
|
|
11880
12461
|
session.updatedAt = new Date(now);
|
|
11881
12462
|
this.sessionManager.save(session);
|
|
11882
12463
|
this.onSessionUpdated?.(sessionId);
|
|
@@ -11889,7 +12470,7 @@ var SessionCreationService = class {
|
|
|
11889
12470
|
...requestId ? { spawnedByRequestId: requestId } : {},
|
|
11890
12471
|
lifecycle: DEFAULT_LIFECYCLE,
|
|
11891
12472
|
title,
|
|
11892
|
-
metadata,
|
|
12473
|
+
metadata: nextMetadata,
|
|
11893
12474
|
createdAt: session.createdAt.toISOString(),
|
|
11894
12475
|
updatedAt: session.updatedAt.toISOString()
|
|
11895
12476
|
};
|
|
@@ -12019,7 +12600,7 @@ function createFailedSessionRequest(params) {
|
|
|
12019
12600
|
};
|
|
12020
12601
|
}
|
|
12021
12602
|
//#endregion
|
|
12022
|
-
//#region src/cli/commands/ncp/session-request/session-request-broker.ts
|
|
12603
|
+
//#region src/cli/commands/ncp/session-request/session-request-broker.service.ts
|
|
12023
12604
|
var SessionRequestBroker = class {
|
|
12024
12605
|
constructor(sessionManager, sessionCreationService, deliveryService, resolveBackend, onSessionUpdated) {
|
|
12025
12606
|
this.sessionManager = sessionManager;
|
|
@@ -12029,13 +12610,14 @@ var SessionRequestBroker = class {
|
|
|
12029
12610
|
this.onSessionUpdated = onSessionUpdated;
|
|
12030
12611
|
}
|
|
12031
12612
|
spawnSessionAndRequest = async (params) => {
|
|
12032
|
-
const { sourceSessionId, sourceToolCallId, sourceSessionMetadata, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId, parentSessionId, notify } = params;
|
|
12613
|
+
const { sourceSessionId, sourceToolCallId, sourceSessionMetadata, metadataOverrides, task, title, model, runtime, handoffDepth, sessionType, thinkingLevel, projectRoot, agentId, parentSessionId, notify } = params;
|
|
12033
12614
|
const requestId = randomUUID();
|
|
12034
12615
|
const createdSession = this.sessionCreationService.createSession({
|
|
12035
12616
|
...parentSessionId ? { parentSessionId } : {},
|
|
12036
12617
|
task,
|
|
12037
12618
|
title,
|
|
12038
12619
|
sourceSessionMetadata,
|
|
12620
|
+
...metadataOverrides ? { metadataOverrides } : {},
|
|
12039
12621
|
agentId,
|
|
12040
12622
|
model,
|
|
12041
12623
|
runtime,
|
|
@@ -12335,25 +12917,423 @@ var SessionRequestDeliveryService = class {
|
|
|
12335
12917
|
};
|
|
12336
12918
|
};
|
|
12337
12919
|
//#endregion
|
|
12338
|
-
//#region src/cli/commands/
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12920
|
+
//#region src/cli/commands/ncp/session-search/session-search-index.manager.ts
|
|
12921
|
+
const AUTO_LABEL_MAX_LENGTH = 64;
|
|
12922
|
+
function normalizeWhitespace$1(value) {
|
|
12923
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
12924
|
+
}
|
|
12925
|
+
function truncateLabel(value) {
|
|
12926
|
+
const characters = Array.from(value);
|
|
12927
|
+
if (characters.length <= AUTO_LABEL_MAX_LENGTH) return value;
|
|
12928
|
+
return `${characters.slice(0, AUTO_LABEL_MAX_LENGTH).join("")}…`;
|
|
12929
|
+
}
|
|
12930
|
+
function resolveSessionLabel(session) {
|
|
12931
|
+
const metadataLabel = normalizeString(session.metadata?.label) ?? normalizeString(session.metadata?.session_label);
|
|
12932
|
+
if (metadataLabel) return metadataLabel;
|
|
12933
|
+
for (const message of session.messages) {
|
|
12934
|
+
if (message.role !== "user") continue;
|
|
12935
|
+
const text = normalizeString(extractTextFromNcpMessage(message));
|
|
12936
|
+
if (text) return truncateLabel(text);
|
|
12937
|
+
}
|
|
12938
|
+
return "";
|
|
12939
|
+
}
|
|
12940
|
+
function buildSearchableContent(session) {
|
|
12941
|
+
const lines = [];
|
|
12942
|
+
for (const message of session.messages) {
|
|
12943
|
+
if (message.role !== "user" && message.role !== "assistant") continue;
|
|
12944
|
+
const text = normalizeString(extractTextFromNcpMessage(message));
|
|
12945
|
+
if (!text) continue;
|
|
12946
|
+
lines.push(`${message.role}: ${normalizeWhitespace$1(text)}`);
|
|
12947
|
+
}
|
|
12948
|
+
return lines.join("\n");
|
|
12949
|
+
}
|
|
12950
|
+
var SessionSearchIndexManager = class {
|
|
12951
|
+
constructor(store) {
|
|
12952
|
+
this.store = store;
|
|
12953
|
+
}
|
|
12954
|
+
indexSession = async (session) => {
|
|
12955
|
+
const document = this.buildDocument(session);
|
|
12956
|
+
if (!document) {
|
|
12957
|
+
await this.store.deleteDocument(session.sessionId);
|
|
12958
|
+
return;
|
|
12959
|
+
}
|
|
12960
|
+
await this.store.upsertDocument(document);
|
|
12961
|
+
};
|
|
12962
|
+
buildDocument(session) {
|
|
12963
|
+
const label = resolveSessionLabel(session);
|
|
12964
|
+
const content = buildSearchableContent(session);
|
|
12965
|
+
if (!label && !content) return null;
|
|
12966
|
+
return {
|
|
12967
|
+
sessionId: session.sessionId,
|
|
12968
|
+
label,
|
|
12969
|
+
content,
|
|
12970
|
+
updatedAt: session.updatedAt
|
|
12971
|
+
};
|
|
12343
12972
|
}
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12973
|
+
};
|
|
12974
|
+
//#endregion
|
|
12975
|
+
//#region src/cli/commands/ncp/session-search/session-search-query.service.ts
|
|
12976
|
+
const SNIPPET_RADIUS = 80;
|
|
12977
|
+
const MAX_FULL_SNIPPET_LENGTH = 180;
|
|
12978
|
+
function normalizeWhitespace(value) {
|
|
12979
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
12980
|
+
}
|
|
12981
|
+
function sanitizeSearchToken(value) {
|
|
12982
|
+
return value.replace(/["*]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
12983
|
+
}
|
|
12984
|
+
function tokenizeSearchTerms(query) {
|
|
12985
|
+
return normalizeWhitespace(query).split(" ").map(sanitizeSearchToken).filter((token) => token.length > 0);
|
|
12986
|
+
}
|
|
12987
|
+
function buildMatchExpression(query) {
|
|
12988
|
+
const terms = tokenizeSearchTerms(query);
|
|
12989
|
+
const normalizedTerms = (terms.length > 0 ? terms : [sanitizeSearchToken(query)]).filter((term) => term.length > 0);
|
|
12990
|
+
if (normalizedTerms.length === 0) throw new Error("query must contain searchable text.");
|
|
12991
|
+
return normalizedTerms.map((term) => `"${term.replace(/"/gu, "\"\"")}"*`).join(" AND ");
|
|
12992
|
+
}
|
|
12993
|
+
function findFirstMatchIndex(text, terms) {
|
|
12994
|
+
const lowerText = text.toLowerCase();
|
|
12995
|
+
let earliestIndex = -1;
|
|
12996
|
+
for (const term of terms) {
|
|
12997
|
+
const index = lowerText.indexOf(term.toLowerCase());
|
|
12998
|
+
if (index < 0) continue;
|
|
12999
|
+
if (earliestIndex < 0 || index < earliestIndex) earliestIndex = index;
|
|
13000
|
+
}
|
|
13001
|
+
return earliestIndex;
|
|
13002
|
+
}
|
|
13003
|
+
function buildSnippet(text, terms) {
|
|
13004
|
+
const normalizedText = normalizeWhitespace(text);
|
|
13005
|
+
if (normalizedText.length <= MAX_FULL_SNIPPET_LENGTH) return normalizedText;
|
|
13006
|
+
const matchIndex = findFirstMatchIndex(normalizedText, terms);
|
|
13007
|
+
if (matchIndex < 0) return `${normalizedText.slice(0, MAX_FULL_SNIPPET_LENGTH).trimEnd()}...`;
|
|
13008
|
+
const start = Math.max(0, matchIndex - SNIPPET_RADIUS);
|
|
13009
|
+
const end = Math.min(normalizedText.length, matchIndex + SNIPPET_RADIUS);
|
|
13010
|
+
const prefix = start > 0 ? "..." : "";
|
|
13011
|
+
const suffix = end < normalizedText.length ? "..." : "";
|
|
13012
|
+
return `${prefix}${normalizedText.slice(start, end).trim()}${suffix}`;
|
|
13013
|
+
}
|
|
13014
|
+
function resolveMatchSource(hit, terms) {
|
|
13015
|
+
return findFirstMatchIndex(hit.label, terms) >= 0 ? "label" : "content";
|
|
13016
|
+
}
|
|
13017
|
+
function normalizeLimit(limit) {
|
|
13018
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) return 5;
|
|
13019
|
+
const roundedLimit = Math.trunc(limit);
|
|
13020
|
+
if (roundedLimit <= 0) return 5;
|
|
13021
|
+
return Math.min(roundedLimit, 10);
|
|
13022
|
+
}
|
|
13023
|
+
var SessionSearchQueryService = class {
|
|
13024
|
+
constructor(store) {
|
|
13025
|
+
this.store = store;
|
|
13026
|
+
}
|
|
13027
|
+
search = async (request) => {
|
|
13028
|
+
const query = normalizeString(request.query);
|
|
13029
|
+
if (!query) throw new Error("query must be a non-empty string.");
|
|
13030
|
+
const terms = tokenizeSearchTerms(query);
|
|
13031
|
+
const matchExpression = buildMatchExpression(query);
|
|
13032
|
+
const excludeSessionId = request.includeCurrentSession === true ? void 0 : normalizeString(request.currentSessionId) ?? void 0;
|
|
13033
|
+
const result = await this.store.searchDocuments({
|
|
13034
|
+
matchExpression,
|
|
13035
|
+
limit: normalizeLimit(request.limit),
|
|
13036
|
+
excludeSessionId
|
|
12349
13037
|
});
|
|
13038
|
+
return {
|
|
13039
|
+
query,
|
|
13040
|
+
totalHits: result.total,
|
|
13041
|
+
hits: result.hits.map((hit) => this.toSearchHit(hit, terms))
|
|
13042
|
+
};
|
|
12350
13043
|
};
|
|
13044
|
+
toSearchHit(hit, terms) {
|
|
13045
|
+
const matchSource = resolveMatchSource(hit, terms);
|
|
13046
|
+
const label = normalizeWhitespace(hit.label) || hit.sessionId;
|
|
13047
|
+
const snippetSource = matchSource === "label" ? label : hit.content;
|
|
13048
|
+
return {
|
|
13049
|
+
sessionId: hit.sessionId,
|
|
13050
|
+
label,
|
|
13051
|
+
updatedAt: hit.updatedAt,
|
|
13052
|
+
snippet: buildSnippet(snippetSource, terms),
|
|
13053
|
+
matchSource,
|
|
13054
|
+
rank: hit.rank
|
|
13055
|
+
};
|
|
13056
|
+
}
|
|
12351
13057
|
};
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
13058
|
+
//#endregion
|
|
13059
|
+
//#region src/cli/commands/ncp/session-search/session-search-store.service.ts
|
|
13060
|
+
const SESSION_SEARCH_TABLE = "session_search_index";
|
|
13061
|
+
var SessionSearchStoreService = class {
|
|
13062
|
+
database = null;
|
|
13063
|
+
constructor(databasePath) {
|
|
13064
|
+
this.databasePath = databasePath;
|
|
13065
|
+
}
|
|
13066
|
+
initialize = async () => {
|
|
13067
|
+
if (this.database) return;
|
|
13068
|
+
mkdirSync(dirname(this.databasePath), { recursive: true });
|
|
13069
|
+
this.database = new DatabaseSync(this.databasePath);
|
|
13070
|
+
this.database.exec(`
|
|
13071
|
+
PRAGMA journal_mode = WAL;
|
|
13072
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS ${SESSION_SEARCH_TABLE}
|
|
13073
|
+
USING fts5(
|
|
13074
|
+
session_id UNINDEXED,
|
|
13075
|
+
label,
|
|
13076
|
+
content,
|
|
13077
|
+
updated_at UNINDEXED,
|
|
13078
|
+
tokenize = 'unicode61'
|
|
13079
|
+
);
|
|
13080
|
+
`);
|
|
13081
|
+
};
|
|
13082
|
+
listIndexedSessionIds = async () => {
|
|
13083
|
+
return this.requireDatabase().prepare(`
|
|
13084
|
+
SELECT session_id AS sessionId
|
|
13085
|
+
FROM ${SESSION_SEARCH_TABLE}
|
|
13086
|
+
`).all().map((row) => typeof row.sessionId === "string" ? row.sessionId : "").filter((sessionId) => sessionId.length > 0);
|
|
13087
|
+
};
|
|
13088
|
+
upsertDocument = async (document) => {
|
|
13089
|
+
const database = this.requireDatabase();
|
|
13090
|
+
this.withTransaction(() => {
|
|
13091
|
+
database.prepare(`
|
|
13092
|
+
DELETE FROM ${SESSION_SEARCH_TABLE}
|
|
13093
|
+
WHERE session_id = ?
|
|
13094
|
+
`).run(document.sessionId);
|
|
13095
|
+
database.prepare(`
|
|
13096
|
+
INSERT INTO ${SESSION_SEARCH_TABLE} (session_id, label, content, updated_at)
|
|
13097
|
+
VALUES (?, ?, ?, ?)
|
|
13098
|
+
`).run(document.sessionId, document.label, document.content, document.updatedAt);
|
|
13099
|
+
});
|
|
13100
|
+
};
|
|
13101
|
+
deleteDocument = async (sessionId) => {
|
|
13102
|
+
this.requireDatabase().prepare(`
|
|
13103
|
+
DELETE FROM ${SESSION_SEARCH_TABLE}
|
|
13104
|
+
WHERE session_id = ?
|
|
13105
|
+
`).run(sessionId);
|
|
13106
|
+
};
|
|
13107
|
+
searchDocuments = async (query) => {
|
|
13108
|
+
const database = this.requireDatabase();
|
|
13109
|
+
const countSql = this.buildSearchSql({
|
|
13110
|
+
includeLimit: false,
|
|
13111
|
+
excludeSessionId: Boolean(query.excludeSessionId)
|
|
13112
|
+
});
|
|
13113
|
+
const searchSql = this.buildSearchSql({
|
|
13114
|
+
includeLimit: true,
|
|
13115
|
+
excludeSessionId: Boolean(query.excludeSessionId)
|
|
13116
|
+
});
|
|
13117
|
+
const sharedParams = query.excludeSessionId ? [query.matchExpression, query.excludeSessionId] : [query.matchExpression];
|
|
13118
|
+
const countRow = database.prepare(countSql).get(...sharedParams);
|
|
13119
|
+
const rows = database.prepare(searchSql).all(...sharedParams, query.limit);
|
|
13120
|
+
return {
|
|
13121
|
+
total: typeof countRow?.total === "number" ? countRow.total : 0,
|
|
13122
|
+
hits: rows.map((row) => ({
|
|
13123
|
+
sessionId: typeof row.sessionId === "string" ? row.sessionId : "",
|
|
13124
|
+
label: typeof row.label === "string" ? row.label : "",
|
|
13125
|
+
content: typeof row.content === "string" ? row.content : "",
|
|
13126
|
+
updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : "",
|
|
13127
|
+
rank: typeof row.rank === "number" ? row.rank : 0
|
|
13128
|
+
}))
|
|
13129
|
+
};
|
|
13130
|
+
};
|
|
13131
|
+
close = async () => {
|
|
13132
|
+
this.database?.close();
|
|
13133
|
+
this.database = null;
|
|
13134
|
+
};
|
|
13135
|
+
buildSearchSql(params) {
|
|
13136
|
+
const extraFilter = params.excludeSessionId ? "AND session_id <> ?" : "";
|
|
13137
|
+
if (!params.includeLimit) return `
|
|
13138
|
+
SELECT COUNT(*) AS total
|
|
13139
|
+
FROM ${SESSION_SEARCH_TABLE}
|
|
13140
|
+
WHERE ${SESSION_SEARCH_TABLE} MATCH ?
|
|
13141
|
+
${extraFilter}
|
|
13142
|
+
`;
|
|
13143
|
+
return `
|
|
13144
|
+
SELECT
|
|
13145
|
+
session_id AS sessionId,
|
|
13146
|
+
label,
|
|
13147
|
+
content,
|
|
13148
|
+
updated_at AS updatedAt,
|
|
13149
|
+
bm25(${SESSION_SEARCH_TABLE}) AS rank
|
|
13150
|
+
FROM ${SESSION_SEARCH_TABLE}
|
|
13151
|
+
WHERE ${SESSION_SEARCH_TABLE} MATCH ?
|
|
13152
|
+
${extraFilter}
|
|
13153
|
+
ORDER BY rank, updated_at DESC
|
|
13154
|
+
LIMIT ?
|
|
13155
|
+
`;
|
|
13156
|
+
}
|
|
13157
|
+
withTransaction(work) {
|
|
13158
|
+
const database = this.requireDatabase();
|
|
13159
|
+
database.exec("BEGIN IMMEDIATE");
|
|
13160
|
+
try {
|
|
13161
|
+
work();
|
|
13162
|
+
database.exec("COMMIT");
|
|
13163
|
+
} catch (error) {
|
|
13164
|
+
database.exec("ROLLBACK");
|
|
13165
|
+
throw error;
|
|
13166
|
+
}
|
|
13167
|
+
}
|
|
13168
|
+
requireDatabase() {
|
|
13169
|
+
if (!this.database) throw new Error("Session search store has not been initialized.");
|
|
13170
|
+
return this.database;
|
|
13171
|
+
}
|
|
13172
|
+
};
|
|
13173
|
+
//#endregion
|
|
13174
|
+
//#region src/cli/commands/ncp/session-search/session-search-tool.service.ts
|
|
13175
|
+
function isRecord$1(value) {
|
|
13176
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13177
|
+
}
|
|
13178
|
+
function readOptionalInteger(value) {
|
|
13179
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
|
13180
|
+
return Math.trunc(value);
|
|
13181
|
+
}
|
|
13182
|
+
function readOptionalBoolean(value) {
|
|
13183
|
+
return typeof value === "boolean" ? value : void 0;
|
|
13184
|
+
}
|
|
13185
|
+
var SessionSearchTool = class {
|
|
13186
|
+
name = "session_search";
|
|
13187
|
+
description = "Search prior sessions by keyword and return structured hits with snippets. Use it to recall earlier discussions before creating new plans or summaries.";
|
|
13188
|
+
parameters = {
|
|
13189
|
+
type: "object",
|
|
13190
|
+
properties: {
|
|
13191
|
+
query: {
|
|
13192
|
+
type: "string",
|
|
13193
|
+
description: "Keyword search query for prior session text or labels."
|
|
13194
|
+
},
|
|
13195
|
+
limit: {
|
|
13196
|
+
type: "integer",
|
|
13197
|
+
minimum: 1,
|
|
13198
|
+
maximum: 10,
|
|
13199
|
+
description: `Optional max hit count. Defaults to 5.`
|
|
13200
|
+
},
|
|
13201
|
+
includeCurrentSession: {
|
|
13202
|
+
type: "boolean",
|
|
13203
|
+
description: "Set true to include the current session in results. Defaults to false."
|
|
13204
|
+
}
|
|
13205
|
+
},
|
|
13206
|
+
required: ["query"],
|
|
13207
|
+
additionalProperties: false
|
|
13208
|
+
};
|
|
13209
|
+
constructor(queryService, context) {
|
|
13210
|
+
this.queryService = queryService;
|
|
13211
|
+
this.context = context;
|
|
13212
|
+
}
|
|
13213
|
+
validateArgs = (args) => {
|
|
13214
|
+
const issues = [];
|
|
13215
|
+
const query = normalizeString(args.query);
|
|
13216
|
+
const limit = readOptionalInteger(args.limit);
|
|
13217
|
+
const includeCurrentSession = readOptionalBoolean(args.includeCurrentSession);
|
|
13218
|
+
if (!query) issues.push("query must be a non-empty string.");
|
|
13219
|
+
if (typeof args.limit !== "undefined" && typeof limit === "undefined") issues.push("limit must be a finite integer.");
|
|
13220
|
+
if (typeof limit === "number" && (limit < 1 || limit > 10)) issues.push(`limit must be between 1 and 10.`);
|
|
13221
|
+
if (typeof args.includeCurrentSession !== "undefined" && typeof includeCurrentSession === "undefined") issues.push("includeCurrentSession must be a boolean.");
|
|
13222
|
+
return issues;
|
|
13223
|
+
};
|
|
13224
|
+
execute = async (args) => {
|
|
13225
|
+
if (!isRecord$1(args)) throw new Error("session_search requires an object argument.");
|
|
13226
|
+
const issues = this.validateArgs(args);
|
|
13227
|
+
if (issues.length > 0) throw new Error(issues.join(" "));
|
|
13228
|
+
return this.queryService.search({
|
|
13229
|
+
query: normalizeString(args.query) ?? "",
|
|
13230
|
+
limit: readOptionalInteger(args.limit),
|
|
13231
|
+
includeCurrentSession: readOptionalBoolean(args.includeCurrentSession),
|
|
13232
|
+
currentSessionId: this.context.currentSessionId
|
|
13233
|
+
});
|
|
13234
|
+
};
|
|
13235
|
+
};
|
|
13236
|
+
//#endregion
|
|
13237
|
+
//#region src/cli/commands/ncp/session-search/session-search-feature.service.ts
|
|
13238
|
+
var SessionSearchFeatureService = class {
|
|
13239
|
+
store;
|
|
13240
|
+
indexManager;
|
|
13241
|
+
queryService;
|
|
13242
|
+
pendingWork = Promise.resolve();
|
|
13243
|
+
initialized = false;
|
|
13244
|
+
disposed = false;
|
|
13245
|
+
constructor(options) {
|
|
13246
|
+
this.options = options;
|
|
13247
|
+
this.store = new SessionSearchStoreService(options.databasePath);
|
|
13248
|
+
this.indexManager = new SessionSearchIndexManager(this.store);
|
|
13249
|
+
this.queryService = new SessionSearchQueryService(this.store);
|
|
13250
|
+
}
|
|
13251
|
+
initialize = async () => {
|
|
13252
|
+
if (this.initialized) return;
|
|
13253
|
+
if (this.disposed) throw new Error("Session search feature has already been disposed.");
|
|
13254
|
+
await this.store.initialize();
|
|
13255
|
+
await this.enqueue(async () => {
|
|
13256
|
+
const sessions = await this.options.sessionStore.listSessions();
|
|
13257
|
+
const activeSessionIds = new Set(sessions.map((session) => session.sessionId));
|
|
13258
|
+
for (const session of sessions) await this.indexManager.indexSession(session);
|
|
13259
|
+
const indexedSessionIds = await this.store.listIndexedSessionIds();
|
|
13260
|
+
for (const sessionId of indexedSessionIds) if (!activeSessionIds.has(sessionId)) await this.store.deleteDocument(sessionId);
|
|
13261
|
+
});
|
|
13262
|
+
this.initialized = true;
|
|
13263
|
+
};
|
|
13264
|
+
createTool = (params) => new SessionSearchTool(this.queryService, params);
|
|
13265
|
+
handleSessionUpdated = async (sessionId) => {
|
|
13266
|
+
if (this.disposed || !this.initialized) return;
|
|
13267
|
+
await this.enqueue(async () => {
|
|
13268
|
+
const session = await this.options.sessionStore.getSession(sessionId);
|
|
13269
|
+
if (!session) {
|
|
13270
|
+
await this.store.deleteDocument(sessionId);
|
|
13271
|
+
return;
|
|
13272
|
+
}
|
|
13273
|
+
await this.indexManager.indexSession(session);
|
|
13274
|
+
});
|
|
13275
|
+
};
|
|
13276
|
+
dispose = async () => {
|
|
13277
|
+
if (this.disposed) return;
|
|
13278
|
+
this.disposed = true;
|
|
13279
|
+
await this.pendingWork;
|
|
13280
|
+
await this.store.close();
|
|
13281
|
+
};
|
|
13282
|
+
enqueue(work) {
|
|
13283
|
+
const next = this.pendingWork.then(work);
|
|
13284
|
+
this.pendingWork = next.catch(() => void 0);
|
|
13285
|
+
return next;
|
|
13286
|
+
}
|
|
13287
|
+
};
|
|
13288
|
+
//#endregion
|
|
13289
|
+
//#region src/cli/commands/ncp/session-search/session-search-runtime.service.ts
|
|
13290
|
+
function formatErrorMessage(error) {
|
|
13291
|
+
return error instanceof Error ? error.message : String(error);
|
|
13292
|
+
}
|
|
13293
|
+
var SessionSearchRuntimeSupport = class {
|
|
13294
|
+
feature;
|
|
13295
|
+
constructor(params) {
|
|
13296
|
+
this.onSessionUpdated = params.onSessionUpdated;
|
|
13297
|
+
this.feature = new SessionSearchFeatureService({
|
|
13298
|
+
sessionStore: new NextclawAgentSessionStore(params.sessionManager),
|
|
13299
|
+
databasePath: params.databasePath
|
|
13300
|
+
});
|
|
13301
|
+
}
|
|
13302
|
+
onSessionUpdated;
|
|
13303
|
+
initialize = async () => {
|
|
13304
|
+
await this.feature.initialize();
|
|
13305
|
+
};
|
|
13306
|
+
createTool = (params) => this.feature.createTool(params);
|
|
13307
|
+
handleSessionUpdated = (sessionKey) => {
|
|
13308
|
+
this.onSessionUpdated?.(sessionKey);
|
|
13309
|
+
this.feature.handleSessionUpdated(sessionKey).catch((error) => {
|
|
13310
|
+
console.warn(`[session-search] Failed to update ${sessionKey}: ${formatErrorMessage(error)}`);
|
|
13311
|
+
});
|
|
13312
|
+
};
|
|
13313
|
+
dispose = async () => {
|
|
13314
|
+
await this.feature.dispose();
|
|
13315
|
+
};
|
|
13316
|
+
};
|
|
13317
|
+
//#endregion
|
|
13318
|
+
//#region src/cli/commands/shared/llm-usage-observer.ts
|
|
13319
|
+
var LlmUsageObserver = class {
|
|
13320
|
+
constructor(recorder, source) {
|
|
13321
|
+
this.recorder = recorder;
|
|
13322
|
+
this.source = source;
|
|
13323
|
+
}
|
|
13324
|
+
observe = (params) => {
|
|
13325
|
+
return this.recorder.record({
|
|
13326
|
+
source: this.source,
|
|
13327
|
+
model: params.model ?? null,
|
|
13328
|
+
usage: params.usage
|
|
13329
|
+
});
|
|
13330
|
+
};
|
|
13331
|
+
};
|
|
13332
|
+
var ObservedProviderManager = class extends ProviderManager {
|
|
13333
|
+
constructor(delegate, observer) {
|
|
13334
|
+
super(delegate.get(null));
|
|
13335
|
+
this.delegate = delegate;
|
|
13336
|
+
this.observer = observer;
|
|
12357
13337
|
}
|
|
12358
13338
|
get(model) {
|
|
12359
13339
|
return this.delegate.get(model);
|
|
@@ -12385,7 +13365,7 @@ var ObservedProviderManager = class extends ProviderManager {
|
|
|
12385
13365
|
//#endregion
|
|
12386
13366
|
//#region src/cli/commands/ncp/runtime/ui-ncp-agent-handle.ts
|
|
12387
13367
|
function createUiNcpAgentHandle(params) {
|
|
12388
|
-
const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, applyExtensionRegistry, applyMcpConfig, dispose, assetStore } = params;
|
|
13368
|
+
const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, refreshConfiguredRuntimeEntries, applyExtensionRegistry, applyMcpConfig, dispose, assetStore } = params;
|
|
12389
13369
|
return {
|
|
12390
13370
|
basePath: "/api/ncp/agent",
|
|
12391
13371
|
agentClientEndpoint: createAgentClientFromServer(backend),
|
|
@@ -12394,6 +13374,7 @@ function createUiNcpAgentHandle(params) {
|
|
|
12394
13374
|
sessionApi: backend,
|
|
12395
13375
|
listSessionTypes: (describeParams) => {
|
|
12396
13376
|
refreshPluginRuntimeRegistrations();
|
|
13377
|
+
refreshConfiguredRuntimeEntries();
|
|
12397
13378
|
return runtimeRegistry.listSessionTypes(describeParams);
|
|
12398
13379
|
},
|
|
12399
13380
|
assetApi: {
|
|
@@ -12412,6 +13393,356 @@ function createUiNcpAgentHandle(params) {
|
|
|
12412
13393
|
};
|
|
12413
13394
|
}
|
|
12414
13395
|
//#endregion
|
|
13396
|
+
//#region src/cli/commands/learning-loop/learning-loop.config.ts
|
|
13397
|
+
const LEARNING_LOOP_DISABLED_METADATA_KEY = "learning_loop_disabled";
|
|
13398
|
+
const LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY = "learning_loop_last_tool_call_count";
|
|
13399
|
+
const LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY = "learning_loop_last_requested_at";
|
|
13400
|
+
const LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY = "learning_loop_last_review_session_id";
|
|
13401
|
+
const LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY = "learning_loop_source_session_id";
|
|
13402
|
+
const LEARNING_LOOP_REQUESTED_SKILLS = ["skill-creator"];
|
|
13403
|
+
function readLearningLoopRuntimeConfig(config) {
|
|
13404
|
+
return {
|
|
13405
|
+
enabled: config.agents.learningLoop.enabled,
|
|
13406
|
+
toolCallThreshold: config.agents.learningLoop.toolCallThreshold ?? 15
|
|
13407
|
+
};
|
|
13408
|
+
}
|
|
13409
|
+
//#endregion
|
|
13410
|
+
//#region src/cli/commands/learning-loop/learning-loop-prompt.utils.ts
|
|
13411
|
+
function pluralizeToolCalls(value) {
|
|
13412
|
+
return value === 1 ? "1 tool call" : `${value} tool calls`;
|
|
13413
|
+
}
|
|
13414
|
+
function buildLearningLoopTask(params) {
|
|
13415
|
+
return [
|
|
13416
|
+
"Run a background learning-loop review for the parent session.",
|
|
13417
|
+
`Parent session id: ${params.sessionId}.`,
|
|
13418
|
+
`There have been ${pluralizeToolCalls(params.toolCallsSinceReview)} since the last learning-loop review (${params.currentToolCallCount} total so far).`,
|
|
13419
|
+
"",
|
|
13420
|
+
"Your job is to extract only the reusable lesson.",
|
|
13421
|
+
"- Review the parent session history and, if helpful, use session_search to compare similar historical sessions.",
|
|
13422
|
+
"- Decide exactly one outcome: no_skill_change, patch_existing_skill, or create_new_skill.",
|
|
13423
|
+
"- Prefer patching an existing skill when the lesson extends an existing workflow.",
|
|
13424
|
+
"- Only create a new skill when the lesson is clearly reusable, repeatable, and likely to recur.",
|
|
13425
|
+
"- Do not create or patch a skill for one-off quirks, noisy transcripts, or narrow local accidents.",
|
|
13426
|
+
"",
|
|
13427
|
+
"If you decide to patch or create a skill:",
|
|
13428
|
+
"- Use the available file tools.",
|
|
13429
|
+
"- Keep the change minimal and maintainable.",
|
|
13430
|
+
"- Write the skill into the current workspace/project skills directory under skills/<slug>/SKILL.md.",
|
|
13431
|
+
"- Follow the built-in skill-creator guidance when it helps.",
|
|
13432
|
+
"",
|
|
13433
|
+
"If nothing reusable should be saved, stop without writing files."
|
|
13434
|
+
].join("\n");
|
|
13435
|
+
}
|
|
13436
|
+
//#endregion
|
|
13437
|
+
//#region src/cli/commands/ncp/lifecycle-events/ncp-lifecycle-event.config.ts
|
|
13438
|
+
const agentRunStartedLifecycleEventKey = createTypedEventKey("agent.run.started");
|
|
13439
|
+
const agentRunFinishedLifecycleEventKey = createTypedEventKey("agent.run.finished");
|
|
13440
|
+
const agentMessageSentLifecycleEventKey = createTypedEventKey("agent.message.sent");
|
|
13441
|
+
const agentSessionUpdatedLifecycleEventKey = createTypedEventKey("agent.session.updated");
|
|
13442
|
+
//#endregion
|
|
13443
|
+
//#region src/cli/commands/learning-loop/learning-loop-feature.service.ts
|
|
13444
|
+
function countToolCallsFromMessage(message) {
|
|
13445
|
+
if (Array.isArray(message.tool_calls)) return message.tool_calls.filter((toolCall) => Boolean(toolCall) && typeof toolCall === "object" && !Array.isArray(toolCall)).length;
|
|
13446
|
+
if (!Array.isArray(message.ncp_parts)) return 0;
|
|
13447
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
13448
|
+
let anonymousCount = 0;
|
|
13449
|
+
for (const part of message.ncp_parts) {
|
|
13450
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) continue;
|
|
13451
|
+
const candidate = part;
|
|
13452
|
+
if (candidate.type !== "tool-invocation") continue;
|
|
13453
|
+
if (typeof candidate.toolCallId === "string" && candidate.toolCallId.trim()) {
|
|
13454
|
+
seenIds.add(candidate.toolCallId.trim());
|
|
13455
|
+
continue;
|
|
13456
|
+
}
|
|
13457
|
+
anonymousCount += 1;
|
|
13458
|
+
}
|
|
13459
|
+
return seenIds.size + anonymousCount;
|
|
13460
|
+
}
|
|
13461
|
+
function countSessionToolCalls(session) {
|
|
13462
|
+
return session.messages.reduce((count, message) => count + countToolCallsFromMessage(message), 0);
|
|
13463
|
+
}
|
|
13464
|
+
function readSessionLabel(metadata) {
|
|
13465
|
+
const label = metadata.label;
|
|
13466
|
+
return typeof label === "string" && label.trim().length > 0 ? label.trim() : void 0;
|
|
13467
|
+
}
|
|
13468
|
+
function isLearningLoopDisabled(metadata) {
|
|
13469
|
+
return metadata[LEARNING_LOOP_DISABLED_METADATA_KEY] === true;
|
|
13470
|
+
}
|
|
13471
|
+
function readLearningLoopLastToolCallCount(metadata) {
|
|
13472
|
+
const value = metadata[LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY];
|
|
13473
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
13474
|
+
}
|
|
13475
|
+
var LearningLoopFeature = class {
|
|
13476
|
+
inFlightSessionIds = /* @__PURE__ */ new Set();
|
|
13477
|
+
unsubscribe = null;
|
|
13478
|
+
constructor(config) {
|
|
13479
|
+
this.config = config;
|
|
13480
|
+
}
|
|
13481
|
+
start = () => {
|
|
13482
|
+
if (this.unsubscribe) return;
|
|
13483
|
+
this.unsubscribe = this.config.eventBus.on(agentRunFinishedLifecycleEventKey, this.handleRunFinished);
|
|
13484
|
+
};
|
|
13485
|
+
dispose = () => {
|
|
13486
|
+
this.unsubscribe?.();
|
|
13487
|
+
this.unsubscribe = null;
|
|
13488
|
+
};
|
|
13489
|
+
handleRunFinished = (event) => {
|
|
13490
|
+
this.handleRunFinishedInBackground(event).catch((error) => {
|
|
13491
|
+
console.warn(`[learning-loop] Failed for ${event.sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13492
|
+
});
|
|
13493
|
+
};
|
|
13494
|
+
handleRunFinishedInBackground = async (event) => {
|
|
13495
|
+
if (event.isChildSession || this.inFlightSessionIds.has(event.sessionId)) return;
|
|
13496
|
+
const session = this.config.sessionStore.getIfExists(event.sessionId);
|
|
13497
|
+
if (!session) return;
|
|
13498
|
+
const runtimeConfig = this.readRuntimeConfig();
|
|
13499
|
+
if (!runtimeConfig.enabled) return;
|
|
13500
|
+
if (isLearningLoopDisabled(session.metadata)) return;
|
|
13501
|
+
const totalToolCalls = countSessionToolCalls(session);
|
|
13502
|
+
const toolCallsSinceReview = totalToolCalls - readLearningLoopLastToolCallCount(session.metadata);
|
|
13503
|
+
if (toolCallsSinceReview < runtimeConfig.toolCallThreshold) return;
|
|
13504
|
+
this.inFlightSessionIds.add(event.sessionId);
|
|
13505
|
+
try {
|
|
13506
|
+
const reviewSession = await this.config.sessionRequester.spawnSessionAndRequest({
|
|
13507
|
+
sourceSessionId: event.sessionId,
|
|
13508
|
+
sourceSessionMetadata: session.metadata,
|
|
13509
|
+
metadataOverrides: {
|
|
13510
|
+
requested_skills: LEARNING_LOOP_REQUESTED_SKILLS,
|
|
13511
|
+
[LEARNING_LOOP_DISABLED_METADATA_KEY]: true,
|
|
13512
|
+
[LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY]: event.sessionId
|
|
13513
|
+
},
|
|
13514
|
+
parentSessionId: event.sessionId,
|
|
13515
|
+
notify: "none",
|
|
13516
|
+
title: this.buildReviewTitle(session.metadata),
|
|
13517
|
+
task: buildLearningLoopTask({
|
|
13518
|
+
sessionId: event.sessionId,
|
|
13519
|
+
toolCallsSinceReview,
|
|
13520
|
+
currentToolCallCount: totalToolCalls
|
|
13521
|
+
})
|
|
13522
|
+
});
|
|
13523
|
+
session.metadata = {
|
|
13524
|
+
...session.metadata,
|
|
13525
|
+
[LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY]: totalToolCalls,
|
|
13526
|
+
[LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY]: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13527
|
+
[LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY]: reviewSession.sessionId
|
|
13528
|
+
};
|
|
13529
|
+
this.config.sessionStore.save(session);
|
|
13530
|
+
} finally {
|
|
13531
|
+
this.inFlightSessionIds.delete(event.sessionId);
|
|
13532
|
+
}
|
|
13533
|
+
};
|
|
13534
|
+
readRuntimeConfig = () => {
|
|
13535
|
+
if (this.config.resolveRuntimeConfig) return this.config.resolveRuntimeConfig();
|
|
13536
|
+
return {
|
|
13537
|
+
enabled: true,
|
|
13538
|
+
toolCallThreshold: this.config.toolCallThreshold ?? 15
|
|
13539
|
+
};
|
|
13540
|
+
};
|
|
13541
|
+
buildReviewTitle = (metadata) => {
|
|
13542
|
+
const label = readSessionLabel(metadata);
|
|
13543
|
+
return label ? `Learning loop: ${label}` : "Learning loop";
|
|
13544
|
+
};
|
|
13545
|
+
};
|
|
13546
|
+
//#endregion
|
|
13547
|
+
//#region src/cli/commands/ncp/lifecycle-events/ncp-lifecycle-event-bridge.service.ts
|
|
13548
|
+
function readSessionType(metadata) {
|
|
13549
|
+
const sessionType = metadata?.session_type;
|
|
13550
|
+
return typeof sessionType === "string" && sessionType.trim().length > 0 ? sessionType.trim() : void 0;
|
|
13551
|
+
}
|
|
13552
|
+
var NcpLifecycleEventBridge = class {
|
|
13553
|
+
constructor(sessionManager, eventBus) {
|
|
13554
|
+
this.sessionManager = sessionManager;
|
|
13555
|
+
this.eventBus = eventBus;
|
|
13556
|
+
}
|
|
13557
|
+
publishSessionUpdated = (sessionId) => {
|
|
13558
|
+
const context = this.buildSessionContext(sessionId);
|
|
13559
|
+
if (!context) return;
|
|
13560
|
+
const payload = { ...context };
|
|
13561
|
+
this.eventBus.emit(agentSessionUpdatedLifecycleEventKey, payload);
|
|
13562
|
+
};
|
|
13563
|
+
handleEndpointEvent = (event) => {
|
|
13564
|
+
switch (event.type) {
|
|
13565
|
+
case NcpEventType.RunStarted:
|
|
13566
|
+
this.publishRunStarted(event);
|
|
13567
|
+
return;
|
|
13568
|
+
case NcpEventType.RunFinished:
|
|
13569
|
+
this.publishRunFinished(event);
|
|
13570
|
+
return;
|
|
13571
|
+
case NcpEventType.MessageSent:
|
|
13572
|
+
this.publishMessageSent(event);
|
|
13573
|
+
return;
|
|
13574
|
+
default: return;
|
|
13575
|
+
}
|
|
13576
|
+
};
|
|
13577
|
+
publishRunStarted = (event) => {
|
|
13578
|
+
const sessionId = event.payload.sessionId?.trim();
|
|
13579
|
+
if (!sessionId) return;
|
|
13580
|
+
const context = this.buildSessionContext(sessionId);
|
|
13581
|
+
if (!context) return;
|
|
13582
|
+
const payload = {
|
|
13583
|
+
...context,
|
|
13584
|
+
...event.payload.runId ? { runId: event.payload.runId } : {},
|
|
13585
|
+
...event.payload.messageId ? { messageId: event.payload.messageId } : {}
|
|
13586
|
+
};
|
|
13587
|
+
this.eventBus.emit(agentRunStartedLifecycleEventKey, payload);
|
|
13588
|
+
};
|
|
13589
|
+
publishRunFinished = (event) => {
|
|
13590
|
+
const sessionId = event.payload.sessionId?.trim();
|
|
13591
|
+
if (!sessionId) return;
|
|
13592
|
+
const context = this.buildSessionContext(sessionId);
|
|
13593
|
+
if (!context) return;
|
|
13594
|
+
const payload = {
|
|
13595
|
+
...context,
|
|
13596
|
+
...event.payload.runId ? { runId: event.payload.runId } : {},
|
|
13597
|
+
...event.payload.messageId ? { messageId: event.payload.messageId } : {}
|
|
13598
|
+
};
|
|
13599
|
+
this.eventBus.emit(agentRunFinishedLifecycleEventKey, payload);
|
|
13600
|
+
};
|
|
13601
|
+
publishMessageSent = (event) => {
|
|
13602
|
+
const sessionId = event.payload.sessionId.trim();
|
|
13603
|
+
const context = this.buildSessionContext(sessionId);
|
|
13604
|
+
if (!context) return;
|
|
13605
|
+
const payload = {
|
|
13606
|
+
...context,
|
|
13607
|
+
messageId: event.payload.message.id,
|
|
13608
|
+
role: event.payload.message.role
|
|
13609
|
+
};
|
|
13610
|
+
this.eventBus.emit(agentMessageSentLifecycleEventKey, payload);
|
|
13611
|
+
};
|
|
13612
|
+
buildSessionContext = (sessionId) => {
|
|
13613
|
+
const metadata = this.sessionManager.getIfExists(sessionId)?.metadata;
|
|
13614
|
+
const sessionType = readSessionType(metadata);
|
|
13615
|
+
const parentSessionId = metadata ? readParentSessionId(metadata) : void 0;
|
|
13616
|
+
return {
|
|
13617
|
+
sessionId,
|
|
13618
|
+
...sessionType ? { sessionType } : {},
|
|
13619
|
+
...parentSessionId ? { parentSessionId } : {},
|
|
13620
|
+
isChildSession: Boolean(parentSessionId),
|
|
13621
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13622
|
+
};
|
|
13623
|
+
};
|
|
13624
|
+
};
|
|
13625
|
+
//#endregion
|
|
13626
|
+
//#region src/cli/commands/learning-loop/learning-loop-runtime.service.ts
|
|
13627
|
+
var LearningLoopRuntimeService = class {
|
|
13628
|
+
globalEventBus;
|
|
13629
|
+
lifecycleEventBridge;
|
|
13630
|
+
learningLoopFeature;
|
|
13631
|
+
unsubscribeEndpointEvents = null;
|
|
13632
|
+
constructor(params) {
|
|
13633
|
+
const { sessionManager, sessionRequestBroker, onSessionUpdated, globalEventBus, resolveLearningLoopConfig } = params;
|
|
13634
|
+
this.globalEventBus = globalEventBus ?? createGlobalTypedEventBus({ onListenerError: ({ key, error }) => {
|
|
13635
|
+
console.warn(`[global-event-bus] listener failed for ${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13636
|
+
} });
|
|
13637
|
+
this.lifecycleEventBridge = new NcpLifecycleEventBridge(sessionManager, this.globalEventBus);
|
|
13638
|
+
this.learningLoopFeature = new LearningLoopFeature({
|
|
13639
|
+
eventBus: this.globalEventBus,
|
|
13640
|
+
sessionStore: sessionManager,
|
|
13641
|
+
sessionRequester: sessionRequestBroker,
|
|
13642
|
+
resolveRuntimeConfig: resolveLearningLoopConfig
|
|
13643
|
+
});
|
|
13644
|
+
this.onSessionUpdated = onSessionUpdated;
|
|
13645
|
+
}
|
|
13646
|
+
onSessionUpdated;
|
|
13647
|
+
handleSessionUpdated = (sessionKey) => {
|
|
13648
|
+
this.onSessionUpdated?.(sessionKey);
|
|
13649
|
+
this.lifecycleEventBridge.publishSessionUpdated(sessionKey);
|
|
13650
|
+
};
|
|
13651
|
+
attachBackend = (backend) => {
|
|
13652
|
+
this.unsubscribeEndpointEvents?.();
|
|
13653
|
+
this.unsubscribeEndpointEvents = backend.subscribe(this.lifecycleEventBridge.handleEndpointEvent);
|
|
13654
|
+
this.learningLoopFeature.start();
|
|
13655
|
+
};
|
|
13656
|
+
dispose = () => {
|
|
13657
|
+
this.learningLoopFeature.dispose();
|
|
13658
|
+
this.unsubscribeEndpointEvents?.();
|
|
13659
|
+
this.unsubscribeEndpointEvents = null;
|
|
13660
|
+
};
|
|
13661
|
+
};
|
|
13662
|
+
//#endregion
|
|
13663
|
+
//#region src/cli/commands/ncp/plugin-runtime-registration.controller.ts
|
|
13664
|
+
const RESERVED_BUILTIN_RUNTIME_KINDS = new Set([NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND]);
|
|
13665
|
+
function buildPluginRuntimeSnapshotKey(extensionRegistry) {
|
|
13666
|
+
return (extensionRegistry?.ncpAgentRuntimes ?? []).map((registration) => [
|
|
13667
|
+
registration.pluginId,
|
|
13668
|
+
registration.kind,
|
|
13669
|
+
registration.label,
|
|
13670
|
+
registration.source
|
|
13671
|
+
].join(":")).join("|");
|
|
13672
|
+
}
|
|
13673
|
+
function createRuntimeFactory(registration) {
|
|
13674
|
+
if (registration.kind !== "codex") return registration.createRuntime;
|
|
13675
|
+
return (runtimeParams) => registration.createRuntime({
|
|
13676
|
+
...runtimeParams,
|
|
13677
|
+
sessionMetadata: {
|
|
13678
|
+
...runtimeParams.sessionMetadata,
|
|
13679
|
+
session_type: "codex",
|
|
13680
|
+
codex_runtime_backend: "codex-sdk"
|
|
13681
|
+
},
|
|
13682
|
+
setSessionMetadata: (nextMetadata) => {
|
|
13683
|
+
runtimeParams.setSessionMetadata({
|
|
13684
|
+
...nextMetadata,
|
|
13685
|
+
session_type: "codex",
|
|
13686
|
+
codex_runtime_backend: "codex-sdk"
|
|
13687
|
+
});
|
|
13688
|
+
}
|
|
13689
|
+
});
|
|
13690
|
+
}
|
|
13691
|
+
var PluginRuntimeRegistrationController = class {
|
|
13692
|
+
pluginRuntimeScopes = /* @__PURE__ */ new Map();
|
|
13693
|
+
pluginRuntimeSnapshotKey = "";
|
|
13694
|
+
activeExtensionRegistry;
|
|
13695
|
+
constructor(runtimeRegistry, getExtensionRegistry) {
|
|
13696
|
+
this.runtimeRegistry = runtimeRegistry;
|
|
13697
|
+
this.getExtensionRegistry = getExtensionRegistry;
|
|
13698
|
+
}
|
|
13699
|
+
refreshPluginRuntimeRegistrations = () => {
|
|
13700
|
+
this.syncPluginRuntimeRegistrations(this.resolveActiveExtensionRegistry());
|
|
13701
|
+
};
|
|
13702
|
+
applyExtensionRegistry = (extensionRegistry) => {
|
|
13703
|
+
this.activeExtensionRegistry = extensionRegistry;
|
|
13704
|
+
this.syncPluginRuntimeRegistrations(extensionRegistry);
|
|
13705
|
+
};
|
|
13706
|
+
dispose = () => {
|
|
13707
|
+
for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
|
|
13708
|
+
this.pluginRuntimeScopes.clear();
|
|
13709
|
+
this.activeExtensionRegistry = void 0;
|
|
13710
|
+
this.pluginRuntimeSnapshotKey = "";
|
|
13711
|
+
};
|
|
13712
|
+
syncPluginRuntimeRegistrations = (extensionRegistry) => {
|
|
13713
|
+
const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
|
|
13714
|
+
if (nextSnapshotKey === this.pluginRuntimeSnapshotKey) return;
|
|
13715
|
+
this.pluginRuntimeSnapshotKey = nextSnapshotKey;
|
|
13716
|
+
for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
|
|
13717
|
+
this.pluginRuntimeScopes.clear();
|
|
13718
|
+
for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
|
|
13719
|
+
if (RESERVED_BUILTIN_RUNTIME_KINDS.has(registration.kind.trim().toLowerCase())) continue;
|
|
13720
|
+
const pluginId = registration.pluginId.trim() || registration.kind;
|
|
13721
|
+
const scope = this.pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
|
|
13722
|
+
this.pluginRuntimeScopes.set(pluginId, scope);
|
|
13723
|
+
const createRuntimeForEntry = registration.createRuntimeForEntry;
|
|
13724
|
+
scope.add(this.runtimeRegistry.register({
|
|
13725
|
+
kind: registration.kind,
|
|
13726
|
+
label: registration.label,
|
|
13727
|
+
createRuntime: createRuntimeFactory(registration),
|
|
13728
|
+
createRuntimeForEntry: createRuntimeForEntry ? ({ entry, runtimeParams }) => createRuntimeForEntry({
|
|
13729
|
+
entry,
|
|
13730
|
+
runtimeParams: {
|
|
13731
|
+
...runtimeParams,
|
|
13732
|
+
sessionMetadata: {
|
|
13733
|
+
...runtimeParams.sessionMetadata,
|
|
13734
|
+
runtime_type: entry.type
|
|
13735
|
+
}
|
|
13736
|
+
}
|
|
13737
|
+
}) : void 0,
|
|
13738
|
+
describeSessionType: registration.describeSessionType,
|
|
13739
|
+
describeSessionTypeForEntry: registration.describeSessionTypeForEntry
|
|
13740
|
+
}));
|
|
13741
|
+
}
|
|
13742
|
+
};
|
|
13743
|
+
resolveActiveExtensionRegistry = () => this.activeExtensionRegistry ?? this.getExtensionRegistry?.();
|
|
13744
|
+
};
|
|
13745
|
+
//#endregion
|
|
12415
13746
|
//#region src/cli/runtime-state/llm-usage-record.ts
|
|
12416
13747
|
var LlmUsageRecordFactory = class {
|
|
12417
13748
|
create = (params) => {
|
|
@@ -12434,6 +13765,9 @@ var LlmUsageRecordFactory = class {
|
|
|
12434
13765
|
}
|
|
12435
13766
|
return next;
|
|
12436
13767
|
};
|
|
13768
|
+
hasTelemetry = (usage) => {
|
|
13769
|
+
return Object.keys(usage).length > 0;
|
|
13770
|
+
};
|
|
12437
13771
|
buildSummary = (usage) => {
|
|
12438
13772
|
const promptTokens = usage.prompt_tokens ?? usage.input_tokens ?? 0;
|
|
12439
13773
|
const completionTokens = usage.completion_tokens ?? usage.output_tokens ?? 0;
|
|
@@ -12464,6 +13798,7 @@ var LlmUsageRecorder = class {
|
|
|
12464
13798
|
}
|
|
12465
13799
|
record = (params) => {
|
|
12466
13800
|
const record = this.recordFactory.create(params);
|
|
13801
|
+
if (!this.recordFactory.hasTelemetry(record.usage)) return null;
|
|
12467
13802
|
this.snapshotStore.write(record);
|
|
12468
13803
|
this.historyStore.append(record);
|
|
12469
13804
|
return record;
|
|
@@ -12480,45 +13815,15 @@ var LlmUsageRecorder = class {
|
|
|
12480
13815
|
};
|
|
12481
13816
|
const llmUsageRecorder = new LlmUsageRecorder();
|
|
12482
13817
|
//#endregion
|
|
12483
|
-
//#region src/cli/commands/ncp/create-ui-ncp-agent.ts
|
|
12484
|
-
const CODEX_RUNTIME_KIND = "codex";
|
|
12485
|
-
const CODEX_DIRECT_RUNTIME_BACKEND = "codex-sdk";
|
|
13818
|
+
//#region src/cli/commands/ncp/create-ui-ncp-agent.service.ts
|
|
12486
13819
|
function isRecord(value) {
|
|
12487
13820
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12488
13821
|
}
|
|
12489
|
-
function decorateCodexRuntimeFactoryParams(runtimeParams, backend) {
|
|
12490
|
-
const nextSessionMetadata = {
|
|
12491
|
-
...runtimeParams.sessionMetadata,
|
|
12492
|
-
session_type: CODEX_RUNTIME_KIND,
|
|
12493
|
-
codex_runtime_backend: backend
|
|
12494
|
-
};
|
|
12495
|
-
const setSessionMetadata = (nextMetadata) => {
|
|
12496
|
-
runtimeParams.setSessionMetadata({
|
|
12497
|
-
...nextMetadata,
|
|
12498
|
-
session_type: CODEX_RUNTIME_KIND,
|
|
12499
|
-
codex_runtime_backend: backend
|
|
12500
|
-
});
|
|
12501
|
-
};
|
|
12502
|
-
setSessionMetadata(nextSessionMetadata);
|
|
12503
|
-
return {
|
|
12504
|
-
...runtimeParams,
|
|
12505
|
-
sessionMetadata: nextSessionMetadata,
|
|
12506
|
-
setSessionMetadata
|
|
12507
|
-
};
|
|
12508
|
-
}
|
|
12509
13822
|
function resolveNativeReasoningNormalizationMode(params) {
|
|
12510
|
-
const runtimeEntry = params.config.ui.ncp.runtimes.native;
|
|
13823
|
+
const runtimeEntry = params.config.agents.runtimes.entries.native?.config ?? params.config.ui.ncp.runtimes.native;
|
|
12511
13824
|
const runtimeMetadata = isRecord(runtimeEntry) ? runtimeEntry : {};
|
|
12512
13825
|
return readAssistantReasoningNormalizationModeFromMetadata(params.sessionMetadata) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalizationMode) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization_mode) ?? "think-tags";
|
|
12513
13826
|
}
|
|
12514
|
-
function buildPluginRuntimeSnapshotKey(extensionRegistry) {
|
|
12515
|
-
return (extensionRegistry?.ncpAgentRuntimes ?? []).map((registration) => [
|
|
12516
|
-
registration.pluginId,
|
|
12517
|
-
registration.kind,
|
|
12518
|
-
registration.label,
|
|
12519
|
-
registration.source
|
|
12520
|
-
].join(":")).join("|");
|
|
12521
|
-
}
|
|
12522
13827
|
async function createMcpRuntimeSupport(getConfig) {
|
|
12523
13828
|
let currentMcpConfig = getConfig();
|
|
12524
13829
|
const mcpRegistryService = new McpRegistryService({
|
|
@@ -12543,7 +13848,7 @@ async function createMcpRuntimeSupport(getConfig) {
|
|
|
12543
13848
|
}
|
|
12544
13849
|
};
|
|
12545
13850
|
}
|
|
12546
|
-
function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker) {
|
|
13851
|
+
function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker, sessionSearchRuntimeSupport) {
|
|
12547
13852
|
const observedProviderManager = new ObservedProviderManager(params.providerManager, new LlmUsageObserver(llmUsageRecorder, "ui-ncp"));
|
|
12548
13853
|
return ({ stateManager, sessionMetadata, setSessionMetadata }) => {
|
|
12549
13854
|
const reasoningNormalizationMode = resolveNativeReasoningNormalizationMode({
|
|
@@ -12561,7 +13866,11 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
12561
13866
|
getExtensionRegistry: params.getExtensionRegistry,
|
|
12562
13867
|
sessionCreationService,
|
|
12563
13868
|
sessionRequestBroker,
|
|
12564
|
-
getAdditionalTools: (context) => [
|
|
13869
|
+
getAdditionalTools: (context) => [
|
|
13870
|
+
...createAssetTools({ assetStore }),
|
|
13871
|
+
...mcpToolRegistryAdapter.listToolsForRun({ agentId: context.agentId }),
|
|
13872
|
+
sessionSearchRuntimeSupport.createTool({ currentSessionId: context.sessionId })
|
|
13873
|
+
]
|
|
12565
13874
|
});
|
|
12566
13875
|
return new DefaultNcpAgentRuntime({
|
|
12567
13876
|
contextBuilder: new NextclawNcpContextBuilder({
|
|
@@ -12578,93 +13887,121 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
12578
13887
|
});
|
|
12579
13888
|
};
|
|
12580
13889
|
}
|
|
12581
|
-
function
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
|
|
13890
|
+
function createResolveOpenAiToolsForRuntime(params) {
|
|
13891
|
+
const toolRegistry = new NextclawNcpToolRegistry({
|
|
13892
|
+
bus: params.bus,
|
|
13893
|
+
providerManager: params.providerManager,
|
|
13894
|
+
sessionManager: params.sessionManager,
|
|
13895
|
+
cronService: params.cronService,
|
|
13896
|
+
gatewayController: params.gatewayController,
|
|
13897
|
+
getConfig: params.getConfig,
|
|
13898
|
+
getExtensionRegistry: params.getExtensionRegistry,
|
|
13899
|
+
sessionCreationService: params.sessionCreationService,
|
|
13900
|
+
sessionRequestBroker: params.sessionRequestBroker,
|
|
13901
|
+
getAdditionalTools: (context) => [
|
|
13902
|
+
...createAssetTools({ assetStore: params.assetStore }),
|
|
13903
|
+
...params.toolRegistryAdapter.listToolsForRun({ agentId: context.agentId }),
|
|
13904
|
+
params.sessionSearchRuntimeSupport.createTool({ currentSessionId: context.sessionId })
|
|
13905
|
+
]
|
|
13906
|
+
});
|
|
13907
|
+
const contextBuilder = new NextclawNcpContextBuilder({
|
|
13908
|
+
sessionManager: params.sessionManager,
|
|
13909
|
+
toolRegistry,
|
|
13910
|
+
getConfig: params.getConfig,
|
|
13911
|
+
resolveMessageToolHints: params.resolveMessageToolHints,
|
|
13912
|
+
assetStore: params.assetStore
|
|
13913
|
+
});
|
|
13914
|
+
return (input) => contextBuilder.prepare(input).tools;
|
|
12589
13915
|
}
|
|
12590
|
-
var PluginRuntimeRegistrationController = class {
|
|
12591
|
-
pluginRuntimeScopes = /* @__PURE__ */ new Map();
|
|
12592
|
-
pluginRuntimeSnapshotKey = "";
|
|
12593
|
-
activeExtensionRegistry;
|
|
12594
|
-
constructor(runtimeRegistry, getExtensionRegistry) {
|
|
12595
|
-
this.runtimeRegistry = runtimeRegistry;
|
|
12596
|
-
this.getExtensionRegistry = getExtensionRegistry;
|
|
12597
|
-
}
|
|
12598
|
-
syncPluginRuntimeRegistrations = (extensionRegistry) => {
|
|
12599
|
-
const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
|
|
12600
|
-
if (nextSnapshotKey === this.pluginRuntimeSnapshotKey) return;
|
|
12601
|
-
this.pluginRuntimeSnapshotKey = nextSnapshotKey;
|
|
12602
|
-
for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
|
|
12603
|
-
this.pluginRuntimeScopes.clear();
|
|
12604
|
-
for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
|
|
12605
|
-
const pluginId = registration.pluginId.trim() || registration.kind;
|
|
12606
|
-
const scope = this.pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
|
|
12607
|
-
this.pluginRuntimeScopes.set(pluginId, scope);
|
|
12608
|
-
scope.add(this.runtimeRegistry.register({
|
|
12609
|
-
kind: registration.kind,
|
|
12610
|
-
label: registration.label,
|
|
12611
|
-
createRuntime: resolveRegisteredRuntimeFactory({ registration }),
|
|
12612
|
-
describeSessionType: registration.describeSessionType
|
|
12613
|
-
}));
|
|
12614
|
-
}
|
|
12615
|
-
};
|
|
12616
|
-
resolveActiveExtensionRegistry = () => this.activeExtensionRegistry ?? this.getExtensionRegistry?.();
|
|
12617
|
-
refreshPluginRuntimeRegistrations = () => {
|
|
12618
|
-
this.syncPluginRuntimeRegistrations(this.resolveActiveExtensionRegistry());
|
|
12619
|
-
};
|
|
12620
|
-
applyExtensionRegistry = (extensionRegistry) => {
|
|
12621
|
-
this.activeExtensionRegistry = extensionRegistry;
|
|
12622
|
-
this.syncPluginRuntimeRegistrations(extensionRegistry);
|
|
12623
|
-
};
|
|
12624
|
-
dispose = () => {
|
|
12625
|
-
for (const scope of this.pluginRuntimeScopes.values()) scope.dispose();
|
|
12626
|
-
this.pluginRuntimeScopes.clear();
|
|
12627
|
-
this.activeExtensionRegistry = void 0;
|
|
12628
|
-
this.pluginRuntimeSnapshotKey = "";
|
|
12629
|
-
};
|
|
12630
|
-
};
|
|
12631
13916
|
async function createUiNcpAgent(params) {
|
|
12632
|
-
const
|
|
13917
|
+
const { getConfig, getExtensionRegistry, globalEventBus, onSessionRunStatusChanged, onSessionUpdated, providerManager, resolveMessageToolHints, sessionManager } = params;
|
|
13918
|
+
const sessionSearchRuntimeSupport = new SessionSearchRuntimeSupport({
|
|
13919
|
+
sessionManager,
|
|
13920
|
+
onSessionUpdated,
|
|
13921
|
+
databasePath: join(getDataDir(), "session-search.db")
|
|
13922
|
+
});
|
|
13923
|
+
const sessionStore = new NextclawAgentSessionStore(sessionManager, { onSessionUpdated: sessionSearchRuntimeSupport.handleSessionUpdated });
|
|
12633
13924
|
const runtimeRegistry = new UiNcpRuntimeRegistry();
|
|
12634
|
-
const { toolRegistryAdapter, applyMcpConfig, dispose: disposeMcpRuntimeSupport } = await createMcpRuntimeSupport(
|
|
13925
|
+
const { toolRegistryAdapter, applyMcpConfig, dispose: disposeMcpRuntimeSupport } = await createMcpRuntimeSupport(getConfig);
|
|
12635
13926
|
const assetStore = new LocalAssetStore({ rootDir: join(getDataDir(), "assets") });
|
|
12636
13927
|
let backend = null;
|
|
12637
|
-
const sessionCreationService = new SessionCreationService(
|
|
12638
|
-
const
|
|
13928
|
+
const sessionCreationService = new SessionCreationService(sessionManager, getConfig, sessionSearchRuntimeSupport.handleSessionUpdated);
|
|
13929
|
+
const sessionRequestBroker = new SessionRequestBroker(sessionManager, sessionCreationService, new SessionRequestDeliveryService(() => backend), () => backend, sessionSearchRuntimeSupport.handleSessionUpdated);
|
|
13930
|
+
const learningLoopRuntime = new LearningLoopRuntimeService({
|
|
13931
|
+
sessionManager,
|
|
13932
|
+
sessionRequestBroker,
|
|
13933
|
+
onSessionUpdated: sessionSearchRuntimeSupport.handleSessionUpdated,
|
|
13934
|
+
globalEventBus,
|
|
13935
|
+
resolveLearningLoopConfig: () => readLearningLoopRuntimeConfig(getConfig())
|
|
13936
|
+
});
|
|
13937
|
+
const createNativeRuntime = createNativeRuntimeFactory({
|
|
13938
|
+
...params,
|
|
13939
|
+
getConfig,
|
|
13940
|
+
getExtensionRegistry,
|
|
13941
|
+
onSessionUpdated,
|
|
13942
|
+
providerManager,
|
|
13943
|
+
resolveMessageToolHints,
|
|
13944
|
+
sessionManager
|
|
13945
|
+
}, toolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker, sessionSearchRuntimeSupport);
|
|
12639
13946
|
runtimeRegistry.register({
|
|
12640
13947
|
kind: "native",
|
|
12641
13948
|
label: "Native",
|
|
12642
13949
|
createRuntime: createNativeRuntime
|
|
12643
13950
|
});
|
|
12644
|
-
const
|
|
13951
|
+
const builtinNarpRegistrations = new BuiltinNarpRuntimeRegistrationService(getConfig).registerInto(runtimeRegistry);
|
|
13952
|
+
const pluginRuntimeRegistrationController = new PluginRuntimeRegistrationController(runtimeRegistry, getExtensionRegistry);
|
|
13953
|
+
const refreshConfiguredRuntimeEntries = () => {
|
|
13954
|
+
runtimeRegistry.applyEntries(resolveUiNcpRuntimeEntries({
|
|
13955
|
+
config: getConfig(),
|
|
13956
|
+
providerKinds: runtimeRegistry.listProviderKinds()
|
|
13957
|
+
}));
|
|
13958
|
+
};
|
|
12645
13959
|
pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
|
|
13960
|
+
refreshConfiguredRuntimeEntries();
|
|
13961
|
+
await sessionSearchRuntimeSupport.initialize();
|
|
12646
13962
|
backend = new DefaultNcpAgentBackend({
|
|
12647
13963
|
endpointId: "nextclaw-ui-agent",
|
|
12648
13964
|
sessionStore,
|
|
12649
|
-
onSessionRunStatusChanged
|
|
13965
|
+
onSessionRunStatusChanged,
|
|
12650
13966
|
createRuntime: (runtimeParams) => {
|
|
12651
13967
|
pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
|
|
13968
|
+
refreshConfiguredRuntimeEntries();
|
|
12652
13969
|
return runtimeRegistry.createRuntime({
|
|
12653
13970
|
...runtimeParams,
|
|
12654
|
-
resolveAssetContentPath: (assetUri) => assetStore.resolveContentPath(assetUri)
|
|
13971
|
+
resolveAssetContentPath: (assetUri) => assetStore.resolveContentPath(assetUri),
|
|
13972
|
+
resolveTools: createResolveOpenAiToolsForRuntime({
|
|
13973
|
+
bus: params.bus,
|
|
13974
|
+
providerManager,
|
|
13975
|
+
sessionManager,
|
|
13976
|
+
cronService: params.cronService,
|
|
13977
|
+
gatewayController: params.gatewayController,
|
|
13978
|
+
getConfig,
|
|
13979
|
+
getExtensionRegistry,
|
|
13980
|
+
resolveMessageToolHints,
|
|
13981
|
+
assetStore,
|
|
13982
|
+
toolRegistryAdapter,
|
|
13983
|
+
sessionCreationService,
|
|
13984
|
+
sessionRequestBroker,
|
|
13985
|
+
sessionSearchRuntimeSupport
|
|
13986
|
+
})
|
|
12655
13987
|
});
|
|
12656
13988
|
}
|
|
12657
13989
|
});
|
|
12658
13990
|
await backend.start();
|
|
13991
|
+
learningLoopRuntime.attachBackend(backend);
|
|
12659
13992
|
return createUiNcpAgentHandle({
|
|
12660
13993
|
backend,
|
|
12661
13994
|
runtimeRegistry,
|
|
12662
13995
|
refreshPluginRuntimeRegistrations: pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations,
|
|
13996
|
+
refreshConfiguredRuntimeEntries,
|
|
12663
13997
|
applyExtensionRegistry: pluginRuntimeRegistrationController.applyExtensionRegistry,
|
|
12664
13998
|
applyMcpConfig,
|
|
12665
13999
|
dispose: async () => {
|
|
14000
|
+
learningLoopRuntime.dispose();
|
|
14001
|
+
for (const registration of builtinNarpRegistrations) registration.dispose();
|
|
12666
14002
|
pluginRuntimeRegistrationController.dispose();
|
|
12667
14003
|
await backend?.stop();
|
|
14004
|
+
await sessionSearchRuntimeSupport.dispose();
|
|
12668
14005
|
await disposeMcpRuntimeSupport();
|
|
12669
14006
|
},
|
|
12670
14007
|
assetStore
|
|
@@ -13503,6 +14840,66 @@ async function runPromptOverNcp(params) {
|
|
|
13503
14840
|
};
|
|
13504
14841
|
}
|
|
13505
14842
|
//#endregion
|
|
14843
|
+
//#region src/cli/commands/ncp/runtime/runner/ncp-event-stream.ts
|
|
14844
|
+
async function* streamPromptOverNcp(params) {
|
|
14845
|
+
const { abortSignal, agent, attachments, content, metadata, onEvent, sessionId } = params;
|
|
14846
|
+
const message = await buildNcpUserMessage({
|
|
14847
|
+
sessionId,
|
|
14848
|
+
content,
|
|
14849
|
+
attachments,
|
|
14850
|
+
metadata,
|
|
14851
|
+
assetApi: agent.assetApi
|
|
14852
|
+
});
|
|
14853
|
+
for await (const event of agent.runApi.send({
|
|
14854
|
+
sessionId,
|
|
14855
|
+
message,
|
|
14856
|
+
metadata
|
|
14857
|
+
}, { ...abortSignal ? { signal: abortSignal } : {} })) {
|
|
14858
|
+
onEvent?.(event);
|
|
14859
|
+
yield event;
|
|
14860
|
+
}
|
|
14861
|
+
}
|
|
14862
|
+
//#endregion
|
|
14863
|
+
//#region src/cli/commands/ncp/runtime/runner/channel-reply.ts
|
|
14864
|
+
function readString(value) {
|
|
14865
|
+
if (typeof value !== "string") return;
|
|
14866
|
+
return value.trim() || void 0;
|
|
14867
|
+
}
|
|
14868
|
+
function isReplyCapableChannel(channel) {
|
|
14869
|
+
return Boolean(channel) && typeof channel.consumeNcpReply === "function";
|
|
14870
|
+
}
|
|
14871
|
+
function resolveChannelReplyRoute(params) {
|
|
14872
|
+
if (!isReplyCapableChannel(params.channel)) return null;
|
|
14873
|
+
const metadata = structuredClone(params.message.metadata ?? {});
|
|
14874
|
+
const accountId = readString(params.route.accountId) ?? readString(metadata.accountId) ?? readString(metadata.account_id);
|
|
14875
|
+
return {
|
|
14876
|
+
target: {
|
|
14877
|
+
conversationId: params.message.chatId,
|
|
14878
|
+
...accountId ? { accountId } : {},
|
|
14879
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
14880
|
+
},
|
|
14881
|
+
channel: params.channel
|
|
14882
|
+
};
|
|
14883
|
+
}
|
|
14884
|
+
async function dispatchChannelReplyRoute(params) {
|
|
14885
|
+
const input = {
|
|
14886
|
+
target: {
|
|
14887
|
+
...params.route.target,
|
|
14888
|
+
...params.agent.assetApi?.resolveContentPath ? { resolveAssetContentPath: params.agent.assetApi.resolveContentPath } : {}
|
|
14889
|
+
},
|
|
14890
|
+
eventStream: streamPromptOverNcp({
|
|
14891
|
+
agent: params.agent,
|
|
14892
|
+
sessionId: params.sessionId,
|
|
14893
|
+
content: params.content,
|
|
14894
|
+
attachments: params.attachments,
|
|
14895
|
+
metadata: params.metadata,
|
|
14896
|
+
abortSignal: params.abortSignal,
|
|
14897
|
+
onEvent: params.onEvent
|
|
14898
|
+
})
|
|
14899
|
+
};
|
|
14900
|
+
await params.route.channel.consumeNcpReply(input);
|
|
14901
|
+
}
|
|
14902
|
+
//#endregion
|
|
13506
14903
|
//#region src/cli/commands/ncp/runtime/nextclaw-ncp-dispatch.ts
|
|
13507
14904
|
function normalizeOptionalString(value) {
|
|
13508
14905
|
if (typeof value !== "string") return;
|
|
@@ -13541,67 +14938,9 @@ function buildRunMetadata(params) {
|
|
|
13541
14938
|
sender_id: params.message.senderId
|
|
13542
14939
|
};
|
|
13543
14940
|
}
|
|
13544
|
-
function parseCommandOptionValue(type, rawValue) {
|
|
13545
|
-
const value = rawValue.trim();
|
|
13546
|
-
if (!value) return;
|
|
13547
|
-
if (type === "number") {
|
|
13548
|
-
const parsed = Number(value);
|
|
13549
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
13550
|
-
}
|
|
13551
|
-
if (type === "boolean") {
|
|
13552
|
-
const lowered = value.toLowerCase();
|
|
13553
|
-
if ([
|
|
13554
|
-
"1",
|
|
13555
|
-
"true",
|
|
13556
|
-
"yes",
|
|
13557
|
-
"on"
|
|
13558
|
-
].includes(lowered)) return true;
|
|
13559
|
-
if ([
|
|
13560
|
-
"0",
|
|
13561
|
-
"false",
|
|
13562
|
-
"no",
|
|
13563
|
-
"off"
|
|
13564
|
-
].includes(lowered)) return false;
|
|
13565
|
-
return;
|
|
13566
|
-
}
|
|
13567
|
-
return value;
|
|
13568
|
-
}
|
|
13569
|
-
function parseCommandArgsFromText(commandName, rawTail, specs) {
|
|
13570
|
-
if (!rawTail) return {};
|
|
13571
|
-
const options = specs.find((item) => item.name.trim().toLowerCase() === commandName)?.options;
|
|
13572
|
-
if (!options || options.length === 0) return {};
|
|
13573
|
-
const tokens = rawTail.split(/\s+/).filter(Boolean);
|
|
13574
|
-
const args = {};
|
|
13575
|
-
let cursor = 0;
|
|
13576
|
-
for (let index = 0; index < options.length; index += 1) {
|
|
13577
|
-
if (cursor >= tokens.length) break;
|
|
13578
|
-
const option = options[index];
|
|
13579
|
-
const isLastOption = index === options.length - 1;
|
|
13580
|
-
const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
|
|
13581
|
-
cursor += isLastOption ? tokens.length - cursor : 1;
|
|
13582
|
-
const parsedValue = parseCommandOptionValue(option.type, rawValue);
|
|
13583
|
-
if (parsedValue !== void 0) args[option.name] = parsedValue;
|
|
13584
|
-
}
|
|
13585
|
-
return args;
|
|
13586
|
-
}
|
|
13587
14941
|
async function executeSlashCommandMaybe(params) {
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
const registry = new CommandRegistry(params.config, params.sessionManager);
|
|
13591
|
-
const executeText = registry.executeText;
|
|
13592
|
-
if (typeof executeText === "function") return (await executeText.call(registry, params.rawContent, {
|
|
13593
|
-
channel: params.channel,
|
|
13594
|
-
chatId: params.chatId,
|
|
13595
|
-
senderId: "user",
|
|
13596
|
-
sessionKey: params.sessionKey
|
|
13597
|
-
}))?.content ?? null;
|
|
13598
|
-
const commandRaw = trimmed.slice(1).trim();
|
|
13599
|
-
if (!commandRaw) return null;
|
|
13600
|
-
const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
|
|
13601
|
-
const commandName = nameToken.trim().toLowerCase();
|
|
13602
|
-
if (!commandName) return null;
|
|
13603
|
-
const args = parseCommandArgsFromText(commandName, restTokens.join(" ").trim(), registry.listSlashCommands());
|
|
13604
|
-
return (await registry.execute(commandName, args, {
|
|
14942
|
+
if (!params.rawContent.trim().startsWith("/")) return null;
|
|
14943
|
+
return (await new CommandRegistry(params.config, params.sessionManager).executeText(params.rawContent, {
|
|
13605
14944
|
channel: params.channel,
|
|
13606
14945
|
chatId: params.chatId,
|
|
13607
14946
|
senderId: "user",
|
|
@@ -13626,6 +14965,48 @@ function formatUserFacingError(error, maxChars = 320) {
|
|
|
13626
14965
|
if (normalized.length <= maxChars) return normalized;
|
|
13627
14966
|
return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
13628
14967
|
}
|
|
14968
|
+
async function dispatchChannelReplyRouteMaybe(params, context) {
|
|
14969
|
+
if (context.message.channel === "system" || !params.getChannels) return false;
|
|
14970
|
+
const replyRoute = resolveChannelReplyRoute({
|
|
14971
|
+
channel: params.getChannels().getChannel(context.message.channel),
|
|
14972
|
+
message: context.message,
|
|
14973
|
+
route: context.route
|
|
14974
|
+
});
|
|
14975
|
+
if (!replyRoute) return false;
|
|
14976
|
+
await dispatchChannelReplyRoute({
|
|
14977
|
+
agent: context.agent,
|
|
14978
|
+
route: replyRoute,
|
|
14979
|
+
sessionId: context.route.sessionKey,
|
|
14980
|
+
content: context.message.content,
|
|
14981
|
+
attachments: context.message.attachments,
|
|
14982
|
+
metadata: context.runMetadata
|
|
14983
|
+
});
|
|
14984
|
+
return true;
|
|
14985
|
+
}
|
|
14986
|
+
async function publishLegacyReply(params, context, result) {
|
|
14987
|
+
if (context.message.channel === "system") {
|
|
14988
|
+
params.onSystemSessionUpdated?.({
|
|
14989
|
+
sessionKey: context.route.sessionKey,
|
|
14990
|
+
message: context.message
|
|
14991
|
+
});
|
|
14992
|
+
return;
|
|
14993
|
+
}
|
|
14994
|
+
if (!result.text.trim()) {
|
|
14995
|
+
await params.bus.publishOutbound(createTypingStopControlMessage(context.message));
|
|
14996
|
+
return;
|
|
14997
|
+
}
|
|
14998
|
+
await params.bus.publishOutbound({
|
|
14999
|
+
channel: context.message.channel,
|
|
15000
|
+
chatId: context.message.chatId,
|
|
15001
|
+
content: result.text,
|
|
15002
|
+
media: [],
|
|
15003
|
+
metadata: buildRunMetadata({
|
|
15004
|
+
message: context.message,
|
|
15005
|
+
route: context.route,
|
|
15006
|
+
metadata: result.completedMessage.metadata
|
|
15007
|
+
})
|
|
15008
|
+
});
|
|
15009
|
+
}
|
|
13629
15010
|
async function dispatchPromptOverNcp(params) {
|
|
13630
15011
|
const { message, route } = resolveDirectRoute({
|
|
13631
15012
|
config: params.config,
|
|
@@ -13664,6 +15045,7 @@ async function dispatchPromptOverNcp(params) {
|
|
|
13664
15045
|
async function runGatewayInboundLoop(params) {
|
|
13665
15046
|
while (true) {
|
|
13666
15047
|
const message = await params.bus.consumeInbound();
|
|
15048
|
+
let usedChannelReplyRoute = false;
|
|
13667
15049
|
try {
|
|
13668
15050
|
const explicitSessionKey = normalizeOptionalString(message.metadata.session_key_override);
|
|
13669
15051
|
const forcedAgentId = normalizeOptionalString(message.metadata.target_agent_id);
|
|
@@ -13673,46 +15055,36 @@ async function runGatewayInboundLoop(params) {
|
|
|
13673
15055
|
sessionKeyOverride: explicitSessionKey
|
|
13674
15056
|
});
|
|
13675
15057
|
const agent = requireNcpAgent(params.resolveNcpAgent, "gateway dispatch");
|
|
15058
|
+
const runMetadata = buildRunMetadata({
|
|
15059
|
+
message,
|
|
15060
|
+
route
|
|
15061
|
+
});
|
|
15062
|
+
const context = {
|
|
15063
|
+
agent,
|
|
15064
|
+
message,
|
|
15065
|
+
route,
|
|
15066
|
+
runMetadata
|
|
15067
|
+
};
|
|
15068
|
+
if (await dispatchChannelReplyRouteMaybe(params, context)) {
|
|
15069
|
+
usedChannelReplyRoute = true;
|
|
15070
|
+
continue;
|
|
15071
|
+
}
|
|
13676
15072
|
if (message.channel !== "system") await params.bus.publishOutbound(createAssistantStreamResetControlMessage(message));
|
|
13677
|
-
|
|
15073
|
+
await publishLegacyReply(params, context, await runPromptOverNcp({
|
|
13678
15074
|
agent,
|
|
13679
15075
|
sessionId: route.sessionKey,
|
|
13680
15076
|
content: message.content,
|
|
13681
15077
|
attachments: message.attachments,
|
|
13682
|
-
metadata:
|
|
13683
|
-
message,
|
|
13684
|
-
route
|
|
13685
|
-
}),
|
|
15078
|
+
metadata: runMetadata,
|
|
13686
15079
|
onAssistantDelta: message.channel !== "system" ? (delta) => {
|
|
13687
15080
|
if (!delta) return;
|
|
13688
15081
|
params.bus.publishOutbound(createAssistantStreamDeltaControlMessage(message, delta));
|
|
13689
15082
|
} : void 0,
|
|
13690
15083
|
missingCompletedMessageError: `session "${route.sessionKey}" completed without a final assistant message`,
|
|
13691
15084
|
runErrorMessage: `session "${route.sessionKey}" failed`
|
|
13692
|
-
});
|
|
13693
|
-
if (message.channel === "system") {
|
|
13694
|
-
params.onSystemSessionUpdated?.({
|
|
13695
|
-
sessionKey: route.sessionKey,
|
|
13696
|
-
message
|
|
13697
|
-
});
|
|
13698
|
-
continue;
|
|
13699
|
-
}
|
|
13700
|
-
if (!result.text.trim()) {
|
|
13701
|
-
await params.bus.publishOutbound(createTypingStopControlMessage(message));
|
|
13702
|
-
continue;
|
|
13703
|
-
}
|
|
13704
|
-
await params.bus.publishOutbound({
|
|
13705
|
-
channel: message.channel,
|
|
13706
|
-
chatId: message.chatId,
|
|
13707
|
-
content: result.text,
|
|
13708
|
-
media: [],
|
|
13709
|
-
metadata: buildRunMetadata({
|
|
13710
|
-
message,
|
|
13711
|
-
route,
|
|
13712
|
-
metadata: result.completedMessage.metadata
|
|
13713
|
-
})
|
|
13714
|
-
});
|
|
15085
|
+
}));
|
|
13715
15086
|
} catch (error) {
|
|
15087
|
+
if (usedChannelReplyRoute) continue;
|
|
13716
15088
|
await params.bus.publishOutbound({
|
|
13717
15089
|
channel: message.channel,
|
|
13718
15090
|
chatId: message.chatId,
|
|
@@ -13833,6 +15205,7 @@ async function startUiShell(params) {
|
|
|
13833
15205
|
cronService: params.cronService,
|
|
13834
15206
|
marketplace: params.marketplace,
|
|
13835
15207
|
remoteAccess: params.remoteAccess,
|
|
15208
|
+
runtimeControl: params.runtimeControl,
|
|
13836
15209
|
getBootstrapStatus: params.getBootstrapStatus,
|
|
13837
15210
|
getPluginChannelBindings: params.getPluginChannelBindings,
|
|
13838
15211
|
getPluginUiMetadata: params.getPluginUiMetadata,
|
|
@@ -13858,6 +15231,7 @@ async function startUiShell(params) {
|
|
|
13858
15231
|
async function startDeferredGatewayStartup(params) {
|
|
13859
15232
|
const { uiStartup, deferredNcpSessionService, bus, sessionManager, providerManager, cronService, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints, hydrateCapabilities, startPluginGateways, startChannels, wakeFromRestartSentinel, onNcpAgentReady, publishSessionChange } = params;
|
|
13860
15233
|
logStartupTrace("service.deferred_startup.begin");
|
|
15234
|
+
if (hydrateCapabilities) await measureStartupAsync("service.deferred_startup.hydrate_capabilities", hydrateCapabilities);
|
|
13861
15235
|
try {
|
|
13862
15236
|
const ncpAgent = await measureStartupAsync("service.deferred_startup.create_ui_ncp_agent", async () => await createUiNcpAgent({
|
|
13863
15237
|
bus,
|
|
@@ -13888,7 +15262,6 @@ async function startDeferredGatewayStartup(params) {
|
|
|
13888
15262
|
} catch (error) {
|
|
13889
15263
|
console.error(`UI NCP agent startup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
13890
15264
|
}
|
|
13891
|
-
if (hydrateCapabilities) await measureStartupAsync("service.deferred_startup.hydrate_capabilities", hydrateCapabilities);
|
|
13892
15265
|
await measureStartupAsync("service.deferred_startup.start_plugin_gateways", startPluginGateways);
|
|
13893
15266
|
await measureStartupAsync("service.deferred_startup.start_channels", startChannels);
|
|
13894
15267
|
await measureStartupAsync("service.deferred_startup.wake_restart_sentinel", wakeFromRestartSentinel);
|
|
@@ -13916,6 +15289,7 @@ async function runConfiguredGatewayRuntime(params) {
|
|
|
13916
15289
|
sessionManager: params.gateway.sessionManager,
|
|
13917
15290
|
getConfig: params.getConfig,
|
|
13918
15291
|
resolveNcpAgent: params.getLiveUiNcpAgent,
|
|
15292
|
+
getChannels: () => params.gateway.reloader.getChannels(),
|
|
13919
15293
|
onSystemSessionUpdated: ({ sessionKey }) => onSystemSessionUpdated({ sessionKey })
|
|
13920
15294
|
}),
|
|
13921
15295
|
startDeferredStartup: () => startDeferredGatewayStartup({
|
|
@@ -13969,13 +15343,15 @@ function createNcpSessionRealtimeChangePublisher(params) {
|
|
|
13969
15343
|
//#endregion
|
|
13970
15344
|
//#region src/cli/commands/ncp/session/ncp-session-summary.ts
|
|
13971
15345
|
function createNcpSessionSummary(params) {
|
|
15346
|
+
const { sessionId, agentId, messages, updatedAt, status, metadata } = params;
|
|
13972
15347
|
return {
|
|
13973
|
-
sessionId
|
|
13974
|
-
...
|
|
13975
|
-
messageCount:
|
|
13976
|
-
updatedAt
|
|
13977
|
-
|
|
13978
|
-
|
|
15348
|
+
sessionId,
|
|
15349
|
+
...agentId ? { agentId } : {},
|
|
15350
|
+
messageCount: messages.length,
|
|
15351
|
+
updatedAt,
|
|
15352
|
+
...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
|
|
15353
|
+
status,
|
|
15354
|
+
...metadata ? { metadata: structuredClone(metadata) } : {}
|
|
13979
15355
|
};
|
|
13980
15356
|
}
|
|
13981
15357
|
//#endregion
|
|
@@ -14673,7 +16049,7 @@ var ServiceCommands = class {
|
|
|
14673
16049
|
runCliSubcommand: (args) => this.runCliSubcommand(args),
|
|
14674
16050
|
installBuiltinSkill: (slug, force) => this.installBuiltinMarketplaceSkill(slug, force)
|
|
14675
16051
|
}).createInstaller();
|
|
14676
|
-
const remoteAccess =
|
|
16052
|
+
const { remoteAccess, runtimeControl } = createServiceUiHosts({
|
|
14677
16053
|
serviceCommands: this,
|
|
14678
16054
|
requestRestart: this.deps.requestRestart,
|
|
14679
16055
|
uiConfig: shellContext.uiConfig,
|
|
@@ -14693,6 +16069,7 @@ var ServiceCommands = class {
|
|
|
14693
16069
|
installer: marketplaceInstaller
|
|
14694
16070
|
},
|
|
14695
16071
|
remoteAccess,
|
|
16072
|
+
runtimeControl,
|
|
14696
16073
|
getBootstrapStatus: () => bootstrapStatus.getStatus(),
|
|
14697
16074
|
openBrowserWindow: shellContext.uiConfig.open,
|
|
14698
16075
|
applyLiveConfigReload,
|
|
@@ -16059,6 +17436,42 @@ function registerAgentsCommands(program, runtime) {
|
|
|
16059
17436
|
agents.command("remove <agentId>").description("Remove an agent").option("--json", "Output JSON", false).action(async (agentId, opts) => runtime.agentsRemove(agentId, opts));
|
|
16060
17437
|
}
|
|
16061
17438
|
//#endregion
|
|
17439
|
+
//#region src/cli/commands/learning-loop/register-learning-loop-commands.ts
|
|
17440
|
+
function readLearningLoopThresholdOrExit(value) {
|
|
17441
|
+
const threshold = Number.parseInt(value, 10);
|
|
17442
|
+
if (!Number.isInteger(threshold) || threshold < 1) {
|
|
17443
|
+
console.error(`Invalid learning loop threshold: ${value}. Expected an integer >= 1.`);
|
|
17444
|
+
process.exit(1);
|
|
17445
|
+
}
|
|
17446
|
+
return threshold;
|
|
17447
|
+
}
|
|
17448
|
+
function registerLearningLoopCommands(program, runtime) {
|
|
17449
|
+
const learningLoop = program.command("learning-loop").description("Manage the learning loop");
|
|
17450
|
+
learningLoop.command("status").description("Show current learning loop settings").option("--json", "Output JSON", false).action((opts) => {
|
|
17451
|
+
const status = readLearningLoopRuntimeConfig(loadConfig());
|
|
17452
|
+
if (opts.json) {
|
|
17453
|
+
console.log(JSON.stringify(status, null, 2));
|
|
17454
|
+
return;
|
|
17455
|
+
}
|
|
17456
|
+
console.log("Learning loop");
|
|
17457
|
+
console.log(` enabled: ${status.enabled}`);
|
|
17458
|
+
console.log(` toolCallThreshold: ${status.toolCallThreshold}`);
|
|
17459
|
+
});
|
|
17460
|
+
learningLoop.command("enable").description("Enable the learning loop").action(async () => {
|
|
17461
|
+
await runtime.configSet("agents.learningLoop.enabled", "true", { json: true });
|
|
17462
|
+
console.log("✓ Enabled learning loop.");
|
|
17463
|
+
});
|
|
17464
|
+
learningLoop.command("disable").description("Disable the learning loop").action(async () => {
|
|
17465
|
+
await runtime.configSet("agents.learningLoop.enabled", "false", { json: true });
|
|
17466
|
+
console.log("✓ Disabled learning loop.");
|
|
17467
|
+
});
|
|
17468
|
+
learningLoop.command("threshold <count>").description("Set the tool-call threshold that triggers a learning-loop review").action(async (count) => {
|
|
17469
|
+
const threshold = readLearningLoopThresholdOrExit(count);
|
|
17470
|
+
await runtime.configSet("agents.learningLoop.toolCallThreshold", String(threshold), { json: true });
|
|
17471
|
+
console.log(`✓ Updated learning loop tool-call threshold to ${threshold}.`);
|
|
17472
|
+
});
|
|
17473
|
+
}
|
|
17474
|
+
//#endregion
|
|
16062
17475
|
//#region src/cli/index.ts
|
|
16063
17476
|
logStartupTrace("cli.index.module_loaded");
|
|
16064
17477
|
const program = new Command();
|
|
@@ -16067,7 +17480,7 @@ const llmUsageCommands = new LlmUsageCommands();
|
|
|
16067
17480
|
program.name(APP_NAME).description(`${LOGO} ${APP_NAME} - ${APP_TAGLINE}`).version(getPackageVersion$1(), "-v, --version", "show version");
|
|
16068
17481
|
program.command("onboard").description(`Initialize ${APP_NAME} configuration and workspace`).action(async () => runtime.onboard());
|
|
16069
17482
|
program.command("init").description(`Initialize ${APP_NAME} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
16070
|
-
program.command("login").description("
|
|
17483
|
+
program.command("login").description("Sign in to NextClaw Platform and save the platform token locally (browser flow by default)").option("--api-base <url>", "Platform API base (supports /v1 suffix)").option("--email <email>", "Login email for direct password sign-in").option("--password <password>", "Login password for direct password sign-in").option("--no-open", "Do not open the browser automatically").action(async (opts) => runtime.login(opts));
|
|
16071
17484
|
registerRemoteCommands(program, runtime.remote);
|
|
16072
17485
|
program.command("gateway").description(`Start the ${APP_NAME} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
|
|
16073
17486
|
program.command("ui").description(`Start the ${APP_NAME} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
|
|
@@ -16107,6 +17520,7 @@ const config = program.command("config").description("Manage config values");
|
|
|
16107
17520
|
config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
|
|
16108
17521
|
config.command("set <path> <value>").description("Set a config value by dot path").option("--json", "Parse value as JSON", false).action((path, value, opts) => runtime.configSet(path, value, opts));
|
|
16109
17522
|
config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
|
|
17523
|
+
registerLearningLoopCommands(program, runtime);
|
|
16110
17524
|
const mcp = program.command("mcp").description("Manage MCP servers");
|
|
16111
17525
|
mcp.command("list").description("List configured MCP servers").option("--json", "Output JSON", false).action((opts) => runtime.mcpList(opts));
|
|
16112
17526
|
mcp.command("add <name> [command...]").description("Add an MCP server (stdio by default, or use --transport http|sse)").allowUnknownOption(true).option("--transport <type>", "Transport type: stdio|http|sse", "stdio").option("--url <url>", "HTTP/SSE endpoint URL").option("--header <key=value>", "Transport header (repeatable)", withRepeatableTag, []).option("--env <key=value>", "stdio env var (repeatable)", withRepeatableTag, []).option("--cwd <dir>", "stdio working directory").option("--timeout-ms <ms>", "HTTP/SSE timeout in milliseconds").option("--stderr <mode>", "stdio stderr handling: inherit|pipe|ignore", "pipe").option("--disabled", "Create the server in disabled state", false).option("--all-agents", "Expose this server to all agents", false).option("--agent <id>", "Expose to an agent id (repeatable)", withRepeatableTag, []).option("--insecure", "Disable TLS verification for HTTP/SSE", false).action(async (name, command, opts) => runtime.mcpAdd(name, command ?? [], opts));
|