nextclaw 0.17.6 → 0.17.9
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 +1926 -979
- package/package.json +12 -12
- package/resources/USAGE.md +21 -1
- package/ui-dist/assets/ChannelsList-D8p4OlM6.js +8 -0
- package/ui-dist/assets/ChatPage-A45t1Rmf.js +58 -0
- package/ui-dist/assets/DocBrowser-B2MpsnU9.js +1 -0
- package/ui-dist/assets/{DocBrowser-QUZ3nfmH.js → DocBrowser-Cse_F8Nn.js} +1 -1
- package/ui-dist/assets/{DocBrowserContext-CpiIfhJO.js → DocBrowserContext-Bai1WU2H.js} +1 -1
- package/ui-dist/assets/{LogoBadge-BUK13xK5.js → LogoBadge-BdxMPc9v.js} +1 -1
- package/ui-dist/assets/MarketplacePage-BNZ3Jx5d.js +1 -0
- package/ui-dist/assets/MarketplacePage-BbpAkllU.js +49 -0
- package/ui-dist/assets/{McpMarketplacePage-BG4T_Pcx.js → McpMarketplacePage-CxPFOgxv.js} +2 -2
- package/ui-dist/assets/ModelConfig-3GLqQ5GY.js +1 -0
- package/ui-dist/assets/{ProviderScopedModelInput-DGn6sFEN.js → ProviderScopedModelInput-BYNouw-i.js} +1 -1
- package/ui-dist/assets/ProvidersList-BR1gJ4Dm.js +1 -0
- package/ui-dist/assets/{RemoteAccessPage-ff15qO-c.js → RemoteAccessPage-DyYVWsyK.js} +1 -1
- package/ui-dist/assets/{RuntimeConfig-TgPandXF.js → RuntimeConfig-ChdfK4Y_.js} +1 -1
- package/ui-dist/assets/SearchConfig-DTeJvp8m.js +1 -0
- package/ui-dist/assets/{SecretsConfig-Bew4EF2A.js → SecretsConfig-CCYO6NcV.js} +2 -2
- package/ui-dist/assets/SessionsConfig-Du39vDgt.js +2 -0
- package/ui-dist/assets/app-query-client-Dr5d-K8d.js +1 -0
- package/ui-dist/assets/{book-open-CJG8Yz3U.js → book-open-Da4OEPqB.js} +1 -1
- package/ui-dist/assets/chat-session-display-CAlPrnlV.js +1 -0
- package/ui-dist/assets/{chunk-JZWAC4HX-D5b3Iyas.js → chunk-JZWAC4HX-CoFVxHXV.js} +1 -1
- package/ui-dist/assets/client-CSk58DcF.js +7 -0
- package/ui-dist/assets/config-D8KzikVB.js +1 -0
- package/ui-dist/assets/{createLucideIcon-_FMJqZw2.js → createLucideIcon-83gaZMtv.js} +1 -1
- package/ui-dist/assets/desktop-update-config-CfoVwf-w.js +1 -0
- package/ui-dist/assets/dist-aTmhMDVh.js +9 -0
- package/ui-dist/assets/{dist-B1fpOuON.js → dist-toEYs-MZ.js} +1 -1
- package/ui-dist/assets/{external-link-b7gAJWYY.js → external-link-QQ0TC6X4.js} +1 -1
- package/ui-dist/assets/{hash-Bhy4TwfZ.js → hash-DaFBEkmi.js} +1 -1
- package/ui-dist/assets/i18n-C3jb83S6.js +1 -0
- package/ui-dist/assets/index-CE4N7ItL.css +1 -0
- package/ui-dist/assets/index-riX7Sg0_.js +6 -0
- package/ui-dist/assets/infiniteQueryBehavior-BmHX_ayZ.js +1 -0
- package/ui-dist/assets/loader-circle-BjMg63eu.js +1 -0
- package/ui-dist/assets/{logos-GMeYU9vc.js → logos-Dzlz30M3.js} +1 -1
- package/ui-dist/assets/{page-layout-C8UbWuMt.js → page-layout-D2eRufRQ.js} +1 -1
- package/ui-dist/assets/plus-CIXME2pD.js +1 -0
- package/ui-dist/assets/{popover-8HSx9wQj.js → popover-BSXxm5bj.js} +1 -1
- package/ui-dist/assets/{refresh-ccw-CA4_C7Zg.js → refresh-ccw-B3zMtN-_.js} +1 -1
- package/ui-dist/assets/refresh-cw-DlZkIHnJ.js +1 -0
- package/ui-dist/assets/{save-BtvMy4lk.js → save-Us9fg4Sj.js} +1 -1
- package/ui-dist/assets/search-B_Qr0f6C.js +1 -0
- package/ui-dist/assets/security-config-BGWYwxNr.js +1 -0
- package/ui-dist/assets/{select-xp_Ac8ip.js → select-DLYqySQK.js} +1 -1
- package/ui-dist/assets/skeleton-CYQJazv6.js +1 -0
- package/ui-dist/assets/{status-dot-Cn4Pp7DZ.js → status-dot-DGayudyB.js} +1 -1
- package/ui-dist/assets/{switch-BTi6UOij.js → switch-Dz2ScsKx.js} +1 -1
- package/ui-dist/assets/{tabs-custom-BiiN8DME.js → tabs-custom-CdKyjiGk.js} +1 -1
- package/ui-dist/assets/{trash-2-BpsF0N-r.js → trash-2-Db-mZOZs.js} +1 -1
- package/ui-dist/assets/use-infinite-scroll-loader-DBJX5hj0.js +1 -0
- package/ui-dist/assets/{useConfirmDialog-BJIwUZjH.js → useConfirmDialog-DL0a-oGC.js} +1 -1
- package/ui-dist/assets/useMutation-BdZm-9PL.js +1 -0
- package/ui-dist/assets/x-B8Tho_xC.js +1 -0
- package/ui-dist/index.html +20 -19
- package/ui-dist/assets/ChannelsList-C6-lh55g.js +0 -8
- package/ui-dist/assets/ChatPage-DOW0gPc2.js +0 -45
- package/ui-dist/assets/DocBrowser-CGyeswYP.js +0 -1
- package/ui-dist/assets/MarketplacePage-BDVwhIYE.js +0 -1
- package/ui-dist/assets/MarketplacePage-LnKKL3xK.js +0 -49
- package/ui-dist/assets/ModelConfig-LtWuogIw.js +0 -1
- package/ui-dist/assets/ProvidersList-ma-_MlLo.js +0 -1
- package/ui-dist/assets/SearchConfig-C9iBt7pl.js +0 -1
- package/ui-dist/assets/SessionsConfig-2r2yAGZg.js +0 -2
- package/ui-dist/assets/chat-session-display-DkAC5OMC.js +0 -1
- package/ui-dist/assets/config-zvnxSXSP.js +0 -1
- package/ui-dist/assets/dist-BCXX7FD-.js +0 -15
- package/ui-dist/assets/i18n-DJg9BPYk.js +0 -1
- package/ui-dist/assets/index-BoJbxdvZ.css +0 -1
- package/ui-dist/assets/index-CtlT4E9Y.js +0 -6
- package/ui-dist/assets/infiniteQueryBehavior-CTcVlD9s.js +0 -1
- package/ui-dist/assets/loader-circle-B60I0hEk.js +0 -1
- package/ui-dist/assets/plus-CR7RfK3H.js +0 -1
- package/ui-dist/assets/react-BB4jko2M.js +0 -1
- package/ui-dist/assets/search-C60UA27E.js +0 -1
- package/ui-dist/assets/security-config-BkFDYZ6j.js +0 -1
- package/ui-dist/assets/skeleton-uxz_5h3A.js +0 -1
- package/ui-dist/assets/use-infinite-scroll-loader-C8jBv11-.js +0 -1
- package/ui-dist/assets/useMutation-BjBOKHj_.js +0 -1
- package/ui-dist/assets/x-BfTu-g7D.js +0 -1
- /package/ui-dist/assets/{config-hints-WtpHP_DW.js → config-hints-GSUMvmSo.js} +0 -0
- /package/ui-dist/assets/{config-layout-LQ10ozRC.js → config-layout-CgBMG7OL.js} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
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,
|
|
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";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import fs, { appendFileSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
7
|
import path, { basename, 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
|
-
import { addPluginLoadPath, buildPluginStatusReport, disablePluginInConfig, discoverPluginStatusReport, enablePluginInConfig, getPluginChannelBindings, getPluginUiMetadataFromRegistry, installPluginFromNpmSpec, installPluginFromPath, loadOpenClawPlugins, loadOpenClawPluginsProgressively, mergePluginConfigView, recordPluginInstall, resolvePluginChannelMessageToolHints, resolveUninstallDirectoryTargets, setPluginRuntimeBridge, startPluginChannelGateways, stopPluginChannelGateways, toPluginConfigView, toPluginConfigView as toPluginConfigView$1, uninstallPlugin } from "@nextclaw/openclaw-compat";
|
|
11
|
-
import { createInterface } from "node:readline";
|
|
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";
|
|
12
11
|
import { fileURLToPath } from "node:url";
|
|
13
12
|
import { spawn, spawnSync } from "node:child_process";
|
|
14
13
|
import { parse } from "yaml";
|
|
14
|
+
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";
|
|
@@ -24,6 +24,7 @@ import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
|
|
|
24
24
|
import { NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
|
|
25
25
|
import { DefaultNcpAgentBackend, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
|
|
26
26
|
import { createHash, randomUUID } from "node:crypto";
|
|
27
|
+
import { readFile } from "node:fs/promises";
|
|
27
28
|
//#region \0rolldown/runtime.js
|
|
28
29
|
var __create = Object.create;
|
|
29
30
|
var __defProp = Object.defineProperty;
|
|
@@ -161,7 +162,7 @@ function maskToken(value) {
|
|
|
161
162
|
if (value.length <= 12) return "<redacted>";
|
|
162
163
|
return `${value.slice(0, 6)}...${value.slice(-4)}`;
|
|
163
164
|
}
|
|
164
|
-
function normalizeOptionalString$
|
|
165
|
+
function normalizeOptionalString$9(value) {
|
|
165
166
|
if (typeof value !== "string") return;
|
|
166
167
|
const trimmed = value.trim();
|
|
167
168
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -259,8 +260,8 @@ var RemotePlatformClient = class {
|
|
|
259
260
|
if (tokenState.reason === "missing") throw new Error("NextClaw platform token is missing. Run \"nextclaw login\" first.");
|
|
260
261
|
if (tokenState.reason === "expired") throw new Error("NextClaw platform token expired. Run \"nextclaw login\" or browser sign-in again.");
|
|
261
262
|
if (tokenState.reason === "malformed") throw new Error("NextClaw platform token is invalid. Run \"nextclaw login\" again.");
|
|
262
|
-
const configuredApiBase = normalizeOptionalString$
|
|
263
|
-
const rawApiBase = normalizeOptionalString$
|
|
263
|
+
const configuredApiBase = normalizeOptionalString$9(config.remote.platformApiBase) ?? (typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "");
|
|
264
|
+
const rawApiBase = normalizeOptionalString$9(opts.apiBase) ?? configuredApiBase;
|
|
264
265
|
if (!rawApiBase) throw new Error("Platform API base is missing. Pass --api-base, run nextclaw login, or set remote.platformApiBase.");
|
|
265
266
|
return {
|
|
266
267
|
platformBase: this.deps.resolvePlatformBase(rawApiBase),
|
|
@@ -269,14 +270,14 @@ var RemotePlatformClient = class {
|
|
|
269
270
|
};
|
|
270
271
|
}
|
|
271
272
|
resolveLocalOrigin(config, opts) {
|
|
272
|
-
const explicitOrigin = normalizeOptionalString$
|
|
273
|
+
const explicitOrigin = normalizeOptionalString$9(opts.localOrigin);
|
|
273
274
|
if (explicitOrigin) return explicitOrigin.replace(/\/$/, "");
|
|
274
275
|
const state = this.deps.readManagedServiceState?.();
|
|
275
276
|
if (state && this.deps.isProcessRunning?.(state.pid) && Number.isFinite(state.uiPort)) return `http://127.0.0.1:${state.uiPort}`;
|
|
276
277
|
return `http://127.0.0.1:${typeof config.ui?.port === "number" && Number.isFinite(config.ui.port) ? config.ui.port : 55667}`;
|
|
277
278
|
}
|
|
278
279
|
resolveDisplayName(config, opts) {
|
|
279
|
-
return normalizeOptionalString$
|
|
280
|
+
return normalizeOptionalString$9(opts.name) ?? normalizeOptionalString$9(config.remote.deviceName) ?? hostname();
|
|
280
281
|
}
|
|
281
282
|
};
|
|
282
283
|
//#endregion
|
|
@@ -4486,7 +4487,7 @@ var RemoteConnector = class {
|
|
|
4486
4487
|
};
|
|
4487
4488
|
//#endregion
|
|
4488
4489
|
//#region ../nextclaw-remote/src/remote-status-store.ts
|
|
4489
|
-
function normalizeOptionalString$
|
|
4490
|
+
function normalizeOptionalString$8(value) {
|
|
4490
4491
|
if (typeof value !== "string") return;
|
|
4491
4492
|
const trimmed = value.trim();
|
|
4492
4493
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -4497,8 +4498,8 @@ function buildConfiguredRemoteState(config) {
|
|
|
4497
4498
|
enabled: Boolean(remote.enabled),
|
|
4498
4499
|
mode: "service",
|
|
4499
4500
|
state: remote.enabled ? "disconnected" : "disabled",
|
|
4500
|
-
...normalizeOptionalString$
|
|
4501
|
-
...normalizeOptionalString$
|
|
4501
|
+
...normalizeOptionalString$8(remote.deviceName) ? { deviceName: normalizeOptionalString$8(remote.deviceName) } : {},
|
|
4502
|
+
...normalizeOptionalString$8(remote.platformApiBase) ? { platformBase: normalizeOptionalString$8(remote.platformApiBase) } : {},
|
|
4502
4503
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4503
4504
|
};
|
|
4504
4505
|
}
|
|
@@ -4511,7 +4512,7 @@ function resolveRemoteStatusSnapshot(params) {
|
|
|
4511
4512
|
configuredEnabled: true,
|
|
4512
4513
|
runtime: {
|
|
4513
4514
|
...buildConfiguredRemoteState(params.config),
|
|
4514
|
-
deviceName: normalizeOptionalString$
|
|
4515
|
+
deviceName: normalizeOptionalString$8(params.config.remote.deviceName) ?? normalizeOptionalString$8(params.fallbackDeviceName) ?? hostname()
|
|
4515
4516
|
}
|
|
4516
4517
|
};
|
|
4517
4518
|
return {
|
|
@@ -4623,6 +4624,299 @@ var RemoteServiceModule = class {
|
|
|
4623
4624
|
}
|
|
4624
4625
|
};
|
|
4625
4626
|
//#endregion
|
|
4627
|
+
//#region src/cli/runtime-state/llm-usage-history.store.ts
|
|
4628
|
+
var LlmUsageHistoryStore = class {
|
|
4629
|
+
constructor(explicitPath) {
|
|
4630
|
+
this.explicitPath = explicitPath;
|
|
4631
|
+
}
|
|
4632
|
+
get path() {
|
|
4633
|
+
return this.explicitPath ?? resolve(getDataDir(), "logs", "llm-usage.jsonl");
|
|
4634
|
+
}
|
|
4635
|
+
list = () => {
|
|
4636
|
+
if (!existsSync(this.path)) return [];
|
|
4637
|
+
try {
|
|
4638
|
+
return readFileSync(this.path, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
|
|
4639
|
+
try {
|
|
4640
|
+
return [JSON.parse(line)];
|
|
4641
|
+
} catch {
|
|
4642
|
+
return [];
|
|
4643
|
+
}
|
|
4644
|
+
});
|
|
4645
|
+
} catch {
|
|
4646
|
+
return [];
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
append = (record) => {
|
|
4650
|
+
mkdirSync(resolve(this.path, ".."), { recursive: true });
|
|
4651
|
+
appendFileSync(this.path, `${JSON.stringify(record)}\n`);
|
|
4652
|
+
};
|
|
4653
|
+
clear = () => {
|
|
4654
|
+
if (existsSync(this.path)) rmSync(this.path, { force: true });
|
|
4655
|
+
};
|
|
4656
|
+
};
|
|
4657
|
+
const llmUsageHistoryStore = new LlmUsageHistoryStore();
|
|
4658
|
+
//#endregion
|
|
4659
|
+
//#region src/cli/runtime-state/llm-usage-snapshot.store.ts
|
|
4660
|
+
var LlmUsageSnapshotStore = class {
|
|
4661
|
+
constructor(explicitPath) {
|
|
4662
|
+
this.explicitPath = explicitPath;
|
|
4663
|
+
}
|
|
4664
|
+
get path() {
|
|
4665
|
+
return this.explicitPath ?? resolve(getDataDir(), "run", "llm-usage.json");
|
|
4666
|
+
}
|
|
4667
|
+
read = () => {
|
|
4668
|
+
if (!existsSync(this.path)) return null;
|
|
4669
|
+
try {
|
|
4670
|
+
const raw = readFileSync(this.path, "utf-8");
|
|
4671
|
+
return JSON.parse(raw);
|
|
4672
|
+
} catch {
|
|
4673
|
+
return null;
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
write = (snapshot) => {
|
|
4677
|
+
mkdirSync(resolve(this.path, ".."), { recursive: true });
|
|
4678
|
+
writeFileSync(this.path, JSON.stringify(snapshot, null, 2));
|
|
4679
|
+
};
|
|
4680
|
+
clear = () => {
|
|
4681
|
+
if (existsSync(this.path)) rmSync(this.path, { force: true });
|
|
4682
|
+
};
|
|
4683
|
+
};
|
|
4684
|
+
const llmUsageSnapshotStore = new LlmUsageSnapshotStore();
|
|
4685
|
+
//#endregion
|
|
4686
|
+
//#region src/cli/commands/shared/llm-usage-query.service.ts
|
|
4687
|
+
var LlmUsageQueryService = class {
|
|
4688
|
+
constructor(deps = {}) {
|
|
4689
|
+
this.deps = deps;
|
|
4690
|
+
}
|
|
4691
|
+
get snapshotPath() {
|
|
4692
|
+
return this.snapshotStore.path;
|
|
4693
|
+
}
|
|
4694
|
+
get historyPath() {
|
|
4695
|
+
return this.historyStore.path;
|
|
4696
|
+
}
|
|
4697
|
+
getSnapshot = () => {
|
|
4698
|
+
return this.snapshotStore.read();
|
|
4699
|
+
};
|
|
4700
|
+
getHistory = (limit) => {
|
|
4701
|
+
const records = this.historyStore.list();
|
|
4702
|
+
const resolvedLimit = this.resolveLimit(limit);
|
|
4703
|
+
return records.slice(-resolvedLimit).reverse();
|
|
4704
|
+
};
|
|
4705
|
+
getStats = () => {
|
|
4706
|
+
const records = this.historyStore.list();
|
|
4707
|
+
const sources = /* @__PURE__ */ new Map();
|
|
4708
|
+
const models = /* @__PURE__ */ new Map();
|
|
4709
|
+
let totalPromptTokens = 0;
|
|
4710
|
+
let totalCompletionTokens = 0;
|
|
4711
|
+
let totalTokens = 0;
|
|
4712
|
+
let totalCachedTokens = 0;
|
|
4713
|
+
let cacheHitRecords = 0;
|
|
4714
|
+
for (const record of records) {
|
|
4715
|
+
totalPromptTokens += record.summary.promptTokens;
|
|
4716
|
+
totalCompletionTokens += record.summary.completionTokens;
|
|
4717
|
+
totalTokens += record.summary.totalTokens;
|
|
4718
|
+
totalCachedTokens += record.summary.cachedTokens;
|
|
4719
|
+
if (record.summary.cacheHit) cacheHitRecords += 1;
|
|
4720
|
+
this.bumpCounter(sources, record.source);
|
|
4721
|
+
this.bumpCounter(models, record.model ?? "unknown");
|
|
4722
|
+
}
|
|
4723
|
+
return {
|
|
4724
|
+
totalRecords: records.length,
|
|
4725
|
+
oldestObservedAt: records[0]?.observedAt ?? null,
|
|
4726
|
+
latestObservedAt: records.at(-1)?.observedAt ?? null,
|
|
4727
|
+
totalPromptTokens,
|
|
4728
|
+
totalCompletionTokens,
|
|
4729
|
+
totalTokens,
|
|
4730
|
+
totalCachedTokens,
|
|
4731
|
+
cacheHitRecords,
|
|
4732
|
+
cacheHitRate: records.length > 0 ? cacheHitRecords / records.length : 0,
|
|
4733
|
+
sources: this.toSortedCounts(sources),
|
|
4734
|
+
models: this.toSortedCounts(models)
|
|
4735
|
+
};
|
|
4736
|
+
};
|
|
4737
|
+
get snapshotStore() {
|
|
4738
|
+
return this.deps.snapshotStore ?? llmUsageSnapshotStore;
|
|
4739
|
+
}
|
|
4740
|
+
get historyStore() {
|
|
4741
|
+
return this.deps.historyStore ?? llmUsageHistoryStore;
|
|
4742
|
+
}
|
|
4743
|
+
resolveLimit = (value) => {
|
|
4744
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
|
|
4745
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
4746
|
+
const parsed = Number(value);
|
|
4747
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
4748
|
+
}
|
|
4749
|
+
return 10;
|
|
4750
|
+
};
|
|
4751
|
+
bumpCounter = (map, value) => {
|
|
4752
|
+
map.set(value, (map.get(value) ?? 0) + 1);
|
|
4753
|
+
};
|
|
4754
|
+
toSortedCounts = (map) => {
|
|
4755
|
+
return [...map.entries()].map(([value, count]) => ({
|
|
4756
|
+
value,
|
|
4757
|
+
count
|
|
4758
|
+
})).sort((left, right) => {
|
|
4759
|
+
if (right.count !== left.count) return right.count - left.count;
|
|
4760
|
+
return left.value.localeCompare(right.value);
|
|
4761
|
+
});
|
|
4762
|
+
};
|
|
4763
|
+
};
|
|
4764
|
+
const llmUsageQueryService = new LlmUsageQueryService();
|
|
4765
|
+
//#endregion
|
|
4766
|
+
//#region src/cli/commands/shared/llm-usage.commands.ts
|
|
4767
|
+
var LlmUsageCommands = class {
|
|
4768
|
+
constructor(deps = {}) {
|
|
4769
|
+
this.deps = deps;
|
|
4770
|
+
}
|
|
4771
|
+
show = async (opts = {}) => {
|
|
4772
|
+
if (opts.history && opts.stats) {
|
|
4773
|
+
console.error("Choose only one usage mode: `--history` or `--stats`.");
|
|
4774
|
+
process.exitCode = 1;
|
|
4775
|
+
return;
|
|
4776
|
+
}
|
|
4777
|
+
if (opts.history) {
|
|
4778
|
+
this.showHistory(opts);
|
|
4779
|
+
return;
|
|
4780
|
+
}
|
|
4781
|
+
if (opts.stats) {
|
|
4782
|
+
this.showStats(opts);
|
|
4783
|
+
return;
|
|
4784
|
+
}
|
|
4785
|
+
this.showSnapshot(opts);
|
|
4786
|
+
};
|
|
4787
|
+
get queryService() {
|
|
4788
|
+
return this.deps.queryService ?? llmUsageQueryService;
|
|
4789
|
+
}
|
|
4790
|
+
showSnapshot = (opts) => {
|
|
4791
|
+
const snapshot = this.queryService.getSnapshot();
|
|
4792
|
+
if (opts.json) {
|
|
4793
|
+
console.log(JSON.stringify({
|
|
4794
|
+
ok: Boolean(snapshot),
|
|
4795
|
+
mode: "snapshot",
|
|
4796
|
+
path: this.queryService.snapshotPath,
|
|
4797
|
+
snapshot
|
|
4798
|
+
}, null, 2));
|
|
4799
|
+
process.exitCode = 0;
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4802
|
+
if (!snapshot) {
|
|
4803
|
+
console.log([
|
|
4804
|
+
"No LLM usage snapshot recorded yet.",
|
|
4805
|
+
`Snapshot path: ${this.queryService.snapshotPath}`,
|
|
4806
|
+
"Run `nextclaw agent -m \"ping\"` or use the local UI once, then retry `nextclaw usage`."
|
|
4807
|
+
].join("\n"));
|
|
4808
|
+
process.exitCode = 0;
|
|
4809
|
+
return;
|
|
4810
|
+
}
|
|
4811
|
+
console.log(this.renderSnapshot(snapshot));
|
|
4812
|
+
process.exitCode = 0;
|
|
4813
|
+
};
|
|
4814
|
+
showHistory = (opts) => {
|
|
4815
|
+
const records = this.queryService.getHistory(opts.limit);
|
|
4816
|
+
if (opts.json) {
|
|
4817
|
+
console.log(JSON.stringify({
|
|
4818
|
+
ok: records.length > 0,
|
|
4819
|
+
mode: "history",
|
|
4820
|
+
path: this.queryService.historyPath,
|
|
4821
|
+
limit: this.resolveLimit(opts.limit),
|
|
4822
|
+
records
|
|
4823
|
+
}, null, 2));
|
|
4824
|
+
process.exitCode = 0;
|
|
4825
|
+
return;
|
|
4826
|
+
}
|
|
4827
|
+
if (records.length === 0) {
|
|
4828
|
+
console.log([
|
|
4829
|
+
"No LLM usage history recorded yet.",
|
|
4830
|
+
`History path: ${this.queryService.historyPath}`,
|
|
4831
|
+
"Run `nextclaw agent -m \"ping\"` or use the local UI once, then retry `nextclaw usage --history`."
|
|
4832
|
+
].join("\n"));
|
|
4833
|
+
process.exitCode = 0;
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
console.log(this.renderHistory(records));
|
|
4837
|
+
process.exitCode = 0;
|
|
4838
|
+
};
|
|
4839
|
+
showStats = (opts) => {
|
|
4840
|
+
const stats = this.queryService.getStats();
|
|
4841
|
+
if (opts.json) {
|
|
4842
|
+
console.log(JSON.stringify({
|
|
4843
|
+
ok: stats.totalRecords > 0,
|
|
4844
|
+
mode: "stats",
|
|
4845
|
+
path: this.queryService.historyPath,
|
|
4846
|
+
stats
|
|
4847
|
+
}, null, 2));
|
|
4848
|
+
process.exitCode = 0;
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
if (stats.totalRecords === 0) {
|
|
4852
|
+
console.log([
|
|
4853
|
+
"No LLM usage history recorded yet.",
|
|
4854
|
+
`History path: ${this.queryService.historyPath}`,
|
|
4855
|
+
"Run `nextclaw agent -m \"ping\"` or use the local UI once, then retry `nextclaw usage --stats`."
|
|
4856
|
+
].join("\n"));
|
|
4857
|
+
process.exitCode = 0;
|
|
4858
|
+
return;
|
|
4859
|
+
}
|
|
4860
|
+
console.log(this.renderStats(stats));
|
|
4861
|
+
process.exitCode = 0;
|
|
4862
|
+
};
|
|
4863
|
+
renderSnapshot = (snapshot) => {
|
|
4864
|
+
const lines = [
|
|
4865
|
+
"Latest LLM usage snapshot",
|
|
4866
|
+
`Observed at: ${snapshot.observedAt}`,
|
|
4867
|
+
`Source: ${snapshot.source}`,
|
|
4868
|
+
`Model: ${snapshot.model ?? "unknown"}`,
|
|
4869
|
+
`Prompt tokens: ${snapshot.summary.promptTokens}`,
|
|
4870
|
+
`Completion tokens: ${snapshot.summary.completionTokens}`,
|
|
4871
|
+
`Total tokens: ${snapshot.summary.totalTokens}`,
|
|
4872
|
+
`Cached tokens: ${snapshot.summary.cachedTokens}`,
|
|
4873
|
+
`Cache hit: ${snapshot.summary.cacheHit ? "yes" : "no"}`,
|
|
4874
|
+
`Snapshot path: ${this.queryService.snapshotPath}`
|
|
4875
|
+
];
|
|
4876
|
+
if (snapshot.summary.cacheMetricKeys.length > 0) lines.push(`Cache metric keys: ${snapshot.summary.cacheMetricKeys.join(", ")}`);
|
|
4877
|
+
if (Object.keys(snapshot.usage).length > 0) lines.push("", "Raw usage:", JSON.stringify(snapshot.usage, null, 2));
|
|
4878
|
+
return lines.join("\n");
|
|
4879
|
+
};
|
|
4880
|
+
renderHistory = (records) => {
|
|
4881
|
+
const lines = [
|
|
4882
|
+
"Recent LLM usage history",
|
|
4883
|
+
`History path: ${this.queryService.historyPath}`,
|
|
4884
|
+
`Showing: ${records.length} record(s)`,
|
|
4885
|
+
""
|
|
4886
|
+
];
|
|
4887
|
+
for (const [index, record] of records.entries()) lines.push(`${index + 1}. ${record.observedAt} | source=${record.source} | model=${record.model ?? "unknown"} | total=${record.summary.totalTokens} | cached=${record.summary.cachedTokens} | cache-hit=${record.summary.cacheHit ? "yes" : "no"}`);
|
|
4888
|
+
return lines.join("\n");
|
|
4889
|
+
};
|
|
4890
|
+
renderStats = (stats) => {
|
|
4891
|
+
const lines = [
|
|
4892
|
+
"LLM usage history stats",
|
|
4893
|
+
`History path: ${this.queryService.historyPath}`,
|
|
4894
|
+
`Records: ${stats.totalRecords}`,
|
|
4895
|
+
`Oldest observed at: ${stats.oldestObservedAt ?? "n/a"}`,
|
|
4896
|
+
`Latest observed at: ${stats.latestObservedAt ?? "n/a"}`,
|
|
4897
|
+
`Prompt tokens: ${stats.totalPromptTokens}`,
|
|
4898
|
+
`Completion tokens: ${stats.totalCompletionTokens}`,
|
|
4899
|
+
`Total tokens: ${stats.totalTokens}`,
|
|
4900
|
+
`Cached tokens: ${stats.totalCachedTokens}`,
|
|
4901
|
+
`Cache hits: ${stats.cacheHitRecords}/${stats.totalRecords} (${this.toPercent(stats.cacheHitRate)})`
|
|
4902
|
+
];
|
|
4903
|
+
if (stats.sources.length > 0) lines.push(`Sources: ${stats.sources.map((item) => `${item.value}=${item.count}`).join(", ")}`);
|
|
4904
|
+
if (stats.models.length > 0) lines.push(`Models: ${stats.models.map((item) => `${item.value}=${item.count}`).join(", ")}`);
|
|
4905
|
+
return lines.join("\n");
|
|
4906
|
+
};
|
|
4907
|
+
resolveLimit = (value) => {
|
|
4908
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
|
|
4909
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
4910
|
+
const parsed = Number(value);
|
|
4911
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
4912
|
+
}
|
|
4913
|
+
return 10;
|
|
4914
|
+
};
|
|
4915
|
+
toPercent = (value) => {
|
|
4916
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
4917
|
+
};
|
|
4918
|
+
};
|
|
4919
|
+
//#endregion
|
|
4626
4920
|
//#region src/cli/restart-coordinator.ts
|
|
4627
4921
|
var RestartCoordinator = class {
|
|
4628
4922
|
restartingService = false;
|
|
@@ -4851,7 +5145,7 @@ function parseSkillFrontmatter(raw) {
|
|
|
4851
5145
|
const message = error instanceof Error ? error.message : String(error);
|
|
4852
5146
|
throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
|
|
4853
5147
|
}
|
|
4854
|
-
if (!isRecord$
|
|
5148
|
+
if (!isRecord$5(parsed)) return {};
|
|
4855
5149
|
const summaryI18n = readLocalizedTextMapField(parsed, [["summaryi18n"], ["summary_i18n"]]);
|
|
4856
5150
|
const descriptionI18n = readLocalizedTextMapField(parsed, [["descriptioni18n"], ["description_i18n"]]);
|
|
4857
5151
|
const summaryZh = readFrontmatterStringField(parsed, [["summaryzh"], ["summary_zh"]]);
|
|
@@ -4882,7 +5176,7 @@ function readMarketplaceMetadataFile(skillDir, explicitMetaFile) {
|
|
|
4882
5176
|
const message = error instanceof Error ? error.message : String(error);
|
|
4883
5177
|
throw new Error(`Invalid marketplace metadata file: ${metadataPath} (${message})`);
|
|
4884
5178
|
}
|
|
4885
|
-
if (!isRecord$
|
|
5179
|
+
if (!isRecord$5(parsed)) throw new Error(`Invalid marketplace metadata file: ${metadataPath} (root must be an object)`);
|
|
4886
5180
|
return {
|
|
4887
5181
|
slug: readMetadataString(parsed, "slug"),
|
|
4888
5182
|
name: readMetadataString(parsed, "name"),
|
|
@@ -4930,7 +5224,7 @@ function readMetadataStringArray(record, fieldName) {
|
|
|
4930
5224
|
function readMetadataLocalizedTextMap(record, fieldName) {
|
|
4931
5225
|
const value = record[fieldName];
|
|
4932
5226
|
if (value == null) return;
|
|
4933
|
-
if (!isRecord$
|
|
5227
|
+
if (!isRecord$5(value)) throw new Error(`Invalid marketplace metadata field: ${fieldName} must be an object`);
|
|
4934
5228
|
const localized = {};
|
|
4935
5229
|
for (const [locale, text] of Object.entries(value)) {
|
|
4936
5230
|
if (typeof text !== "string") throw new Error(`Invalid marketplace metadata field: ${fieldName}.${locale} must be a string`);
|
|
@@ -4951,7 +5245,7 @@ function readFrontmatterStringField(record, keyPaths) {
|
|
|
4951
5245
|
function readLocalizedTextMapField(record, keyPaths) {
|
|
4952
5246
|
for (const keyPath of keyPaths) {
|
|
4953
5247
|
const value = readNestedFrontmatterValue(record, keyPath);
|
|
4954
|
-
if (!isRecord$
|
|
5248
|
+
if (!isRecord$5(value)) continue;
|
|
4955
5249
|
const normalized = {};
|
|
4956
5250
|
for (const [locale, text] of Object.entries(value)) {
|
|
4957
5251
|
if (typeof text !== "string") continue;
|
|
@@ -4975,7 +5269,7 @@ function readFrontmatterTags(record) {
|
|
|
4975
5269
|
function readNestedFrontmatterValue(record, keyPath) {
|
|
4976
5270
|
let current = record;
|
|
4977
5271
|
for (const rawKey of keyPath) {
|
|
4978
|
-
if (!isRecord$
|
|
5272
|
+
if (!isRecord$5(current)) return;
|
|
4979
5273
|
const normalizedKey = normalizeFrontmatterKey(rawKey);
|
|
4980
5274
|
const matchingKey = Object.keys(current).find((candidate) => normalizeFrontmatterKey(candidate) === normalizedKey);
|
|
4981
5275
|
if (!matchingKey) return;
|
|
@@ -4989,7 +5283,7 @@ function normalizeFrontmatterKey(raw) {
|
|
|
4989
5283
|
function normalizeLocaleTag(raw) {
|
|
4990
5284
|
return raw.trim().toLowerCase();
|
|
4991
5285
|
}
|
|
4992
|
-
function isRecord$
|
|
5286
|
+
function isRecord$5(value) {
|
|
4993
5287
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4994
5288
|
}
|
|
4995
5289
|
//#endregion
|
|
@@ -5130,7 +5424,10 @@ function resolveUiConfig(config, overrides) {
|
|
|
5130
5424
|
};
|
|
5131
5425
|
}
|
|
5132
5426
|
function resolveUiApiBase(host, port) {
|
|
5133
|
-
return
|
|
5427
|
+
return resolveLocalUiBaseUrl({
|
|
5428
|
+
host,
|
|
5429
|
+
port
|
|
5430
|
+
});
|
|
5134
5431
|
}
|
|
5135
5432
|
function isLoopbackHost(host) {
|
|
5136
5433
|
const normalized = host.trim().toLowerCase();
|
|
@@ -5161,37 +5458,8 @@ async function resolvePublicIp(timeoutMs = 1500) {
|
|
|
5161
5458
|
}
|
|
5162
5459
|
return null;
|
|
5163
5460
|
}
|
|
5164
|
-
function readServiceState() {
|
|
5165
|
-
const path = resolveServiceStatePath();
|
|
5166
|
-
if (!existsSync(path)) return null;
|
|
5167
|
-
try {
|
|
5168
|
-
const raw = readFileSync(path, "utf-8");
|
|
5169
|
-
return JSON.parse(raw);
|
|
5170
|
-
} catch {
|
|
5171
|
-
return null;
|
|
5172
|
-
}
|
|
5173
|
-
}
|
|
5174
|
-
function writeServiceState(state) {
|
|
5175
|
-
const path = resolveServiceStatePath();
|
|
5176
|
-
mkdirSync(resolve(path, ".."), { recursive: true });
|
|
5177
|
-
writeFileSync(path, JSON.stringify(state, null, 2));
|
|
5178
|
-
}
|
|
5179
|
-
function updateServiceState(updater) {
|
|
5180
|
-
const current = readServiceState();
|
|
5181
|
-
if (!current) return null;
|
|
5182
|
-
const next = updater(current);
|
|
5183
|
-
writeServiceState(next);
|
|
5184
|
-
return next;
|
|
5185
|
-
}
|
|
5186
|
-
function clearServiceState() {
|
|
5187
|
-
const path = resolveServiceStatePath();
|
|
5188
|
-
if (existsSync(path)) rmSync(path, { force: true });
|
|
5189
|
-
}
|
|
5190
|
-
function resolveServiceStatePath() {
|
|
5191
|
-
return resolve(getDataDir(), "run", "service.json");
|
|
5192
|
-
}
|
|
5193
5461
|
function resolveServiceLogPath() {
|
|
5194
|
-
return resolve(
|
|
5462
|
+
return resolve(getLogsPath(), "service.log");
|
|
5195
5463
|
}
|
|
5196
5464
|
function isProcessRunning(pid) {
|
|
5197
5465
|
try {
|
|
@@ -5584,9 +5852,9 @@ async function fetchMarketplaceSkillFiles(apiBase, slug) {
|
|
|
5584
5852
|
const message = payload.error?.message || `marketplace skill file fetch failed: ${response.status}`;
|
|
5585
5853
|
throw new Error(message);
|
|
5586
5854
|
}
|
|
5587
|
-
if (!isRecord$
|
|
5855
|
+
if (!isRecord$4(payload.data) || !Array.isArray(payload.data.files)) throw new Error("Invalid marketplace skill file manifest response");
|
|
5588
5856
|
return { files: payload.data.files.map((entry, index) => {
|
|
5589
|
-
if (!isRecord$
|
|
5857
|
+
if (!isRecord$4(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) throw new Error(`Invalid marketplace skill file manifest at index ${index}`);
|
|
5590
5858
|
const normalized = { path: entry.path.trim() };
|
|
5591
5859
|
if (typeof entry.downloadPath === "string" && entry.downloadPath.trim().length > 0) normalized.downloadPath = entry.downloadPath.trim();
|
|
5592
5860
|
if (typeof entry.contentBase64 === "string" && entry.contentBase64.trim().length > 0) normalized.contentBase64 = entry.contentBase64.trim();
|
|
@@ -5613,7 +5881,7 @@ async function readMarketplaceEnvelope(response) {
|
|
|
5613
5881
|
} catch {
|
|
5614
5882
|
throw new Error(`Invalid marketplace response: ${response.status}`);
|
|
5615
5883
|
}
|
|
5616
|
-
if (!isRecord$
|
|
5884
|
+
if (!isRecord$4(payload) || typeof payload.ok !== "boolean") throw new Error(`Invalid marketplace response shape: ${response.status}`);
|
|
5617
5885
|
return payload;
|
|
5618
5886
|
}
|
|
5619
5887
|
function resolveSkillFileDownloadUrl(apiBase, slug, file) {
|
|
@@ -5628,7 +5896,7 @@ function extractMarketplaceErrorMessage(raw, fallbackStatus) {
|
|
|
5628
5896
|
return raw || `Request failed (${fallbackStatus})`;
|
|
5629
5897
|
}
|
|
5630
5898
|
}
|
|
5631
|
-
function isRecord$
|
|
5899
|
+
function isRecord$4(value) {
|
|
5632
5900
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5633
5901
|
}
|
|
5634
5902
|
//#endregion
|
|
@@ -5936,6 +6204,41 @@ function decodeMarketplaceFileContent(path, contentBase64) {
|
|
|
5936
6204
|
return Buffer.from(normalized, "base64");
|
|
5937
6205
|
}
|
|
5938
6206
|
//#endregion
|
|
6207
|
+
//#region src/cli/update/self-update-report.utils.ts
|
|
6208
|
+
function reportSelfUpdateResult(params) {
|
|
6209
|
+
const { appName, currentVersion, result, readInstalledVersion } = params;
|
|
6210
|
+
const printSteps = () => {
|
|
6211
|
+
for (const step of result.steps) {
|
|
6212
|
+
console.log(`- ${step.cmd} ${step.args.join(" ")} (code ${step.code ?? "?"})`);
|
|
6213
|
+
if (step.stderr) console.log(` stderr: ${step.stderr}`);
|
|
6214
|
+
if (step.stdout) console.log(` stdout: ${step.stdout}`);
|
|
6215
|
+
}
|
|
6216
|
+
};
|
|
6217
|
+
if (!result.ok) {
|
|
6218
|
+
console.error(`Update failed: ${result.error ?? "unknown error"}`);
|
|
6219
|
+
if (result.steps.length > 0) printSteps();
|
|
6220
|
+
return {
|
|
6221
|
+
ok: false,
|
|
6222
|
+
shouldSuggestRestart: false
|
|
6223
|
+
};
|
|
6224
|
+
}
|
|
6225
|
+
if (result.strategy === "noop") {
|
|
6226
|
+
console.log(`✓ ${appName} is already up to date (${result.latestVersion ?? currentVersion})`);
|
|
6227
|
+
return {
|
|
6228
|
+
ok: true,
|
|
6229
|
+
shouldSuggestRestart: false
|
|
6230
|
+
};
|
|
6231
|
+
}
|
|
6232
|
+
const versionAfter = result.latestVersion ?? readInstalledVersion();
|
|
6233
|
+
console.log(`✓ Update complete (${result.strategy})`);
|
|
6234
|
+
if (versionAfter === currentVersion) console.log(`Version unchanged: ${currentVersion}`);
|
|
6235
|
+
else console.log(`Version updated: ${currentVersion} -> ${versionAfter}`);
|
|
6236
|
+
return {
|
|
6237
|
+
ok: true,
|
|
6238
|
+
shouldSuggestRestart: true
|
|
6239
|
+
};
|
|
6240
|
+
}
|
|
6241
|
+
//#endregion
|
|
5939
6242
|
//#region src/cli/update/runner.ts
|
|
5940
6243
|
const DEFAULT_TIMEOUT_MS = 20 * 6e4;
|
|
5941
6244
|
function runSelfUpdate(options = {}) {
|
|
@@ -5943,6 +6246,7 @@ function runSelfUpdate(options = {}) {
|
|
|
5943
6246
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
5944
6247
|
const updateCommand = options.updateCommand ?? process.env.NEXTCLAW_UPDATE_COMMAND?.trim();
|
|
5945
6248
|
const packageName = options.packageName ?? "nextclaw";
|
|
6249
|
+
const currentVersion = options.currentVersion?.trim() || null;
|
|
5946
6250
|
const resolveShellCommand = (command) => {
|
|
5947
6251
|
if (process.platform === "win32") return {
|
|
5948
6252
|
cmd: process.env.ComSpec || "cmd.exe",
|
|
@@ -5966,19 +6270,33 @@ function runSelfUpdate(options = {}) {
|
|
|
5966
6270
|
timeout: timeoutMs,
|
|
5967
6271
|
stdio: "pipe"
|
|
5968
6272
|
});
|
|
6273
|
+
const stdout = (result.stdout ?? "").toString().slice(0, 4e3);
|
|
6274
|
+
const stderr = (result.stderr ?? "").toString().slice(0, 4e3);
|
|
5969
6275
|
steps.push({
|
|
5970
6276
|
cmd,
|
|
5971
6277
|
args,
|
|
5972
6278
|
cwd,
|
|
5973
6279
|
code: result.status,
|
|
5974
|
-
stdout
|
|
5975
|
-
stderr
|
|
6280
|
+
stdout,
|
|
6281
|
+
stderr
|
|
5976
6282
|
});
|
|
5977
6283
|
return {
|
|
5978
6284
|
ok: result.status === 0,
|
|
5979
|
-
code: result.status
|
|
6285
|
+
code: result.status,
|
|
6286
|
+
stdout,
|
|
6287
|
+
stderr
|
|
5980
6288
|
};
|
|
5981
6289
|
};
|
|
6290
|
+
const parseLatestVersion = (raw) => {
|
|
6291
|
+
const trimmed = raw.trim();
|
|
6292
|
+
if (!trimmed) return null;
|
|
6293
|
+
try {
|
|
6294
|
+
const parsed = JSON.parse(trimmed);
|
|
6295
|
+
return typeof parsed === "string" && parsed.trim() ? parsed.trim() : null;
|
|
6296
|
+
} catch {
|
|
6297
|
+
return trimmed;
|
|
6298
|
+
}
|
|
6299
|
+
};
|
|
5982
6300
|
if (updateCommand) {
|
|
5983
6301
|
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
|
5984
6302
|
const shellCommand = resolveShellCommand(updateCommand);
|
|
@@ -5996,19 +6314,35 @@ function runSelfUpdate(options = {}) {
|
|
|
5996
6314
|
}
|
|
5997
6315
|
const npmExecutable = findExecutableOnPath("npm");
|
|
5998
6316
|
if (npmExecutable) {
|
|
6317
|
+
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
|
6318
|
+
const latestVersionStep = runStep(npmExecutable, [
|
|
6319
|
+
"view",
|
|
6320
|
+
packageName,
|
|
6321
|
+
"version",
|
|
6322
|
+
"--json"
|
|
6323
|
+
], cwd);
|
|
6324
|
+
const latestVersion = latestVersionStep.ok ? parseLatestVersion(latestVersionStep.stdout) : null;
|
|
6325
|
+
if (latestVersion && currentVersion && latestVersion === currentVersion) return {
|
|
6326
|
+
ok: true,
|
|
6327
|
+
strategy: "noop",
|
|
6328
|
+
latestVersion,
|
|
6329
|
+
steps
|
|
6330
|
+
};
|
|
5999
6331
|
if (!runStep(npmExecutable, [
|
|
6000
6332
|
"i",
|
|
6001
6333
|
"-g",
|
|
6002
6334
|
packageName
|
|
6003
|
-
],
|
|
6335
|
+
], cwd).ok) return {
|
|
6004
6336
|
ok: false,
|
|
6005
6337
|
error: `npm install -g ${packageName} failed`,
|
|
6006
6338
|
strategy: "npm",
|
|
6339
|
+
latestVersion: latestVersion ?? void 0,
|
|
6007
6340
|
steps
|
|
6008
6341
|
};
|
|
6009
6342
|
return {
|
|
6010
6343
|
ok: true,
|
|
6011
6344
|
strategy: "npm",
|
|
6345
|
+
latestVersion: latestVersion ?? void 0,
|
|
6012
6346
|
steps
|
|
6013
6347
|
};
|
|
6014
6348
|
}
|
|
@@ -6020,6 +6354,40 @@ function runSelfUpdate(options = {}) {
|
|
|
6020
6354
|
};
|
|
6021
6355
|
}
|
|
6022
6356
|
//#endregion
|
|
6357
|
+
//#region src/cli/runtime-state/managed-service-state.store.ts
|
|
6358
|
+
var ManagedServiceStateStore = class {
|
|
6359
|
+
get path() {
|
|
6360
|
+
return resolve(getDataDir(), "run", "service.json");
|
|
6361
|
+
}
|
|
6362
|
+
read = () => {
|
|
6363
|
+
if (!existsSync(this.path)) return null;
|
|
6364
|
+
try {
|
|
6365
|
+
const raw = readFileSync(this.path, "utf-8");
|
|
6366
|
+
return JSON.parse(raw);
|
|
6367
|
+
} catch {
|
|
6368
|
+
return null;
|
|
6369
|
+
}
|
|
6370
|
+
};
|
|
6371
|
+
write = (state) => {
|
|
6372
|
+
mkdirSync(resolve(this.path, ".."), { recursive: true });
|
|
6373
|
+
writeFileSync(this.path, JSON.stringify(state, null, 2));
|
|
6374
|
+
};
|
|
6375
|
+
update = (updater) => {
|
|
6376
|
+
const current = this.read();
|
|
6377
|
+
if (!current) return null;
|
|
6378
|
+
const next = updater(current);
|
|
6379
|
+
this.write(next);
|
|
6380
|
+
return next;
|
|
6381
|
+
};
|
|
6382
|
+
clear = () => {
|
|
6383
|
+
if (existsSync(this.path)) rmSync(this.path, { force: true });
|
|
6384
|
+
};
|
|
6385
|
+
clearIfOwnedByProcess = (pid = process.pid) => {
|
|
6386
|
+
if (this.read()?.pid === pid) this.clear();
|
|
6387
|
+
};
|
|
6388
|
+
};
|
|
6389
|
+
const managedServiceStateStore = new ManagedServiceStateStore();
|
|
6390
|
+
//#endregion
|
|
6023
6391
|
//#region src/cli/commands/plugin/plugin-command-utils.ts
|
|
6024
6392
|
const RESERVED_PROVIDER_IDS$1 = builtinProviderIds();
|
|
6025
6393
|
const RESERVED_TOOL_NAMES = [
|
|
@@ -6045,7 +6413,6 @@ function buildReservedPluginLoadOptions() {
|
|
|
6045
6413
|
reservedToolNames: [...RESERVED_TOOL_NAMES],
|
|
6046
6414
|
reservedChannelIds: [],
|
|
6047
6415
|
reservedProviderIds: RESERVED_PROVIDER_IDS$1,
|
|
6048
|
-
reservedEngineKinds: ["native"],
|
|
6049
6416
|
reservedNcpAgentRuntimeKinds: ["native"]
|
|
6050
6417
|
};
|
|
6051
6418
|
}
|
|
@@ -6053,11 +6420,10 @@ function appendPluginCapabilityLines(lines, plugin) {
|
|
|
6053
6420
|
if (plugin.toolNames.length > 0) lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
|
|
6054
6421
|
if (plugin.channelIds.length > 0) lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
|
|
6055
6422
|
if (plugin.providerIds.length > 0) lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
|
|
6056
|
-
if (plugin.engineKinds.length > 0) lines.push(`Engines: ${plugin.engineKinds.join(", ")}`);
|
|
6057
6423
|
if (plugin.ncpAgentRuntimeKinds.length > 0) lines.push(`NCP runtimes: ${plugin.ncpAgentRuntimeKinds.join(", ")}`);
|
|
6058
6424
|
}
|
|
6059
6425
|
//#endregion
|
|
6060
|
-
//#region src/cli/commands/plugin/
|
|
6426
|
+
//#region src/cli/commands/plugin/development-source/first-party-plugin-load-paths.ts
|
|
6061
6427
|
const readJsonFile = (filePath) => {
|
|
6062
6428
|
try {
|
|
6063
6429
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -6074,7 +6440,7 @@ const readString = (value) => {
|
|
|
6074
6440
|
const resolveDevFirstPartyPluginDir = (explicitDir, moduleDir = path.dirname(fileURLToPath(import.meta.url))) => {
|
|
6075
6441
|
const configured = explicitDir?.trim();
|
|
6076
6442
|
if (configured) return configured;
|
|
6077
|
-
const inferred = path.resolve(moduleDir, "
|
|
6443
|
+
const inferred = path.resolve(moduleDir, "../../../../../extensions");
|
|
6078
6444
|
return fs.existsSync(inferred) ? inferred : void 0;
|
|
6079
6445
|
};
|
|
6080
6446
|
const hasOpenClawExtensions = (pkg) => {
|
|
@@ -6083,6 +6449,14 @@ const hasOpenClawExtensions = (pkg) => {
|
|
|
6083
6449
|
const extensions = openclaw.extensions;
|
|
6084
6450
|
return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
6085
6451
|
};
|
|
6452
|
+
const hasOpenClawDevelopmentExtensions = (pkg) => {
|
|
6453
|
+
const openclaw = pkg.openclaw;
|
|
6454
|
+
if (!openclaw || typeof openclaw !== "object" || Array.isArray(openclaw)) return false;
|
|
6455
|
+
const development = openclaw.development;
|
|
6456
|
+
if (!development || typeof development !== "object" || Array.isArray(development)) return false;
|
|
6457
|
+
const extensions = development.extensions;
|
|
6458
|
+
return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
6459
|
+
};
|
|
6086
6460
|
const normalizePackageSpec = (spec) => {
|
|
6087
6461
|
const trimmed = spec.trim();
|
|
6088
6462
|
if (!trimmed) return;
|
|
@@ -6108,11 +6482,38 @@ const readWorkspacePluginPackages = (workspaceExtensionsDir) => {
|
|
|
6108
6482
|
if (!packageName?.startsWith("@nextclaw/")) continue;
|
|
6109
6483
|
packages.push({
|
|
6110
6484
|
packageName,
|
|
6111
|
-
dir: packageDir
|
|
6485
|
+
dir: packageDir,
|
|
6486
|
+
supportsDevelopmentSource: hasOpenClawDevelopmentExtensions(pkg)
|
|
6112
6487
|
});
|
|
6113
6488
|
}
|
|
6114
6489
|
return packages;
|
|
6115
6490
|
};
|
|
6491
|
+
const mergeLoadPaths$1 = (existingLoadPaths, devLoadPaths) => {
|
|
6492
|
+
const mergedLoadPaths = [...devLoadPaths];
|
|
6493
|
+
for (const entry of existingLoadPaths) if (!mergedLoadPaths.includes(entry)) mergedLoadPaths.push(entry);
|
|
6494
|
+
return mergedLoadPaths;
|
|
6495
|
+
};
|
|
6496
|
+
const buildDevelopmentSourceEntryDefaults = (config, workspacePackages) => {
|
|
6497
|
+
const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
|
|
6498
|
+
const nextEntries = { ...config.plugins.entries ?? {} };
|
|
6499
|
+
let didDefaultDevelopmentSource = false;
|
|
6500
|
+
for (const [pluginId, installRecord] of Object.entries(config.plugins.installs ?? {})) {
|
|
6501
|
+
const packageName = normalizePackageSpec(installRecord.spec ?? "");
|
|
6502
|
+
if (!packageName) continue;
|
|
6503
|
+
if (!packageByName.get(packageName)?.supportsDevelopmentSource) continue;
|
|
6504
|
+
const existingEntry = nextEntries[pluginId];
|
|
6505
|
+
if (existingEntry?.source) continue;
|
|
6506
|
+
nextEntries[pluginId] = {
|
|
6507
|
+
...existingEntry,
|
|
6508
|
+
source: "development"
|
|
6509
|
+
};
|
|
6510
|
+
didDefaultDevelopmentSource = true;
|
|
6511
|
+
}
|
|
6512
|
+
return {
|
|
6513
|
+
didDefaultDevelopmentSource,
|
|
6514
|
+
nextEntries
|
|
6515
|
+
};
|
|
6516
|
+
};
|
|
6116
6517
|
const resolveDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
6117
6518
|
const rootDir = resolveDevFirstPartyPluginDir(workspaceExtensionsDir);
|
|
6118
6519
|
if (!rootDir) return [];
|
|
@@ -6147,15 +6548,19 @@ const resolveDevFirstPartyPluginInstallRoots = (config, workspaceExtensionsDir)
|
|
|
6147
6548
|
return installRoots;
|
|
6148
6549
|
};
|
|
6149
6550
|
const applyDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
6150
|
-
const
|
|
6551
|
+
const rootDir = resolveDevFirstPartyPluginDir(workspaceExtensionsDir);
|
|
6552
|
+
if (!rootDir) return config;
|
|
6553
|
+
const workspacePackages = readWorkspacePluginPackages(rootDir);
|
|
6554
|
+
if (workspacePackages.length === 0) return config;
|
|
6555
|
+
const devLoadPaths = resolveDevFirstPartyPluginLoadPaths(config, rootDir);
|
|
6151
6556
|
if (devLoadPaths.length === 0) return config;
|
|
6152
|
-
const
|
|
6153
|
-
const
|
|
6154
|
-
for (const entry of existingLoadPaths) if (!mergedLoadPaths.includes(entry)) mergedLoadPaths.push(entry);
|
|
6557
|
+
const mergedLoadPaths = mergeLoadPaths$1(Array.isArray(config.plugins.load?.paths) ? config.plugins.load.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [], devLoadPaths);
|
|
6558
|
+
const { didDefaultDevelopmentSource, nextEntries } = buildDevelopmentSourceEntryDefaults(config, workspacePackages);
|
|
6155
6559
|
return {
|
|
6156
6560
|
...config,
|
|
6157
6561
|
plugins: {
|
|
6158
6562
|
...config.plugins,
|
|
6563
|
+
entries: didDefaultDevelopmentSource ? nextEntries : config.plugins.entries,
|
|
6159
6564
|
load: {
|
|
6160
6565
|
...config.plugins.load,
|
|
6161
6566
|
paths: mergedLoadPaths
|
|
@@ -6164,6 +6569,112 @@ const applyDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
|
6164
6569
|
};
|
|
6165
6570
|
};
|
|
6166
6571
|
//#endregion
|
|
6572
|
+
//#region src/cli/commands/plugin/development-source/dev-plugin-overrides.utils.ts
|
|
6573
|
+
const DEV_PLUGIN_OVERRIDES_ENV = "NEXTCLAW_DEV_PLUGIN_OVERRIDES";
|
|
6574
|
+
function isRecord$3(value) {
|
|
6575
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6576
|
+
}
|
|
6577
|
+
function readOptionalString$6(value) {
|
|
6578
|
+
if (typeof value !== "string") return;
|
|
6579
|
+
return value.trim() || void 0;
|
|
6580
|
+
}
|
|
6581
|
+
function readPackageManifest(pluginPath) {
|
|
6582
|
+
const packageJsonPath = path.join(pluginPath, "package.json");
|
|
6583
|
+
if (!fs.existsSync(packageJsonPath)) return null;
|
|
6584
|
+
try {
|
|
6585
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
6586
|
+
} catch {
|
|
6587
|
+
return null;
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
function assertOverridePluginReadable(override) {
|
|
6591
|
+
if (!fs.existsSync(override.pluginPath)) throw new Error(`[dev-plugin-override] plugin path does not exist for "${override.pluginId}": ${override.pluginPath}`);
|
|
6592
|
+
const packageManifest = readPackageManifest(override.pluginPath);
|
|
6593
|
+
if (!packageManifest) throw new Error(`[dev-plugin-override] package.json is missing or invalid for "${override.pluginId}": ${override.pluginPath}`);
|
|
6594
|
+
const pluginManifest = loadPluginManifest(override.pluginPath);
|
|
6595
|
+
if (!pluginManifest.ok) throw new Error(`[dev-plugin-override] ${pluginManifest.error} for "${override.pluginId}": ${override.pluginPath}`);
|
|
6596
|
+
if (pluginManifest.manifest.id !== override.pluginId) throw new Error(`[dev-plugin-override] plugin id mismatch: expected "${override.pluginId}" but found "${pluginManifest.manifest.id}" at ${override.pluginPath}`);
|
|
6597
|
+
if (getPackageManifestExtensions(packageManifest, override.source).length === 0) {
|
|
6598
|
+
const missingEntry = override.source === "development" ? "openclaw.development.extensions" : "openclaw.extensions";
|
|
6599
|
+
throw new Error(`[dev-plugin-override] ${missingEntry} is missing for "${override.pluginId}" at ${override.pluginPath}`);
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
function readOverrideRecord(value, index) {
|
|
6603
|
+
if (!isRecord$3(value)) throw new Error(`[dev-plugin-override] override[${index}] must be an object`);
|
|
6604
|
+
const pluginId = readOptionalString$6(value.pluginId);
|
|
6605
|
+
const pluginPath = readOptionalString$6(value.pluginPath);
|
|
6606
|
+
const source = value.source === "development" ? "development" : "production";
|
|
6607
|
+
if (!pluginId || !pluginPath) throw new Error(`[dev-plugin-override] override[${index}] requires pluginId and pluginPath`);
|
|
6608
|
+
const normalized = {
|
|
6609
|
+
pluginId,
|
|
6610
|
+
pluginPath: path.resolve(pluginPath),
|
|
6611
|
+
source
|
|
6612
|
+
};
|
|
6613
|
+
assertOverridePluginReadable(normalized);
|
|
6614
|
+
return normalized;
|
|
6615
|
+
}
|
|
6616
|
+
function resolveDevPluginOverrides(rawEnv = process.env[DEV_PLUGIN_OVERRIDES_ENV]) {
|
|
6617
|
+
if (typeof rawEnv !== "string" || rawEnv.trim().length === 0) return [];
|
|
6618
|
+
let parsed;
|
|
6619
|
+
try {
|
|
6620
|
+
parsed = JSON.parse(rawEnv);
|
|
6621
|
+
} catch (error) {
|
|
6622
|
+
throw new Error(`[dev-plugin-override] failed to parse ${DEV_PLUGIN_OVERRIDES_ENV}: ${error instanceof Error ? error.message : String(error)}`);
|
|
6623
|
+
}
|
|
6624
|
+
if (!Array.isArray(parsed)) throw new Error(`[dev-plugin-override] ${DEV_PLUGIN_OVERRIDES_ENV} must be a JSON array`);
|
|
6625
|
+
const seenPluginIds = /* @__PURE__ */ new Set();
|
|
6626
|
+
const overrides = parsed.map((entry, index) => readOverrideRecord(entry, index));
|
|
6627
|
+
for (const entry of overrides) {
|
|
6628
|
+
if (seenPluginIds.has(entry.pluginId)) throw new Error(`[dev-plugin-override] duplicate plugin override for "${entry.pluginId}"`);
|
|
6629
|
+
seenPluginIds.add(entry.pluginId);
|
|
6630
|
+
}
|
|
6631
|
+
return overrides;
|
|
6632
|
+
}
|
|
6633
|
+
function mergeLoadPaths(existingLoadPaths, overrideLoadPaths) {
|
|
6634
|
+
const merged = [...overrideLoadPaths];
|
|
6635
|
+
for (const entry of existingLoadPaths) if (!merged.includes(entry)) merged.push(entry);
|
|
6636
|
+
return merged;
|
|
6637
|
+
}
|
|
6638
|
+
function applyExplicitDevPluginOverrides(config, overrides) {
|
|
6639
|
+
if (overrides.length === 0) return config;
|
|
6640
|
+
const nextEntries = { ...config.plugins.entries ?? {} };
|
|
6641
|
+
for (const override of overrides) nextEntries[override.pluginId] = {
|
|
6642
|
+
...nextEntries[override.pluginId] ?? {},
|
|
6643
|
+
source: override.source
|
|
6644
|
+
};
|
|
6645
|
+
const existingLoadPaths = Array.isArray(config.plugins.load?.paths) ? config.plugins.load.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
6646
|
+
return {
|
|
6647
|
+
...config,
|
|
6648
|
+
plugins: {
|
|
6649
|
+
...config.plugins,
|
|
6650
|
+
entries: nextEntries,
|
|
6651
|
+
load: {
|
|
6652
|
+
...config.plugins.load,
|
|
6653
|
+
paths: mergeLoadPaths(existingLoadPaths, overrides.map((entry) => entry.pluginPath))
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
};
|
|
6657
|
+
}
|
|
6658
|
+
function resolveDevPluginOverrideInstallRoots(config, overrides) {
|
|
6659
|
+
const installRoots = [];
|
|
6660
|
+
for (const override of overrides) {
|
|
6661
|
+
const installRecord = config.plugins.installs?.[override.pluginId];
|
|
6662
|
+
const installPath = readOptionalString$6(installRecord?.installPath);
|
|
6663
|
+
if (!installPath || installRoots.includes(installPath)) continue;
|
|
6664
|
+
installRoots.push(installPath);
|
|
6665
|
+
}
|
|
6666
|
+
return installRoots;
|
|
6667
|
+
}
|
|
6668
|
+
function resolveDevPluginLoadingContext(config, workspaceExtensionsDir, rawOverridesEnv = process.env[DEV_PLUGIN_OVERRIDES_ENV]) {
|
|
6669
|
+
const configWithFirstPartyOverrides = applyDevFirstPartyPluginLoadPaths(config, workspaceExtensionsDir);
|
|
6670
|
+
const overrides = resolveDevPluginOverrides(rawOverridesEnv);
|
|
6671
|
+
return {
|
|
6672
|
+
configWithDevPluginOverrides: applyExplicitDevPluginOverrides(configWithFirstPartyOverrides, overrides),
|
|
6673
|
+
excludedRoots: [...resolveDevFirstPartyPluginInstallRoots(config, workspaceExtensionsDir), ...resolveDevPluginOverrideInstallRoots(config, overrides)].filter((entry, index, list) => list.indexOf(entry) === index),
|
|
6674
|
+
overrides
|
|
6675
|
+
};
|
|
6676
|
+
}
|
|
6677
|
+
//#endregion
|
|
6167
6678
|
//#region src/cli/commands/plugin/plugin-mutation-actions.ts
|
|
6168
6679
|
const pluginInstallLogger = {
|
|
6169
6680
|
info: (message) => console.log(message),
|
|
@@ -6335,12 +6846,6 @@ function toExtensionRegistry(pluginRegistry) {
|
|
|
6335
6846
|
channel: channel.channel,
|
|
6336
6847
|
source: channel.source
|
|
6337
6848
|
})),
|
|
6338
|
-
engines: pluginRegistry.engines.map((engine) => ({
|
|
6339
|
-
extensionId: engine.pluginId,
|
|
6340
|
-
kind: engine.kind,
|
|
6341
|
-
factory: engine.factory,
|
|
6342
|
-
source: engine.source
|
|
6343
|
-
})),
|
|
6344
6849
|
ncpAgentRuntimes: pluginRegistry.ncpAgentRuntimes.map((runtime) => ({
|
|
6345
6850
|
pluginId: runtime.pluginId,
|
|
6346
6851
|
kind: runtime.kind,
|
|
@@ -6360,11 +6865,11 @@ function toExtensionRegistry(pluginRegistry) {
|
|
|
6360
6865
|
//#endregion
|
|
6361
6866
|
//#region src/cli/commands/plugins.ts
|
|
6362
6867
|
function loadPluginRegistry(config, workspaceDir) {
|
|
6363
|
-
const
|
|
6868
|
+
const { configWithDevPluginOverrides, excludedRoots } = resolveDevPluginLoadingContext(config, resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR));
|
|
6364
6869
|
return loadOpenClawPlugins({
|
|
6365
|
-
config:
|
|
6870
|
+
config: configWithDevPluginOverrides,
|
|
6366
6871
|
workspaceDir,
|
|
6367
|
-
excludeRoots:
|
|
6872
|
+
excludeRoots: excludedRoots,
|
|
6368
6873
|
...buildReservedPluginLoadOptions(),
|
|
6369
6874
|
logger: {
|
|
6370
6875
|
info: (message) => console.log(message),
|
|
@@ -6894,7 +7399,7 @@ var ConfigCommands = class {
|
|
|
6894
7399
|
};
|
|
6895
7400
|
//#endregion
|
|
6896
7401
|
//#region src/cli/commands/mcp.ts
|
|
6897
|
-
function normalizeOptionalString$
|
|
7402
|
+
function normalizeOptionalString$7(value) {
|
|
6898
7403
|
if (typeof value !== "string") return;
|
|
6899
7404
|
return value.trim() || void 0;
|
|
6900
7405
|
}
|
|
@@ -6917,9 +7422,9 @@ function parseTimeoutMs$1(value) {
|
|
|
6917
7422
|
return Math.trunc(parsed);
|
|
6918
7423
|
}
|
|
6919
7424
|
function buildMcpServerDefinition(command, opts) {
|
|
6920
|
-
const transport = (normalizeOptionalString$
|
|
7425
|
+
const transport = (normalizeOptionalString$7(opts.transport) ?? "stdio").toLowerCase();
|
|
6921
7426
|
const disabled = Boolean(opts.disabled);
|
|
6922
|
-
const explicitAgents = Array.from(new Set((opts.agent ?? []).map((agentId) => normalizeOptionalString$
|
|
7427
|
+
const explicitAgents = Array.from(new Set((opts.agent ?? []).map((agentId) => normalizeOptionalString$7(agentId)).filter((agentId) => Boolean(agentId))));
|
|
6923
7428
|
const allAgents = explicitAgents.length === 0 ? true : Boolean(opts.allAgents);
|
|
6924
7429
|
if (transport === "stdio") {
|
|
6925
7430
|
if (command.length === 0) throw new Error("stdio transport requires a command after --");
|
|
@@ -6929,9 +7434,9 @@ function buildMcpServerDefinition(command, opts) {
|
|
|
6929
7434
|
type: "stdio",
|
|
6930
7435
|
command: command[0],
|
|
6931
7436
|
args: command.slice(1),
|
|
6932
|
-
cwd: normalizeOptionalString$
|
|
7437
|
+
cwd: normalizeOptionalString$7(opts.cwd),
|
|
6933
7438
|
env: parsePairs(opts.env, "env"),
|
|
6934
|
-
stderr: normalizeOptionalString$
|
|
7439
|
+
stderr: normalizeOptionalString$7(opts.stderr) ?? "pipe"
|
|
6935
7440
|
},
|
|
6936
7441
|
scope: {
|
|
6937
7442
|
allAgents,
|
|
@@ -6947,7 +7452,7 @@ function buildMcpServerDefinition(command, opts) {
|
|
|
6947
7452
|
}
|
|
6948
7453
|
};
|
|
6949
7454
|
}
|
|
6950
|
-
const url = normalizeOptionalString$
|
|
7455
|
+
const url = normalizeOptionalString$7(opts.url);
|
|
6951
7456
|
if (!url) throw new Error(`${transport} transport requires --url`);
|
|
6952
7457
|
const timeoutMs = parseTimeoutMs$1(opts.timeoutMs);
|
|
6953
7458
|
const shared = {
|
|
@@ -7094,7 +7599,7 @@ function normalizeSecretSource(value) {
|
|
|
7094
7599
|
const normalized = value.trim().toLowerCase();
|
|
7095
7600
|
return SECRET_SOURCES.includes(normalized) ? normalized : null;
|
|
7096
7601
|
}
|
|
7097
|
-
function normalizeOptionalString$
|
|
7602
|
+
function normalizeOptionalString$6(value) {
|
|
7098
7603
|
if (typeof value !== "string") return;
|
|
7099
7604
|
return value.trim() || void 0;
|
|
7100
7605
|
}
|
|
@@ -7105,9 +7610,9 @@ function parseTimeoutMs(value) {
|
|
|
7105
7610
|
return Math.trunc(parsed);
|
|
7106
7611
|
}
|
|
7107
7612
|
function inferProviderAlias(config, ref) {
|
|
7108
|
-
const explicit = normalizeOptionalString$
|
|
7613
|
+
const explicit = normalizeOptionalString$6(ref.provider);
|
|
7109
7614
|
if (explicit) return explicit;
|
|
7110
|
-
const defaultAlias = normalizeOptionalString$
|
|
7615
|
+
const defaultAlias = normalizeOptionalString$6(config.secrets.defaults[ref.source]);
|
|
7111
7616
|
if (defaultAlias) return defaultAlias;
|
|
7112
7617
|
return ref.source;
|
|
7113
7618
|
}
|
|
@@ -7125,8 +7630,8 @@ function parseRefsPatch(raw) {
|
|
|
7125
7630
|
for (const [path, value] of Object.entries(raw)) {
|
|
7126
7631
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`invalid ref for ${path}`);
|
|
7127
7632
|
const source = normalizeSecretSource(value.source);
|
|
7128
|
-
const id = normalizeOptionalString$
|
|
7129
|
-
const provider = normalizeOptionalString$
|
|
7633
|
+
const id = normalizeOptionalString$6(value.id);
|
|
7634
|
+
const provider = normalizeOptionalString$6(value.provider);
|
|
7130
7635
|
if (!source || !id) throw new Error(`invalid ref for ${path}: source/id is required`);
|
|
7131
7636
|
output[path] = {
|
|
7132
7637
|
source,
|
|
@@ -7213,7 +7718,7 @@ var SecretsCommands = class {
|
|
|
7213
7718
|
if (Boolean(opts.strict) && summary.failed > 0) process.exitCode = 1;
|
|
7214
7719
|
}
|
|
7215
7720
|
async secretsConfigure(opts) {
|
|
7216
|
-
const alias = normalizeOptionalString$
|
|
7721
|
+
const alias = normalizeOptionalString$6(opts.provider);
|
|
7217
7722
|
if (!alias) throw new Error("provider alias is required");
|
|
7218
7723
|
const prevConfig = loadConfig();
|
|
7219
7724
|
const nextConfig = structuredClone(prevConfig);
|
|
@@ -7226,10 +7731,10 @@ var SecretsCommands = class {
|
|
|
7226
7731
|
if (!source) throw new Error("source is required and must be one of env/file/exec");
|
|
7227
7732
|
if (source === "env") nextConfig.secrets.providers[alias] = {
|
|
7228
7733
|
source,
|
|
7229
|
-
...normalizeOptionalString$
|
|
7734
|
+
...normalizeOptionalString$6(opts.prefix) ? { prefix: normalizeOptionalString$6(opts.prefix) } : {}
|
|
7230
7735
|
};
|
|
7231
7736
|
else if (source === "file") {
|
|
7232
|
-
const path = normalizeOptionalString$
|
|
7737
|
+
const path = normalizeOptionalString$6(opts.path);
|
|
7233
7738
|
if (!path) throw new Error("file source requires --path");
|
|
7234
7739
|
nextConfig.secrets.providers[alias] = {
|
|
7235
7740
|
source,
|
|
@@ -7237,13 +7742,13 @@ var SecretsCommands = class {
|
|
|
7237
7742
|
format: "json"
|
|
7238
7743
|
};
|
|
7239
7744
|
} else {
|
|
7240
|
-
const command = normalizeOptionalString$
|
|
7745
|
+
const command = normalizeOptionalString$6(opts.command);
|
|
7241
7746
|
if (!command) throw new Error("exec source requires --command");
|
|
7242
7747
|
nextConfig.secrets.providers[alias] = {
|
|
7243
7748
|
source,
|
|
7244
7749
|
command,
|
|
7245
7750
|
args: Array.isArray(opts.arg) ? opts.arg : [],
|
|
7246
|
-
...normalizeOptionalString$
|
|
7751
|
+
...normalizeOptionalString$6(opts.cwd) ? { cwd: normalizeOptionalString$6(opts.cwd) } : {},
|
|
7247
7752
|
timeoutMs: parseTimeoutMs(opts.timeoutMs) ?? 5e3
|
|
7248
7753
|
};
|
|
7249
7754
|
}
|
|
@@ -7288,9 +7793,9 @@ var SecretsCommands = class {
|
|
|
7288
7793
|
if (opts.remove) delete nextConfig.secrets.refs[path];
|
|
7289
7794
|
else {
|
|
7290
7795
|
const source = normalizeSecretSource(opts.source);
|
|
7291
|
-
const id = normalizeOptionalString$
|
|
7796
|
+
const id = normalizeOptionalString$6(opts.id);
|
|
7292
7797
|
if (!source || !id) throw new Error("apply single ref requires --source and --id");
|
|
7293
|
-
const provider = normalizeOptionalString$
|
|
7798
|
+
const provider = normalizeOptionalString$6(opts.provider);
|
|
7294
7799
|
nextConfig.secrets.refs[path] = {
|
|
7295
7800
|
source,
|
|
7296
7801
|
id,
|
|
@@ -7636,34 +8141,114 @@ function printCronJobs(jobs) {
|
|
|
7636
8141
|
for (const job of jobs) console.log(`${job.id} [${job.enabled ? "enabled" : "disabled"}] ${job.name} ${formatCronSchedule(job.schedule)}`);
|
|
7637
8142
|
}
|
|
7638
8143
|
//#endregion
|
|
7639
|
-
//#region src/cli/
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
if (!isProcessRunning(state.pid)) return null;
|
|
7644
|
-
if (typeof state.uiUrl === "string" && state.uiUrl.trim().length > 0) return state.uiUrl.replace(/\/+$/, "");
|
|
7645
|
-
if (typeof state.apiUrl === "string" && state.apiUrl.trim().length > 0) return state.apiUrl.replace(/\/api\/?$/, "").replace(/\/+$/, "");
|
|
7646
|
-
return null;
|
|
7647
|
-
}
|
|
7648
|
-
var UiBridgeApiClient = class {
|
|
7649
|
-
cookie;
|
|
7650
|
-
constructor(apiBase) {
|
|
7651
|
-
this.apiBase = apiBase;
|
|
8144
|
+
//#region src/cli/runtime-state/local-ui-runtime.store.ts
|
|
8145
|
+
var LocalUiRuntimeStore = class {
|
|
8146
|
+
get path() {
|
|
8147
|
+
return resolve(getDataDir(), "run", "ui-runtime.json");
|
|
7652
8148
|
}
|
|
7653
|
-
|
|
7654
|
-
if (this.
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
const payload = await response.json();
|
|
7662
|
-
if (!payload.ok) throw new Error(payload.error?.message ?? "bridge auth failed");
|
|
7663
|
-
this.cookie = typeof payload.data.cookie === "string" && payload.data.cookie.trim() ? payload.data.cookie.trim() : null;
|
|
7664
|
-
return this.cookie;
|
|
8149
|
+
read = () => {
|
|
8150
|
+
if (!existsSync(this.path)) return null;
|
|
8151
|
+
try {
|
|
8152
|
+
const raw = readFileSync(this.path, "utf-8");
|
|
8153
|
+
return JSON.parse(raw);
|
|
8154
|
+
} catch {
|
|
8155
|
+
return null;
|
|
8156
|
+
}
|
|
7665
8157
|
};
|
|
7666
|
-
|
|
8158
|
+
write = (state) => {
|
|
8159
|
+
mkdirSync(resolve(this.path, ".."), { recursive: true });
|
|
8160
|
+
writeFileSync(this.path, JSON.stringify(state, null, 2));
|
|
8161
|
+
};
|
|
8162
|
+
update = (updater) => {
|
|
8163
|
+
const current = this.read();
|
|
8164
|
+
if (!current) return null;
|
|
8165
|
+
const next = updater(current);
|
|
8166
|
+
this.write(next);
|
|
8167
|
+
return next;
|
|
8168
|
+
};
|
|
8169
|
+
clear = () => {
|
|
8170
|
+
if (existsSync(this.path)) rmSync(this.path, { force: true });
|
|
8171
|
+
};
|
|
8172
|
+
clearIfOwnedByProcess = (pid = process.pid) => {
|
|
8173
|
+
if (this.read()?.pid === pid) this.clear();
|
|
8174
|
+
};
|
|
8175
|
+
writeCurrentProcess = (uiConfig, pid = process.pid) => {
|
|
8176
|
+
const existing = this.read();
|
|
8177
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
8178
|
+
const state = {
|
|
8179
|
+
pid,
|
|
8180
|
+
startedAt: existing?.pid === pid && typeof existing.startedAt === "string" ? existing.startedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
8181
|
+
uiUrl,
|
|
8182
|
+
apiUrl: `${uiUrl}/api`,
|
|
8183
|
+
uiHost: uiConfig.host,
|
|
8184
|
+
uiPort: uiConfig.port,
|
|
8185
|
+
...existing?.remote ? { remote: existing.remote } : {}
|
|
8186
|
+
};
|
|
8187
|
+
this.write(state);
|
|
8188
|
+
return state;
|
|
8189
|
+
};
|
|
8190
|
+
};
|
|
8191
|
+
const localUiRuntimeStore = new LocalUiRuntimeStore();
|
|
8192
|
+
//#endregion
|
|
8193
|
+
//#region src/cli/runtime-state/local-ui-discovery.service.ts
|
|
8194
|
+
var LocalUiDiscoveryService = class {
|
|
8195
|
+
constructor(localUiStore = localUiRuntimeStore, managedServiceStore = managedServiceStateStore, isProcessRunningFn = (pid) => isProcessRunning(pid)) {
|
|
8196
|
+
this.localUiStore = localUiStore;
|
|
8197
|
+
this.managedServiceStore = managedServiceStore;
|
|
8198
|
+
this.isProcessRunningFn = isProcessRunningFn;
|
|
8199
|
+
}
|
|
8200
|
+
readRunningState = (state) => {
|
|
8201
|
+
if (!state || !this.isProcessRunningFn(state.pid)) return null;
|
|
8202
|
+
return state;
|
|
8203
|
+
};
|
|
8204
|
+
readRunningRuntimeState = () => {
|
|
8205
|
+
return this.readRunningState(this.localUiStore.read()) ?? this.readRunningState(this.managedServiceStore.read());
|
|
8206
|
+
};
|
|
8207
|
+
resolveApiBase = () => {
|
|
8208
|
+
const state = this.readRunningRuntimeState();
|
|
8209
|
+
if (!state) return null;
|
|
8210
|
+
if (typeof state.uiUrl === "string" && state.uiUrl.trim().length > 0) return state.uiUrl.replace(/\/+$/, "");
|
|
8211
|
+
if (typeof state.apiUrl === "string" && state.apiUrl.trim().length > 0) return state.apiUrl.replace(/\/api\/?$/, "").replace(/\/+$/, "");
|
|
8212
|
+
return null;
|
|
8213
|
+
};
|
|
8214
|
+
resolveLocalOrigin = (config) => {
|
|
8215
|
+
const state = this.readRunningRuntimeState();
|
|
8216
|
+
const runtimePort = state && typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : null;
|
|
8217
|
+
if (runtimePort !== null) return resolveLocalUiBaseUrl({
|
|
8218
|
+
host: "0.0.0.0",
|
|
8219
|
+
port: runtimePort
|
|
8220
|
+
});
|
|
8221
|
+
return resolveLocalUiBaseUrl({
|
|
8222
|
+
host: "0.0.0.0",
|
|
8223
|
+
port: typeof config.ui.port === "number" && Number.isFinite(config.ui.port) ? config.ui.port : 55667
|
|
8224
|
+
});
|
|
8225
|
+
};
|
|
8226
|
+
};
|
|
8227
|
+
const localUiDiscoveryService = new LocalUiDiscoveryService();
|
|
8228
|
+
//#endregion
|
|
8229
|
+
//#region src/cli/commands/shared/ui-bridge-api.service.ts
|
|
8230
|
+
function resolveLocalUiApiBase() {
|
|
8231
|
+
return localUiDiscoveryService.resolveApiBase();
|
|
8232
|
+
}
|
|
8233
|
+
var UiBridgeApiClient = class {
|
|
8234
|
+
cookie;
|
|
8235
|
+
constructor(apiBase) {
|
|
8236
|
+
this.apiBase = apiBase;
|
|
8237
|
+
}
|
|
8238
|
+
getCookie = async () => {
|
|
8239
|
+
if (this.cookie !== void 0) return this.cookie;
|
|
8240
|
+
const bridgeSecret = ensureUiBridgeSecret();
|
|
8241
|
+
const response = await fetch(`${this.apiBase}/api/auth/bridge`, {
|
|
8242
|
+
method: "POST",
|
|
8243
|
+
headers: { "x-nextclaw-ui-bridge-secret": bridgeSecret }
|
|
8244
|
+
});
|
|
8245
|
+
if (!response.ok) throw new Error(`bridge auth failed with status ${response.status}`);
|
|
8246
|
+
const payload = await response.json();
|
|
8247
|
+
if (!payload.ok) throw new Error(payload.error?.message ?? "bridge auth failed");
|
|
8248
|
+
this.cookie = typeof payload.data.cookie === "string" && payload.data.cookie.trim() ? payload.data.cookie.trim() : null;
|
|
8249
|
+
return this.cookie;
|
|
8250
|
+
};
|
|
8251
|
+
request = async (params) => {
|
|
7667
8252
|
const cookie = await this.getCookie();
|
|
7668
8253
|
const response = await fetch(`${this.apiBase}${params.path}`, {
|
|
7669
8254
|
method: params.method ?? "GET",
|
|
@@ -7690,7 +8275,7 @@ var CronCommands = class {
|
|
|
7690
8275
|
this.local = local;
|
|
7691
8276
|
}
|
|
7692
8277
|
createApiClient = () => {
|
|
7693
|
-
const apiBase =
|
|
8278
|
+
const apiBase = resolveLocalUiApiBase();
|
|
7694
8279
|
if (!apiBase) return null;
|
|
7695
8280
|
return new UiBridgeApiClient(apiBase);
|
|
7696
8281
|
};
|
|
@@ -7857,13 +8442,13 @@ function createUnusedRuntime(_params) {
|
|
|
7857
8442
|
throw new Error("runtime creation is not available during runtime listing");
|
|
7858
8443
|
}
|
|
7859
8444
|
function loadRuntimeOnlyPluginRegistry(config, workspaceDir) {
|
|
7860
|
-
const
|
|
8445
|
+
const { configWithDevPluginOverrides, excludedRoots } = resolveDevPluginLoadingContext(config, resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR));
|
|
7861
8446
|
return loadOpenClawPlugins({
|
|
7862
|
-
config:
|
|
8447
|
+
config: configWithDevPluginOverrides,
|
|
7863
8448
|
workspaceDir,
|
|
7864
8449
|
includeBundled: false,
|
|
7865
8450
|
kinds: ["agent-runtime"],
|
|
7866
|
-
excludeRoots:
|
|
8451
|
+
excludeRoots: excludedRoots,
|
|
7867
8452
|
...buildReservedPluginLoadOptions(),
|
|
7868
8453
|
logger: {
|
|
7869
8454
|
info: (message) => console.log(message),
|
|
@@ -8025,7 +8610,7 @@ var AgentCommands = class {
|
|
|
8025
8610
|
//#region src/cli/commands/remote-support/remote-runtime-support.ts
|
|
8026
8611
|
let currentProcessRemoteRuntimeState = null;
|
|
8027
8612
|
function hasRunningNextclawManagedService() {
|
|
8028
|
-
const state =
|
|
8613
|
+
const state = managedServiceStateStore.read();
|
|
8029
8614
|
return Boolean(state && isProcessRunning(state.pid));
|
|
8030
8615
|
}
|
|
8031
8616
|
function createNextclawRemotePlatformClient() {
|
|
@@ -8038,7 +8623,7 @@ function createNextclawRemotePlatformClient() {
|
|
|
8038
8623
|
requireConfigured: true
|
|
8039
8624
|
}).platformBase,
|
|
8040
8625
|
readManagedServiceState: () => {
|
|
8041
|
-
const state =
|
|
8626
|
+
const state = managedServiceStateStore.read();
|
|
8042
8627
|
if (!state) return null;
|
|
8043
8628
|
return {
|
|
8044
8629
|
pid: state.pid,
|
|
@@ -8057,9 +8642,13 @@ function createNextclawRemoteConnector(params = {}) {
|
|
|
8057
8642
|
function createNextclawRemoteStatusStore(mode) {
|
|
8058
8643
|
return new RemoteStatusStore(mode, { writeRemoteState: (next) => {
|
|
8059
8644
|
currentProcessRemoteRuntimeState = next;
|
|
8060
|
-
|
|
8645
|
+
if (localUiRuntimeStore.read()?.pid === process.pid) localUiRuntimeStore.update((state) => ({
|
|
8646
|
+
...state,
|
|
8647
|
+
remote: next
|
|
8648
|
+
}));
|
|
8649
|
+
const serviceState = managedServiceStateStore.read();
|
|
8061
8650
|
if (!serviceState || serviceState.pid !== process.pid) return;
|
|
8062
|
-
|
|
8651
|
+
managedServiceStateStore.update((state) => ({
|
|
8063
8652
|
...state,
|
|
8064
8653
|
remote: next
|
|
8065
8654
|
}));
|
|
@@ -8069,10 +8658,12 @@ function buildNextclawConfiguredRemoteState(config) {
|
|
|
8069
8658
|
return buildConfiguredRemoteState(config);
|
|
8070
8659
|
}
|
|
8071
8660
|
function readCurrentNextclawRemoteRuntimeState() {
|
|
8072
|
-
const
|
|
8073
|
-
const
|
|
8661
|
+
const uiRuntimeState = localUiRuntimeStore.read();
|
|
8662
|
+
const serviceState = managedServiceStateStore.read();
|
|
8663
|
+
const currentRemoteState = currentProcessRemoteRuntimeState ?? uiRuntimeState?.remote ?? serviceState?.remote ?? null;
|
|
8074
8664
|
if (!currentRemoteState) return null;
|
|
8075
|
-
|
|
8665
|
+
const owningRuntime = uiRuntimeState ?? serviceState;
|
|
8666
|
+
if (!owningRuntime || isProcessRunning(owningRuntime.pid)) return currentRemoteState;
|
|
8076
8667
|
return {
|
|
8077
8668
|
...currentRemoteState,
|
|
8078
8669
|
state: currentRemoteState.enabled ? "disconnected" : "disabled",
|
|
@@ -8088,15 +8679,13 @@ function resolveNextclawRemoteStatusSnapshot(config) {
|
|
|
8088
8679
|
}
|
|
8089
8680
|
//#endregion
|
|
8090
8681
|
//#region src/cli/commands/remote.ts
|
|
8091
|
-
function normalizeOptionalString$
|
|
8682
|
+
function normalizeOptionalString$5(value) {
|
|
8092
8683
|
if (typeof value !== "string") return;
|
|
8093
8684
|
const trimmed = value.trim();
|
|
8094
8685
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
8095
8686
|
}
|
|
8096
8687
|
function resolveConfiguredLocalOrigin(config) {
|
|
8097
|
-
|
|
8098
|
-
if (state && isProcessRunning(state.pid) && Number.isFinite(state.uiPort)) return `http://127.0.0.1:${state.uiPort}`;
|
|
8099
|
-
return `http://127.0.0.1:${typeof config.ui.port === "number" && Number.isFinite(config.ui.port) ? config.ui.port : 55667}`;
|
|
8688
|
+
return localUiDiscoveryService.resolveLocalOrigin(config);
|
|
8100
8689
|
}
|
|
8101
8690
|
async function probeLocalUi(localOrigin) {
|
|
8102
8691
|
try {
|
|
@@ -8187,8 +8776,8 @@ var RemoteCommands = class {
|
|
|
8187
8776
|
configuredEnabled: snapshot.configuredEnabled,
|
|
8188
8777
|
runtime: snapshot.runtime,
|
|
8189
8778
|
localOrigin: resolvedLocalOrigin,
|
|
8190
|
-
deviceName: snapshot.runtime?.deviceName ?? normalizeOptionalString$
|
|
8191
|
-
platformBase: snapshot.runtime?.platformBase ?? normalizeOptionalString$
|
|
8779
|
+
deviceName: snapshot.runtime?.deviceName ?? normalizeOptionalString$5(config.remote.deviceName) ?? hostname(),
|
|
8780
|
+
platformBase: snapshot.runtime?.platformBase ?? normalizeOptionalString$5(config.remote.platformApiBase) ?? normalizeOptionalString$5(config.providers.nextclaw?.apiBase) ?? null
|
|
8192
8781
|
};
|
|
8193
8782
|
}
|
|
8194
8783
|
async status(opts = {}) {
|
|
@@ -8214,8 +8803,8 @@ var RemoteCommands = class {
|
|
|
8214
8803
|
const snapshot = resolveNextclawRemoteStatusSnapshot(config);
|
|
8215
8804
|
const localOrigin = snapshot.runtime?.localOrigin ?? this.deps.currentLocalOrigin ?? resolveConfiguredLocalOrigin(config);
|
|
8216
8805
|
const localUi = await probeLocalUi(localOrigin);
|
|
8217
|
-
const token = normalizeOptionalString$
|
|
8218
|
-
const platformApiBase = normalizeOptionalString$
|
|
8806
|
+
const token = normalizeOptionalString$5(config.providers.nextclaw?.apiKey);
|
|
8807
|
+
const platformApiBase = normalizeOptionalString$5(config.remote.platformApiBase) ?? normalizeOptionalString$5(config.providers.nextclaw?.apiBase);
|
|
8219
8808
|
const checks = [
|
|
8220
8809
|
{
|
|
8221
8810
|
name: "remote-enabled",
|
|
@@ -8324,7 +8913,7 @@ var DiagnosticsCommands = class {
|
|
|
8324
8913
|
constructor(deps) {
|
|
8325
8914
|
this.deps = deps;
|
|
8326
8915
|
}
|
|
8327
|
-
async
|
|
8916
|
+
status = async (opts = {}) => {
|
|
8328
8917
|
const report = await this.collectRuntimeStatus({
|
|
8329
8918
|
verbose: Boolean(opts.verbose),
|
|
8330
8919
|
fix: Boolean(opts.fix)
|
|
@@ -8340,25 +8929,53 @@ var DiagnosticsCommands = class {
|
|
|
8340
8929
|
verbose: Boolean(opts.verbose)
|
|
8341
8930
|
});
|
|
8342
8931
|
process.exitCode = 0;
|
|
8343
|
-
}
|
|
8344
|
-
async
|
|
8932
|
+
};
|
|
8933
|
+
doctor = async (opts = {}) => {
|
|
8345
8934
|
const report = await this.collectRuntimeStatus({
|
|
8346
8935
|
verbose: Boolean(opts.verbose),
|
|
8347
8936
|
fix: Boolean(opts.fix)
|
|
8348
8937
|
});
|
|
8349
|
-
const checkPort = await this.checkPortAvailability(
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
})
|
|
8938
|
+
const checkPort = await this.checkPortAvailability(this.resolveDoctorPortCheckTarget(report));
|
|
8939
|
+
const checks = this.buildDoctorChecks(report, checkPort);
|
|
8940
|
+
const exitCode = this.resolveDoctorExitCode(checks);
|
|
8941
|
+
if (opts.json) {
|
|
8942
|
+
console.log(JSON.stringify({
|
|
8943
|
+
generatedAt: report.generatedAt,
|
|
8944
|
+
checks,
|
|
8945
|
+
status: report,
|
|
8946
|
+
exitCode
|
|
8947
|
+
}, null, 2));
|
|
8948
|
+
process.exitCode = exitCode;
|
|
8949
|
+
return;
|
|
8950
|
+
}
|
|
8951
|
+
printDoctorReport({
|
|
8952
|
+
logo: this.deps.logo,
|
|
8953
|
+
generatedAt: report.generatedAt,
|
|
8954
|
+
checks,
|
|
8955
|
+
recommendations: report.recommendations,
|
|
8956
|
+
verbose: Boolean(opts.verbose),
|
|
8957
|
+
logTail: report.logTail
|
|
8359
8958
|
});
|
|
8959
|
+
process.exitCode = exitCode;
|
|
8960
|
+
};
|
|
8961
|
+
resolveDoctorPortCheckTarget = (report) => {
|
|
8962
|
+
const host = report.process.running && report.endpoints.uiUrl ? new URL(report.endpoints.uiUrl).hostname : "127.0.0.1";
|
|
8963
|
+
try {
|
|
8964
|
+
const base = report.process.running && report.endpoints.uiUrl ? report.endpoints.uiUrl : report.endpoints.configuredUiUrl;
|
|
8965
|
+
return {
|
|
8966
|
+
host,
|
|
8967
|
+
port: Number(new URL(base).port || 80)
|
|
8968
|
+
};
|
|
8969
|
+
} catch {
|
|
8970
|
+
return {
|
|
8971
|
+
host,
|
|
8972
|
+
port: 55667
|
|
8973
|
+
};
|
|
8974
|
+
}
|
|
8975
|
+
};
|
|
8976
|
+
buildDoctorChecks = (report, checkPort) => {
|
|
8360
8977
|
const providerConfigured = report.providers.some((provider) => provider.configured);
|
|
8361
|
-
|
|
8978
|
+
return [
|
|
8362
8979
|
{
|
|
8363
8980
|
name: "config-file",
|
|
8364
8981
|
status: report.configExists ? "pass" : "fail",
|
|
@@ -8376,12 +8993,12 @@ var DiagnosticsCommands = class {
|
|
|
8376
8993
|
},
|
|
8377
8994
|
{
|
|
8378
8995
|
name: "service-health",
|
|
8379
|
-
status: report.process.running ? report.health.managed.state === "ok" ? "pass" : "fail" :
|
|
8996
|
+
status: report.process.running ? report.health.managed.state === "ok" ? "pass" : "fail" : "warn",
|
|
8380
8997
|
detail: report.process.running ? `${report.health.managed.state}: ${report.health.managed.detail}` : `${report.health.configured.state}: ${report.health.configured.detail}`
|
|
8381
8998
|
},
|
|
8382
8999
|
{
|
|
8383
9000
|
name: "ui-port-availability",
|
|
8384
|
-
status: report.process.running
|
|
9001
|
+
status: report.process.running || checkPort.available ? "pass" : "fail",
|
|
8385
9002
|
detail: report.process.running ? "managed by running service" : checkPort.available ? "available" : checkPort.detail
|
|
8386
9003
|
},
|
|
8387
9004
|
{
|
|
@@ -8390,40 +9007,23 @@ var DiagnosticsCommands = class {
|
|
|
8390
9007
|
detail: providerConfigured ? "at least one provider configured" : "no provider api key configured"
|
|
8391
9008
|
}
|
|
8392
9009
|
];
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
if (
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
status: report,
|
|
8401
|
-
exitCode
|
|
8402
|
-
}, null, 2));
|
|
8403
|
-
process.exitCode = exitCode;
|
|
8404
|
-
return;
|
|
8405
|
-
}
|
|
8406
|
-
printDoctorReport({
|
|
8407
|
-
logo: this.deps.logo,
|
|
8408
|
-
generatedAt: report.generatedAt,
|
|
8409
|
-
checks,
|
|
8410
|
-
recommendations: report.recommendations,
|
|
8411
|
-
verbose: Boolean(opts.verbose),
|
|
8412
|
-
logTail: report.logTail
|
|
8413
|
-
});
|
|
8414
|
-
process.exitCode = exitCode;
|
|
8415
|
-
}
|
|
8416
|
-
async collectRuntimeStatus(params) {
|
|
9010
|
+
};
|
|
9011
|
+
resolveDoctorExitCode = (checks) => {
|
|
9012
|
+
if (checks.some((check) => check.status === "fail")) return 1;
|
|
9013
|
+
if (checks.some((check) => check.status === "warn")) return 1;
|
|
9014
|
+
return 0;
|
|
9015
|
+
};
|
|
9016
|
+
collectRuntimeStatus = async (params) => {
|
|
8417
9017
|
const configPath = getConfigPath();
|
|
8418
9018
|
const config = loadConfig();
|
|
8419
9019
|
const workspacePath = getWorkspacePath(config.agents.defaults.workspace);
|
|
8420
|
-
const serviceStatePath =
|
|
9020
|
+
const serviceStatePath = managedServiceStateStore.path;
|
|
8421
9021
|
const fixActions = [];
|
|
8422
|
-
let serviceState =
|
|
9022
|
+
let serviceState = managedServiceStateStore.read();
|
|
8423
9023
|
if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
|
|
8424
|
-
|
|
9024
|
+
managedServiceStateStore.clear();
|
|
8425
9025
|
fixActions.push("Cleared stale service state file.");
|
|
8426
|
-
serviceState =
|
|
9026
|
+
serviceState = managedServiceStateStore.read();
|
|
8427
9027
|
}
|
|
8428
9028
|
const managedByState = Boolean(serviceState);
|
|
8429
9029
|
const running = Boolean(serviceState && isProcessRunning(serviceState.pid));
|
|
@@ -8459,7 +9059,7 @@ var DiagnosticsCommands = class {
|
|
|
8459
9059
|
issues,
|
|
8460
9060
|
recommendations
|
|
8461
9061
|
});
|
|
8462
|
-
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ??
|
|
9062
|
+
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveAppLogPath("service"), 25) : [];
|
|
8463
9063
|
const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
|
|
8464
9064
|
return {
|
|
8465
9065
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8497,8 +9097,8 @@ var DiagnosticsCommands = class {
|
|
|
8497
9097
|
level,
|
|
8498
9098
|
exitCode: 0
|
|
8499
9099
|
};
|
|
8500
|
-
}
|
|
8501
|
-
async
|
|
9100
|
+
};
|
|
9101
|
+
probeApiHealth = async (url, timeoutMs = 1500) => {
|
|
8502
9102
|
const controller = new AbortController();
|
|
8503
9103
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8504
9104
|
try {
|
|
@@ -8529,8 +9129,8 @@ var DiagnosticsCommands = class {
|
|
|
8529
9129
|
} finally {
|
|
8530
9130
|
clearTimeout(timer);
|
|
8531
9131
|
}
|
|
8532
|
-
}
|
|
8533
|
-
listProviderStatuses(config) {
|
|
9132
|
+
};
|
|
9133
|
+
listProviderStatuses = (config) => {
|
|
8534
9134
|
return listBuiltinProviders().map((spec) => {
|
|
8535
9135
|
const provider = config.providers[spec.name];
|
|
8536
9136
|
const apiKeyRefSet = hasSecretRef(config, `providers.${spec.name}.apiKey`);
|
|
@@ -8555,8 +9155,8 @@ var DiagnosticsCommands = class {
|
|
|
8555
9155
|
detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
|
|
8556
9156
|
};
|
|
8557
9157
|
});
|
|
8558
|
-
}
|
|
8559
|
-
collectRuntimeIssues(params) {
|
|
9158
|
+
};
|
|
9159
|
+
collectRuntimeIssues = (params) => {
|
|
8560
9160
|
if (!existsSync(params.configPath)) {
|
|
8561
9161
|
params.issues.push("Config file is missing.");
|
|
8562
9162
|
params.recommendations.push(`Run ${APP_NAME} init to create config files.`);
|
|
@@ -8571,7 +9171,7 @@ var DiagnosticsCommands = class {
|
|
|
8571
9171
|
}
|
|
8572
9172
|
if (params.running && params.managedHealth.state !== "ok") {
|
|
8573
9173
|
params.issues.push(`Managed service health check failed: ${params.managedHealth.detail}`);
|
|
8574
|
-
params.recommendations.push(`Check logs at ${params.serviceState?.logPath ??
|
|
9174
|
+
params.recommendations.push(`Check logs at ${params.serviceState?.logPath ?? resolveAppLogPath("service")}.`);
|
|
8575
9175
|
}
|
|
8576
9176
|
if (params.running && params.serviceState?.startupState === "degraded" && params.managedHealth.state !== "ok") {
|
|
8577
9177
|
const startupHint = params.serviceState.startupLastProbeError ? ` (${params.serviceState.startupLastProbeError})` : "";
|
|
@@ -8584,8 +9184,8 @@ var DiagnosticsCommands = class {
|
|
|
8584
9184
|
params.recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
8585
9185
|
}
|
|
8586
9186
|
if (!params.providers.some((provider) => provider.configured)) params.recommendations.push("Configure at least one provider API key in UI or config before expecting agent replies.");
|
|
8587
|
-
}
|
|
8588
|
-
readLogTail(path, maxLines = 25) {
|
|
9187
|
+
};
|
|
9188
|
+
readLogTail = (path, maxLines = 25) => {
|
|
8589
9189
|
if (!existsSync(path)) return [];
|
|
8590
9190
|
try {
|
|
8591
9191
|
const lines = readFileSync(path, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
@@ -8594,8 +9194,8 @@ var DiagnosticsCommands = class {
|
|
|
8594
9194
|
} catch {
|
|
8595
9195
|
return [];
|
|
8596
9196
|
}
|
|
8597
|
-
}
|
|
8598
|
-
async
|
|
9197
|
+
};
|
|
9198
|
+
checkPortAvailability = async (params) => {
|
|
8599
9199
|
return await new Promise((resolve) => {
|
|
8600
9200
|
const server = createServer();
|
|
8601
9201
|
server.once("error", (error) => {
|
|
@@ -8613,7 +9213,34 @@ var DiagnosticsCommands = class {
|
|
|
8613
9213
|
});
|
|
8614
9214
|
});
|
|
8615
9215
|
});
|
|
8616
|
-
}
|
|
9216
|
+
};
|
|
9217
|
+
};
|
|
9218
|
+
//#endregion
|
|
9219
|
+
//#region src/cli/commands/logs.ts
|
|
9220
|
+
var LogsCommands = class {
|
|
9221
|
+
constructor(runtime = getLoggingRuntime()) {
|
|
9222
|
+
this.runtime = runtime;
|
|
9223
|
+
}
|
|
9224
|
+
logsPath = () => {
|
|
9225
|
+
const paths = this.runtime.getPaths();
|
|
9226
|
+
console.log([
|
|
9227
|
+
`Logs directory: ${paths.logsDir}`,
|
|
9228
|
+
`Service log: ${paths.serviceLogPath}`,
|
|
9229
|
+
`Crash log: ${paths.crashLogPath}`,
|
|
9230
|
+
`Archive: ${paths.archiveDir}`
|
|
9231
|
+
].join("\n"));
|
|
9232
|
+
};
|
|
9233
|
+
logsTail = (opts = {}) => {
|
|
9234
|
+
const kind = opts.crash ? "crash" : "service";
|
|
9235
|
+
const rawLines = Number(opts.lines);
|
|
9236
|
+
const lines = Number.isFinite(rawLines) && rawLines > 0 ? Math.floor(rawLines) : 40;
|
|
9237
|
+
const output = this.runtime.tail(kind, lines);
|
|
9238
|
+
if (output.length === 0) {
|
|
9239
|
+
console.log(`No log entries found in ${this.runtime.resolveLogPath(kind)}.`);
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9242
|
+
console.log(output.join("\n"));
|
|
9243
|
+
};
|
|
8617
9244
|
};
|
|
8618
9245
|
//#endregion
|
|
8619
9246
|
//#region src/cli/commands/service-support/runtime/service-port-probe.ts
|
|
@@ -8732,17 +9359,42 @@ async function probeHealthEndpoint(healthUrl) {
|
|
|
8732
9359
|
req.end();
|
|
8733
9360
|
});
|
|
8734
9361
|
}
|
|
9362
|
+
async function inspectUiTarget(params) {
|
|
9363
|
+
const { checkPortAvailabilityFn, healthUrl, host, port, probeHealthEndpointFn } = params;
|
|
9364
|
+
const availability = await (checkPortAvailabilityFn ?? checkPortAvailability)({
|
|
9365
|
+
host,
|
|
9366
|
+
port
|
|
9367
|
+
});
|
|
9368
|
+
if (availability.available) return {
|
|
9369
|
+
state: "available",
|
|
9370
|
+
availabilityDetail: availability.detail,
|
|
9371
|
+
probeError: null
|
|
9372
|
+
};
|
|
9373
|
+
const probe = await (probeHealthEndpointFn ?? probeHealthEndpoint)(healthUrl);
|
|
9374
|
+
if (probe.healthy) return {
|
|
9375
|
+
state: "healthy-existing",
|
|
9376
|
+
availabilityDetail: availability.detail,
|
|
9377
|
+
probeError: null
|
|
9378
|
+
};
|
|
9379
|
+
return {
|
|
9380
|
+
state: "occupied-unhealthy",
|
|
9381
|
+
availabilityDetail: availability.detail,
|
|
9382
|
+
probeError: probe.error
|
|
9383
|
+
};
|
|
9384
|
+
}
|
|
8735
9385
|
async function describeUnmanagedHealthyTargetMessage(params) {
|
|
8736
9386
|
const uiConfig = resolveUiConfig(loadConfig(), params.uiOverrides);
|
|
8737
9387
|
const healthUrl = `${resolveUiApiBase(uiConfig.host, uiConfig.port)}/api/health`;
|
|
8738
|
-
if ((await (
|
|
9388
|
+
if ((await inspectUiTarget({
|
|
8739
9389
|
host: uiConfig.host,
|
|
8740
|
-
port: uiConfig.port
|
|
8741
|
-
|
|
8742
|
-
|
|
9390
|
+
port: uiConfig.port,
|
|
9391
|
+
healthUrl,
|
|
9392
|
+
checkPortAvailabilityFn: params.checkPortAvailabilityFn,
|
|
9393
|
+
probeHealthEndpointFn: params.probeHealthEndpointFn
|
|
9394
|
+
})).state !== "healthy-existing") return null;
|
|
8743
9395
|
return [
|
|
8744
9396
|
`Target UI health: ${healthUrl}`,
|
|
8745
|
-
`A healthy ${APP_NAME} service is already responding on this port, but it is not tracked by ${
|
|
9397
|
+
`A healthy ${APP_NAME} service is already responding on this port, but it is not tracked by ${managedServiceStateStore.path}.`,
|
|
8746
9398
|
`${APP_NAME} restart only stops the background service recorded in managed state; it will not auto-kill Docker or other external listeners.`,
|
|
8747
9399
|
`Fix: stop that external service first or rerun with --ui-port <port>.`
|
|
8748
9400
|
].join("\n");
|
|
@@ -9076,7 +9728,7 @@ function claimManagedRemoteRuntimeOwnership(params) {
|
|
|
9076
9728
|
const managedConflict = detectManagedRemoteOwnershipConflict({
|
|
9077
9729
|
currentPid,
|
|
9078
9730
|
isProcessRunningFn,
|
|
9079
|
-
readServiceStateFn: params.readServiceStateFn ??
|
|
9731
|
+
readServiceStateFn: params.readServiceStateFn ?? managedServiceStateStore.read
|
|
9080
9732
|
});
|
|
9081
9733
|
if (managedConflict) return {
|
|
9082
9734
|
ok: false,
|
|
@@ -9119,7 +9771,7 @@ function createManagedRemoteModuleForUi(params) {
|
|
|
9119
9771
|
});
|
|
9120
9772
|
}
|
|
9121
9773
|
function writeInitialManagedServiceState(params) {
|
|
9122
|
-
|
|
9774
|
+
managedServiceStateStore.write({
|
|
9123
9775
|
pid: params.snapshot.pid,
|
|
9124
9776
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9125
9777
|
uiUrl: params.snapshot.uiUrl,
|
|
@@ -9134,7 +9786,7 @@ function writeInitialManagedServiceState(params) {
|
|
|
9134
9786
|
});
|
|
9135
9787
|
}
|
|
9136
9788
|
function writeReadyManagedServiceState(params) {
|
|
9137
|
-
const currentState =
|
|
9789
|
+
const currentState = managedServiceStateStore.read();
|
|
9138
9790
|
const state = {
|
|
9139
9791
|
pid: params.snapshot.pid,
|
|
9140
9792
|
startedAt: currentState?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9149,7 +9801,7 @@ function writeReadyManagedServiceState(params) {
|
|
|
9149
9801
|
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9150
9802
|
...currentState?.remote ? { remote: currentState.remote } : {}
|
|
9151
9803
|
};
|
|
9152
|
-
|
|
9804
|
+
managedServiceStateStore.write(state);
|
|
9153
9805
|
return state;
|
|
9154
9806
|
}
|
|
9155
9807
|
//#endregion
|
|
@@ -9197,8 +9849,8 @@ function resolveSessionRouteCandidate(params) {
|
|
|
9197
9849
|
function spawnManagedService(params) {
|
|
9198
9850
|
const { appName, config, uiConfig, uiUrl, apiUrl, healthUrl, startupTimeoutMs, resolveStartupTimeoutMs, appendStartupStage, printStartupFailureDiagnostics, resolveServiceLogPath } = params;
|
|
9199
9851
|
const logPath = resolveServiceLogPath();
|
|
9200
|
-
|
|
9201
|
-
|
|
9852
|
+
new FileLogSink({ serviceLogPath: logPath }).ensureReady();
|
|
9853
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
9202
9854
|
const readinessTimeoutMs = resolveStartupTimeoutMs(startupTimeoutMs);
|
|
9203
9855
|
const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
|
|
9204
9856
|
const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);
|
|
@@ -9218,15 +9870,10 @@ function spawnManagedService(params) {
|
|
|
9218
9870
|
appendStartupStage(logPath, `spawning background process: ${cliLaunch.command} ${childArgs.join(" ")}`);
|
|
9219
9871
|
const child = spawn(cliLaunch.command, childArgs, {
|
|
9220
9872
|
env: process.env,
|
|
9221
|
-
stdio:
|
|
9222
|
-
"ignore",
|
|
9223
|
-
logFd,
|
|
9224
|
-
logFd
|
|
9225
|
-
],
|
|
9873
|
+
stdio: "ignore",
|
|
9226
9874
|
detached: true
|
|
9227
9875
|
});
|
|
9228
9876
|
appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
|
|
9229
|
-
closeSync(logFd);
|
|
9230
9877
|
if (!child.pid) {
|
|
9231
9878
|
appendStartupStage(logPath, "spawn failed: child pid missing");
|
|
9232
9879
|
console.error("Error: Failed to start background service.");
|
|
@@ -9371,32 +10018,10 @@ var ServiceFileWatcherRegistry = class {
|
|
|
9371
10018
|
}));
|
|
9372
10019
|
};
|
|
9373
10020
|
};
|
|
9374
|
-
function writeLocalServiceDiscoveryState(uiConfig, pid = process.pid) {
|
|
9375
|
-
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
9376
|
-
const apiUrl = `${uiUrl}/api`;
|
|
9377
|
-
const existing = readServiceState();
|
|
9378
|
-
writeServiceState({
|
|
9379
|
-
pid,
|
|
9380
|
-
startedAt: existing?.pid === pid && typeof existing.startedAt === "string" ? existing.startedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
9381
|
-
uiUrl,
|
|
9382
|
-
apiUrl,
|
|
9383
|
-
uiHost: uiConfig.host,
|
|
9384
|
-
uiPort: uiConfig.port,
|
|
9385
|
-
logPath: existing?.logPath ?? resolveServiceLogPath(),
|
|
9386
|
-
startupState: existing?.startupState ?? "ready",
|
|
9387
|
-
startupLastProbeError: existing?.startupLastProbeError ?? null,
|
|
9388
|
-
startupTimeoutMs: existing?.startupTimeoutMs,
|
|
9389
|
-
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9390
|
-
...existing?.remote ? { remote: existing.remote } : {}
|
|
9391
|
-
});
|
|
9392
|
-
}
|
|
9393
|
-
function clearOwnedServiceState(pid = process.pid) {
|
|
9394
|
-
if (readServiceState()?.pid === pid) clearServiceState();
|
|
9395
|
-
}
|
|
9396
10021
|
function finalizeLocalUiStartup(params) {
|
|
9397
10022
|
const { setUiEventPublisher, uiConfig, uiStartup } = params;
|
|
9398
10023
|
setUiEventPublisher(uiStartup?.publish);
|
|
9399
|
-
if (uiStartup)
|
|
10024
|
+
if (uiStartup) localUiRuntimeStore.writeCurrentProcess(uiConfig);
|
|
9400
10025
|
}
|
|
9401
10026
|
async function startGatewayRuntimeSupport(params) {
|
|
9402
10027
|
const { cronJobs, cronStorePath, reloadCronStore, remoteModule, startCron, startHeartbeat, watchConfigFile, watcherRegistry } = params;
|
|
@@ -9423,7 +10048,7 @@ function resolveRemoteServiceView(currentUi) {
|
|
|
9423
10048
|
uiUrl: resolveUiApiBase(currentUi.host, currentUi.port),
|
|
9424
10049
|
uiPort: currentUi.port
|
|
9425
10050
|
};
|
|
9426
|
-
const serviceState =
|
|
10051
|
+
const serviceState = managedServiceStateStore.read();
|
|
9427
10052
|
const serviceRunning = Boolean(serviceState && isProcessRunning(serviceState.pid));
|
|
9428
10053
|
return {
|
|
9429
10054
|
running: serviceRunning,
|
|
@@ -9462,7 +10087,7 @@ async function controlCurrentProcessRuntime(action, controller) {
|
|
|
9462
10087
|
};
|
|
9463
10088
|
}
|
|
9464
10089
|
async function controlManagedService(action, deps) {
|
|
9465
|
-
const state =
|
|
10090
|
+
const state = managedServiceStateStore.read();
|
|
9466
10091
|
const running = Boolean(state && isProcessRunning(state.pid));
|
|
9467
10092
|
const currentProcess = Boolean(running && state?.pid === process.pid);
|
|
9468
10093
|
const uiOverrides = resolveManagedUiOverrides();
|
|
@@ -9548,7 +10173,7 @@ function launchManagedSelfControl(params = {}) {
|
|
|
9548
10173
|
"const { spawn } = require(\"node:child_process\");",
|
|
9549
10174
|
"const { rmSync } = require(\"node:fs\");",
|
|
9550
10175
|
`const parentPid = ${process.pid};`,
|
|
9551
|
-
`const serviceStatePath = ${JSON.stringify(
|
|
10176
|
+
`const serviceStatePath = ${JSON.stringify(managedServiceStateStore.path)};`,
|
|
9552
10177
|
`const command = ${JSON.stringify(params.command ?? null)};`,
|
|
9553
10178
|
`const args = ${JSON.stringify(params.args ?? [])};`,
|
|
9554
10179
|
`const cwd = ${JSON.stringify(process.cwd())};`,
|
|
@@ -9597,7 +10222,7 @@ function launchManagedSelfControl(params = {}) {
|
|
|
9597
10222
|
}
|
|
9598
10223
|
//#endregion
|
|
9599
10224
|
//#region src/cli/commands/remote-support/remote-access-host.ts
|
|
9600
|
-
function normalizeOptionalString$
|
|
10225
|
+
function normalizeOptionalString$4(value) {
|
|
9601
10226
|
if (typeof value !== "string") return null;
|
|
9602
10227
|
const trimmed = value.trim();
|
|
9603
10228
|
return trimmed.length > 0 ? trimmed : null;
|
|
@@ -9626,8 +10251,8 @@ var RemoteAccessHost = class {
|
|
|
9626
10251
|
const status = this.deps.remoteCommands.getStatusView();
|
|
9627
10252
|
return {
|
|
9628
10253
|
account: this.readAccountView({
|
|
9629
|
-
token: normalizeOptionalString$
|
|
9630
|
-
apiBase: normalizeOptionalString$
|
|
10254
|
+
token: normalizeOptionalString$4(config.providers.nextclaw?.apiKey),
|
|
10255
|
+
apiBase: normalizeOptionalString$4(config.providers.nextclaw?.apiBase),
|
|
9631
10256
|
platformBase: status.platformBase
|
|
9632
10257
|
}),
|
|
9633
10258
|
settings: {
|
|
@@ -9658,7 +10283,7 @@ var RemoteAccessHost = class {
|
|
|
9658
10283
|
pollBrowserAuth = async (input) => {
|
|
9659
10284
|
const config = loadConfig(getConfigPath());
|
|
9660
10285
|
const result = await this.deps.platformAuthCommands.pollBrowserAuth({
|
|
9661
|
-
apiBase: normalizeOptionalString$
|
|
10286
|
+
apiBase: normalizeOptionalString$4(input.apiBase) ?? normalizeOptionalString$4(config.remote.platformApiBase) ?? normalizeOptionalString$4(config.providers.nextclaw?.apiBase) ?? void 0,
|
|
9662
10287
|
sessionId: input.sessionId
|
|
9663
10288
|
});
|
|
9664
10289
|
if (result.status !== "authorized") return result;
|
|
@@ -9784,51 +10409,71 @@ var AssetPutTool = class {
|
|
|
9784
10409
|
description = "Put a normal file path or base64 bytes into the managed asset store.";
|
|
9785
10410
|
parameters = {
|
|
9786
10411
|
type: "object",
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
10412
|
+
oneOf: [{
|
|
10413
|
+
type: "object",
|
|
10414
|
+
properties: {
|
|
10415
|
+
path: {
|
|
10416
|
+
type: "string",
|
|
10417
|
+
description: "Existing local file path to put into the asset store."
|
|
10418
|
+
},
|
|
10419
|
+
fileName: {
|
|
10420
|
+
type: "string",
|
|
10421
|
+
description: "Optional asset file name override."
|
|
10422
|
+
},
|
|
10423
|
+
mimeType: {
|
|
10424
|
+
type: "string",
|
|
10425
|
+
description: "Optional mime type override."
|
|
10426
|
+
}
|
|
9795
10427
|
},
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
10428
|
+
required: ["path"],
|
|
10429
|
+
additionalProperties: false
|
|
10430
|
+
}, {
|
|
10431
|
+
type: "object",
|
|
10432
|
+
properties: {
|
|
10433
|
+
bytesBase64: {
|
|
10434
|
+
type: "string",
|
|
10435
|
+
description: "Base64 file bytes. Use together with fileName when no source path exists."
|
|
10436
|
+
},
|
|
10437
|
+
fileName: {
|
|
10438
|
+
type: "string",
|
|
10439
|
+
description: "Asset file name. Required when using bytesBase64."
|
|
10440
|
+
},
|
|
10441
|
+
mimeType: {
|
|
10442
|
+
type: "string",
|
|
10443
|
+
description: "Optional mime type override."
|
|
10444
|
+
}
|
|
9799
10445
|
},
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
}
|
|
9804
|
-
}
|
|
10446
|
+
required: ["bytesBase64", "fileName"],
|
|
10447
|
+
additionalProperties: false
|
|
10448
|
+
}]
|
|
9805
10449
|
};
|
|
9806
10450
|
constructor(assetStore, contentBasePath) {
|
|
9807
10451
|
this.assetStore = assetStore;
|
|
9808
10452
|
this.contentBasePath = contentBasePath;
|
|
9809
10453
|
}
|
|
9810
|
-
async
|
|
10454
|
+
execute = async (args) => {
|
|
9811
10455
|
const path = readOptionalString$5(args?.path);
|
|
9812
10456
|
const fileName = readOptionalString$5(args?.fileName);
|
|
9813
10457
|
const mimeType = readOptionalString$5(args?.mimeType);
|
|
9814
10458
|
const bytes = readOptionalBase64Bytes(args?.bytesBase64);
|
|
9815
|
-
|
|
9816
|
-
if (path) record = await this.assetStore.putPath({
|
|
9817
|
-
path,
|
|
9818
|
-
fileName: fileName ?? void 0,
|
|
9819
|
-
mimeType
|
|
9820
|
-
});
|
|
9821
|
-
else if (bytes && fileName) record = await this.assetStore.putBytes({
|
|
9822
|
-
fileName,
|
|
9823
|
-
mimeType,
|
|
9824
|
-
bytes
|
|
9825
|
-
});
|
|
9826
|
-
else throw new Error("asset_put requires either path, or bytesBase64 + fileName.");
|
|
9827
|
-
return {
|
|
10459
|
+
if (path) return {
|
|
9828
10460
|
ok: true,
|
|
9829
|
-
asset: toAssetPayload(
|
|
10461
|
+
asset: toAssetPayload(await this.assetStore.putPath({
|
|
10462
|
+
path,
|
|
10463
|
+
fileName: fileName ?? void 0,
|
|
10464
|
+
mimeType
|
|
10465
|
+
}), this.contentBasePath)
|
|
9830
10466
|
};
|
|
9831
|
-
|
|
10467
|
+
if (bytes && fileName) return {
|
|
10468
|
+
ok: true,
|
|
10469
|
+
asset: toAssetPayload(await this.assetStore.putBytes({
|
|
10470
|
+
fileName,
|
|
10471
|
+
mimeType,
|
|
10472
|
+
bytes
|
|
10473
|
+
}), this.contentBasePath)
|
|
10474
|
+
};
|
|
10475
|
+
throw new Error("asset_put received invalid arguments after validation.");
|
|
10476
|
+
};
|
|
9832
10477
|
};
|
|
9833
10478
|
var AssetExportTool = class {
|
|
9834
10479
|
name = "asset_export";
|
|
@@ -9845,12 +10490,13 @@ var AssetExportTool = class {
|
|
|
9845
10490
|
description: "Destination file path."
|
|
9846
10491
|
}
|
|
9847
10492
|
},
|
|
9848
|
-
required: ["assetUri", "targetPath"]
|
|
10493
|
+
required: ["assetUri", "targetPath"],
|
|
10494
|
+
additionalProperties: false
|
|
9849
10495
|
};
|
|
9850
10496
|
constructor(assetStore) {
|
|
9851
10497
|
this.assetStore = assetStore;
|
|
9852
10498
|
}
|
|
9853
|
-
async
|
|
10499
|
+
execute = async (args) => {
|
|
9854
10500
|
const assetUri = readOptionalString$5(args?.assetUri);
|
|
9855
10501
|
const targetPath = readOptionalString$5(args?.targetPath);
|
|
9856
10502
|
if (!assetUri || !targetPath) throw new Error("asset_export requires assetUri and targetPath.");
|
|
@@ -9859,7 +10505,7 @@ var AssetExportTool = class {
|
|
|
9859
10505
|
assetUri,
|
|
9860
10506
|
exportedPath: await this.assetStore.export({ uri: assetUri }, resolve(targetPath))
|
|
9861
10507
|
};
|
|
9862
|
-
}
|
|
10508
|
+
};
|
|
9863
10509
|
};
|
|
9864
10510
|
var AssetStatTool = class {
|
|
9865
10511
|
name = "asset_stat";
|
|
@@ -9870,13 +10516,14 @@ var AssetStatTool = class {
|
|
|
9870
10516
|
type: "string",
|
|
9871
10517
|
description: "Managed asset URI to inspect."
|
|
9872
10518
|
} },
|
|
9873
|
-
required: ["assetUri"]
|
|
10519
|
+
required: ["assetUri"],
|
|
10520
|
+
additionalProperties: false
|
|
9874
10521
|
};
|
|
9875
10522
|
constructor(assetStore, contentBasePath) {
|
|
9876
10523
|
this.assetStore = assetStore;
|
|
9877
10524
|
this.contentBasePath = contentBasePath;
|
|
9878
10525
|
}
|
|
9879
|
-
async
|
|
10526
|
+
execute = async (args) => {
|
|
9880
10527
|
const assetUri = readOptionalString$5(args?.assetUri);
|
|
9881
10528
|
if (!assetUri) throw new Error("asset_stat requires assetUri.");
|
|
9882
10529
|
const record = await this.assetStore.statRecord(assetUri);
|
|
@@ -9891,7 +10538,7 @@ var AssetStatTool = class {
|
|
|
9891
10538
|
ok: true,
|
|
9892
10539
|
asset: toAssetPayload(record, this.contentBasePath)
|
|
9893
10540
|
};
|
|
9894
|
-
}
|
|
10541
|
+
};
|
|
9895
10542
|
};
|
|
9896
10543
|
function createAssetTools(params) {
|
|
9897
10544
|
const contentBasePath = params.contentBasePath ?? "/api/ncp/assets/content";
|
|
@@ -10042,7 +10689,7 @@ function toLegacyMessages(messages, options = {}) {
|
|
|
10042
10689
|
}
|
|
10043
10690
|
//#endregion
|
|
10044
10691
|
//#region src/cli/commands/ncp/context/nextclaw-ncp-session-preferences.ts
|
|
10045
|
-
function normalizeOptionalString$
|
|
10692
|
+
function normalizeOptionalString$3(value) {
|
|
10046
10693
|
if (typeof value !== "string") return;
|
|
10047
10694
|
const trimmed = value.trim();
|
|
10048
10695
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -10055,7 +10702,7 @@ function readMetadataModel(metadata) {
|
|
|
10055
10702
|
metadata.session_model
|
|
10056
10703
|
];
|
|
10057
10704
|
for (const candidate of candidates) {
|
|
10058
|
-
const normalized = normalizeOptionalString$
|
|
10705
|
+
const normalized = normalizeOptionalString$3(candidate);
|
|
10059
10706
|
if (normalized) return normalized;
|
|
10060
10707
|
}
|
|
10061
10708
|
return null;
|
|
@@ -10082,7 +10729,7 @@ function resolveEffectiveModel(params) {
|
|
|
10082
10729
|
if (params.requestMetadata.clear_model === true || params.requestMetadata.reset_model === true) delete params.session.metadata.preferred_model;
|
|
10083
10730
|
const inboundModel = readMetadataModel(params.requestMetadata);
|
|
10084
10731
|
if (inboundModel) params.session.metadata.preferred_model = inboundModel;
|
|
10085
|
-
return normalizeOptionalString$
|
|
10732
|
+
return normalizeOptionalString$3(params.session.metadata.preferred_model) ?? params.fallbackModel;
|
|
10086
10733
|
}
|
|
10087
10734
|
function syncSessionThinkingPreference(params) {
|
|
10088
10735
|
if (params.requestMetadata.clear_thinking === true || params.requestMetadata.reset_thinking === true) delete params.session.metadata.preferred_thinking;
|
|
@@ -10094,8 +10741,8 @@ function syncSessionThinkingPreference(params) {
|
|
|
10094
10741
|
if (inboundThinking) params.session.metadata.preferred_thinking = inboundThinking;
|
|
10095
10742
|
}
|
|
10096
10743
|
function resolveSessionChannelContext(params) {
|
|
10097
|
-
const channel = normalizeOptionalString$
|
|
10098
|
-
const chatId = normalizeOptionalString$
|
|
10744
|
+
const channel = normalizeOptionalString$3(params.requestMetadata.channel) ?? normalizeOptionalString$3(params.session.metadata.last_channel) ?? "ui";
|
|
10745
|
+
const chatId = normalizeOptionalString$3(params.requestMetadata.chatId) ?? normalizeOptionalString$3(params.requestMetadata.chat_id) ?? normalizeOptionalString$3(params.session.metadata.last_to) ?? "web-ui";
|
|
10099
10746
|
params.session.metadata.last_channel = channel;
|
|
10100
10747
|
params.session.metadata.last_to = chatId;
|
|
10101
10748
|
return {
|
|
@@ -10739,7 +11386,7 @@ var NextclawNcpContextBuilder = class {
|
|
|
10739
11386
|
accountId: accountId ?? null
|
|
10740
11387
|
});
|
|
10741
11388
|
const toolDefinitions = this.options.toolRegistry.getToolDefinitions();
|
|
10742
|
-
const additionalSystemSections = [buildSessionOrchestrationSection()];
|
|
11389
|
+
const additionalSystemSections = [buildSessionOrchestrationSection(), buildMinimalSystemExecutionPrompt(effectiveModel)].filter(Boolean);
|
|
10743
11390
|
const contextBuilder = new ContextBuilder(effectiveWorkspace, config.agents.context, {
|
|
10744
11391
|
hostWorkspace: profile.workspace,
|
|
10745
11392
|
sessionProjectRoot: readSessionProjectRoot(session.metadata)
|
|
@@ -11302,12 +11949,6 @@ function extractSessionMessageText(message) {
|
|
|
11302
11949
|
if (parts.length === 0) return;
|
|
11303
11950
|
return parts.join("\n\n");
|
|
11304
11951
|
}
|
|
11305
|
-
function findLatestAssistantMessage(messages) {
|
|
11306
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
11307
|
-
const message = messages[index];
|
|
11308
|
-
if (message?.role === "assistant") return message;
|
|
11309
|
-
}
|
|
11310
|
-
}
|
|
11311
11952
|
function readParentSessionId(metadata) {
|
|
11312
11953
|
return readOptionalString$1(metadata?.["parent_session_id"]) ?? void 0;
|
|
11313
11954
|
}
|
|
@@ -11527,8 +12168,6 @@ var SessionRequestBroker = class {
|
|
|
11527
12168
|
task
|
|
11528
12169
|
});
|
|
11529
12170
|
if (streamedMessage) return streamedMessage;
|
|
11530
|
-
const fallbackMessage = findLatestAssistantMessage(await backend.listSessionMessages(request.targetSessionId));
|
|
11531
|
-
if (fallbackMessage) return fallbackMessage;
|
|
11532
12171
|
throw new Error("Session request completed without a final reply.");
|
|
11533
12172
|
};
|
|
11534
12173
|
appendRequestEvents = (request, type) => {
|
|
@@ -11710,33 +12349,151 @@ var SessionRequestDeliveryService = class {
|
|
|
11710
12349
|
};
|
|
11711
12350
|
};
|
|
11712
12351
|
//#endregion
|
|
12352
|
+
//#region src/cli/commands/shared/llm-usage-observer.ts
|
|
12353
|
+
var LlmUsageObserver = class {
|
|
12354
|
+
constructor(recorder, source) {
|
|
12355
|
+
this.recorder = recorder;
|
|
12356
|
+
this.source = source;
|
|
12357
|
+
}
|
|
12358
|
+
observe = (params) => {
|
|
12359
|
+
return this.recorder.record({
|
|
12360
|
+
source: this.source,
|
|
12361
|
+
model: params.model ?? null,
|
|
12362
|
+
usage: params.usage
|
|
12363
|
+
});
|
|
12364
|
+
};
|
|
12365
|
+
};
|
|
12366
|
+
var ObservedProviderManager = class extends ProviderManager {
|
|
12367
|
+
constructor(delegate, observer) {
|
|
12368
|
+
super(delegate.get(null));
|
|
12369
|
+
this.delegate = delegate;
|
|
12370
|
+
this.observer = observer;
|
|
12371
|
+
}
|
|
12372
|
+
get(model) {
|
|
12373
|
+
return this.delegate.get(model);
|
|
12374
|
+
}
|
|
12375
|
+
set(next) {
|
|
12376
|
+
this.delegate.set(next);
|
|
12377
|
+
}
|
|
12378
|
+
setConfig(nextConfig) {
|
|
12379
|
+
this.delegate.setConfig(nextConfig);
|
|
12380
|
+
}
|
|
12381
|
+
async chat(params) {
|
|
12382
|
+
const response = await this.delegate.chat(params);
|
|
12383
|
+
this.observer.observe({
|
|
12384
|
+
model: params.model ?? null,
|
|
12385
|
+
usage: response.usage
|
|
12386
|
+
});
|
|
12387
|
+
return response;
|
|
12388
|
+
}
|
|
12389
|
+
async *chatStream(params) {
|
|
12390
|
+
for await (const event of this.delegate.chatStream(params)) {
|
|
12391
|
+
if (event.type === "done") this.observer.observe({
|
|
12392
|
+
model: params.model ?? null,
|
|
12393
|
+
usage: event.response.usage
|
|
12394
|
+
});
|
|
12395
|
+
yield event;
|
|
12396
|
+
}
|
|
12397
|
+
}
|
|
12398
|
+
};
|
|
12399
|
+
//#endregion
|
|
11713
12400
|
//#region src/cli/commands/ncp/runtime/ui-ncp-agent-handle.ts
|
|
11714
12401
|
function createUiNcpAgentHandle(params) {
|
|
12402
|
+
const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, applyExtensionRegistry, applyMcpConfig, dispose, assetStore } = params;
|
|
11715
12403
|
return {
|
|
11716
12404
|
basePath: "/api/ncp/agent",
|
|
11717
|
-
agentClientEndpoint: createAgentClientFromServer(
|
|
11718
|
-
streamProvider:
|
|
11719
|
-
|
|
12405
|
+
agentClientEndpoint: createAgentClientFromServer(backend),
|
|
12406
|
+
streamProvider: backend,
|
|
12407
|
+
runApi: backend,
|
|
12408
|
+
sessionApi: backend,
|
|
11720
12409
|
listSessionTypes: (describeParams) => {
|
|
11721
|
-
|
|
11722
|
-
return
|
|
12410
|
+
refreshPluginRuntimeRegistrations();
|
|
12411
|
+
return runtimeRegistry.listSessionTypes(describeParams);
|
|
11723
12412
|
},
|
|
11724
12413
|
assetApi: {
|
|
11725
|
-
put: (input) =>
|
|
12414
|
+
put: (input) => assetStore.putBytes({
|
|
11726
12415
|
fileName: input.fileName,
|
|
11727
12416
|
mimeType: input.mimeType,
|
|
11728
12417
|
bytes: input.bytes,
|
|
11729
12418
|
createdAt: input.createdAt
|
|
11730
12419
|
}),
|
|
11731
|
-
stat: (uri) =>
|
|
11732
|
-
resolveContentPath: (uri) =>
|
|
12420
|
+
stat: (uri) => assetStore.statRecord(uri),
|
|
12421
|
+
resolveContentPath: (uri) => assetStore.resolveContentPath(uri)
|
|
11733
12422
|
},
|
|
11734
|
-
applyExtensionRegistry
|
|
11735
|
-
applyMcpConfig
|
|
11736
|
-
dispose
|
|
12423
|
+
applyExtensionRegistry,
|
|
12424
|
+
applyMcpConfig,
|
|
12425
|
+
dispose
|
|
11737
12426
|
};
|
|
11738
12427
|
}
|
|
11739
12428
|
//#endregion
|
|
12429
|
+
//#region src/cli/runtime-state/llm-usage-record.ts
|
|
12430
|
+
var LlmUsageRecordFactory = class {
|
|
12431
|
+
create = (params) => {
|
|
12432
|
+
const { observedAt, source, model, usage: rawUsage } = params;
|
|
12433
|
+
const usage = this.sanitizeUsage(rawUsage);
|
|
12434
|
+
return {
|
|
12435
|
+
version: 1,
|
|
12436
|
+
observedAt: observedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12437
|
+
source,
|
|
12438
|
+
model: this.normalizeModel(model),
|
|
12439
|
+
usage,
|
|
12440
|
+
summary: this.buildSummary(usage)
|
|
12441
|
+
};
|
|
12442
|
+
};
|
|
12443
|
+
sanitizeUsage = (usage) => {
|
|
12444
|
+
const next = {};
|
|
12445
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
12446
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) continue;
|
|
12447
|
+
next[key] = Math.floor(value);
|
|
12448
|
+
}
|
|
12449
|
+
return next;
|
|
12450
|
+
};
|
|
12451
|
+
buildSummary = (usage) => {
|
|
12452
|
+
const promptTokens = usage.prompt_tokens ?? usage.input_tokens ?? 0;
|
|
12453
|
+
const completionTokens = usage.completion_tokens ?? usage.output_tokens ?? 0;
|
|
12454
|
+
const totalTokens = usage.total_tokens ?? promptTokens + completionTokens;
|
|
12455
|
+
const cacheMetricKeys = Object.keys(usage).filter((key) => key.endsWith("cached_tokens"));
|
|
12456
|
+
const cachedTokens = cacheMetricKeys.reduce((max, key) => Math.max(max, usage[key] ?? 0), 0);
|
|
12457
|
+
return {
|
|
12458
|
+
promptTokens,
|
|
12459
|
+
completionTokens,
|
|
12460
|
+
totalTokens,
|
|
12461
|
+
cachedTokens,
|
|
12462
|
+
cacheHit: cachedTokens > 0,
|
|
12463
|
+
cacheMetricKeys
|
|
12464
|
+
};
|
|
12465
|
+
};
|
|
12466
|
+
normalizeModel = (value) => {
|
|
12467
|
+
if (typeof value !== "string") return null;
|
|
12468
|
+
const trimmed = value.trim();
|
|
12469
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
12470
|
+
};
|
|
12471
|
+
};
|
|
12472
|
+
const llmUsageRecordFactory = new LlmUsageRecordFactory();
|
|
12473
|
+
//#endregion
|
|
12474
|
+
//#region src/cli/commands/shared/llm-usage-recorder.ts
|
|
12475
|
+
var LlmUsageRecorder = class {
|
|
12476
|
+
constructor(deps = {}) {
|
|
12477
|
+
this.deps = deps;
|
|
12478
|
+
}
|
|
12479
|
+
record = (params) => {
|
|
12480
|
+
const record = this.recordFactory.create(params);
|
|
12481
|
+
this.snapshotStore.write(record);
|
|
12482
|
+
this.historyStore.append(record);
|
|
12483
|
+
return record;
|
|
12484
|
+
};
|
|
12485
|
+
get snapshotStore() {
|
|
12486
|
+
return this.deps.snapshotStore ?? llmUsageSnapshotStore;
|
|
12487
|
+
}
|
|
12488
|
+
get historyStore() {
|
|
12489
|
+
return this.deps.historyStore ?? llmUsageHistoryStore;
|
|
12490
|
+
}
|
|
12491
|
+
get recordFactory() {
|
|
12492
|
+
return this.deps.recordFactory ?? llmUsageRecordFactory;
|
|
12493
|
+
}
|
|
12494
|
+
};
|
|
12495
|
+
const llmUsageRecorder = new LlmUsageRecorder();
|
|
12496
|
+
//#endregion
|
|
11740
12497
|
//#region src/cli/commands/ncp/create-ui-ncp-agent.ts
|
|
11741
12498
|
const CODEX_RUNTIME_KIND = "codex";
|
|
11742
12499
|
const CODEX_DIRECT_RUNTIME_BACKEND = "codex-sdk";
|
|
@@ -11801,6 +12558,7 @@ async function createMcpRuntimeSupport(getConfig) {
|
|
|
11801
12558
|
};
|
|
11802
12559
|
}
|
|
11803
12560
|
function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker) {
|
|
12561
|
+
const observedProviderManager = new ObservedProviderManager(params.providerManager, new LlmUsageObserver(llmUsageRecorder, "ui-ncp"));
|
|
11804
12562
|
return ({ stateManager, sessionMetadata, setSessionMetadata }) => {
|
|
11805
12563
|
const reasoningNormalizationMode = resolveNativeReasoningNormalizationMode({
|
|
11806
12564
|
config: params.getConfig(),
|
|
@@ -11809,7 +12567,7 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
11809
12567
|
if (reasoningNormalizationMode !== "off" && readAssistantReasoningNormalizationModeFromMetadata(sessionMetadata) !== reasoningNormalizationMode) setSessionMetadata(writeAssistantReasoningNormalizationModeToMetadata(sessionMetadata, reasoningNormalizationMode));
|
|
11810
12568
|
const toolRegistry = new NextclawNcpToolRegistry({
|
|
11811
12569
|
bus: params.bus,
|
|
11812
|
-
providerManager:
|
|
12570
|
+
providerManager: observedProviderManager,
|
|
11813
12571
|
sessionManager: params.sessionManager,
|
|
11814
12572
|
cronService: params.cronService,
|
|
11815
12573
|
gatewayController: params.gatewayController,
|
|
@@ -11827,7 +12585,7 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
11827
12585
|
resolveMessageToolHints: params.resolveMessageToolHints,
|
|
11828
12586
|
assetStore
|
|
11829
12587
|
}),
|
|
11830
|
-
llmApi: new ProviderManagerNcpLLMApi(
|
|
12588
|
+
llmApi: new ProviderManagerNcpLLMApi(observedProviderManager),
|
|
11831
12589
|
toolRegistry,
|
|
11832
12590
|
stateManager,
|
|
11833
12591
|
reasoningNormalizationMode
|
|
@@ -11905,7 +12663,10 @@ async function createUiNcpAgent(params) {
|
|
|
11905
12663
|
onSessionRunStatusChanged: params.onSessionRunStatusChanged,
|
|
11906
12664
|
createRuntime: (runtimeParams) => {
|
|
11907
12665
|
pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
|
|
11908
|
-
return runtimeRegistry.createRuntime(
|
|
12666
|
+
return runtimeRegistry.createRuntime({
|
|
12667
|
+
...runtimeParams,
|
|
12668
|
+
resolveAssetContentPath: (assetUri) => assetStore.resolveContentPath(assetUri)
|
|
12669
|
+
});
|
|
11909
12670
|
}
|
|
11910
12671
|
});
|
|
11911
12672
|
await backend.start();
|
|
@@ -12372,430 +13133,92 @@ var ConfigReloader = class {
|
|
|
12372
13133
|
}
|
|
12373
13134
|
};
|
|
12374
13135
|
//#endregion
|
|
12375
|
-
//#region src/cli/commands/
|
|
12376
|
-
function
|
|
12377
|
-
|
|
13136
|
+
//#region src/cli/commands/service-support/gateway/service-cron-job-handler.ts
|
|
13137
|
+
function normalizeOptionalString$2(value) {
|
|
13138
|
+
if (typeof value !== "string") return;
|
|
13139
|
+
return value.trim() || void 0;
|
|
12378
13140
|
}
|
|
12379
|
-
function
|
|
12380
|
-
|
|
12381
|
-
|
|
13141
|
+
function buildCronSessionMetadata(params) {
|
|
13142
|
+
const { job, agentId, accountId } = params;
|
|
13143
|
+
const channel = normalizeOptionalString$2(job.payload.channel) ?? "cli";
|
|
13144
|
+
const chatId = normalizeOptionalString$2(job.payload.to) ?? "direct";
|
|
13145
|
+
const metadata = {
|
|
13146
|
+
agentId,
|
|
13147
|
+
agent_id: agentId,
|
|
13148
|
+
channel,
|
|
13149
|
+
chatId,
|
|
13150
|
+
chat_id: chatId,
|
|
13151
|
+
label: job.name,
|
|
13152
|
+
cron_job_id: job.id,
|
|
13153
|
+
cron_job_name: job.name,
|
|
13154
|
+
session_origin: "cron"
|
|
13155
|
+
};
|
|
13156
|
+
if (accountId) {
|
|
13157
|
+
metadata.accountId = accountId;
|
|
13158
|
+
metadata.account_id = accountId;
|
|
13159
|
+
}
|
|
13160
|
+
return metadata;
|
|
12382
13161
|
}
|
|
12383
|
-
function
|
|
12384
|
-
|
|
12385
|
-
|
|
13162
|
+
function buildCronUserMessage(params) {
|
|
13163
|
+
const { sessionId, content, metadata } = params;
|
|
13164
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13165
|
+
return {
|
|
13166
|
+
id: `${sessionId}:user:cron:${timestamp}`,
|
|
13167
|
+
sessionId,
|
|
13168
|
+
role: "user",
|
|
13169
|
+
status: "final",
|
|
13170
|
+
timestamp,
|
|
13171
|
+
parts: [{
|
|
13172
|
+
type: "text",
|
|
13173
|
+
text: content
|
|
13174
|
+
}],
|
|
13175
|
+
metadata: structuredClone(metadata)
|
|
13176
|
+
};
|
|
12386
13177
|
}
|
|
12387
|
-
function
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
id: defaultAgentId,
|
|
12393
|
-
workspace: defaults.workspace
|
|
12394
|
-
}];
|
|
12395
|
-
const unique = /* @__PURE__ */ new Map();
|
|
12396
|
-
for (const entry of seed) if (!unique.has(entry.id)) unique.set(entry.id, entry);
|
|
12397
|
-
return Array.from(unique.values()).map((entry) => ({
|
|
12398
|
-
id: entry.id,
|
|
12399
|
-
workspace: getWorkspacePath(entry.workspace),
|
|
12400
|
-
model: entry.model ?? defaults.model,
|
|
12401
|
-
engine: normalizeEngineKind(entry.engine ?? defaults.engine),
|
|
12402
|
-
engineConfig: toRecord(entry.engineConfig) ?? toRecord(defaults.engineConfig),
|
|
12403
|
-
maxIterations: entry.maxToolIterations ?? defaults.maxToolIterations,
|
|
12404
|
-
contextTokens: entry.contextTokens ?? defaults.contextTokens
|
|
12405
|
-
}));
|
|
13178
|
+
function extractMessageText(message) {
|
|
13179
|
+
return message.parts.flatMap((part) => {
|
|
13180
|
+
if (part.type === "text" || part.type === "rich-text") return [part.text];
|
|
13181
|
+
return [];
|
|
13182
|
+
}).map((text) => text.trim()).filter((text) => text.length > 0).join("\n\n");
|
|
12406
13183
|
}
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
this.rebuild(options.config);
|
|
12419
|
-
}
|
|
12420
|
-
get primaryAgentId() {
|
|
12421
|
-
return this.defaultAgentId;
|
|
12422
|
-
}
|
|
12423
|
-
applyRuntimeConfig(config) {
|
|
12424
|
-
this.options.config = config;
|
|
12425
|
-
this.options.contextConfig = config.agents.context;
|
|
12426
|
-
this.options.execConfig = config.tools.exec;
|
|
12427
|
-
this.options.restrictToWorkspace = config.tools.restrictToWorkspace;
|
|
12428
|
-
this.options.searchConfig = config.search;
|
|
12429
|
-
this.routeResolver.updateConfig(config);
|
|
12430
|
-
this.rebuild(config);
|
|
12431
|
-
}
|
|
12432
|
-
applyExtensionRegistry(extensionRegistry) {
|
|
12433
|
-
this.options.extensionRegistry = extensionRegistry;
|
|
12434
|
-
this.rebuild(this.options.config);
|
|
12435
|
-
}
|
|
12436
|
-
setSystemSessionUpdatedHandler(handler) {
|
|
12437
|
-
this.onSystemSessionUpdated = handler;
|
|
12438
|
-
}
|
|
12439
|
-
listAvailableEngineKinds() {
|
|
12440
|
-
const kinds = new Set(["native"]);
|
|
12441
|
-
for (const runtime of this.runtimes.values()) kinds.add(normalizeEngineKind(runtime.engine.kind));
|
|
12442
|
-
for (const registration of this.options.extensionRegistry?.engines ?? []) kinds.add(normalizeEngineKind(registration.kind));
|
|
12443
|
-
return Array.from(kinds).sort((left, right) => {
|
|
12444
|
-
if (left === "native") return -1;
|
|
12445
|
-
if (right === "native") return 1;
|
|
12446
|
-
return left.localeCompare(right);
|
|
12447
|
-
});
|
|
12448
|
-
}
|
|
12449
|
-
async processDirect(params) {
|
|
12450
|
-
const { message, route } = this.resolveDirectRoute({
|
|
12451
|
-
content: params.content,
|
|
12452
|
-
sessionKey: params.sessionKey,
|
|
12453
|
-
channel: params.channel,
|
|
12454
|
-
chatId: params.chatId,
|
|
12455
|
-
attachments: params.attachments,
|
|
12456
|
-
metadata: params.metadata,
|
|
12457
|
-
agentId: params.agentId
|
|
12458
|
-
});
|
|
12459
|
-
const forcedEngineKind = this.readForcedEngineKind(message.metadata);
|
|
12460
|
-
const commandResult = await this.executeDirectCommand(params.content, {
|
|
12461
|
-
channel: message.channel,
|
|
12462
|
-
chatId: message.chatId,
|
|
12463
|
-
sessionKey: route.sessionKey
|
|
12464
|
-
});
|
|
12465
|
-
if (commandResult) return commandResult;
|
|
12466
|
-
const runtime = forcedEngineKind ? this.resolveRuntimeForEngineKind(forcedEngineKind, route.agentId) : this.resolveRuntime(route.agentId);
|
|
12467
|
-
if (message.attachments.length > 0) return (await runtime.engine.handleInbound({
|
|
12468
|
-
message,
|
|
12469
|
-
sessionKey: route.sessionKey,
|
|
12470
|
-
publishResponse: false,
|
|
12471
|
-
onAssistantDelta: params.onAssistantDelta
|
|
12472
|
-
}))?.content ?? "";
|
|
12473
|
-
return runtime.engine.processDirect({
|
|
12474
|
-
content: params.content,
|
|
12475
|
-
sessionKey: route.sessionKey,
|
|
12476
|
-
channel: message.channel,
|
|
12477
|
-
chatId: message.chatId,
|
|
12478
|
-
metadata: message.metadata,
|
|
12479
|
-
abortSignal: params.abortSignal,
|
|
12480
|
-
onAssistantDelta: params.onAssistantDelta,
|
|
12481
|
-
onSessionEvent: params.onSessionEvent
|
|
12482
|
-
});
|
|
12483
|
-
}
|
|
12484
|
-
async executeDirectCommand(rawContent, ctx) {
|
|
12485
|
-
const trimmed = rawContent.trim();
|
|
12486
|
-
if (!trimmed.startsWith("/")) return null;
|
|
12487
|
-
const registry = new CommandRegistry(this.options.config, this.options.sessionManager);
|
|
12488
|
-
const executeText = registry.executeText;
|
|
12489
|
-
if (typeof executeText === "function") return (await executeText.call(registry, rawContent, {
|
|
12490
|
-
channel: ctx.channel,
|
|
12491
|
-
chatId: ctx.chatId,
|
|
12492
|
-
senderId: "user",
|
|
12493
|
-
sessionKey: ctx.sessionKey
|
|
12494
|
-
}))?.content ?? null;
|
|
12495
|
-
const commandRaw = trimmed.slice(1).trim();
|
|
12496
|
-
if (!commandRaw) return null;
|
|
12497
|
-
const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
|
|
12498
|
-
const commandName = nameToken.trim().toLowerCase();
|
|
12499
|
-
if (!commandName) return null;
|
|
12500
|
-
const args = parseCommandArgsFromText(commandName, restTokens.join(" ").trim(), registry.listSlashCommands());
|
|
12501
|
-
return (await registry.execute(commandName, args, {
|
|
12502
|
-
channel: ctx.channel,
|
|
12503
|
-
chatId: ctx.chatId,
|
|
12504
|
-
senderId: "user",
|
|
12505
|
-
sessionKey: ctx.sessionKey
|
|
12506
|
-
}))?.content ?? null;
|
|
12507
|
-
}
|
|
12508
|
-
supportsTurnAbort(params) {
|
|
12509
|
-
const { route } = this.resolveDirectRoute({
|
|
12510
|
-
content: "",
|
|
12511
|
-
sessionKey: params.sessionKey,
|
|
12512
|
-
channel: params.channel,
|
|
12513
|
-
chatId: params.chatId,
|
|
12514
|
-
metadata: params.metadata,
|
|
12515
|
-
agentId: params.agentId
|
|
12516
|
-
});
|
|
12517
|
-
const forcedEngineKind = this.readForcedEngineKind(params.metadata);
|
|
12518
|
-
let runtime;
|
|
12519
|
-
try {
|
|
12520
|
-
runtime = forcedEngineKind ? this.resolveRuntimeForEngineKind(forcedEngineKind, route.agentId) : this.resolveRuntime(route.agentId);
|
|
12521
|
-
} catch (error) {
|
|
12522
|
-
return {
|
|
12523
|
-
supported: false,
|
|
12524
|
-
agentId: route.agentId,
|
|
12525
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
12526
|
-
};
|
|
12527
|
-
}
|
|
12528
|
-
if (!(runtime.engine.supportsAbort ?? runtime.engine.kind === "native")) return {
|
|
12529
|
-
supported: false,
|
|
12530
|
-
agentId: route.agentId,
|
|
12531
|
-
reason: `engine "${runtime.engine.kind}" does not support server-side stop yet`
|
|
12532
|
-
};
|
|
12533
|
-
return {
|
|
12534
|
-
supported: true,
|
|
12535
|
-
agentId: route.agentId
|
|
12536
|
-
};
|
|
12537
|
-
}
|
|
12538
|
-
async run() {
|
|
12539
|
-
this.running = true;
|
|
12540
|
-
while (this.running) {
|
|
12541
|
-
const message = await this.options.bus.consumeInbound();
|
|
12542
|
-
try {
|
|
12543
|
-
const explicitSessionKey = this.readString(message.metadata.session_key_override);
|
|
12544
|
-
const forcedAgentId = this.readString(message.metadata.target_agent_id);
|
|
12545
|
-
const route = this.routeResolver.resolveInbound({
|
|
12546
|
-
message,
|
|
12547
|
-
forcedAgentId,
|
|
12548
|
-
sessionKeyOverride: explicitSessionKey
|
|
12549
|
-
});
|
|
12550
|
-
const runtime = this.resolveRuntime(route.agentId);
|
|
12551
|
-
if (message.channel !== "system") await this.options.bus.publishOutbound(createAssistantStreamResetControlMessage(message));
|
|
12552
|
-
await runtime.engine.handleInbound({
|
|
12553
|
-
message,
|
|
12554
|
-
sessionKey: route.sessionKey,
|
|
12555
|
-
publishResponse: true,
|
|
12556
|
-
onAssistantDelta: message.channel !== "system" ? (delta) => {
|
|
12557
|
-
if (!delta) return;
|
|
12558
|
-
this.options.bus.publishOutbound(createAssistantStreamDeltaControlMessage(message, delta));
|
|
12559
|
-
} : void 0
|
|
12560
|
-
});
|
|
12561
|
-
if (message.channel === "system") this.onSystemSessionUpdated?.({
|
|
12562
|
-
sessionKey: route.sessionKey,
|
|
12563
|
-
message
|
|
12564
|
-
});
|
|
12565
|
-
} catch (error) {
|
|
12566
|
-
await this.options.bus.publishOutbound({
|
|
12567
|
-
channel: message.channel,
|
|
12568
|
-
chatId: message.chatId,
|
|
12569
|
-
content: `Sorry, I encountered an error: ${formatUserFacingError(error)}`,
|
|
12570
|
-
media: [],
|
|
12571
|
-
metadata: {}
|
|
12572
|
-
});
|
|
12573
|
-
}
|
|
12574
|
-
}
|
|
12575
|
-
}
|
|
12576
|
-
readString(value) {
|
|
12577
|
-
if (typeof value !== "string") return;
|
|
12578
|
-
return value.trim() || void 0;
|
|
12579
|
-
}
|
|
12580
|
-
resolveDirectRoute(params) {
|
|
12581
|
-
const message = {
|
|
12582
|
-
channel: params.channel ?? "cli",
|
|
12583
|
-
senderId: "user",
|
|
12584
|
-
chatId: params.chatId ?? "direct",
|
|
12585
|
-
content: params.content,
|
|
12586
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
12587
|
-
attachments: params.attachments ?? [],
|
|
12588
|
-
metadata: params.metadata ?? {}
|
|
12589
|
-
};
|
|
12590
|
-
const forcedAgentId = this.readString(params.agentId) ?? parseAgentScopedSessionKey(params.sessionKey)?.agentId ?? void 0;
|
|
12591
|
-
return {
|
|
12592
|
-
message,
|
|
12593
|
-
route: this.routeResolver.resolveInbound({
|
|
12594
|
-
message,
|
|
12595
|
-
forcedAgentId,
|
|
12596
|
-
sessionKeyOverride: params.sessionKey
|
|
12597
|
-
})
|
|
12598
|
-
};
|
|
12599
|
-
}
|
|
12600
|
-
resolveRuntime(agentId) {
|
|
12601
|
-
const normalized = normalizeAgentId(agentId);
|
|
12602
|
-
const runtime = this.runtimes.get(normalized);
|
|
12603
|
-
if (runtime) return runtime;
|
|
12604
|
-
const fallback = this.runtimes.get(this.defaultAgentId);
|
|
12605
|
-
if (fallback) return fallback;
|
|
12606
|
-
throw new Error("No agent runtime available");
|
|
12607
|
-
}
|
|
12608
|
-
readForcedEngineKind(metadata) {
|
|
12609
|
-
if (!metadata || typeof metadata !== "object") return;
|
|
12610
|
-
const raw = this.readString(metadata.session_type) ?? this.readString(metadata.sessionType) ?? this.readString(metadata.engine_kind) ?? this.readString(metadata.engineKind);
|
|
12611
|
-
return raw ? normalizeEngineKind(raw) : void 0;
|
|
12612
|
-
}
|
|
12613
|
-
findRuntimeByEngineKind(kind, preferredAgentId) {
|
|
12614
|
-
const normalizedKind = normalizeEngineKind(kind);
|
|
12615
|
-
const preferred = preferredAgentId ? this.runtimes.get(normalizeAgentId(preferredAgentId)) : null;
|
|
12616
|
-
if (preferred && normalizeEngineKind(preferred.engine.kind) === normalizedKind) return preferred;
|
|
12617
|
-
for (const runtime of this.runtimes.values()) if (normalizeEngineKind(runtime.engine.kind) === normalizedKind) return runtime;
|
|
12618
|
-
return null;
|
|
12619
|
-
}
|
|
12620
|
-
resolveBaseProfileForDynamicEngine(agentId) {
|
|
12621
|
-
const normalizedAgentId = normalizeAgentId(agentId);
|
|
12622
|
-
return this.resolvedProfiles.find((profile) => profile.id === normalizedAgentId) ?? this.resolvedProfiles.find((profile) => profile.id === this.defaultAgentId) ?? this.resolvedProfiles[0] ?? {
|
|
12623
|
-
id: this.defaultAgentId,
|
|
12624
|
-
workspace: getWorkspacePath(this.options.config.agents.defaults.workspace),
|
|
12625
|
-
model: this.options.config.agents.defaults.model,
|
|
12626
|
-
maxIterations: this.options.config.agents.defaults.maxToolIterations,
|
|
12627
|
-
contextTokens: this.options.config.agents.defaults.contextTokens,
|
|
12628
|
-
engine: "native",
|
|
12629
|
-
engineConfig: toRecord(this.options.config.agents.defaults.engineConfig)
|
|
12630
|
-
};
|
|
12631
|
-
}
|
|
12632
|
-
resolveRuntimeForEngineKind(kind, fallbackAgentId) {
|
|
12633
|
-
const normalizedKind = normalizeEngineKind(kind);
|
|
12634
|
-
const existing = this.findRuntimeByEngineKind(normalizedKind, fallbackAgentId);
|
|
12635
|
-
if (existing) return existing;
|
|
12636
|
-
if (!this.listAvailableEngineKinds().includes(normalizedKind)) throw new Error(`engine "${normalizedKind}" is not available`);
|
|
12637
|
-
const cached = this.dynamicEngineRuntimes.get(normalizedKind);
|
|
12638
|
-
if (cached) return cached;
|
|
12639
|
-
const dynamicProfile = {
|
|
12640
|
-
...this.resolveBaseProfileForDynamicEngine(fallbackAgentId),
|
|
12641
|
-
id: `__session_engine__${normalizedKind}`,
|
|
12642
|
-
engine: normalizedKind
|
|
12643
|
-
};
|
|
12644
|
-
const runtime = {
|
|
12645
|
-
id: dynamicProfile.id,
|
|
12646
|
-
engine: this.createEngine(dynamicProfile, this.options.config)
|
|
12647
|
-
};
|
|
12648
|
-
this.dynamicEngineRuntimes.set(normalizedKind, runtime);
|
|
12649
|
-
return runtime;
|
|
12650
|
-
}
|
|
12651
|
-
createNativeEngineFactory() {
|
|
12652
|
-
return (context) => new NativeAgentEngine({
|
|
12653
|
-
bus: context.bus,
|
|
12654
|
-
providerManager: context.providerManager,
|
|
12655
|
-
workspace: context.workspace,
|
|
12656
|
-
model: context.model,
|
|
12657
|
-
maxIterations: context.maxIterations,
|
|
12658
|
-
contextTokens: context.contextTokens,
|
|
12659
|
-
searchConfig: context.searchConfig,
|
|
12660
|
-
execConfig: context.execConfig,
|
|
12661
|
-
cronService: context.cronService,
|
|
12662
|
-
restrictToWorkspace: context.restrictToWorkspace,
|
|
12663
|
-
sessionManager: context.sessionManager,
|
|
12664
|
-
contextConfig: context.contextConfig,
|
|
12665
|
-
gatewayController: context.gatewayController,
|
|
12666
|
-
config: context.config,
|
|
12667
|
-
extensionRegistry: context.extensionRegistry,
|
|
12668
|
-
resolveMessageToolHints: context.resolveMessageToolHints,
|
|
12669
|
-
agentId: context.agentId
|
|
12670
|
-
});
|
|
12671
|
-
}
|
|
12672
|
-
resolveEngineFactory(kind) {
|
|
12673
|
-
if (kind === "native") return this.createNativeEngineFactory();
|
|
12674
|
-
const matched = (this.options.extensionRegistry?.engines ?? []).find((entry) => normalizeEngineKind(entry.kind) === kind);
|
|
12675
|
-
if (matched) return matched.factory;
|
|
12676
|
-
console.warn(`[engine] unknown engine "${kind}", fallback to "native"`);
|
|
12677
|
-
return this.createNativeEngineFactory();
|
|
12678
|
-
}
|
|
12679
|
-
createEngine(profile, config) {
|
|
12680
|
-
const kind = normalizeEngineKind(profile.engine);
|
|
12681
|
-
const factory = this.resolveEngineFactory(kind);
|
|
12682
|
-
const context = {
|
|
12683
|
-
agentId: profile.id,
|
|
12684
|
-
workspace: profile.workspace,
|
|
12685
|
-
model: profile.model,
|
|
12686
|
-
maxIterations: profile.maxIterations,
|
|
12687
|
-
contextTokens: profile.contextTokens,
|
|
12688
|
-
engineConfig: profile.engineConfig,
|
|
12689
|
-
bus: this.options.bus,
|
|
12690
|
-
providerManager: this.options.providerManager,
|
|
12691
|
-
sessionManager: this.options.sessionManager,
|
|
12692
|
-
cronService: this.options.cronService,
|
|
12693
|
-
restrictToWorkspace: this.options.restrictToWorkspace,
|
|
12694
|
-
searchConfig: this.options.searchConfig,
|
|
12695
|
-
execConfig: this.options.execConfig,
|
|
12696
|
-
contextConfig: this.options.contextConfig,
|
|
12697
|
-
gatewayController: this.options.gatewayController,
|
|
12698
|
-
config,
|
|
12699
|
-
extensionRegistry: this.options.extensionRegistry,
|
|
12700
|
-
resolveMessageToolHints: this.options.resolveMessageToolHints
|
|
12701
|
-
};
|
|
12702
|
-
try {
|
|
12703
|
-
return factory(context);
|
|
12704
|
-
} catch (error) {
|
|
12705
|
-
if (kind === "native") throw error;
|
|
12706
|
-
console.warn(`[engine] failed to create "${kind}" for agent "${profile.id}": ${String(error)}`);
|
|
12707
|
-
return this.createNativeEngineFactory()(context);
|
|
12708
|
-
}
|
|
12709
|
-
}
|
|
12710
|
-
rebuild(config) {
|
|
12711
|
-
const profiles = resolveAgentProfiles(config);
|
|
12712
|
-
this.resolvedProfiles = profiles;
|
|
12713
|
-
this.defaultAgentId = this.readString(config.agents.list.find((entry) => entry.default)?.id) ?? profiles[0]?.id ?? "main";
|
|
12714
|
-
const nextRuntimes = /* @__PURE__ */ new Map();
|
|
12715
|
-
for (const profile of profiles) {
|
|
12716
|
-
const engine = this.createEngine(profile, config);
|
|
12717
|
-
nextRuntimes.set(profile.id, {
|
|
12718
|
-
id: profile.id,
|
|
12719
|
-
engine
|
|
12720
|
-
});
|
|
12721
|
-
}
|
|
12722
|
-
this.runtimes = nextRuntimes;
|
|
12723
|
-
this.dynamicEngineRuntimes.clear();
|
|
12724
|
-
}
|
|
12725
|
-
};
|
|
12726
|
-
function parseCommandArgsFromText(commandName, rawTail, specs) {
|
|
12727
|
-
if (!rawTail) return {};
|
|
12728
|
-
const options = specs.find((item) => item.name.trim().toLowerCase() === commandName)?.options;
|
|
12729
|
-
if (!options || options.length === 0) return {};
|
|
12730
|
-
const tokens = rawTail.split(/\s+/).filter(Boolean);
|
|
12731
|
-
const args = {};
|
|
12732
|
-
let cursor = 0;
|
|
12733
|
-
for (let i = 0; i < options.length; i += 1) {
|
|
12734
|
-
if (cursor >= tokens.length) break;
|
|
12735
|
-
const option = options[i];
|
|
12736
|
-
const isLastOption = i === options.length - 1;
|
|
12737
|
-
const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
|
|
12738
|
-
cursor += isLastOption ? tokens.length - cursor : 1;
|
|
12739
|
-
const parsedValue = parseCommandOptionValue(option.type, rawValue);
|
|
12740
|
-
if (parsedValue !== void 0) args[option.name] = parsedValue;
|
|
12741
|
-
}
|
|
12742
|
-
return args;
|
|
12743
|
-
}
|
|
12744
|
-
function parseCommandOptionValue(type, rawValue) {
|
|
12745
|
-
const value = rawValue.trim();
|
|
12746
|
-
if (!value) return;
|
|
12747
|
-
if (type === "number") {
|
|
12748
|
-
const parsed = Number(value);
|
|
12749
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
12750
|
-
}
|
|
12751
|
-
if (type === "boolean") {
|
|
12752
|
-
const lowered = value.toLowerCase();
|
|
12753
|
-
if ([
|
|
12754
|
-
"1",
|
|
12755
|
-
"true",
|
|
12756
|
-
"yes",
|
|
12757
|
-
"on"
|
|
12758
|
-
].includes(lowered)) return true;
|
|
12759
|
-
if ([
|
|
12760
|
-
"0",
|
|
12761
|
-
"false",
|
|
12762
|
-
"no",
|
|
12763
|
-
"off"
|
|
12764
|
-
].includes(lowered)) return false;
|
|
12765
|
-
return;
|
|
13184
|
+
async function runJobOverNcp(params) {
|
|
13185
|
+
const { agent, sessionId, message, metadata, missingCompletedMessageError, runErrorMessage } = params;
|
|
13186
|
+
let completedMessage;
|
|
13187
|
+
for await (const event of agent.runApi.send({
|
|
13188
|
+
sessionId,
|
|
13189
|
+
message,
|
|
13190
|
+
metadata
|
|
13191
|
+
})) {
|
|
13192
|
+
if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
|
|
13193
|
+
if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? runErrorMessage);
|
|
13194
|
+
if (event.type === NcpEventType.MessageCompleted) completedMessage = event.payload.message;
|
|
12766
13195
|
}
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
function formatUserFacingError(error, maxChars = 320) {
|
|
12770
|
-
const normalized = (error instanceof Error ? error.message || error.name || "Unknown error" : String(error ?? "Unknown error")).replace(/\s+/g, " ").trim();
|
|
12771
|
-
if (!normalized) return "Unknown error";
|
|
12772
|
-
if (normalized.length <= maxChars) return normalized;
|
|
12773
|
-
return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
12774
|
-
}
|
|
12775
|
-
//#endregion
|
|
12776
|
-
//#region src/cli/commands/service-support/gateway/service-cron-job-handler.ts
|
|
12777
|
-
function normalizeOptionalString(value) {
|
|
12778
|
-
if (typeof value !== "string") return;
|
|
12779
|
-
return value.trim() || void 0;
|
|
12780
|
-
}
|
|
12781
|
-
function buildCronJobMetadata(accountId) {
|
|
12782
|
-
if (!accountId) return {};
|
|
12783
|
-
return {
|
|
12784
|
-
accountId,
|
|
12785
|
-
account_id: accountId
|
|
12786
|
-
};
|
|
13196
|
+
if (!completedMessage) throw new Error(missingCompletedMessageError);
|
|
13197
|
+
return extractMessageText(completedMessage);
|
|
12787
13198
|
}
|
|
12788
13199
|
function createCronJobHandler(params) {
|
|
12789
13200
|
return async (job) => {
|
|
12790
|
-
const
|
|
12791
|
-
|
|
12792
|
-
const
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
13201
|
+
const ncpAgent = params.resolveNcpAgent();
|
|
13202
|
+
if (!ncpAgent) throw new Error("NCP agent is not ready for cron execution.");
|
|
13203
|
+
const accountId = normalizeOptionalString$2(job.payload.accountId);
|
|
13204
|
+
const agentId = normalizeOptionalString$2(job.payload.agentId) ?? "main";
|
|
13205
|
+
const sessionId = `cron:${job.id}`;
|
|
13206
|
+
const metadata = buildCronSessionMetadata({
|
|
13207
|
+
job,
|
|
13208
|
+
agentId,
|
|
13209
|
+
accountId
|
|
13210
|
+
});
|
|
13211
|
+
const response = await runJobOverNcp({
|
|
13212
|
+
agent: ncpAgent,
|
|
13213
|
+
sessionId,
|
|
13214
|
+
message: buildCronUserMessage({
|
|
13215
|
+
sessionId,
|
|
13216
|
+
content: job.payload.message,
|
|
13217
|
+
metadata
|
|
13218
|
+
}),
|
|
12797
13219
|
metadata,
|
|
12798
|
-
|
|
13220
|
+
missingCompletedMessageError: "cron job completed without a final assistant message",
|
|
13221
|
+
runErrorMessage: "cron job failed"
|
|
12799
13222
|
});
|
|
12800
13223
|
if (job.payload.deliver && job.payload.to) await params.bus.publishOutbound({
|
|
12801
13224
|
channel: job.payload.channel ?? "cli",
|
|
@@ -12807,21 +13230,81 @@ function createCronJobHandler(params) {
|
|
|
12807
13230
|
return response;
|
|
12808
13231
|
};
|
|
12809
13232
|
}
|
|
13233
|
+
function buildHeartbeatMetadata(params) {
|
|
13234
|
+
return {
|
|
13235
|
+
agentId: params.agentId,
|
|
13236
|
+
agent_id: params.agentId,
|
|
13237
|
+
channel: "cli",
|
|
13238
|
+
chatId: "direct",
|
|
13239
|
+
chat_id: "direct",
|
|
13240
|
+
label: "heartbeat",
|
|
13241
|
+
session_origin: "heartbeat"
|
|
13242
|
+
};
|
|
13243
|
+
}
|
|
13244
|
+
function buildHeartbeatUserMessage(params) {
|
|
13245
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13246
|
+
return {
|
|
13247
|
+
id: `heartbeat:user:${timestamp}`,
|
|
13248
|
+
sessionId: "heartbeat",
|
|
13249
|
+
role: "user",
|
|
13250
|
+
status: "final",
|
|
13251
|
+
timestamp,
|
|
13252
|
+
parts: [{
|
|
13253
|
+
type: "text",
|
|
13254
|
+
text: params.content
|
|
13255
|
+
}],
|
|
13256
|
+
metadata: structuredClone(params.metadata)
|
|
13257
|
+
};
|
|
13258
|
+
}
|
|
13259
|
+
function createHeartbeatJobHandler(params) {
|
|
13260
|
+
return async (prompt) => {
|
|
13261
|
+
const ncpAgent = params.resolveNcpAgent();
|
|
13262
|
+
if (!ncpAgent) throw new Error("NCP agent is not ready for heartbeat execution.");
|
|
13263
|
+
const metadata = buildHeartbeatMetadata({ agentId: params.resolveAgentId() });
|
|
13264
|
+
return await runJobOverNcp({
|
|
13265
|
+
agent: ncpAgent,
|
|
13266
|
+
sessionId: "heartbeat",
|
|
13267
|
+
message: buildHeartbeatUserMessage({
|
|
13268
|
+
content: prompt,
|
|
13269
|
+
metadata
|
|
13270
|
+
}),
|
|
13271
|
+
metadata,
|
|
13272
|
+
missingCompletedMessageError: "heartbeat execution completed without a final assistant message",
|
|
13273
|
+
runErrorMessage: "heartbeat execution failed"
|
|
13274
|
+
});
|
|
13275
|
+
};
|
|
13276
|
+
}
|
|
12810
13277
|
//#endregion
|
|
12811
13278
|
//#region src/cli/commands/service-support/gateway/service-gateway-context.ts
|
|
12812
|
-
const { ChannelManager: ChannelManager$1, CronService: CronService$1, getConfigPath: getConfigPath$2, getDataDir: getDataDir$1, getWorkspacePath: getWorkspacePath$2, HeartbeatService, loadConfig: loadConfig$3, MessageBus: MessageBus$2, ProviderManager: ProviderManager$1, resolveConfigSecrets: resolveConfigSecrets$3, saveConfig: saveConfig$1, SessionManager: SessionManager$
|
|
13279
|
+
const { ChannelManager: ChannelManager$1, CronService: CronService$1, getConfigPath: getConfigPath$2, getDataDir: getDataDir$1, getWorkspacePath: getWorkspacePath$2, HeartbeatService, loadConfig: loadConfig$3, MessageBus: MessageBus$2, ProviderManager: ProviderManager$1, resolveConfigSecrets: resolveConfigSecrets$3, resolveDefaultAgentProfileId: resolveDefaultAgentProfileId$1, saveConfig: saveConfig$1, SessionManager: SessionManager$2 } = NextclawCore;
|
|
12813
13280
|
function applyGatewayCapabilityState(gateway, next) {
|
|
12814
13281
|
gateway.pluginRegistry = next.pluginRegistry;
|
|
12815
13282
|
gateway.pluginChannelBindings = next.pluginChannelBindings;
|
|
12816
13283
|
gateway.extensionRegistry = next.extensionRegistry;
|
|
12817
13284
|
}
|
|
13285
|
+
function normalizeAgentId(value) {
|
|
13286
|
+
return (value ?? "").trim().toLowerCase() || "main";
|
|
13287
|
+
}
|
|
13288
|
+
function createGatewayHeartbeat(state, params) {
|
|
13289
|
+
const handleHeartbeat = createHeartbeatJobHandler({
|
|
13290
|
+
resolveNcpAgent: () => params.getLiveUiNcpAgent?.() ?? null,
|
|
13291
|
+
resolveAgentId: () => normalizeAgentId(resolveDefaultAgentProfileId$1(resolveConfigSecrets$3(loadConfig$3(), { configPath: state.runtimeConfigPath })))
|
|
13292
|
+
});
|
|
13293
|
+
return new HeartbeatService(state.workspace, async (promptText) => await handleHeartbeat(promptText), 1800, true);
|
|
13294
|
+
}
|
|
13295
|
+
function createGatewayCronJobHandler(params) {
|
|
13296
|
+
return createCronJobHandler({
|
|
13297
|
+
resolveNcpAgent: () => params.getLiveUiNcpAgent?.() ?? null,
|
|
13298
|
+
bus: params.bus
|
|
13299
|
+
});
|
|
13300
|
+
}
|
|
12818
13301
|
function createGatewayShellContext(params) {
|
|
12819
13302
|
const runtimeConfigPath = getConfigPath$2();
|
|
12820
13303
|
const config = resolveConfigSecrets$3(loadConfig$3(), { configPath: runtimeConfigPath });
|
|
12821
13304
|
const workspace = getWorkspacePath$2(config.agents.defaults.workspace);
|
|
12822
13305
|
const homeDir = getDataDir$1();
|
|
12823
13306
|
const cronStorePath = join(getDataDir$1(), "cron", "jobs.json");
|
|
12824
|
-
const sessionManager = measureStartupSync("service.gateway_shell_context.session_manager", () => new SessionManager$
|
|
13307
|
+
const sessionManager = measureStartupSync("service.gateway_shell_context.session_manager", () => new SessionManager$2({
|
|
12825
13308
|
workspace,
|
|
12826
13309
|
homeDir
|
|
12827
13310
|
}));
|
|
@@ -12842,10 +13325,11 @@ function createGatewayShellContext(params) {
|
|
|
12842
13325
|
};
|
|
12843
13326
|
}
|
|
12844
13327
|
function createGatewayStartupContext(params) {
|
|
13328
|
+
const { shellContext: providedShellContext, uiOverrides, allowMissingProvider, uiStaticDir, initialPluginRegistry, makeProvider, makeMissingProvider, requestRestart, getLiveUiNcpAgent } = params;
|
|
12845
13329
|
const state = {};
|
|
12846
|
-
const shellContext =
|
|
12847
|
-
uiOverrides
|
|
12848
|
-
uiStaticDir
|
|
13330
|
+
const shellContext = providedShellContext ?? createGatewayShellContext({
|
|
13331
|
+
uiOverrides,
|
|
13332
|
+
uiStaticDir
|
|
12849
13333
|
});
|
|
12850
13334
|
state.runtimeConfigPath = shellContext.runtimeConfigPath;
|
|
12851
13335
|
state.config = shellContext.config;
|
|
@@ -12855,14 +13339,14 @@ function createGatewayStartupContext(params) {
|
|
|
12855
13339
|
state.uiConfig = shellContext.uiConfig;
|
|
12856
13340
|
state.uiStaticDir = shellContext.uiStaticDir;
|
|
12857
13341
|
state.remoteModule = shellContext.remoteModule;
|
|
12858
|
-
state.pluginRegistry =
|
|
13342
|
+
state.pluginRegistry = initialPluginRegistry ?? measureStartupSync("service.gateway_context.load_plugin_registry", () => loadPluginRegistry(state.config, state.workspace));
|
|
12859
13343
|
state.pluginChannelBindings = measureStartupSync("service.gateway_context.get_plugin_channel_bindings", () => getPluginChannelBindings(state.pluginRegistry));
|
|
12860
13344
|
state.extensionRegistry = measureStartupSync("service.gateway_context.to_extension_registry", () => toExtensionRegistry(state.pluginRegistry));
|
|
12861
13345
|
logPluginDiagnostics(state.pluginRegistry);
|
|
12862
13346
|
state.bus = new MessageBus$2();
|
|
12863
|
-
const provider =
|
|
13347
|
+
const provider = allowMissingProvider === true ? makeProvider(state.config, { allowMissing: true }) : makeProvider(state.config);
|
|
12864
13348
|
state.providerManager = measureStartupSync("service.gateway_context.provider_manager", () => new ProviderManager$1({
|
|
12865
|
-
defaultProvider: provider ??
|
|
13349
|
+
defaultProvider: provider ?? makeMissingProvider(state.config),
|
|
12866
13350
|
config: state.config
|
|
12867
13351
|
}));
|
|
12868
13352
|
if (!provider) console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
|
|
@@ -12873,12 +13357,12 @@ function createGatewayStartupContext(params) {
|
|
|
12873
13357
|
bus: state.bus,
|
|
12874
13358
|
sessionManager: state.sessionManager,
|
|
12875
13359
|
providerManager: state.providerManager,
|
|
12876
|
-
makeProvider: (nextConfig) =>
|
|
13360
|
+
makeProvider: (nextConfig) => makeProvider(nextConfig, { allowMissing: true }) ?? makeMissingProvider(nextConfig),
|
|
12877
13361
|
loadConfig: () => resolveConfigSecrets$3(loadConfig$3(), { configPath: state.runtimeConfigPath }),
|
|
12878
13362
|
resolveChannelConfig: (nextConfig) => resolveChannelConfigView(nextConfig, state.pluginChannelBindings),
|
|
12879
13363
|
getExtensionChannels: () => state.extensionRegistry.channels,
|
|
12880
13364
|
onRestartRequired: (paths) => {
|
|
12881
|
-
|
|
13365
|
+
requestRestart({
|
|
12882
13366
|
reason: `config reload requires restart: ${paths.join(", ")}`,
|
|
12883
13367
|
manualMessage: `Config changes require restart: ${paths.join(", ")}`,
|
|
12884
13368
|
strategy: "background-service-or-manual"
|
|
@@ -12895,7 +13379,7 @@ function createGatewayStartupContext(params) {
|
|
|
12895
13379
|
getConfigPath: getConfigPath$2,
|
|
12896
13380
|
saveConfig: saveConfig$1,
|
|
12897
13381
|
requestRestart: async (options) => {
|
|
12898
|
-
await
|
|
13382
|
+
await requestRestart({
|
|
12899
13383
|
reason: options?.reason ?? "gateway tool restart",
|
|
12900
13384
|
manualMessage: "Restart the gateway to apply changes.",
|
|
12901
13385
|
strategy: "background-service-or-exit",
|
|
@@ -12904,37 +13388,356 @@ function createGatewayStartupContext(params) {
|
|
|
12904
13388
|
});
|
|
12905
13389
|
}
|
|
12906
13390
|
}));
|
|
12907
|
-
state.
|
|
13391
|
+
state.cron.onJob = createGatewayCronJobHandler({
|
|
12908
13392
|
bus: state.bus,
|
|
12909
|
-
|
|
12910
|
-
sessionManager: state.sessionManager,
|
|
12911
|
-
config: state.config,
|
|
12912
|
-
cronService: state.cron,
|
|
12913
|
-
restrictToWorkspace: state.config.tools.restrictToWorkspace,
|
|
12914
|
-
searchConfig: state.config.search,
|
|
12915
|
-
execConfig: state.config.tools.exec,
|
|
12916
|
-
contextConfig: state.config.agents.context,
|
|
12917
|
-
gatewayController: state.gatewayController,
|
|
12918
|
-
extensionRegistry: state.extensionRegistry,
|
|
12919
|
-
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
12920
|
-
registry: state.pluginRegistry,
|
|
12921
|
-
channel,
|
|
12922
|
-
cfg: resolveConfigSecrets$3(loadConfig$3(), { configPath: state.runtimeConfigPath }),
|
|
12923
|
-
accountId
|
|
12924
|
-
})
|
|
12925
|
-
}));
|
|
12926
|
-
state.cron.onJob = createCronJobHandler({
|
|
12927
|
-
runtimePool: state.runtimePool,
|
|
12928
|
-
bus: state.bus
|
|
13393
|
+
getLiveUiNcpAgent
|
|
12929
13394
|
});
|
|
12930
|
-
state.heartbeat =
|
|
12931
|
-
content: promptText,
|
|
12932
|
-
sessionKey: "heartbeat",
|
|
12933
|
-
agentId: state.runtimePool.primaryAgentId
|
|
12934
|
-
}), 1800, true);
|
|
13395
|
+
state.heartbeat = createGatewayHeartbeat(state, { getLiveUiNcpAgent });
|
|
12935
13396
|
return state;
|
|
12936
13397
|
}
|
|
12937
13398
|
//#endregion
|
|
13399
|
+
//#region src/cli/commands/ncp/runtime/nextclaw-ncp-runner.ts
|
|
13400
|
+
function normalizeOptionalString$1(value) {
|
|
13401
|
+
if (typeof value !== "string") return;
|
|
13402
|
+
return value.trim() || void 0;
|
|
13403
|
+
}
|
|
13404
|
+
function resolveAttachmentName(attachment) {
|
|
13405
|
+
const explicitName = normalizeOptionalString$1(attachment.name);
|
|
13406
|
+
if (explicitName) return explicitName;
|
|
13407
|
+
const explicitPath = normalizeOptionalString$1(attachment.path);
|
|
13408
|
+
if (explicitPath) return basename(explicitPath);
|
|
13409
|
+
const explicitUrl = normalizeOptionalString$1(attachment.url);
|
|
13410
|
+
if (explicitUrl) try {
|
|
13411
|
+
return basename(new URL(explicitUrl).pathname) || "asset.bin";
|
|
13412
|
+
} catch {
|
|
13413
|
+
return basename(explicitUrl) || "asset.bin";
|
|
13414
|
+
}
|
|
13415
|
+
return "asset.bin";
|
|
13416
|
+
}
|
|
13417
|
+
async function attachmentToPart(attachment, assetApi) {
|
|
13418
|
+
const assetUri = normalizeOptionalString$1(attachment.assetUri);
|
|
13419
|
+
if (assetUri) return createFilePartFromAssetUri(attachment, assetUri);
|
|
13420
|
+
const remoteUrl = normalizeOptionalString$1(attachment.url);
|
|
13421
|
+
const localPath = normalizeOptionalString$1(attachment.path);
|
|
13422
|
+
if (localPath) return await createFilePartFromLocalPath(attachment, localPath, assetApi);
|
|
13423
|
+
if (remoteUrl) return createFilePartFromRemoteUrl(attachment, remoteUrl);
|
|
13424
|
+
throw new Error(`Unsupported attachment payload for "${resolveAttachmentName(attachment)}".`);
|
|
13425
|
+
}
|
|
13426
|
+
function createBaseFilePart(attachment) {
|
|
13427
|
+
return {
|
|
13428
|
+
...attachment.name ? { name: attachment.name } : {},
|
|
13429
|
+
...attachment.mimeType ? { mimeType: attachment.mimeType } : {},
|
|
13430
|
+
...typeof attachment.size === "number" ? { sizeBytes: attachment.size } : {}
|
|
13431
|
+
};
|
|
13432
|
+
}
|
|
13433
|
+
function createFilePartFromAssetUri(attachment, assetUri) {
|
|
13434
|
+
return {
|
|
13435
|
+
type: "file",
|
|
13436
|
+
assetUri,
|
|
13437
|
+
...createBaseFilePart(attachment)
|
|
13438
|
+
};
|
|
13439
|
+
}
|
|
13440
|
+
async function createFilePartFromLocalPath(attachment, localPath, assetApi) {
|
|
13441
|
+
if (!assetApi) throw new Error("NCP asset api is unavailable for local attachments.");
|
|
13442
|
+
const fileName = resolveAttachmentName(attachment);
|
|
13443
|
+
const bytes = await readFile(localPath);
|
|
13444
|
+
return {
|
|
13445
|
+
type: "file",
|
|
13446
|
+
assetUri: (await assetApi.put({
|
|
13447
|
+
fileName,
|
|
13448
|
+
mimeType: attachment.mimeType ?? null,
|
|
13449
|
+
bytes
|
|
13450
|
+
})).uri,
|
|
13451
|
+
name: attachment.name ?? fileName,
|
|
13452
|
+
...attachment.mimeType ? { mimeType: attachment.mimeType } : {},
|
|
13453
|
+
...typeof attachment.size === "number" ? { sizeBytes: attachment.size } : {}
|
|
13454
|
+
};
|
|
13455
|
+
}
|
|
13456
|
+
function createFilePartFromRemoteUrl(attachment, remoteUrl) {
|
|
13457
|
+
return {
|
|
13458
|
+
type: "file",
|
|
13459
|
+
url: remoteUrl,
|
|
13460
|
+
name: attachment.name ?? resolveAttachmentName(attachment),
|
|
13461
|
+
...attachment.mimeType ? { mimeType: attachment.mimeType } : {},
|
|
13462
|
+
...typeof attachment.size === "number" ? { sizeBytes: attachment.size } : {}
|
|
13463
|
+
};
|
|
13464
|
+
}
|
|
13465
|
+
async function buildUserMessageParts(params) {
|
|
13466
|
+
const parts = [];
|
|
13467
|
+
if (params.content.length > 0) parts.push({
|
|
13468
|
+
type: "text",
|
|
13469
|
+
text: params.content
|
|
13470
|
+
});
|
|
13471
|
+
for (const attachment of params.attachments ?? []) parts.push(await attachmentToPart(attachment, params.assetApi));
|
|
13472
|
+
return parts;
|
|
13473
|
+
}
|
|
13474
|
+
async function buildNcpUserMessage(params) {
|
|
13475
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13476
|
+
return {
|
|
13477
|
+
id: `${params.sessionId}:user:${timestamp}`,
|
|
13478
|
+
sessionId: params.sessionId,
|
|
13479
|
+
role: "user",
|
|
13480
|
+
status: "final",
|
|
13481
|
+
timestamp,
|
|
13482
|
+
parts: await buildUserMessageParts({
|
|
13483
|
+
content: params.content,
|
|
13484
|
+
attachments: params.attachments,
|
|
13485
|
+
assetApi: params.assetApi
|
|
13486
|
+
}),
|
|
13487
|
+
metadata: structuredClone(params.metadata ?? {})
|
|
13488
|
+
};
|
|
13489
|
+
}
|
|
13490
|
+
async function runPromptOverNcp(params) {
|
|
13491
|
+
const message = await buildNcpUserMessage({
|
|
13492
|
+
sessionId: params.sessionId,
|
|
13493
|
+
content: params.content,
|
|
13494
|
+
attachments: params.attachments,
|
|
13495
|
+
metadata: params.metadata,
|
|
13496
|
+
assetApi: params.agent.assetApi
|
|
13497
|
+
});
|
|
13498
|
+
let completedMessage;
|
|
13499
|
+
for await (const event of params.agent.runApi.send({
|
|
13500
|
+
sessionId: params.sessionId,
|
|
13501
|
+
message,
|
|
13502
|
+
metadata: params.metadata
|
|
13503
|
+
}, { ...params.abortSignal ? { signal: params.abortSignal } : {} })) {
|
|
13504
|
+
params.onEvent?.(event);
|
|
13505
|
+
if (event.type === NcpEventType.MessageTextDelta) {
|
|
13506
|
+
params.onAssistantDelta?.(event.payload.delta);
|
|
13507
|
+
continue;
|
|
13508
|
+
}
|
|
13509
|
+
if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
|
|
13510
|
+
if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? params.runErrorMessage ?? "NCP run failed.");
|
|
13511
|
+
if (event.type === NcpEventType.MessageCompleted) completedMessage = event.payload.message;
|
|
13512
|
+
}
|
|
13513
|
+
if (!completedMessage) throw new Error(params.missingCompletedMessageError ?? "NCP run completed without a final assistant message.");
|
|
13514
|
+
return {
|
|
13515
|
+
text: extractTextFromNcpMessage(completedMessage),
|
|
13516
|
+
completedMessage
|
|
13517
|
+
};
|
|
13518
|
+
}
|
|
13519
|
+
//#endregion
|
|
13520
|
+
//#region src/cli/commands/ncp/runtime/nextclaw-ncp-dispatch.ts
|
|
13521
|
+
function normalizeOptionalString(value) {
|
|
13522
|
+
if (typeof value !== "string") return;
|
|
13523
|
+
return value.trim() || void 0;
|
|
13524
|
+
}
|
|
13525
|
+
function requireNcpAgent(resolveNcpAgent, purpose) {
|
|
13526
|
+
const agent = resolveNcpAgent?.() ?? null;
|
|
13527
|
+
if (!agent) throw new Error(`NCP agent is not ready for ${purpose}.`);
|
|
13528
|
+
return agent;
|
|
13529
|
+
}
|
|
13530
|
+
function createDirectInboundMessage(params) {
|
|
13531
|
+
return {
|
|
13532
|
+
channel: params.channel ?? "cli",
|
|
13533
|
+
senderId: "user",
|
|
13534
|
+
chatId: params.chatId ?? "direct",
|
|
13535
|
+
content: params.content,
|
|
13536
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
13537
|
+
attachments: params.attachments ?? [],
|
|
13538
|
+
metadata: structuredClone(params.metadata ?? {})
|
|
13539
|
+
};
|
|
13540
|
+
}
|
|
13541
|
+
function buildRunMetadata(params) {
|
|
13542
|
+
return {
|
|
13543
|
+
...params.message.metadata ?? {},
|
|
13544
|
+
...params.metadata ?? {},
|
|
13545
|
+
channel: params.message.channel,
|
|
13546
|
+
chatId: params.message.chatId,
|
|
13547
|
+
chat_id: params.message.chatId,
|
|
13548
|
+
accountId: params.route.accountId,
|
|
13549
|
+
account_id: params.route.accountId,
|
|
13550
|
+
agentId: params.route.agentId,
|
|
13551
|
+
agent_id: params.route.agentId,
|
|
13552
|
+
sessionKey: params.route.sessionKey,
|
|
13553
|
+
session_key: params.route.sessionKey,
|
|
13554
|
+
senderId: params.message.senderId,
|
|
13555
|
+
sender_id: params.message.senderId
|
|
13556
|
+
};
|
|
13557
|
+
}
|
|
13558
|
+
function parseCommandOptionValue(type, rawValue) {
|
|
13559
|
+
const value = rawValue.trim();
|
|
13560
|
+
if (!value) return;
|
|
13561
|
+
if (type === "number") {
|
|
13562
|
+
const parsed = Number(value);
|
|
13563
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
13564
|
+
}
|
|
13565
|
+
if (type === "boolean") {
|
|
13566
|
+
const lowered = value.toLowerCase();
|
|
13567
|
+
if ([
|
|
13568
|
+
"1",
|
|
13569
|
+
"true",
|
|
13570
|
+
"yes",
|
|
13571
|
+
"on"
|
|
13572
|
+
].includes(lowered)) return true;
|
|
13573
|
+
if ([
|
|
13574
|
+
"0",
|
|
13575
|
+
"false",
|
|
13576
|
+
"no",
|
|
13577
|
+
"off"
|
|
13578
|
+
].includes(lowered)) return false;
|
|
13579
|
+
return;
|
|
13580
|
+
}
|
|
13581
|
+
return value;
|
|
13582
|
+
}
|
|
13583
|
+
function parseCommandArgsFromText(commandName, rawTail, specs) {
|
|
13584
|
+
if (!rawTail) return {};
|
|
13585
|
+
const options = specs.find((item) => item.name.trim().toLowerCase() === commandName)?.options;
|
|
13586
|
+
if (!options || options.length === 0) return {};
|
|
13587
|
+
const tokens = rawTail.split(/\s+/).filter(Boolean);
|
|
13588
|
+
const args = {};
|
|
13589
|
+
let cursor = 0;
|
|
13590
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
13591
|
+
if (cursor >= tokens.length) break;
|
|
13592
|
+
const option = options[index];
|
|
13593
|
+
const isLastOption = index === options.length - 1;
|
|
13594
|
+
const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
|
|
13595
|
+
cursor += isLastOption ? tokens.length - cursor : 1;
|
|
13596
|
+
const parsedValue = parseCommandOptionValue(option.type, rawValue);
|
|
13597
|
+
if (parsedValue !== void 0) args[option.name] = parsedValue;
|
|
13598
|
+
}
|
|
13599
|
+
return args;
|
|
13600
|
+
}
|
|
13601
|
+
async function executeSlashCommandMaybe(params) {
|
|
13602
|
+
const trimmed = params.rawContent.trim();
|
|
13603
|
+
if (!trimmed.startsWith("/")) return null;
|
|
13604
|
+
const registry = new CommandRegistry(params.config, params.sessionManager);
|
|
13605
|
+
const executeText = registry.executeText;
|
|
13606
|
+
if (typeof executeText === "function") return (await executeText.call(registry, params.rawContent, {
|
|
13607
|
+
channel: params.channel,
|
|
13608
|
+
chatId: params.chatId,
|
|
13609
|
+
senderId: "user",
|
|
13610
|
+
sessionKey: params.sessionKey
|
|
13611
|
+
}))?.content ?? null;
|
|
13612
|
+
const commandRaw = trimmed.slice(1).trim();
|
|
13613
|
+
if (!commandRaw) return null;
|
|
13614
|
+
const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
|
|
13615
|
+
const commandName = nameToken.trim().toLowerCase();
|
|
13616
|
+
if (!commandName) return null;
|
|
13617
|
+
const args = parseCommandArgsFromText(commandName, restTokens.join(" ").trim(), registry.listSlashCommands());
|
|
13618
|
+
return (await registry.execute(commandName, args, {
|
|
13619
|
+
channel: params.channel,
|
|
13620
|
+
chatId: params.chatId,
|
|
13621
|
+
senderId: "user",
|
|
13622
|
+
sessionKey: params.sessionKey
|
|
13623
|
+
}))?.content ?? null;
|
|
13624
|
+
}
|
|
13625
|
+
function resolveDirectRoute(params) {
|
|
13626
|
+
const message = createDirectInboundMessage(params);
|
|
13627
|
+
const forcedAgentId = normalizeOptionalString(params.agentId) ?? parseAgentScopedSessionKey(params.sessionKey)?.agentId ?? void 0;
|
|
13628
|
+
return {
|
|
13629
|
+
message,
|
|
13630
|
+
route: new AgentRouteResolver(params.config).resolveInbound({
|
|
13631
|
+
message,
|
|
13632
|
+
forcedAgentId,
|
|
13633
|
+
sessionKeyOverride: params.sessionKey
|
|
13634
|
+
})
|
|
13635
|
+
};
|
|
13636
|
+
}
|
|
13637
|
+
function formatUserFacingError(error, maxChars = 320) {
|
|
13638
|
+
const normalized = (error instanceof Error ? error.message || error.name || "Unknown error" : String(error ?? "Unknown error")).replace(/\s+/g, " ").trim();
|
|
13639
|
+
if (!normalized) return "Unknown error";
|
|
13640
|
+
if (normalized.length <= maxChars) return normalized;
|
|
13641
|
+
return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
13642
|
+
}
|
|
13643
|
+
async function dispatchPromptOverNcp(params) {
|
|
13644
|
+
const { message, route } = resolveDirectRoute({
|
|
13645
|
+
config: params.config,
|
|
13646
|
+
content: params.content,
|
|
13647
|
+
sessionKey: params.sessionKey,
|
|
13648
|
+
channel: params.channel,
|
|
13649
|
+
chatId: params.chatId,
|
|
13650
|
+
attachments: params.attachments,
|
|
13651
|
+
metadata: params.metadata,
|
|
13652
|
+
agentId: params.agentId
|
|
13653
|
+
});
|
|
13654
|
+
const commandResult = await executeSlashCommandMaybe({
|
|
13655
|
+
config: params.config,
|
|
13656
|
+
sessionManager: params.sessionManager,
|
|
13657
|
+
rawContent: params.content,
|
|
13658
|
+
channel: message.channel,
|
|
13659
|
+
chatId: message.chatId,
|
|
13660
|
+
sessionKey: route.sessionKey
|
|
13661
|
+
});
|
|
13662
|
+
if (commandResult) return commandResult;
|
|
13663
|
+
return (await runPromptOverNcp({
|
|
13664
|
+
agent: requireNcpAgent(params.resolveNcpAgent, "direct dispatch"),
|
|
13665
|
+
sessionId: route.sessionKey,
|
|
13666
|
+
content: params.content,
|
|
13667
|
+
attachments: params.attachments,
|
|
13668
|
+
metadata: buildRunMetadata({
|
|
13669
|
+
message,
|
|
13670
|
+
route
|
|
13671
|
+
}),
|
|
13672
|
+
abortSignal: params.abortSignal,
|
|
13673
|
+
onAssistantDelta: params.onAssistantDelta,
|
|
13674
|
+
missingCompletedMessageError: `session "${route.sessionKey}" completed without a final assistant message`,
|
|
13675
|
+
runErrorMessage: `session "${route.sessionKey}" failed`
|
|
13676
|
+
})).text;
|
|
13677
|
+
}
|
|
13678
|
+
async function runGatewayInboundLoop(params) {
|
|
13679
|
+
while (true) {
|
|
13680
|
+
const message = await params.bus.consumeInbound();
|
|
13681
|
+
try {
|
|
13682
|
+
const explicitSessionKey = normalizeOptionalString(message.metadata.session_key_override);
|
|
13683
|
+
const forcedAgentId = normalizeOptionalString(message.metadata.target_agent_id);
|
|
13684
|
+
const route = new AgentRouteResolver(params.getConfig()).resolveInbound({
|
|
13685
|
+
message,
|
|
13686
|
+
forcedAgentId,
|
|
13687
|
+
sessionKeyOverride: explicitSessionKey
|
|
13688
|
+
});
|
|
13689
|
+
const agent = requireNcpAgent(params.resolveNcpAgent, "gateway dispatch");
|
|
13690
|
+
if (message.channel !== "system") await params.bus.publishOutbound(createAssistantStreamResetControlMessage(message));
|
|
13691
|
+
const result = await runPromptOverNcp({
|
|
13692
|
+
agent,
|
|
13693
|
+
sessionId: route.sessionKey,
|
|
13694
|
+
content: message.content,
|
|
13695
|
+
attachments: message.attachments,
|
|
13696
|
+
metadata: buildRunMetadata({
|
|
13697
|
+
message,
|
|
13698
|
+
route
|
|
13699
|
+
}),
|
|
13700
|
+
onAssistantDelta: message.channel !== "system" ? (delta) => {
|
|
13701
|
+
if (!delta) return;
|
|
13702
|
+
params.bus.publishOutbound(createAssistantStreamDeltaControlMessage(message, delta));
|
|
13703
|
+
} : void 0,
|
|
13704
|
+
missingCompletedMessageError: `session "${route.sessionKey}" completed without a final assistant message`,
|
|
13705
|
+
runErrorMessage: `session "${route.sessionKey}" failed`
|
|
13706
|
+
});
|
|
13707
|
+
if (message.channel === "system") {
|
|
13708
|
+
params.onSystemSessionUpdated?.({
|
|
13709
|
+
sessionKey: route.sessionKey,
|
|
13710
|
+
message
|
|
13711
|
+
});
|
|
13712
|
+
continue;
|
|
13713
|
+
}
|
|
13714
|
+
if (!result.text.trim()) {
|
|
13715
|
+
await params.bus.publishOutbound(createTypingStopControlMessage(message));
|
|
13716
|
+
continue;
|
|
13717
|
+
}
|
|
13718
|
+
await params.bus.publishOutbound({
|
|
13719
|
+
channel: message.channel,
|
|
13720
|
+
chatId: message.chatId,
|
|
13721
|
+
content: result.text,
|
|
13722
|
+
media: [],
|
|
13723
|
+
metadata: buildRunMetadata({
|
|
13724
|
+
message,
|
|
13725
|
+
route,
|
|
13726
|
+
metadata: result.completedMessage.metadata
|
|
13727
|
+
})
|
|
13728
|
+
});
|
|
13729
|
+
} catch (error) {
|
|
13730
|
+
await params.bus.publishOutbound({
|
|
13731
|
+
channel: message.channel,
|
|
13732
|
+
chatId: message.chatId,
|
|
13733
|
+
content: `Sorry, I encountered an error: ${formatUserFacingError(error)}`,
|
|
13734
|
+
media: [],
|
|
13735
|
+
metadata: {}
|
|
13736
|
+
});
|
|
13737
|
+
}
|
|
13738
|
+
}
|
|
13739
|
+
}
|
|
13740
|
+
//#endregion
|
|
12938
13741
|
//#region src/cli/commands/service-support/session/service-deferred-ncp-agent.ts
|
|
12939
13742
|
const DEFAULT_BASE_PATH = "/api/ncp/agent";
|
|
12940
13743
|
const DEFERRED_NCP_AGENT_UNAVAILABLE = "ncp agent unavailable during startup";
|
|
@@ -13020,13 +13823,13 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
|
|
|
13020
13823
|
}
|
|
13021
13824
|
//#endregion
|
|
13022
13825
|
//#region src/cli/commands/service-support/gateway/service-gateway-startup.ts
|
|
13023
|
-
function
|
|
13024
|
-
|
|
13826
|
+
function createSystemSessionUpdatedPublisher(params) {
|
|
13827
|
+
return ({ sessionKey }) => {
|
|
13025
13828
|
params.publishUiEvent?.({
|
|
13026
13829
|
type: "session.updated",
|
|
13027
13830
|
payload: { sessionKey }
|
|
13028
13831
|
});
|
|
13029
|
-
}
|
|
13832
|
+
};
|
|
13030
13833
|
}
|
|
13031
13834
|
async function startUiShell(params) {
|
|
13032
13835
|
logStartupTrace("service.start_ui_shell.begin");
|
|
@@ -13067,54 +13870,90 @@ async function startUiShell(params) {
|
|
|
13067
13870
|
};
|
|
13068
13871
|
}
|
|
13069
13872
|
async function startDeferredGatewayStartup(params) {
|
|
13873
|
+
const { uiStartup, deferredNcpSessionService, bus, sessionManager, providerManager, cronService, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints, hydrateCapabilities, startPluginGateways, startChannels, wakeFromRestartSentinel, onNcpAgentReady, publishSessionChange } = params;
|
|
13070
13874
|
logStartupTrace("service.deferred_startup.begin");
|
|
13071
|
-
|
|
13875
|
+
try {
|
|
13072
13876
|
const ncpAgent = await measureStartupAsync("service.deferred_startup.create_ui_ncp_agent", async () => await createUiNcpAgent({
|
|
13073
|
-
bus
|
|
13074
|
-
providerManager
|
|
13075
|
-
sessionManager
|
|
13076
|
-
cronService
|
|
13077
|
-
gatewayController
|
|
13078
|
-
getConfig
|
|
13079
|
-
getExtensionRegistry
|
|
13080
|
-
onSessionUpdated:
|
|
13877
|
+
bus,
|
|
13878
|
+
providerManager,
|
|
13879
|
+
sessionManager,
|
|
13880
|
+
cronService,
|
|
13881
|
+
gatewayController,
|
|
13882
|
+
getConfig,
|
|
13883
|
+
getExtensionRegistry,
|
|
13884
|
+
onSessionUpdated: publishSessionChange,
|
|
13081
13885
|
onSessionRunStatusChanged: (payload) => {
|
|
13082
|
-
|
|
13886
|
+
uiStartup?.publish({
|
|
13083
13887
|
type: "session.run-status",
|
|
13084
13888
|
payload
|
|
13085
13889
|
});
|
|
13086
13890
|
},
|
|
13087
|
-
resolveMessageToolHints: ({ channel, accountId }) =>
|
|
13891
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolveMessageToolHints({
|
|
13088
13892
|
channel,
|
|
13089
13893
|
accountId
|
|
13090
13894
|
})
|
|
13091
13895
|
}));
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13896
|
+
deferredNcpSessionService.activate(ncpAgent.sessionApi);
|
|
13897
|
+
onNcpAgentReady(ncpAgent);
|
|
13898
|
+
if (uiStartup) {
|
|
13899
|
+
uiStartup.deferredNcpAgent.activate(ncpAgent);
|
|
13900
|
+
console.log("✓ UI NCP agent: ready");
|
|
13901
|
+
} else console.log("✓ Service NCP agent: ready");
|
|
13096
13902
|
} catch (error) {
|
|
13097
13903
|
console.error(`UI NCP agent startup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
13098
13904
|
}
|
|
13099
|
-
if (
|
|
13100
|
-
await measureStartupAsync("service.deferred_startup.start_plugin_gateways",
|
|
13101
|
-
await measureStartupAsync("service.deferred_startup.start_channels",
|
|
13102
|
-
await measureStartupAsync("service.deferred_startup.wake_restart_sentinel",
|
|
13905
|
+
if (hydrateCapabilities) await measureStartupAsync("service.deferred_startup.hydrate_capabilities", hydrateCapabilities);
|
|
13906
|
+
await measureStartupAsync("service.deferred_startup.start_plugin_gateways", startPluginGateways);
|
|
13907
|
+
await measureStartupAsync("service.deferred_startup.start_channels", startChannels);
|
|
13908
|
+
await measureStartupAsync("service.deferred_startup.wake_restart_sentinel", wakeFromRestartSentinel);
|
|
13103
13909
|
console.log("✓ Deferred startup: plugin gateways and channels settled");
|
|
13104
13910
|
logStartupTrace("service.deferred_startup.end");
|
|
13105
13911
|
}
|
|
13106
13912
|
async function runGatewayRuntimeLoop(params) {
|
|
13107
13913
|
let startupTask = null;
|
|
13108
13914
|
try {
|
|
13109
|
-
const
|
|
13915
|
+
const runtimeLoopTask = params.runRuntimeLoop();
|
|
13110
13916
|
startupTask = params.startDeferredStartup();
|
|
13111
13917
|
startupTask.catch(params.onDeferredStartupError);
|
|
13112
|
-
await
|
|
13918
|
+
await runtimeLoopTask;
|
|
13113
13919
|
} finally {
|
|
13114
13920
|
if (startupTask) await startupTask.catch(() => void 0);
|
|
13115
13921
|
await params.cleanup();
|
|
13116
13922
|
}
|
|
13117
13923
|
}
|
|
13924
|
+
async function runConfiguredGatewayRuntime(params) {
|
|
13925
|
+
const onSystemSessionUpdated = createSystemSessionUpdatedPublisher({ publishUiEvent: params.publishUiEvent });
|
|
13926
|
+
logStartupTrace("service.start_gateway.runtime_loop_begin");
|
|
13927
|
+
await runGatewayRuntimeLoop({
|
|
13928
|
+
runRuntimeLoop: () => runGatewayInboundLoop({
|
|
13929
|
+
bus: params.gateway.bus,
|
|
13930
|
+
sessionManager: params.gateway.sessionManager,
|
|
13931
|
+
getConfig: params.getConfig,
|
|
13932
|
+
resolveNcpAgent: params.getLiveUiNcpAgent,
|
|
13933
|
+
onSystemSessionUpdated: ({ sessionKey }) => onSystemSessionUpdated({ sessionKey })
|
|
13934
|
+
}),
|
|
13935
|
+
startDeferredStartup: () => startDeferredGatewayStartup({
|
|
13936
|
+
uiStartup: params.uiStartup,
|
|
13937
|
+
deferredNcpSessionService: params.deferredNcpSessionService,
|
|
13938
|
+
bus: params.gateway.bus,
|
|
13939
|
+
sessionManager: params.gateway.sessionManager,
|
|
13940
|
+
providerManager: params.gateway.providerManager,
|
|
13941
|
+
cronService: params.gateway.cron,
|
|
13942
|
+
gatewayController: params.gateway.gatewayController,
|
|
13943
|
+
getConfig: params.getConfig,
|
|
13944
|
+
getExtensionRegistry: params.getExtensionRegistry,
|
|
13945
|
+
resolveMessageToolHints: params.resolveMessageToolHints,
|
|
13946
|
+
hydrateCapabilities: params.deferredStartupHooks.hydrateCapabilities,
|
|
13947
|
+
startPluginGateways: params.deferredStartupHooks.startPluginGateways,
|
|
13948
|
+
startChannels: params.deferredStartupHooks.startChannels,
|
|
13949
|
+
wakeFromRestartSentinel: params.deferredStartupHooks.wakeFromRestartSentinel,
|
|
13950
|
+
onNcpAgentReady: params.deferredStartupHooks.onNcpAgentReady,
|
|
13951
|
+
publishSessionChange: params.publishSessionChange
|
|
13952
|
+
}),
|
|
13953
|
+
onDeferredStartupError: params.onDeferredStartupError,
|
|
13954
|
+
cleanup: params.cleanup
|
|
13955
|
+
});
|
|
13956
|
+
}
|
|
13118
13957
|
//#endregion
|
|
13119
13958
|
//#region src/cli/commands/ncp/session/ncp-session-realtime-change.ts
|
|
13120
13959
|
function toNcpSessionRealtimeEvent(change) {
|
|
@@ -13242,6 +14081,10 @@ function createDeferredUiNcpSessionService(fallbackService) {
|
|
|
13242
14081
|
}
|
|
13243
14082
|
//#endregion
|
|
13244
14083
|
//#region src/cli/commands/service-support/session/service-ncp-session-realtime-bridge.ts
|
|
14084
|
+
function formatBackgroundTaskError(error) {
|
|
14085
|
+
if (error instanceof Error) return error.stack ?? error.message;
|
|
14086
|
+
return String(error);
|
|
14087
|
+
}
|
|
13245
14088
|
function createLatestOnlySessionChangePublisher(publishSessionChange) {
|
|
13246
14089
|
const inFlightTasks = /* @__PURE__ */ new Map();
|
|
13247
14090
|
const rerunKeys = /* @__PURE__ */ new Set();
|
|
@@ -13272,7 +14115,9 @@ function createServiceNcpSessionRealtimeBridge(params) {
|
|
|
13272
14115
|
let publishSessionChange = async (_sessionKey) => {};
|
|
13273
14116
|
let scheduleSessionChange = async (_sessionKey) => {};
|
|
13274
14117
|
const deferredSessionService = createDeferredUiNcpSessionService(new UiSessionService(params.sessionManager, { onSessionUpdated: (sessionKey) => {
|
|
13275
|
-
scheduleSessionChange(sessionKey)
|
|
14118
|
+
scheduleSessionChange(sessionKey).catch((error) => {
|
|
14119
|
+
console.error(`[session-realtime] failed to publish session change for ${sessionKey}: ${formatBackgroundTaskError(error)}`);
|
|
14120
|
+
});
|
|
13276
14121
|
} }));
|
|
13277
14122
|
const publishLatestSessionChange = async (sessionKey) => {
|
|
13278
14123
|
await createNcpSessionRealtimeChangePublisher({
|
|
@@ -13297,35 +14142,26 @@ function createServiceNcpSessionRealtimeBridge(params) {
|
|
|
13297
14142
|
//#endregion
|
|
13298
14143
|
//#region src/cli/commands/plugin/plugin-registry-loader.ts
|
|
13299
14144
|
function createPluginLogger() {
|
|
13300
|
-
return
|
|
13301
|
-
info: (message) => console.log(message),
|
|
13302
|
-
warn: (message) => console.warn(message),
|
|
13303
|
-
error: (message) => console.error(message),
|
|
13304
|
-
debug: (message) => console.debug(message)
|
|
13305
|
-
};
|
|
14145
|
+
return getAppLogger("plugin.registry_loader");
|
|
13306
14146
|
}
|
|
13307
14147
|
function withDevFirstPartyPluginPaths(config) {
|
|
13308
|
-
|
|
13309
|
-
return {
|
|
13310
|
-
workspaceExtensionsDir,
|
|
13311
|
-
configWithDevPluginPaths: applyDevFirstPartyPluginLoadPaths(config, workspaceExtensionsDir)
|
|
13312
|
-
};
|
|
14148
|
+
return resolveDevPluginLoadingContext(config, resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR));
|
|
13313
14149
|
}
|
|
13314
14150
|
async function loadPluginRegistryProgressively(config, workspaceDir, options = {}) {
|
|
13315
|
-
const {
|
|
14151
|
+
const { configWithDevPluginOverrides, excludedRoots } = withDevFirstPartyPluginPaths(config);
|
|
13316
14152
|
return await loadOpenClawPluginsProgressively({
|
|
13317
|
-
config:
|
|
14153
|
+
config: configWithDevPluginOverrides,
|
|
13318
14154
|
workspaceDir,
|
|
13319
|
-
excludeRoots:
|
|
14155
|
+
excludeRoots: excludedRoots,
|
|
13320
14156
|
...buildReservedPluginLoadOptions(),
|
|
13321
14157
|
onPluginProcessed: options.onPluginProcessed,
|
|
13322
14158
|
logger: createPluginLogger()
|
|
13323
14159
|
});
|
|
13324
14160
|
}
|
|
13325
14161
|
function discoverPluginRegistryStatus(config, workspaceDir) {
|
|
13326
|
-
const {
|
|
14162
|
+
const { configWithDevPluginOverrides } = withDevFirstPartyPluginPaths(config);
|
|
13327
14163
|
return discoverPluginStatusReport({
|
|
13328
|
-
config:
|
|
14164
|
+
config: configWithDevPluginOverrides,
|
|
13329
14165
|
workspaceDir
|
|
13330
14166
|
});
|
|
13331
14167
|
}
|
|
@@ -13335,7 +14171,6 @@ function createEmptyPluginRegistry() {
|
|
|
13335
14171
|
tools: [],
|
|
13336
14172
|
channels: [],
|
|
13337
14173
|
providers: [],
|
|
13338
|
-
engines: [],
|
|
13339
14174
|
ncpAgentRuntimes: [],
|
|
13340
14175
|
diagnostics: [],
|
|
13341
14176
|
resolvedTools: []
|
|
@@ -13554,8 +14389,6 @@ async function hydrateServiceCapabilities(params) {
|
|
|
13554
14389
|
params.state.extensionRegistry = nextExtensionRegistry;
|
|
13555
14390
|
params.state.pluginChannelBindings = nextPluginChannelBindings;
|
|
13556
14391
|
params.state.pluginUiMetadata = nextPluginUiMetadata;
|
|
13557
|
-
params.gateway.runtimePool.applyExtensionRegistry(nextExtensionRegistry);
|
|
13558
|
-
params.gateway.runtimePool.applyRuntimeConfig(nextConfig);
|
|
13559
14392
|
params.getLiveUiNcpAgent()?.applyExtensionRegistry?.(nextExtensionRegistry);
|
|
13560
14393
|
if (shouldRebuildChannels) await params.gateway.reloader.rebuildChannels(nextConfig, { start: false });
|
|
13561
14394
|
params.uiStartup?.publish({
|
|
@@ -13579,7 +14412,7 @@ async function hydrateServiceCapabilities(params) {
|
|
|
13579
14412
|
//#endregion
|
|
13580
14413
|
//#region src/cli/commands/service-support/plugin/service-plugin-runtime-bridge.ts
|
|
13581
14414
|
function installPluginRuntimeBridge(params) {
|
|
13582
|
-
const {
|
|
14415
|
+
const { dispatchPrompt, runtimeConfigPath, getPluginChannelBindings } = params;
|
|
13583
14416
|
setPluginRuntimeBridge({
|
|
13584
14417
|
loadConfig: () => toPluginConfigView$1(resolveConfigSecrets(loadConfig(), { configPath: runtimeConfigPath }), getPluginChannelBindings()),
|
|
13585
14418
|
writeConfigFile: async (nextConfigView) => {
|
|
@@ -13591,7 +14424,7 @@ function installPluginRuntimeBridge(params) {
|
|
|
13591
14424
|
if (!request) return;
|
|
13592
14425
|
try {
|
|
13593
14426
|
await dispatcherOptions.onReplyStart?.();
|
|
13594
|
-
const response = await
|
|
14427
|
+
const response = await dispatchPrompt(request);
|
|
13595
14428
|
const replyText = typeof response === "string" ? response : String(response ?? "");
|
|
13596
14429
|
if (replyText.trim()) await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
|
|
13597
14430
|
} catch (error) {
|
|
@@ -13723,7 +14556,6 @@ function applyGatewayRuntimeCapabilityState(params) {
|
|
|
13723
14556
|
params.state.pluginChannelBindings = params.next.pluginChannelBindings;
|
|
13724
14557
|
}
|
|
13725
14558
|
function configureGatewayPluginRuntime(params) {
|
|
13726
|
-
params.gateway.reloader.setApplyAgentRuntimeConfig((nextConfig) => params.gateway.runtimePool.applyRuntimeConfig(nextConfig));
|
|
13727
14559
|
params.gateway.reloader.setReloadPlugins(async ({ config: nextConfig, changedPaths }) => {
|
|
13728
14560
|
const result = await reloadServicePlugins({
|
|
13729
14561
|
nextConfig,
|
|
@@ -13746,9 +14578,7 @@ function configureGatewayPluginRuntime(params) {
|
|
|
13746
14578
|
});
|
|
13747
14579
|
params.state.pluginUiMetadata = getPluginUiMetadataFromRegistry(result.pluginRegistry);
|
|
13748
14580
|
params.state.pluginGatewayHandles = result.pluginGatewayHandles;
|
|
13749
|
-
params.gateway.runtimePool.applyExtensionRegistry(result.extensionRegistry);
|
|
13750
14581
|
params.getLiveUiNcpAgent()?.applyExtensionRegistry?.(result.extensionRegistry);
|
|
13751
|
-
params.gateway.runtimePool.applyRuntimeConfig(nextConfig);
|
|
13752
14582
|
if (result.restartChannels) console.log("Config reload: plugin channel gateways restarted.");
|
|
13753
14583
|
return { restartChannels: result.restartChannels };
|
|
13754
14584
|
});
|
|
@@ -13756,7 +14586,12 @@ function configureGatewayPluginRuntime(params) {
|
|
|
13756
14586
|
await params.getLiveUiNcpAgent()?.applyMcpConfig?.(nextConfig);
|
|
13757
14587
|
});
|
|
13758
14588
|
installPluginRuntimeBridge({
|
|
13759
|
-
|
|
14589
|
+
dispatchPrompt: async (request) => await dispatchPromptOverNcp({
|
|
14590
|
+
config: resolveConfigSecrets$2(loadConfig$2(), { configPath: params.gateway.runtimeConfigPath }),
|
|
14591
|
+
sessionManager: params.gateway.sessionManager,
|
|
14592
|
+
resolveNcpAgent: () => params.getLiveUiNcpAgent(),
|
|
14593
|
+
...request
|
|
14594
|
+
}),
|
|
13760
14595
|
runtimeConfigPath: params.gateway.runtimeConfigPath,
|
|
13761
14596
|
getPluginChannelBindings: () => params.state.pluginChannelBindings
|
|
13762
14597
|
});
|
|
@@ -13796,8 +14631,26 @@ function createDeferredGatewayStartupHooks(params) {
|
|
|
13796
14631
|
};
|
|
13797
14632
|
}
|
|
13798
14633
|
//#endregion
|
|
14634
|
+
//#region src/cli/commands/service-support/gateway/service-gateway-runtime-lifecycle.ts
|
|
14635
|
+
function handleGatewayDeferredStartupError(params) {
|
|
14636
|
+
const message = params.error instanceof Error ? params.error.message : String(params.error);
|
|
14637
|
+
params.bootstrapStatus.markError(message);
|
|
14638
|
+
if (params.bootstrapStatus.getStatus().pluginHydration.state === "running") params.bootstrapStatus.markPluginHydrationError(message);
|
|
14639
|
+
console.error(`Deferred startup failed: ${params.error instanceof Error ? params.error.message : String(params.error)}`);
|
|
14640
|
+
}
|
|
14641
|
+
async function cleanupGatewayRuntime(params) {
|
|
14642
|
+
localUiRuntimeStore.clearIfOwnedByProcess();
|
|
14643
|
+
await params.fileWatchers.clear();
|
|
14644
|
+
params.resetRuntimeState();
|
|
14645
|
+
params.clearRealtimeBridge();
|
|
14646
|
+
await params.uiStartup?.deferredNcpAgent.close();
|
|
14647
|
+
await params.remoteModule?.stop();
|
|
14648
|
+
await stopPluginChannelGateways(params.runtimeState?.pluginGatewayHandles ?? []);
|
|
14649
|
+
setPluginRuntimeBridge(null);
|
|
14650
|
+
}
|
|
14651
|
+
//#endregion
|
|
13799
14652
|
//#region src/cli/commands/service.ts
|
|
13800
|
-
const { APP_NAME: APP_NAME$1, getApiBase, getConfigPath: getConfigPath$1, getProvider, getProviderName, getWorkspacePath: getWorkspacePath$1, LiteLLMProvider, loadConfig: loadConfig$1, MessageBus: MessageBus$1, resolveConfigSecrets: resolveConfigSecrets$1, SessionManager, parseAgentScopedSessionKey: parseAgentScopedSessionKey$1 } = NextclawCore;
|
|
14653
|
+
const { APP_NAME: APP_NAME$1, getApiBase, getConfigPath: getConfigPath$1, getProvider, getProviderName, getWorkspacePath: getWorkspacePath$1, LiteLLMProvider, loadConfig: loadConfig$1, MessageBus: MessageBus$1, resolveConfigSecrets: resolveConfigSecrets$1, SessionManager: SessionManager$1, parseAgentScopedSessionKey: parseAgentScopedSessionKey$1 } = NextclawCore;
|
|
13801
14654
|
function createSkillsLoader(workspace) {
|
|
13802
14655
|
const ctor = NextclawCore.SkillsLoader;
|
|
13803
14656
|
if (!ctor) return null;
|
|
@@ -13807,10 +14660,14 @@ var ServiceCommands = class {
|
|
|
13807
14660
|
applyLiveConfigReload = null;
|
|
13808
14661
|
liveUiNcpAgent = null;
|
|
13809
14662
|
fileWatchers = new ServiceFileWatcherRegistry();
|
|
14663
|
+
loggingRuntime = NextclawCore.getLoggingRuntime();
|
|
14664
|
+
serviceLogger = this.loggingRuntime.getLogger("service");
|
|
14665
|
+
loggingInstalled = false;
|
|
13810
14666
|
constructor(deps) {
|
|
13811
14667
|
this.deps = deps;
|
|
13812
14668
|
}
|
|
13813
14669
|
startGateway = async (options = {}) => {
|
|
14670
|
+
this.ensureRuntimeLoggingInstalled();
|
|
13814
14671
|
logStartupTrace("service.start_gateway.begin");
|
|
13815
14672
|
await this.fileWatchers.clear();
|
|
13816
14673
|
this.applyLiveConfigReload = null;
|
|
@@ -13871,9 +14728,11 @@ var ServiceCommands = class {
|
|
|
13871
14728
|
initialPluginRegistry: createEmptyPluginRegistry(),
|
|
13872
14729
|
makeProvider: (config, providerOptions) => providerOptions?.allowMissing === true ? this.makeProvider(config, { allowMissing: true }) : this.makeProvider(config),
|
|
13873
14730
|
makeMissingProvider: (config) => this.makeMissingProvider(config),
|
|
13874
|
-
requestRestart: (params) => this.deps.requestRestart(params)
|
|
14731
|
+
requestRestart: (params) => this.deps.requestRestart(params),
|
|
14732
|
+
getLiveUiNcpAgent: () => this.liveUiNcpAgent
|
|
13875
14733
|
}));
|
|
13876
14734
|
this.applyLiveConfigReload = gateway.applyLiveConfigReload;
|
|
14735
|
+
const loadGatewayConfig = () => resolveConfigSecrets$1(loadConfig$1(), { configPath: gateway.runtimeConfigPath });
|
|
13877
14736
|
const gatewayRuntimeState = createGatewayRuntimeState(gateway);
|
|
13878
14737
|
runtimeState = gatewayRuntimeState;
|
|
13879
14738
|
uiStartup?.publish({
|
|
@@ -13889,10 +14748,6 @@ var ServiceCommands = class {
|
|
|
13889
14748
|
state: gatewayRuntimeState,
|
|
13890
14749
|
getLiveUiNcpAgent: () => this.liveUiNcpAgent
|
|
13891
14750
|
});
|
|
13892
|
-
wireSystemSessionUpdatedPublisher({
|
|
13893
|
-
runtimePool: gateway.runtimePool,
|
|
13894
|
-
publishUiEvent: uiStartup?.publish
|
|
13895
|
-
});
|
|
13896
14751
|
console.log("✓ Capability hydration: scheduled in background");
|
|
13897
14752
|
await measureStartupAsync("service.start_gateway_support_services", async () => await startGatewayRuntimeSupport({
|
|
13898
14753
|
cronJobs: gateway.cron.status().jobs,
|
|
@@ -13922,49 +14777,37 @@ var ServiceCommands = class {
|
|
|
13922
14777
|
sessionManager: gateway.sessionManager
|
|
13923
14778
|
})
|
|
13924
14779
|
});
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
getConfig: () => resolveConfigSecrets$1(loadConfig$1(), { configPath: gateway.runtimeConfigPath }),
|
|
13937
|
-
getExtensionRegistry: () => gatewayRuntimeState.extensionRegistry,
|
|
13938
|
-
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
13939
|
-
registry: gatewayRuntimeState.pluginRegistry,
|
|
13940
|
-
channel,
|
|
13941
|
-
cfg: resolveConfigSecrets$1(loadConfig$1(), { configPath: gateway.runtimeConfigPath }),
|
|
13942
|
-
accountId
|
|
13943
|
-
}),
|
|
13944
|
-
hydrateCapabilities: deferredGatewayStartupHooks.hydrateCapabilities,
|
|
13945
|
-
startPluginGateways: deferredGatewayStartupHooks.startPluginGateways,
|
|
13946
|
-
startChannels: deferredGatewayStartupHooks.startChannels,
|
|
13947
|
-
wakeFromRestartSentinel: deferredGatewayStartupHooks.wakeFromRestartSentinel,
|
|
13948
|
-
onNcpAgentReady: deferredGatewayStartupHooks.onNcpAgentReady,
|
|
13949
|
-
publishSessionChange: ncpSessionRealtimeBridge.publishSessionChange
|
|
14780
|
+
await runConfiguredGatewayRuntime({
|
|
14781
|
+
uiStartup,
|
|
14782
|
+
gateway,
|
|
14783
|
+
deferredNcpSessionService: ncpSessionRealtimeBridge.deferredSessionService,
|
|
14784
|
+
getConfig: loadGatewayConfig,
|
|
14785
|
+
getExtensionRegistry: () => gatewayRuntimeState.extensionRegistry,
|
|
14786
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
14787
|
+
registry: gatewayRuntimeState.pluginRegistry,
|
|
14788
|
+
channel,
|
|
14789
|
+
cfg: loadGatewayConfig(),
|
|
14790
|
+
accountId
|
|
13950
14791
|
}),
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
this.
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14792
|
+
deferredStartupHooks: deferredGatewayStartupHooks,
|
|
14793
|
+
getLiveUiNcpAgent: () => this.liveUiNcpAgent,
|
|
14794
|
+
publishSessionChange: ncpSessionRealtimeBridge.publishSessionChange,
|
|
14795
|
+
publishUiEvent: uiStartup?.publish,
|
|
14796
|
+
onDeferredStartupError: (error) => handleGatewayDeferredStartupError({
|
|
14797
|
+
bootstrapStatus,
|
|
14798
|
+
error
|
|
14799
|
+
}),
|
|
14800
|
+
cleanup: async () => await cleanupGatewayRuntime({
|
|
14801
|
+
fileWatchers: this.fileWatchers,
|
|
14802
|
+
resetRuntimeState: () => {
|
|
14803
|
+
this.applyLiveConfigReload = null;
|
|
14804
|
+
this.liveUiNcpAgent = null;
|
|
14805
|
+
},
|
|
14806
|
+
clearRealtimeBridge: () => ncpSessionRealtimeBridge.clear(),
|
|
14807
|
+
uiStartup,
|
|
14808
|
+
remoteModule: gateway.remoteModule,
|
|
14809
|
+
runtimeState
|
|
14810
|
+
})
|
|
13968
14811
|
});
|
|
13969
14812
|
logStartupTrace("service.start_gateway.end");
|
|
13970
14813
|
};
|
|
@@ -14068,7 +14911,7 @@ var ServiceCommands = class {
|
|
|
14068
14911
|
if (binding.host !== uiConfig.host || binding.port !== uiConfig.port) {
|
|
14069
14912
|
console.log(`Detected running service UI bind (${binding.host}:${binding.port}); enforcing (${uiConfig.host}:${uiConfig.port})...`);
|
|
14070
14913
|
await this.stopService();
|
|
14071
|
-
const stateAfterStop =
|
|
14914
|
+
const stateAfterStop = managedServiceStateStore.read();
|
|
14072
14915
|
if (stateAfterStop && isProcessRunning(stateAfterStop.pid)) {
|
|
14073
14916
|
process.exitCode = 1;
|
|
14074
14917
|
console.error("Error: Failed to stop running service while enforcing public UI exposure.");
|
|
@@ -14083,12 +14926,14 @@ var ServiceCommands = class {
|
|
|
14083
14926
|
return true;
|
|
14084
14927
|
};
|
|
14085
14928
|
startService = async (options) => {
|
|
14929
|
+
this.loggingRuntime.ensureReady();
|
|
14930
|
+
const { open, startupTimeoutMs, uiOverrides } = options;
|
|
14086
14931
|
const config = loadConfig$1();
|
|
14087
|
-
const uiConfig = resolveUiConfig(config,
|
|
14932
|
+
const uiConfig = resolveUiConfig(config, uiOverrides);
|
|
14088
14933
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
14089
14934
|
const apiUrl = `${uiUrl}/api`;
|
|
14090
14935
|
const staticDir = resolveUiStaticDir();
|
|
14091
|
-
const existing =
|
|
14936
|
+
const existing = managedServiceStateStore.read();
|
|
14092
14937
|
if (existing && isProcessRunning(existing.pid)) {
|
|
14093
14938
|
await this.handleExistingManagedService({
|
|
14094
14939
|
existing,
|
|
@@ -14097,7 +14942,7 @@ var ServiceCommands = class {
|
|
|
14097
14942
|
});
|
|
14098
14943
|
return;
|
|
14099
14944
|
}
|
|
14100
|
-
if (existing)
|
|
14945
|
+
if (existing) managedServiceStateStore.clear();
|
|
14101
14946
|
if (!staticDir) {
|
|
14102
14947
|
process.exitCode = 1, console.error(`Error: ${APP_NAME$1} UI frontend bundle not found. Reinstall or rebuild ${APP_NAME$1}. For dev-only overrides, set NEXTCLAW_UI_STATIC_DIR to a built frontend directory.`);
|
|
14103
14948
|
return;
|
|
@@ -14112,6 +14957,40 @@ var ServiceCommands = class {
|
|
|
14112
14957
|
process.exitCode = 1, console.error(`Error: Cannot start ${APP_NAME$1} because UI port ${uiConfig.port} is already occupied.`), console.error(portPreflight.message);
|
|
14113
14958
|
return;
|
|
14114
14959
|
}
|
|
14960
|
+
if (portPreflight.reusedExistingHealthyTarget) {
|
|
14961
|
+
await this.reuseExistingHealthyStartTarget({
|
|
14962
|
+
uiConfig,
|
|
14963
|
+
uiUrl,
|
|
14964
|
+
apiUrl,
|
|
14965
|
+
open
|
|
14966
|
+
});
|
|
14967
|
+
return;
|
|
14968
|
+
}
|
|
14969
|
+
await this.startNewManagedServiceTarget({
|
|
14970
|
+
config,
|
|
14971
|
+
uiConfig,
|
|
14972
|
+
uiUrl,
|
|
14973
|
+
apiUrl,
|
|
14974
|
+
healthUrl,
|
|
14975
|
+
startupTimeoutMs
|
|
14976
|
+
});
|
|
14977
|
+
if (open) openBrowser(uiUrl);
|
|
14978
|
+
};
|
|
14979
|
+
reuseExistingHealthyStartTarget = async (params) => {
|
|
14980
|
+
const { apiUrl, open, uiConfig, uiUrl } = params;
|
|
14981
|
+
console.log(`✓ ${APP_NAME$1} is already serving the target UI/API port`);
|
|
14982
|
+
console.log(`UI: ${uiUrl}`);
|
|
14983
|
+
console.log(`API: ${apiUrl}`);
|
|
14984
|
+
console.warn([
|
|
14985
|
+
`Warning: The healthy listener on ${uiConfig.port} is not tracked by ${managedServiceStateStore.path}.`,
|
|
14986
|
+
"This start call reused the existing runtime instead of spawning another one.",
|
|
14987
|
+
"Use the owning process or port-level tools to stop it; managed stop/restart will not control it automatically."
|
|
14988
|
+
].join(" "));
|
|
14989
|
+
await this.printPublicUiUrls(uiConfig.host, uiConfig.port);
|
|
14990
|
+
if (open) openBrowser(uiUrl);
|
|
14991
|
+
};
|
|
14992
|
+
startNewManagedServiceTarget = async (params) => {
|
|
14993
|
+
const { apiUrl, config, healthUrl, startupTimeoutMs, uiConfig, uiUrl } = params;
|
|
14115
14994
|
const startup = spawnManagedService({
|
|
14116
14995
|
appName: APP_NAME$1,
|
|
14117
14996
|
config,
|
|
@@ -14119,13 +14998,14 @@ var ServiceCommands = class {
|
|
|
14119
14998
|
uiUrl,
|
|
14120
14999
|
apiUrl,
|
|
14121
15000
|
healthUrl,
|
|
14122
|
-
startupTimeoutMs
|
|
15001
|
+
startupTimeoutMs,
|
|
14123
15002
|
resolveStartupTimeoutMs: this.resolveStartupTimeoutMs,
|
|
14124
15003
|
appendStartupStage: this.appendStartupStage,
|
|
14125
15004
|
printStartupFailureDiagnostics: this.printStartupFailureDiagnostics,
|
|
14126
15005
|
resolveServiceLogPath
|
|
14127
15006
|
});
|
|
14128
15007
|
if (!startup) {
|
|
15008
|
+
this.serviceLogger.fatal("managed service startup aborted", { reason: "child_process_not_created" });
|
|
14129
15009
|
process.exitCode = 1;
|
|
14130
15010
|
return;
|
|
14131
15011
|
}
|
|
@@ -14141,22 +15021,27 @@ var ServiceCommands = class {
|
|
|
14141
15021
|
waitForBackgroundServiceReady: this.waitForBackgroundServiceReady,
|
|
14142
15022
|
isProcessRunning
|
|
14143
15023
|
});
|
|
14144
|
-
if (!readiness.ready) {
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
|
|
15024
|
+
if (!readiness.ready && !isProcessRunning(startup.snapshot.pid)) {
|
|
15025
|
+
process.exitCode = 1;
|
|
15026
|
+
managedServiceStateStore.clear();
|
|
15027
|
+
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
15028
|
+
this.appendStartupStage(startup.logPath, `startup failed: process exited before ready.${hint}`);
|
|
15029
|
+
this.serviceLogger.fatal("managed service exited before readiness completed", {
|
|
15030
|
+
uiUrl,
|
|
15031
|
+
apiUrl,
|
|
15032
|
+
healthUrl,
|
|
15033
|
+
logPath: startup.logPath,
|
|
15034
|
+
...readiness.lastProbeError ? { lastProbeError: readiness.lastProbeError } : {}
|
|
15035
|
+
});
|
|
15036
|
+
console.error(`Error: Failed to start background service. Check logs: ${startup.logPath}.${hint}`);
|
|
15037
|
+
this.printStartupFailureDiagnostics({
|
|
15038
|
+
uiUrl,
|
|
15039
|
+
apiUrl,
|
|
15040
|
+
healthUrl,
|
|
15041
|
+
logPath: startup.logPath,
|
|
15042
|
+
lastProbeError: readiness.lastProbeError
|
|
15043
|
+
});
|
|
15044
|
+
return;
|
|
14160
15045
|
}
|
|
14161
15046
|
startup.child.unref();
|
|
14162
15047
|
await reportManagedServiceStart({
|
|
@@ -14174,17 +15059,16 @@ var ServiceCommands = class {
|
|
|
14174
15059
|
printPublicUiUrls: this.printPublicUiUrls,
|
|
14175
15060
|
printServiceControlHints: this.printServiceControlHints
|
|
14176
15061
|
});
|
|
14177
|
-
if (options.open) openBrowser(uiUrl);
|
|
14178
15062
|
};
|
|
14179
15063
|
stopService = async () => {
|
|
14180
|
-
const state =
|
|
15064
|
+
const state = managedServiceStateStore.read();
|
|
14181
15065
|
if (!state) {
|
|
14182
|
-
console.log("No running service found.");
|
|
15066
|
+
console.log("No running background service found.");
|
|
14183
15067
|
return;
|
|
14184
15068
|
}
|
|
14185
15069
|
if (!isProcessRunning(state.pid)) {
|
|
14186
15070
|
console.log("Service is not running. Cleaning up state.");
|
|
14187
|
-
|
|
15071
|
+
managedServiceStateStore.clear();
|
|
14188
15072
|
return;
|
|
14189
15073
|
}
|
|
14190
15074
|
console.log(`Stopping ${APP_NAME$1} (PID ${state.pid})...`);
|
|
@@ -14203,7 +15087,8 @@ var ServiceCommands = class {
|
|
|
14203
15087
|
}
|
|
14204
15088
|
await waitForExit(state.pid, 2e3);
|
|
14205
15089
|
}
|
|
14206
|
-
|
|
15090
|
+
managedServiceStateStore.clear();
|
|
15091
|
+
localUiRuntimeStore.clearIfOwnedByProcess(state.pid);
|
|
14207
15092
|
console.log(`✓ ${APP_NAME$1} stopped`);
|
|
14208
15093
|
};
|
|
14209
15094
|
waitForBackgroundServiceReady = async (params) => {
|
|
@@ -14243,14 +15128,14 @@ var ServiceCommands = class {
|
|
|
14243
15128
|
};
|
|
14244
15129
|
appendStartupStage = (logPath, message) => {
|
|
14245
15130
|
try {
|
|
14246
|
-
|
|
15131
|
+
this.serviceLogger.child("startup").info(message, { logPath });
|
|
14247
15132
|
} catch (error) {
|
|
14248
15133
|
const detail = error instanceof Error ? error.message : String(error);
|
|
14249
15134
|
console.error(`Warning: failed to write startup diagnostics log (${logPath}): ${detail}`);
|
|
14250
15135
|
}
|
|
14251
15136
|
};
|
|
14252
15137
|
printStartupFailureDiagnostics = (params) => {
|
|
14253
|
-
const statePath =
|
|
15138
|
+
const statePath = managedServiceStateStore.path;
|
|
14254
15139
|
const lines = [
|
|
14255
15140
|
"Startup diagnostics:",
|
|
14256
15141
|
`- UI URL: ${params.uiUrl}`,
|
|
@@ -14264,20 +15149,22 @@ var ServiceCommands = class {
|
|
|
14264
15149
|
};
|
|
14265
15150
|
checkUiPortPreflight = async (params) => {
|
|
14266
15151
|
const { healthUrl, host, port } = params;
|
|
14267
|
-
const
|
|
15152
|
+
const target = await inspectUiTarget({
|
|
14268
15153
|
host,
|
|
14269
|
-
port
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
}
|
|
15154
|
+
port,
|
|
15155
|
+
healthUrl
|
|
15156
|
+
});
|
|
15157
|
+
if (target.state === "available") return {
|
|
15158
|
+
ok: true,
|
|
15159
|
+
reusedExistingHealthyTarget: false
|
|
15160
|
+
};
|
|
15161
|
+
if (target.state === "healthy-existing") return {
|
|
15162
|
+
ok: true,
|
|
15163
|
+
reusedExistingHealthyTarget: true
|
|
15164
|
+
};
|
|
15165
|
+
const lines = [`Port probe: ${target.availabilityDetail}`];
|
|
15166
|
+
if (target.probeError) lines.push(`Health probe: ${target.probeError}`);
|
|
15167
|
+
lines.push("The port is occupied by a process that does not answer as a healthy NextClaw HTTP server.");
|
|
14281
15168
|
lines.push(`Fix: free port ${port} or start NextClaw with another port via --ui-port <port>.`);
|
|
14282
15169
|
lines.push(`Inspect locally with: ss -ltnp | grep ${port} || lsof -iTCP:${port} -sTCP:LISTEN -n -P`);
|
|
14283
15170
|
return {
|
|
@@ -14334,6 +15221,17 @@ var ServiceCommands = class {
|
|
|
14334
15221
|
console.log("Service controls:");
|
|
14335
15222
|
console.log(` - Check status: ${APP_NAME$1} status`);
|
|
14336
15223
|
console.log(` - If you need to stop the service, run: ${APP_NAME$1} stop`);
|
|
15224
|
+
console.log(` - View log paths: ${APP_NAME$1} logs path`);
|
|
15225
|
+
console.log(` - Tail recent logs: ${APP_NAME$1} logs tail`);
|
|
15226
|
+
};
|
|
15227
|
+
ensureRuntimeLoggingInstalled = () => {
|
|
15228
|
+
if (this.loggingInstalled) return;
|
|
15229
|
+
NextclawCore.configureAppLogging({
|
|
15230
|
+
installConsoleMirror: true,
|
|
15231
|
+
installProcessCrashMonitor: true
|
|
15232
|
+
});
|
|
15233
|
+
this.serviceLogger.info("runtime logging ready", { startupId: this.loggingRuntime.getStartupId() });
|
|
15234
|
+
this.loggingInstalled = true;
|
|
14337
15235
|
};
|
|
14338
15236
|
installBuiltinMarketplaceSkill = (slug, _force) => {
|
|
14339
15237
|
if (!(createSkillsLoader(getWorkspacePath$1(loadConfig$1().agents.defaults.workspace))?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin")) return null;
|
|
@@ -14538,8 +15436,7 @@ var WorkspaceManager = class {
|
|
|
14538
15436
|
}
|
|
14539
15437
|
};
|
|
14540
15438
|
//#endregion
|
|
14541
|
-
//#region src/cli/
|
|
14542
|
-
const LOGO = "🤖";
|
|
15439
|
+
//#region src/cli/commands/agent/cli-agent-runner.ts
|
|
14543
15440
|
const EXIT_COMMANDS = new Set([
|
|
14544
15441
|
"exit",
|
|
14545
15442
|
"quit",
|
|
@@ -14547,6 +15444,91 @@ const EXIT_COMMANDS = new Set([
|
|
|
14547
15444
|
"/quit",
|
|
14548
15445
|
":q"
|
|
14549
15446
|
]);
|
|
15447
|
+
function buildCliSharedMetadata(opts) {
|
|
15448
|
+
return typeof opts.model === "string" && opts.model.trim() ? { model: opts.model.trim() } : {};
|
|
15449
|
+
}
|
|
15450
|
+
function createCliHistoryInterface() {
|
|
15451
|
+
const historyFile = join(getDataDir(), "history", "cli_history");
|
|
15452
|
+
mkdirSync(resolve(historyFile, ".."), { recursive: true });
|
|
15453
|
+
const history = existsSync(historyFile) ? readFileSync(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
15454
|
+
const rl = createInterface({
|
|
15455
|
+
input: process.stdin,
|
|
15456
|
+
output: process.stdout
|
|
15457
|
+
});
|
|
15458
|
+
rl.on("close", () => {
|
|
15459
|
+
writeFileSync(historyFile, history.concat(rl.history ?? []).join("\n"));
|
|
15460
|
+
process.exit(0);
|
|
15461
|
+
});
|
|
15462
|
+
return rl;
|
|
15463
|
+
}
|
|
15464
|
+
async function runCliInteractiveLoop(params) {
|
|
15465
|
+
console.log(`${params.logo} Interactive mode (type exit or Ctrl+C to quit)\n`);
|
|
15466
|
+
const rl = createCliHistoryInterface();
|
|
15467
|
+
let running = true;
|
|
15468
|
+
while (running) {
|
|
15469
|
+
const trimmed = (await prompt(rl, "You: ")).trim();
|
|
15470
|
+
if (!trimmed) continue;
|
|
15471
|
+
if (EXIT_COMMANDS.has(trimmed.toLowerCase())) {
|
|
15472
|
+
rl.close();
|
|
15473
|
+
running = false;
|
|
15474
|
+
break;
|
|
15475
|
+
}
|
|
15476
|
+
printAgentResponse(await dispatchPromptOverNcp({
|
|
15477
|
+
config: params.config,
|
|
15478
|
+
sessionManager: params.sessionManager,
|
|
15479
|
+
resolveNcpAgent: () => params.ncpAgent,
|
|
15480
|
+
sessionKey: params.sessionKey,
|
|
15481
|
+
content: trimmed,
|
|
15482
|
+
metadata: params.metadata
|
|
15483
|
+
}));
|
|
15484
|
+
}
|
|
15485
|
+
}
|
|
15486
|
+
async function runCliAgentCommand(params) {
|
|
15487
|
+
const bus = new MessageBus();
|
|
15488
|
+
const sessionManager = new SessionManager({
|
|
15489
|
+
workspace: params.workspace,
|
|
15490
|
+
homeDir: getDataDir()
|
|
15491
|
+
});
|
|
15492
|
+
const ncpAgent = await createUiNcpAgent({
|
|
15493
|
+
bus,
|
|
15494
|
+
providerManager: params.providerManager,
|
|
15495
|
+
sessionManager,
|
|
15496
|
+
getConfig: params.loadResolvedConfig,
|
|
15497
|
+
getExtensionRegistry: () => params.extensionRegistry,
|
|
15498
|
+
resolveMessageToolHints: ({ channel, accountId }) => params.resolveMessageToolHints({
|
|
15499
|
+
channel,
|
|
15500
|
+
accountId
|
|
15501
|
+
})
|
|
15502
|
+
});
|
|
15503
|
+
try {
|
|
15504
|
+
const sessionKey = params.opts.session ?? "cli:default";
|
|
15505
|
+
const sharedMetadata = buildCliSharedMetadata(params.opts);
|
|
15506
|
+
if (params.opts.message) {
|
|
15507
|
+
printAgentResponse(await dispatchPromptOverNcp({
|
|
15508
|
+
config: params.config,
|
|
15509
|
+
sessionManager,
|
|
15510
|
+
resolveNcpAgent: () => ncpAgent,
|
|
15511
|
+
sessionKey,
|
|
15512
|
+
content: params.opts.message,
|
|
15513
|
+
metadata: sharedMetadata
|
|
15514
|
+
}));
|
|
15515
|
+
return;
|
|
15516
|
+
}
|
|
15517
|
+
await runCliInteractiveLoop({
|
|
15518
|
+
logo: params.logo,
|
|
15519
|
+
config: params.config,
|
|
15520
|
+
sessionManager,
|
|
15521
|
+
ncpAgent,
|
|
15522
|
+
sessionKey,
|
|
15523
|
+
metadata: sharedMetadata
|
|
15524
|
+
});
|
|
15525
|
+
} finally {
|
|
15526
|
+
await ncpAgent.dispose?.();
|
|
15527
|
+
}
|
|
15528
|
+
}
|
|
15529
|
+
//#endregion
|
|
15530
|
+
//#region src/cli/runtime.ts
|
|
15531
|
+
const LOGO = "🤖";
|
|
14550
15532
|
const FORCED_PUBLIC_UI_HOST = "0.0.0.0";
|
|
14551
15533
|
var CliRuntime = class {
|
|
14552
15534
|
logo;
|
|
@@ -14566,6 +15548,7 @@ var CliRuntime = class {
|
|
|
14566
15548
|
remoteCommands;
|
|
14567
15549
|
remote;
|
|
14568
15550
|
diagnosticsCommands;
|
|
15551
|
+
logsCommands;
|
|
14569
15552
|
constructor(options = {}) {
|
|
14570
15553
|
logStartupTrace("cli.runtime.constructor.begin");
|
|
14571
15554
|
this.logo = options.logo ?? "🤖";
|
|
@@ -14598,8 +15581,9 @@ var CliRuntime = class {
|
|
|
14598
15581
|
hasRunningManagedService: hasRunningNextclawManagedService
|
|
14599
15582
|
}));
|
|
14600
15583
|
this.diagnosticsCommands = measureStartupSync("cli.runtime.diagnostics_commands", () => new DiagnosticsCommands({ logo: this.logo }));
|
|
15584
|
+
this.logsCommands = measureStartupSync("cli.runtime.logs_commands", () => new LogsCommands());
|
|
14601
15585
|
this.restartCoordinator = measureStartupSync("cli.runtime.restart_coordinator", () => new RestartCoordinator({
|
|
14602
|
-
readServiceState,
|
|
15586
|
+
readServiceState: managedServiceStateStore.read,
|
|
14603
15587
|
isProcessRunning,
|
|
14604
15588
|
currentPid: () => process.pid,
|
|
14605
15589
|
restartBackgroundService: async (reason) => this.restartBackgroundService(reason),
|
|
@@ -14619,7 +15603,7 @@ var CliRuntime = class {
|
|
|
14619
15603
|
restartBackgroundService = async (reason) => {
|
|
14620
15604
|
if (this.serviceRestartTask) return this.serviceRestartTask;
|
|
14621
15605
|
this.serviceRestartTask = (async () => {
|
|
14622
|
-
const state =
|
|
15606
|
+
const state = managedServiceStateStore.read();
|
|
14623
15607
|
if (!state || !isProcessRunning(state.pid) || state.pid === process.pid) return false;
|
|
14624
15608
|
const uiHost = FORCED_PUBLIC_UI_HOST;
|
|
14625
15609
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 55667;
|
|
@@ -14645,7 +15629,7 @@ var CliRuntime = class {
|
|
|
14645
15629
|
const strategy = params.strategy ?? "background-service-or-manual";
|
|
14646
15630
|
if (strategy !== "background-service-or-exit" && strategy !== "exit-process") return;
|
|
14647
15631
|
if (this.selfRelaunchArmed) return;
|
|
14648
|
-
const state =
|
|
15632
|
+
const state = managedServiceStateStore.read();
|
|
14649
15633
|
if (!state || state.pid !== process.pid) return;
|
|
14650
15634
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 55667;
|
|
14651
15635
|
const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
|
|
@@ -14655,7 +15639,7 @@ var CliRuntime = class {
|
|
|
14655
15639
|
"--ui-port",
|
|
14656
15640
|
String(uiPort)
|
|
14657
15641
|
];
|
|
14658
|
-
const serviceStatePath =
|
|
15642
|
+
const serviceStatePath = managedServiceStateStore.path;
|
|
14659
15643
|
const helperScript = [
|
|
14660
15644
|
"const { spawnSync } = require(\"node:child_process\");",
|
|
14661
15645
|
"const { readFileSync } = require(\"node:fs\");",
|
|
@@ -14840,13 +15824,13 @@ var CliRuntime = class {
|
|
|
14840
15824
|
uiPort: opts.uiPort,
|
|
14841
15825
|
forcedPublicHost: FORCED_PUBLIC_UI_HOST
|
|
14842
15826
|
});
|
|
14843
|
-
const state =
|
|
15827
|
+
const state = managedServiceStateStore.read();
|
|
14844
15828
|
if (state && isProcessRunning(state.pid)) {
|
|
14845
15829
|
console.log(`Restarting ${APP_NAME}...`);
|
|
14846
15830
|
await this.serviceCommands.stopService();
|
|
14847
15831
|
} else {
|
|
14848
15832
|
if (state) {
|
|
14849
|
-
|
|
15833
|
+
managedServiceStateStore.clear();
|
|
14850
15834
|
console.log("Service state was stale and has been cleaned up.");
|
|
14851
15835
|
}
|
|
14852
15836
|
const unmanagedHealthyServiceMessage = await describeUnmanagedHealthyTargetMessage({ uiOverrides });
|
|
@@ -14888,22 +15872,19 @@ var CliRuntime = class {
|
|
|
14888
15872
|
}
|
|
14889
15873
|
});
|
|
14890
15874
|
try {
|
|
14891
|
-
const
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
maxIterations: config.agents.defaults.maxToolIterations,
|
|
14900
|
-
contextTokens: config.agents.defaults.contextTokens,
|
|
14901
|
-
searchConfig: config.search,
|
|
14902
|
-
execConfig: config.tools.exec,
|
|
14903
|
-
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
14904
|
-
contextConfig: config.agents.context,
|
|
15875
|
+
const provider = this.serviceCommands.createProvider(config) ?? this.serviceCommands.createMissingProvider(config);
|
|
15876
|
+
const providerManager = this.createObservedProviderManager(new ProviderManager({
|
|
15877
|
+
defaultProvider: provider,
|
|
15878
|
+
config
|
|
15879
|
+
}), "cli-agent");
|
|
15880
|
+
await runCliAgentCommand({
|
|
15881
|
+
logo: this.logo,
|
|
15882
|
+
opts,
|
|
14905
15883
|
config,
|
|
15884
|
+
workspace,
|
|
15885
|
+
providerManager,
|
|
14906
15886
|
extensionRegistry,
|
|
15887
|
+
loadResolvedConfig: () => resolveConfigSecrets(loadConfig(), { configPath }),
|
|
14907
15888
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
14908
15889
|
registry: pluginRegistry,
|
|
14909
15890
|
channel,
|
|
@@ -14911,43 +15892,6 @@ var CliRuntime = class {
|
|
|
14911
15892
|
accountId
|
|
14912
15893
|
})
|
|
14913
15894
|
});
|
|
14914
|
-
if (opts.message) {
|
|
14915
|
-
printAgentResponse(await agentLoop.processDirect({
|
|
14916
|
-
content: opts.message,
|
|
14917
|
-
sessionKey: opts.session ?? "cli:default",
|
|
14918
|
-
channel: "cli",
|
|
14919
|
-
chatId: "direct",
|
|
14920
|
-
metadata: typeof opts.model === "string" && opts.model.trim() ? { model: opts.model.trim() } : {}
|
|
14921
|
-
}));
|
|
14922
|
-
return;
|
|
14923
|
-
}
|
|
14924
|
-
console.log(`${this.logo} Interactive mode (type exit or Ctrl+C to quit)\n`);
|
|
14925
|
-
const historyFile = join(getDataDir(), "history", "cli_history");
|
|
14926
|
-
mkdirSync(resolve(historyFile, ".."), { recursive: true });
|
|
14927
|
-
const history = existsSync(historyFile) ? readFileSync(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
14928
|
-
const rl = createInterface({
|
|
14929
|
-
input: process.stdin,
|
|
14930
|
-
output: process.stdout
|
|
14931
|
-
});
|
|
14932
|
-
rl.on("close", () => {
|
|
14933
|
-
writeFileSync(historyFile, history.concat(rl.history ?? []).join("\n"));
|
|
14934
|
-
process.exit(0);
|
|
14935
|
-
});
|
|
14936
|
-
let running = true;
|
|
14937
|
-
while (running) {
|
|
14938
|
-
const trimmed = (await prompt(rl, "You: ")).trim();
|
|
14939
|
-
if (!trimmed) continue;
|
|
14940
|
-
if (EXIT_COMMANDS.has(trimmed.toLowerCase())) {
|
|
14941
|
-
rl.close();
|
|
14942
|
-
running = false;
|
|
14943
|
-
break;
|
|
14944
|
-
}
|
|
14945
|
-
printAgentResponse(await agentLoop.processDirect({
|
|
14946
|
-
content: trimmed,
|
|
14947
|
-
sessionKey: opts.session ?? "cli:default",
|
|
14948
|
-
metadata: typeof opts.model === "string" && opts.model.trim() ? { model: opts.model.trim() } : {}
|
|
14949
|
-
}));
|
|
14950
|
-
}
|
|
14951
15895
|
} finally {
|
|
14952
15896
|
setPluginRuntimeBridge(null);
|
|
14953
15897
|
}
|
|
@@ -14964,28 +15908,19 @@ var CliRuntime = class {
|
|
|
14964
15908
|
}
|
|
14965
15909
|
const versionBefore = getPackageVersion$1();
|
|
14966
15910
|
console.log(`Current version: ${versionBefore}`);
|
|
14967
|
-
const
|
|
14968
|
-
|
|
14969
|
-
|
|
15911
|
+
const report = reportSelfUpdateResult({
|
|
15912
|
+
appName: APP_NAME,
|
|
15913
|
+
currentVersion: versionBefore,
|
|
15914
|
+
result: runSelfUpdate({
|
|
15915
|
+
timeoutMs,
|
|
15916
|
+
cwd: process.cwd(),
|
|
15917
|
+
currentVersion: versionBefore
|
|
15918
|
+
}),
|
|
15919
|
+
readInstalledVersion: getPackageVersion$1
|
|
14970
15920
|
});
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
if (step.stderr) console.log(` stderr: ${step.stderr}`);
|
|
14975
|
-
if (step.stdout) console.log(` stdout: ${step.stdout}`);
|
|
14976
|
-
}
|
|
14977
|
-
};
|
|
14978
|
-
if (!result.ok) {
|
|
14979
|
-
console.error(`Update failed: ${result.error ?? "unknown error"}`);
|
|
14980
|
-
if (result.steps.length > 0) printSteps();
|
|
14981
|
-
process.exit(1);
|
|
14982
|
-
}
|
|
14983
|
-
const versionAfter = getPackageVersion$1();
|
|
14984
|
-
console.log(`✓ Update complete (${result.strategy})`);
|
|
14985
|
-
if (versionAfter === versionBefore) console.log(`Version unchanged: ${versionBefore}`);
|
|
14986
|
-
else console.log(`Version updated: ${versionBefore} -> ${versionAfter}`);
|
|
14987
|
-
const state = readServiceState();
|
|
14988
|
-
if (state && isProcessRunning(state.pid)) console.log(`Tip: restart ${APP_NAME} to apply the update.`);
|
|
15921
|
+
if (!report.ok) process.exit(1);
|
|
15922
|
+
const state = managedServiceStateStore.read();
|
|
15923
|
+
if (report.shouldSuggestRestart && state && isProcessRunning(state.pid)) console.log(`Tip: restart ${APP_NAME} to apply the update.`);
|
|
14989
15924
|
};
|
|
14990
15925
|
agentsList = (opts = {}) => {
|
|
14991
15926
|
this.agentCommands.agentsList(opts);
|
|
@@ -15092,6 +16027,12 @@ var CliRuntime = class {
|
|
|
15092
16027
|
doctor = async (opts = {}) => {
|
|
15093
16028
|
await this.diagnosticsCommands.doctor(opts);
|
|
15094
16029
|
};
|
|
16030
|
+
logsPath = () => {
|
|
16031
|
+
this.logsCommands.logsPath();
|
|
16032
|
+
};
|
|
16033
|
+
logsTail = (opts = {}) => {
|
|
16034
|
+
this.logsCommands.logsTail(opts);
|
|
16035
|
+
};
|
|
15095
16036
|
skillsInstall = async (options) => {
|
|
15096
16037
|
const config = loadConfig();
|
|
15097
16038
|
const workdir = resolveSkillsInstallWorkdir({
|
|
@@ -15119,6 +16060,7 @@ var CliRuntime = class {
|
|
|
15119
16060
|
console.log(` Alias: ${result.slug}`);
|
|
15120
16061
|
console.log(` Files: ${result.fileCount}`);
|
|
15121
16062
|
};
|
|
16063
|
+
createObservedProviderManager = (providerManager, source) => new ObservedProviderManager(providerManager, new LlmUsageObserver(llmUsageRecorder, source));
|
|
15122
16064
|
};
|
|
15123
16065
|
//#endregion
|
|
15124
16066
|
//#region src/cli/register-agents-commands.ts
|
|
@@ -15135,6 +16077,7 @@ function registerAgentsCommands(program, runtime) {
|
|
|
15135
16077
|
logStartupTrace("cli.index.module_loaded");
|
|
15136
16078
|
const program = new Command();
|
|
15137
16079
|
const runtime = measureStartupSync("cli.runtime.construct", () => new CliRuntime({ logo: LOGO }));
|
|
16080
|
+
const llmUsageCommands = new LlmUsageCommands();
|
|
15138
16081
|
program.name(APP_NAME).description(`${LOGO} ${APP_NAME} - ${APP_TAGLINE}`).version(getPackageVersion$1(), "-v, --version", "show version");
|
|
15139
16082
|
program.command("onboard").description(`Initialize ${APP_NAME} configuration and workspace`).action(async () => runtime.onboard());
|
|
15140
16083
|
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) }));
|
|
@@ -15203,6 +16146,10 @@ cron.command("disable <jobId>").action(async (jobId) => runtime.cronEnable(jobId
|
|
|
15203
16146
|
cron.command("run <jobId>").option("-f, --force", "Run even if disabled").action(async (jobId, opts) => runtime.cronRun(jobId, opts));
|
|
15204
16147
|
program.command("status").description(`Show ${APP_NAME} status`).option("--json", "Output JSON", false).option("--verbose", "Show extra diagnostics", false).option("--fix", "Fix stale service state when safe", false).action(async (opts) => runtime.status(opts));
|
|
15205
16148
|
program.command("doctor").description(`Run ${APP_NAME} diagnostics`).option("--json", "Output JSON", false).option("--verbose", "Show extra diagnostics", false).option("--fix", "Fix stale service state when safe", false).action(async (opts) => runtime.doctor(opts));
|
|
16149
|
+
const logs = program.command("logs").description("Inspect local runtime logs");
|
|
16150
|
+
logs.command("path").description("Show local log file paths").action(() => runtime.logsPath());
|
|
16151
|
+
logs.command("tail").description("Show recent local log entries").option("--lines <n>", "Number of lines to show", "40").option("--crash", "Tail crash.log instead of service.log", false).action((opts) => runtime.logsTail(opts));
|
|
16152
|
+
program.command("usage").description("Show observed LLM usage snapshots, history, and prompt cache stats").option("--history", "Show recent usage history", false).option("--stats", "Show aggregated usage stats from local history", false).option("--limit <n>", "Maximum number of history records to show", "10").option("--json", "Output JSON", false).action(async (opts) => llmUsageCommands.show(opts));
|
|
15206
16153
|
program.parseAsync(process.argv);
|
|
15207
16154
|
//#endregion
|
|
15208
16155
|
export {};
|