nextclaw 0.17.6 → 0.17.8
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 +1833 -943
- 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
|
|
@@ -6020,6 +6288,40 @@ function runSelfUpdate(options = {}) {
|
|
|
6020
6288
|
};
|
|
6021
6289
|
}
|
|
6022
6290
|
//#endregion
|
|
6291
|
+
//#region src/cli/runtime-state/managed-service-state.store.ts
|
|
6292
|
+
var ManagedServiceStateStore = class {
|
|
6293
|
+
get path() {
|
|
6294
|
+
return resolve(getDataDir(), "run", "service.json");
|
|
6295
|
+
}
|
|
6296
|
+
read = () => {
|
|
6297
|
+
if (!existsSync(this.path)) return null;
|
|
6298
|
+
try {
|
|
6299
|
+
const raw = readFileSync(this.path, "utf-8");
|
|
6300
|
+
return JSON.parse(raw);
|
|
6301
|
+
} catch {
|
|
6302
|
+
return null;
|
|
6303
|
+
}
|
|
6304
|
+
};
|
|
6305
|
+
write = (state) => {
|
|
6306
|
+
mkdirSync(resolve(this.path, ".."), { recursive: true });
|
|
6307
|
+
writeFileSync(this.path, JSON.stringify(state, null, 2));
|
|
6308
|
+
};
|
|
6309
|
+
update = (updater) => {
|
|
6310
|
+
const current = this.read();
|
|
6311
|
+
if (!current) return null;
|
|
6312
|
+
const next = updater(current);
|
|
6313
|
+
this.write(next);
|
|
6314
|
+
return next;
|
|
6315
|
+
};
|
|
6316
|
+
clear = () => {
|
|
6317
|
+
if (existsSync(this.path)) rmSync(this.path, { force: true });
|
|
6318
|
+
};
|
|
6319
|
+
clearIfOwnedByProcess = (pid = process.pid) => {
|
|
6320
|
+
if (this.read()?.pid === pid) this.clear();
|
|
6321
|
+
};
|
|
6322
|
+
};
|
|
6323
|
+
const managedServiceStateStore = new ManagedServiceStateStore();
|
|
6324
|
+
//#endregion
|
|
6023
6325
|
//#region src/cli/commands/plugin/plugin-command-utils.ts
|
|
6024
6326
|
const RESERVED_PROVIDER_IDS$1 = builtinProviderIds();
|
|
6025
6327
|
const RESERVED_TOOL_NAMES = [
|
|
@@ -6045,7 +6347,6 @@ function buildReservedPluginLoadOptions() {
|
|
|
6045
6347
|
reservedToolNames: [...RESERVED_TOOL_NAMES],
|
|
6046
6348
|
reservedChannelIds: [],
|
|
6047
6349
|
reservedProviderIds: RESERVED_PROVIDER_IDS$1,
|
|
6048
|
-
reservedEngineKinds: ["native"],
|
|
6049
6350
|
reservedNcpAgentRuntimeKinds: ["native"]
|
|
6050
6351
|
};
|
|
6051
6352
|
}
|
|
@@ -6053,11 +6354,10 @@ function appendPluginCapabilityLines(lines, plugin) {
|
|
|
6053
6354
|
if (plugin.toolNames.length > 0) lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
|
|
6054
6355
|
if (plugin.channelIds.length > 0) lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
|
|
6055
6356
|
if (plugin.providerIds.length > 0) lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
|
|
6056
|
-
if (plugin.engineKinds.length > 0) lines.push(`Engines: ${plugin.engineKinds.join(", ")}`);
|
|
6057
6357
|
if (plugin.ncpAgentRuntimeKinds.length > 0) lines.push(`NCP runtimes: ${plugin.ncpAgentRuntimeKinds.join(", ")}`);
|
|
6058
6358
|
}
|
|
6059
6359
|
//#endregion
|
|
6060
|
-
//#region src/cli/commands/plugin/
|
|
6360
|
+
//#region src/cli/commands/plugin/development-source/first-party-plugin-load-paths.ts
|
|
6061
6361
|
const readJsonFile = (filePath) => {
|
|
6062
6362
|
try {
|
|
6063
6363
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -6074,7 +6374,7 @@ const readString = (value) => {
|
|
|
6074
6374
|
const resolveDevFirstPartyPluginDir = (explicitDir, moduleDir = path.dirname(fileURLToPath(import.meta.url))) => {
|
|
6075
6375
|
const configured = explicitDir?.trim();
|
|
6076
6376
|
if (configured) return configured;
|
|
6077
|
-
const inferred = path.resolve(moduleDir, "
|
|
6377
|
+
const inferred = path.resolve(moduleDir, "../../../../../extensions");
|
|
6078
6378
|
return fs.existsSync(inferred) ? inferred : void 0;
|
|
6079
6379
|
};
|
|
6080
6380
|
const hasOpenClawExtensions = (pkg) => {
|
|
@@ -6083,6 +6383,14 @@ const hasOpenClawExtensions = (pkg) => {
|
|
|
6083
6383
|
const extensions = openclaw.extensions;
|
|
6084
6384
|
return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
6085
6385
|
};
|
|
6386
|
+
const hasOpenClawDevelopmentExtensions = (pkg) => {
|
|
6387
|
+
const openclaw = pkg.openclaw;
|
|
6388
|
+
if (!openclaw || typeof openclaw !== "object" || Array.isArray(openclaw)) return false;
|
|
6389
|
+
const development = openclaw.development;
|
|
6390
|
+
if (!development || typeof development !== "object" || Array.isArray(development)) return false;
|
|
6391
|
+
const extensions = development.extensions;
|
|
6392
|
+
return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
6393
|
+
};
|
|
6086
6394
|
const normalizePackageSpec = (spec) => {
|
|
6087
6395
|
const trimmed = spec.trim();
|
|
6088
6396
|
if (!trimmed) return;
|
|
@@ -6108,11 +6416,38 @@ const readWorkspacePluginPackages = (workspaceExtensionsDir) => {
|
|
|
6108
6416
|
if (!packageName?.startsWith("@nextclaw/")) continue;
|
|
6109
6417
|
packages.push({
|
|
6110
6418
|
packageName,
|
|
6111
|
-
dir: packageDir
|
|
6419
|
+
dir: packageDir,
|
|
6420
|
+
supportsDevelopmentSource: hasOpenClawDevelopmentExtensions(pkg)
|
|
6112
6421
|
});
|
|
6113
6422
|
}
|
|
6114
6423
|
return packages;
|
|
6115
6424
|
};
|
|
6425
|
+
const mergeLoadPaths$1 = (existingLoadPaths, devLoadPaths) => {
|
|
6426
|
+
const mergedLoadPaths = [...devLoadPaths];
|
|
6427
|
+
for (const entry of existingLoadPaths) if (!mergedLoadPaths.includes(entry)) mergedLoadPaths.push(entry);
|
|
6428
|
+
return mergedLoadPaths;
|
|
6429
|
+
};
|
|
6430
|
+
const buildDevelopmentSourceEntryDefaults = (config, workspacePackages) => {
|
|
6431
|
+
const packageByName = new Map(workspacePackages.map((entry) => [entry.packageName, entry]));
|
|
6432
|
+
const nextEntries = { ...config.plugins.entries ?? {} };
|
|
6433
|
+
let didDefaultDevelopmentSource = false;
|
|
6434
|
+
for (const [pluginId, installRecord] of Object.entries(config.plugins.installs ?? {})) {
|
|
6435
|
+
const packageName = normalizePackageSpec(installRecord.spec ?? "");
|
|
6436
|
+
if (!packageName) continue;
|
|
6437
|
+
if (!packageByName.get(packageName)?.supportsDevelopmentSource) continue;
|
|
6438
|
+
const existingEntry = nextEntries[pluginId];
|
|
6439
|
+
if (existingEntry?.source) continue;
|
|
6440
|
+
nextEntries[pluginId] = {
|
|
6441
|
+
...existingEntry,
|
|
6442
|
+
source: "development"
|
|
6443
|
+
};
|
|
6444
|
+
didDefaultDevelopmentSource = true;
|
|
6445
|
+
}
|
|
6446
|
+
return {
|
|
6447
|
+
didDefaultDevelopmentSource,
|
|
6448
|
+
nextEntries
|
|
6449
|
+
};
|
|
6450
|
+
};
|
|
6116
6451
|
const resolveDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
6117
6452
|
const rootDir = resolveDevFirstPartyPluginDir(workspaceExtensionsDir);
|
|
6118
6453
|
if (!rootDir) return [];
|
|
@@ -6147,15 +6482,19 @@ const resolveDevFirstPartyPluginInstallRoots = (config, workspaceExtensionsDir)
|
|
|
6147
6482
|
return installRoots;
|
|
6148
6483
|
};
|
|
6149
6484
|
const applyDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
6150
|
-
const
|
|
6485
|
+
const rootDir = resolveDevFirstPartyPluginDir(workspaceExtensionsDir);
|
|
6486
|
+
if (!rootDir) return config;
|
|
6487
|
+
const workspacePackages = readWorkspacePluginPackages(rootDir);
|
|
6488
|
+
if (workspacePackages.length === 0) return config;
|
|
6489
|
+
const devLoadPaths = resolveDevFirstPartyPluginLoadPaths(config, rootDir);
|
|
6151
6490
|
if (devLoadPaths.length === 0) return config;
|
|
6152
|
-
const
|
|
6153
|
-
const
|
|
6154
|
-
for (const entry of existingLoadPaths) if (!mergedLoadPaths.includes(entry)) mergedLoadPaths.push(entry);
|
|
6491
|
+
const mergedLoadPaths = mergeLoadPaths$1(Array.isArray(config.plugins.load?.paths) ? config.plugins.load.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [], devLoadPaths);
|
|
6492
|
+
const { didDefaultDevelopmentSource, nextEntries } = buildDevelopmentSourceEntryDefaults(config, workspacePackages);
|
|
6155
6493
|
return {
|
|
6156
6494
|
...config,
|
|
6157
6495
|
plugins: {
|
|
6158
6496
|
...config.plugins,
|
|
6497
|
+
entries: didDefaultDevelopmentSource ? nextEntries : config.plugins.entries,
|
|
6159
6498
|
load: {
|
|
6160
6499
|
...config.plugins.load,
|
|
6161
6500
|
paths: mergedLoadPaths
|
|
@@ -6164,6 +6503,112 @@ const applyDevFirstPartyPluginLoadPaths = (config, workspaceExtensionsDir) => {
|
|
|
6164
6503
|
};
|
|
6165
6504
|
};
|
|
6166
6505
|
//#endregion
|
|
6506
|
+
//#region src/cli/commands/plugin/development-source/dev-plugin-overrides.utils.ts
|
|
6507
|
+
const DEV_PLUGIN_OVERRIDES_ENV = "NEXTCLAW_DEV_PLUGIN_OVERRIDES";
|
|
6508
|
+
function isRecord$3(value) {
|
|
6509
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6510
|
+
}
|
|
6511
|
+
function readOptionalString$6(value) {
|
|
6512
|
+
if (typeof value !== "string") return;
|
|
6513
|
+
return value.trim() || void 0;
|
|
6514
|
+
}
|
|
6515
|
+
function readPackageManifest(pluginPath) {
|
|
6516
|
+
const packageJsonPath = path.join(pluginPath, "package.json");
|
|
6517
|
+
if (!fs.existsSync(packageJsonPath)) return null;
|
|
6518
|
+
try {
|
|
6519
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
6520
|
+
} catch {
|
|
6521
|
+
return null;
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
function assertOverridePluginReadable(override) {
|
|
6525
|
+
if (!fs.existsSync(override.pluginPath)) throw new Error(`[dev-plugin-override] plugin path does not exist for "${override.pluginId}": ${override.pluginPath}`);
|
|
6526
|
+
const packageManifest = readPackageManifest(override.pluginPath);
|
|
6527
|
+
if (!packageManifest) throw new Error(`[dev-plugin-override] package.json is missing or invalid for "${override.pluginId}": ${override.pluginPath}`);
|
|
6528
|
+
const pluginManifest = loadPluginManifest(override.pluginPath);
|
|
6529
|
+
if (!pluginManifest.ok) throw new Error(`[dev-plugin-override] ${pluginManifest.error} for "${override.pluginId}": ${override.pluginPath}`);
|
|
6530
|
+
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}`);
|
|
6531
|
+
if (getPackageManifestExtensions(packageManifest, override.source).length === 0) {
|
|
6532
|
+
const missingEntry = override.source === "development" ? "openclaw.development.extensions" : "openclaw.extensions";
|
|
6533
|
+
throw new Error(`[dev-plugin-override] ${missingEntry} is missing for "${override.pluginId}" at ${override.pluginPath}`);
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
function readOverrideRecord(value, index) {
|
|
6537
|
+
if (!isRecord$3(value)) throw new Error(`[dev-plugin-override] override[${index}] must be an object`);
|
|
6538
|
+
const pluginId = readOptionalString$6(value.pluginId);
|
|
6539
|
+
const pluginPath = readOptionalString$6(value.pluginPath);
|
|
6540
|
+
const source = value.source === "development" ? "development" : "production";
|
|
6541
|
+
if (!pluginId || !pluginPath) throw new Error(`[dev-plugin-override] override[${index}] requires pluginId and pluginPath`);
|
|
6542
|
+
const normalized = {
|
|
6543
|
+
pluginId,
|
|
6544
|
+
pluginPath: path.resolve(pluginPath),
|
|
6545
|
+
source
|
|
6546
|
+
};
|
|
6547
|
+
assertOverridePluginReadable(normalized);
|
|
6548
|
+
return normalized;
|
|
6549
|
+
}
|
|
6550
|
+
function resolveDevPluginOverrides(rawEnv = process.env[DEV_PLUGIN_OVERRIDES_ENV]) {
|
|
6551
|
+
if (typeof rawEnv !== "string" || rawEnv.trim().length === 0) return [];
|
|
6552
|
+
let parsed;
|
|
6553
|
+
try {
|
|
6554
|
+
parsed = JSON.parse(rawEnv);
|
|
6555
|
+
} catch (error) {
|
|
6556
|
+
throw new Error(`[dev-plugin-override] failed to parse ${DEV_PLUGIN_OVERRIDES_ENV}: ${error instanceof Error ? error.message : String(error)}`);
|
|
6557
|
+
}
|
|
6558
|
+
if (!Array.isArray(parsed)) throw new Error(`[dev-plugin-override] ${DEV_PLUGIN_OVERRIDES_ENV} must be a JSON array`);
|
|
6559
|
+
const seenPluginIds = /* @__PURE__ */ new Set();
|
|
6560
|
+
const overrides = parsed.map((entry, index) => readOverrideRecord(entry, index));
|
|
6561
|
+
for (const entry of overrides) {
|
|
6562
|
+
if (seenPluginIds.has(entry.pluginId)) throw new Error(`[dev-plugin-override] duplicate plugin override for "${entry.pluginId}"`);
|
|
6563
|
+
seenPluginIds.add(entry.pluginId);
|
|
6564
|
+
}
|
|
6565
|
+
return overrides;
|
|
6566
|
+
}
|
|
6567
|
+
function mergeLoadPaths(existingLoadPaths, overrideLoadPaths) {
|
|
6568
|
+
const merged = [...overrideLoadPaths];
|
|
6569
|
+
for (const entry of existingLoadPaths) if (!merged.includes(entry)) merged.push(entry);
|
|
6570
|
+
return merged;
|
|
6571
|
+
}
|
|
6572
|
+
function applyExplicitDevPluginOverrides(config, overrides) {
|
|
6573
|
+
if (overrides.length === 0) return config;
|
|
6574
|
+
const nextEntries = { ...config.plugins.entries ?? {} };
|
|
6575
|
+
for (const override of overrides) nextEntries[override.pluginId] = {
|
|
6576
|
+
...nextEntries[override.pluginId] ?? {},
|
|
6577
|
+
source: override.source
|
|
6578
|
+
};
|
|
6579
|
+
const existingLoadPaths = Array.isArray(config.plugins.load?.paths) ? config.plugins.load.paths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
6580
|
+
return {
|
|
6581
|
+
...config,
|
|
6582
|
+
plugins: {
|
|
6583
|
+
...config.plugins,
|
|
6584
|
+
entries: nextEntries,
|
|
6585
|
+
load: {
|
|
6586
|
+
...config.plugins.load,
|
|
6587
|
+
paths: mergeLoadPaths(existingLoadPaths, overrides.map((entry) => entry.pluginPath))
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
};
|
|
6591
|
+
}
|
|
6592
|
+
function resolveDevPluginOverrideInstallRoots(config, overrides) {
|
|
6593
|
+
const installRoots = [];
|
|
6594
|
+
for (const override of overrides) {
|
|
6595
|
+
const installRecord = config.plugins.installs?.[override.pluginId];
|
|
6596
|
+
const installPath = readOptionalString$6(installRecord?.installPath);
|
|
6597
|
+
if (!installPath || installRoots.includes(installPath)) continue;
|
|
6598
|
+
installRoots.push(installPath);
|
|
6599
|
+
}
|
|
6600
|
+
return installRoots;
|
|
6601
|
+
}
|
|
6602
|
+
function resolveDevPluginLoadingContext(config, workspaceExtensionsDir, rawOverridesEnv = process.env[DEV_PLUGIN_OVERRIDES_ENV]) {
|
|
6603
|
+
const configWithFirstPartyOverrides = applyDevFirstPartyPluginLoadPaths(config, workspaceExtensionsDir);
|
|
6604
|
+
const overrides = resolveDevPluginOverrides(rawOverridesEnv);
|
|
6605
|
+
return {
|
|
6606
|
+
configWithDevPluginOverrides: applyExplicitDevPluginOverrides(configWithFirstPartyOverrides, overrides),
|
|
6607
|
+
excludedRoots: [...resolveDevFirstPartyPluginInstallRoots(config, workspaceExtensionsDir), ...resolveDevPluginOverrideInstallRoots(config, overrides)].filter((entry, index, list) => list.indexOf(entry) === index),
|
|
6608
|
+
overrides
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
//#endregion
|
|
6167
6612
|
//#region src/cli/commands/plugin/plugin-mutation-actions.ts
|
|
6168
6613
|
const pluginInstallLogger = {
|
|
6169
6614
|
info: (message) => console.log(message),
|
|
@@ -6335,12 +6780,6 @@ function toExtensionRegistry(pluginRegistry) {
|
|
|
6335
6780
|
channel: channel.channel,
|
|
6336
6781
|
source: channel.source
|
|
6337
6782
|
})),
|
|
6338
|
-
engines: pluginRegistry.engines.map((engine) => ({
|
|
6339
|
-
extensionId: engine.pluginId,
|
|
6340
|
-
kind: engine.kind,
|
|
6341
|
-
factory: engine.factory,
|
|
6342
|
-
source: engine.source
|
|
6343
|
-
})),
|
|
6344
6783
|
ncpAgentRuntimes: pluginRegistry.ncpAgentRuntimes.map((runtime) => ({
|
|
6345
6784
|
pluginId: runtime.pluginId,
|
|
6346
6785
|
kind: runtime.kind,
|
|
@@ -6360,11 +6799,11 @@ function toExtensionRegistry(pluginRegistry) {
|
|
|
6360
6799
|
//#endregion
|
|
6361
6800
|
//#region src/cli/commands/plugins.ts
|
|
6362
6801
|
function loadPluginRegistry(config, workspaceDir) {
|
|
6363
|
-
const
|
|
6802
|
+
const { configWithDevPluginOverrides, excludedRoots } = resolveDevPluginLoadingContext(config, resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR));
|
|
6364
6803
|
return loadOpenClawPlugins({
|
|
6365
|
-
config:
|
|
6804
|
+
config: configWithDevPluginOverrides,
|
|
6366
6805
|
workspaceDir,
|
|
6367
|
-
excludeRoots:
|
|
6806
|
+
excludeRoots: excludedRoots,
|
|
6368
6807
|
...buildReservedPluginLoadOptions(),
|
|
6369
6808
|
logger: {
|
|
6370
6809
|
info: (message) => console.log(message),
|
|
@@ -6894,7 +7333,7 @@ var ConfigCommands = class {
|
|
|
6894
7333
|
};
|
|
6895
7334
|
//#endregion
|
|
6896
7335
|
//#region src/cli/commands/mcp.ts
|
|
6897
|
-
function normalizeOptionalString$
|
|
7336
|
+
function normalizeOptionalString$7(value) {
|
|
6898
7337
|
if (typeof value !== "string") return;
|
|
6899
7338
|
return value.trim() || void 0;
|
|
6900
7339
|
}
|
|
@@ -6917,9 +7356,9 @@ function parseTimeoutMs$1(value) {
|
|
|
6917
7356
|
return Math.trunc(parsed);
|
|
6918
7357
|
}
|
|
6919
7358
|
function buildMcpServerDefinition(command, opts) {
|
|
6920
|
-
const transport = (normalizeOptionalString$
|
|
7359
|
+
const transport = (normalizeOptionalString$7(opts.transport) ?? "stdio").toLowerCase();
|
|
6921
7360
|
const disabled = Boolean(opts.disabled);
|
|
6922
|
-
const explicitAgents = Array.from(new Set((opts.agent ?? []).map((agentId) => normalizeOptionalString$
|
|
7361
|
+
const explicitAgents = Array.from(new Set((opts.agent ?? []).map((agentId) => normalizeOptionalString$7(agentId)).filter((agentId) => Boolean(agentId))));
|
|
6923
7362
|
const allAgents = explicitAgents.length === 0 ? true : Boolean(opts.allAgents);
|
|
6924
7363
|
if (transport === "stdio") {
|
|
6925
7364
|
if (command.length === 0) throw new Error("stdio transport requires a command after --");
|
|
@@ -6929,9 +7368,9 @@ function buildMcpServerDefinition(command, opts) {
|
|
|
6929
7368
|
type: "stdio",
|
|
6930
7369
|
command: command[0],
|
|
6931
7370
|
args: command.slice(1),
|
|
6932
|
-
cwd: normalizeOptionalString$
|
|
7371
|
+
cwd: normalizeOptionalString$7(opts.cwd),
|
|
6933
7372
|
env: parsePairs(opts.env, "env"),
|
|
6934
|
-
stderr: normalizeOptionalString$
|
|
7373
|
+
stderr: normalizeOptionalString$7(opts.stderr) ?? "pipe"
|
|
6935
7374
|
},
|
|
6936
7375
|
scope: {
|
|
6937
7376
|
allAgents,
|
|
@@ -6947,7 +7386,7 @@ function buildMcpServerDefinition(command, opts) {
|
|
|
6947
7386
|
}
|
|
6948
7387
|
};
|
|
6949
7388
|
}
|
|
6950
|
-
const url = normalizeOptionalString$
|
|
7389
|
+
const url = normalizeOptionalString$7(opts.url);
|
|
6951
7390
|
if (!url) throw new Error(`${transport} transport requires --url`);
|
|
6952
7391
|
const timeoutMs = parseTimeoutMs$1(opts.timeoutMs);
|
|
6953
7392
|
const shared = {
|
|
@@ -7094,7 +7533,7 @@ function normalizeSecretSource(value) {
|
|
|
7094
7533
|
const normalized = value.trim().toLowerCase();
|
|
7095
7534
|
return SECRET_SOURCES.includes(normalized) ? normalized : null;
|
|
7096
7535
|
}
|
|
7097
|
-
function normalizeOptionalString$
|
|
7536
|
+
function normalizeOptionalString$6(value) {
|
|
7098
7537
|
if (typeof value !== "string") return;
|
|
7099
7538
|
return value.trim() || void 0;
|
|
7100
7539
|
}
|
|
@@ -7105,9 +7544,9 @@ function parseTimeoutMs(value) {
|
|
|
7105
7544
|
return Math.trunc(parsed);
|
|
7106
7545
|
}
|
|
7107
7546
|
function inferProviderAlias(config, ref) {
|
|
7108
|
-
const explicit = normalizeOptionalString$
|
|
7547
|
+
const explicit = normalizeOptionalString$6(ref.provider);
|
|
7109
7548
|
if (explicit) return explicit;
|
|
7110
|
-
const defaultAlias = normalizeOptionalString$
|
|
7549
|
+
const defaultAlias = normalizeOptionalString$6(config.secrets.defaults[ref.source]);
|
|
7111
7550
|
if (defaultAlias) return defaultAlias;
|
|
7112
7551
|
return ref.source;
|
|
7113
7552
|
}
|
|
@@ -7125,8 +7564,8 @@ function parseRefsPatch(raw) {
|
|
|
7125
7564
|
for (const [path, value] of Object.entries(raw)) {
|
|
7126
7565
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`invalid ref for ${path}`);
|
|
7127
7566
|
const source = normalizeSecretSource(value.source);
|
|
7128
|
-
const id = normalizeOptionalString$
|
|
7129
|
-
const provider = normalizeOptionalString$
|
|
7567
|
+
const id = normalizeOptionalString$6(value.id);
|
|
7568
|
+
const provider = normalizeOptionalString$6(value.provider);
|
|
7130
7569
|
if (!source || !id) throw new Error(`invalid ref for ${path}: source/id is required`);
|
|
7131
7570
|
output[path] = {
|
|
7132
7571
|
source,
|
|
@@ -7213,7 +7652,7 @@ var SecretsCommands = class {
|
|
|
7213
7652
|
if (Boolean(opts.strict) && summary.failed > 0) process.exitCode = 1;
|
|
7214
7653
|
}
|
|
7215
7654
|
async secretsConfigure(opts) {
|
|
7216
|
-
const alias = normalizeOptionalString$
|
|
7655
|
+
const alias = normalizeOptionalString$6(opts.provider);
|
|
7217
7656
|
if (!alias) throw new Error("provider alias is required");
|
|
7218
7657
|
const prevConfig = loadConfig();
|
|
7219
7658
|
const nextConfig = structuredClone(prevConfig);
|
|
@@ -7226,10 +7665,10 @@ var SecretsCommands = class {
|
|
|
7226
7665
|
if (!source) throw new Error("source is required and must be one of env/file/exec");
|
|
7227
7666
|
if (source === "env") nextConfig.secrets.providers[alias] = {
|
|
7228
7667
|
source,
|
|
7229
|
-
...normalizeOptionalString$
|
|
7668
|
+
...normalizeOptionalString$6(opts.prefix) ? { prefix: normalizeOptionalString$6(opts.prefix) } : {}
|
|
7230
7669
|
};
|
|
7231
7670
|
else if (source === "file") {
|
|
7232
|
-
const path = normalizeOptionalString$
|
|
7671
|
+
const path = normalizeOptionalString$6(opts.path);
|
|
7233
7672
|
if (!path) throw new Error("file source requires --path");
|
|
7234
7673
|
nextConfig.secrets.providers[alias] = {
|
|
7235
7674
|
source,
|
|
@@ -7237,13 +7676,13 @@ var SecretsCommands = class {
|
|
|
7237
7676
|
format: "json"
|
|
7238
7677
|
};
|
|
7239
7678
|
} else {
|
|
7240
|
-
const command = normalizeOptionalString$
|
|
7679
|
+
const command = normalizeOptionalString$6(opts.command);
|
|
7241
7680
|
if (!command) throw new Error("exec source requires --command");
|
|
7242
7681
|
nextConfig.secrets.providers[alias] = {
|
|
7243
7682
|
source,
|
|
7244
7683
|
command,
|
|
7245
7684
|
args: Array.isArray(opts.arg) ? opts.arg : [],
|
|
7246
|
-
...normalizeOptionalString$
|
|
7685
|
+
...normalizeOptionalString$6(opts.cwd) ? { cwd: normalizeOptionalString$6(opts.cwd) } : {},
|
|
7247
7686
|
timeoutMs: parseTimeoutMs(opts.timeoutMs) ?? 5e3
|
|
7248
7687
|
};
|
|
7249
7688
|
}
|
|
@@ -7288,9 +7727,9 @@ var SecretsCommands = class {
|
|
|
7288
7727
|
if (opts.remove) delete nextConfig.secrets.refs[path];
|
|
7289
7728
|
else {
|
|
7290
7729
|
const source = normalizeSecretSource(opts.source);
|
|
7291
|
-
const id = normalizeOptionalString$
|
|
7730
|
+
const id = normalizeOptionalString$6(opts.id);
|
|
7292
7731
|
if (!source || !id) throw new Error("apply single ref requires --source and --id");
|
|
7293
|
-
const provider = normalizeOptionalString$
|
|
7732
|
+
const provider = normalizeOptionalString$6(opts.provider);
|
|
7294
7733
|
nextConfig.secrets.refs[path] = {
|
|
7295
7734
|
source,
|
|
7296
7735
|
id,
|
|
@@ -7636,14 +8075,94 @@ function printCronJobs(jobs) {
|
|
|
7636
8075
|
for (const job of jobs) console.log(`${job.id} [${job.enabled ? "enabled" : "disabled"}] ${job.name} ${formatCronSchedule(job.schedule)}`);
|
|
7637
8076
|
}
|
|
7638
8077
|
//#endregion
|
|
8078
|
+
//#region src/cli/runtime-state/local-ui-runtime.store.ts
|
|
8079
|
+
var LocalUiRuntimeStore = class {
|
|
8080
|
+
get path() {
|
|
8081
|
+
return resolve(getDataDir(), "run", "ui-runtime.json");
|
|
8082
|
+
}
|
|
8083
|
+
read = () => {
|
|
8084
|
+
if (!existsSync(this.path)) return null;
|
|
8085
|
+
try {
|
|
8086
|
+
const raw = readFileSync(this.path, "utf-8");
|
|
8087
|
+
return JSON.parse(raw);
|
|
8088
|
+
} catch {
|
|
8089
|
+
return null;
|
|
8090
|
+
}
|
|
8091
|
+
};
|
|
8092
|
+
write = (state) => {
|
|
8093
|
+
mkdirSync(resolve(this.path, ".."), { recursive: true });
|
|
8094
|
+
writeFileSync(this.path, JSON.stringify(state, null, 2));
|
|
8095
|
+
};
|
|
8096
|
+
update = (updater) => {
|
|
8097
|
+
const current = this.read();
|
|
8098
|
+
if (!current) return null;
|
|
8099
|
+
const next = updater(current);
|
|
8100
|
+
this.write(next);
|
|
8101
|
+
return next;
|
|
8102
|
+
};
|
|
8103
|
+
clear = () => {
|
|
8104
|
+
if (existsSync(this.path)) rmSync(this.path, { force: true });
|
|
8105
|
+
};
|
|
8106
|
+
clearIfOwnedByProcess = (pid = process.pid) => {
|
|
8107
|
+
if (this.read()?.pid === pid) this.clear();
|
|
8108
|
+
};
|
|
8109
|
+
writeCurrentProcess = (uiConfig, pid = process.pid) => {
|
|
8110
|
+
const existing = this.read();
|
|
8111
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
8112
|
+
const state = {
|
|
8113
|
+
pid,
|
|
8114
|
+
startedAt: existing?.pid === pid && typeof existing.startedAt === "string" ? existing.startedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
8115
|
+
uiUrl,
|
|
8116
|
+
apiUrl: `${uiUrl}/api`,
|
|
8117
|
+
uiHost: uiConfig.host,
|
|
8118
|
+
uiPort: uiConfig.port,
|
|
8119
|
+
...existing?.remote ? { remote: existing.remote } : {}
|
|
8120
|
+
};
|
|
8121
|
+
this.write(state);
|
|
8122
|
+
return state;
|
|
8123
|
+
};
|
|
8124
|
+
};
|
|
8125
|
+
const localUiRuntimeStore = new LocalUiRuntimeStore();
|
|
8126
|
+
//#endregion
|
|
8127
|
+
//#region src/cli/runtime-state/local-ui-discovery.service.ts
|
|
8128
|
+
var LocalUiDiscoveryService = class {
|
|
8129
|
+
constructor(localUiStore = localUiRuntimeStore, managedServiceStore = managedServiceStateStore, isProcessRunningFn = (pid) => isProcessRunning(pid)) {
|
|
8130
|
+
this.localUiStore = localUiStore;
|
|
8131
|
+
this.managedServiceStore = managedServiceStore;
|
|
8132
|
+
this.isProcessRunningFn = isProcessRunningFn;
|
|
8133
|
+
}
|
|
8134
|
+
readRunningState = (state) => {
|
|
8135
|
+
if (!state || !this.isProcessRunningFn(state.pid)) return null;
|
|
8136
|
+
return state;
|
|
8137
|
+
};
|
|
8138
|
+
readRunningRuntimeState = () => {
|
|
8139
|
+
return this.readRunningState(this.localUiStore.read()) ?? this.readRunningState(this.managedServiceStore.read());
|
|
8140
|
+
};
|
|
8141
|
+
resolveApiBase = () => {
|
|
8142
|
+
const state = this.readRunningRuntimeState();
|
|
8143
|
+
if (!state) return null;
|
|
8144
|
+
if (typeof state.uiUrl === "string" && state.uiUrl.trim().length > 0) return state.uiUrl.replace(/\/+$/, "");
|
|
8145
|
+
if (typeof state.apiUrl === "string" && state.apiUrl.trim().length > 0) return state.apiUrl.replace(/\/api\/?$/, "").replace(/\/+$/, "");
|
|
8146
|
+
return null;
|
|
8147
|
+
};
|
|
8148
|
+
resolveLocalOrigin = (config) => {
|
|
8149
|
+
const state = this.readRunningRuntimeState();
|
|
8150
|
+
const runtimePort = state && typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : null;
|
|
8151
|
+
if (runtimePort !== null) return resolveLocalUiBaseUrl({
|
|
8152
|
+
host: "0.0.0.0",
|
|
8153
|
+
port: runtimePort
|
|
8154
|
+
});
|
|
8155
|
+
return resolveLocalUiBaseUrl({
|
|
8156
|
+
host: "0.0.0.0",
|
|
8157
|
+
port: typeof config.ui.port === "number" && Number.isFinite(config.ui.port) ? config.ui.port : 55667
|
|
8158
|
+
});
|
|
8159
|
+
};
|
|
8160
|
+
};
|
|
8161
|
+
const localUiDiscoveryService = new LocalUiDiscoveryService();
|
|
8162
|
+
//#endregion
|
|
7639
8163
|
//#region src/cli/commands/shared/ui-bridge-api.service.ts
|
|
7640
|
-
function
|
|
7641
|
-
|
|
7642
|
-
if (!state?.pid) return null;
|
|
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;
|
|
8164
|
+
function resolveLocalUiApiBase() {
|
|
8165
|
+
return localUiDiscoveryService.resolveApiBase();
|
|
7647
8166
|
}
|
|
7648
8167
|
var UiBridgeApiClient = class {
|
|
7649
8168
|
cookie;
|
|
@@ -7690,7 +8209,7 @@ var CronCommands = class {
|
|
|
7690
8209
|
this.local = local;
|
|
7691
8210
|
}
|
|
7692
8211
|
createApiClient = () => {
|
|
7693
|
-
const apiBase =
|
|
8212
|
+
const apiBase = resolveLocalUiApiBase();
|
|
7694
8213
|
if (!apiBase) return null;
|
|
7695
8214
|
return new UiBridgeApiClient(apiBase);
|
|
7696
8215
|
};
|
|
@@ -7857,13 +8376,13 @@ function createUnusedRuntime(_params) {
|
|
|
7857
8376
|
throw new Error("runtime creation is not available during runtime listing");
|
|
7858
8377
|
}
|
|
7859
8378
|
function loadRuntimeOnlyPluginRegistry(config, workspaceDir) {
|
|
7860
|
-
const
|
|
8379
|
+
const { configWithDevPluginOverrides, excludedRoots } = resolveDevPluginLoadingContext(config, resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR));
|
|
7861
8380
|
return loadOpenClawPlugins({
|
|
7862
|
-
config:
|
|
8381
|
+
config: configWithDevPluginOverrides,
|
|
7863
8382
|
workspaceDir,
|
|
7864
8383
|
includeBundled: false,
|
|
7865
8384
|
kinds: ["agent-runtime"],
|
|
7866
|
-
excludeRoots:
|
|
8385
|
+
excludeRoots: excludedRoots,
|
|
7867
8386
|
...buildReservedPluginLoadOptions(),
|
|
7868
8387
|
logger: {
|
|
7869
8388
|
info: (message) => console.log(message),
|
|
@@ -8025,7 +8544,7 @@ var AgentCommands = class {
|
|
|
8025
8544
|
//#region src/cli/commands/remote-support/remote-runtime-support.ts
|
|
8026
8545
|
let currentProcessRemoteRuntimeState = null;
|
|
8027
8546
|
function hasRunningNextclawManagedService() {
|
|
8028
|
-
const state =
|
|
8547
|
+
const state = managedServiceStateStore.read();
|
|
8029
8548
|
return Boolean(state && isProcessRunning(state.pid));
|
|
8030
8549
|
}
|
|
8031
8550
|
function createNextclawRemotePlatformClient() {
|
|
@@ -8038,7 +8557,7 @@ function createNextclawRemotePlatformClient() {
|
|
|
8038
8557
|
requireConfigured: true
|
|
8039
8558
|
}).platformBase,
|
|
8040
8559
|
readManagedServiceState: () => {
|
|
8041
|
-
const state =
|
|
8560
|
+
const state = managedServiceStateStore.read();
|
|
8042
8561
|
if (!state) return null;
|
|
8043
8562
|
return {
|
|
8044
8563
|
pid: state.pid,
|
|
@@ -8057,9 +8576,13 @@ function createNextclawRemoteConnector(params = {}) {
|
|
|
8057
8576
|
function createNextclawRemoteStatusStore(mode) {
|
|
8058
8577
|
return new RemoteStatusStore(mode, { writeRemoteState: (next) => {
|
|
8059
8578
|
currentProcessRemoteRuntimeState = next;
|
|
8060
|
-
|
|
8579
|
+
if (localUiRuntimeStore.read()?.pid === process.pid) localUiRuntimeStore.update((state) => ({
|
|
8580
|
+
...state,
|
|
8581
|
+
remote: next
|
|
8582
|
+
}));
|
|
8583
|
+
const serviceState = managedServiceStateStore.read();
|
|
8061
8584
|
if (!serviceState || serviceState.pid !== process.pid) return;
|
|
8062
|
-
|
|
8585
|
+
managedServiceStateStore.update((state) => ({
|
|
8063
8586
|
...state,
|
|
8064
8587
|
remote: next
|
|
8065
8588
|
}));
|
|
@@ -8069,10 +8592,12 @@ function buildNextclawConfiguredRemoteState(config) {
|
|
|
8069
8592
|
return buildConfiguredRemoteState(config);
|
|
8070
8593
|
}
|
|
8071
8594
|
function readCurrentNextclawRemoteRuntimeState() {
|
|
8072
|
-
const
|
|
8073
|
-
const
|
|
8595
|
+
const uiRuntimeState = localUiRuntimeStore.read();
|
|
8596
|
+
const serviceState = managedServiceStateStore.read();
|
|
8597
|
+
const currentRemoteState = currentProcessRemoteRuntimeState ?? uiRuntimeState?.remote ?? serviceState?.remote ?? null;
|
|
8074
8598
|
if (!currentRemoteState) return null;
|
|
8075
|
-
|
|
8599
|
+
const owningRuntime = uiRuntimeState ?? serviceState;
|
|
8600
|
+
if (!owningRuntime || isProcessRunning(owningRuntime.pid)) return currentRemoteState;
|
|
8076
8601
|
return {
|
|
8077
8602
|
...currentRemoteState,
|
|
8078
8603
|
state: currentRemoteState.enabled ? "disconnected" : "disabled",
|
|
@@ -8088,15 +8613,13 @@ function resolveNextclawRemoteStatusSnapshot(config) {
|
|
|
8088
8613
|
}
|
|
8089
8614
|
//#endregion
|
|
8090
8615
|
//#region src/cli/commands/remote.ts
|
|
8091
|
-
function normalizeOptionalString$
|
|
8616
|
+
function normalizeOptionalString$5(value) {
|
|
8092
8617
|
if (typeof value !== "string") return;
|
|
8093
8618
|
const trimmed = value.trim();
|
|
8094
8619
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
8095
8620
|
}
|
|
8096
8621
|
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}`;
|
|
8622
|
+
return localUiDiscoveryService.resolveLocalOrigin(config);
|
|
8100
8623
|
}
|
|
8101
8624
|
async function probeLocalUi(localOrigin) {
|
|
8102
8625
|
try {
|
|
@@ -8187,8 +8710,8 @@ var RemoteCommands = class {
|
|
|
8187
8710
|
configuredEnabled: snapshot.configuredEnabled,
|
|
8188
8711
|
runtime: snapshot.runtime,
|
|
8189
8712
|
localOrigin: resolvedLocalOrigin,
|
|
8190
|
-
deviceName: snapshot.runtime?.deviceName ?? normalizeOptionalString$
|
|
8191
|
-
platformBase: snapshot.runtime?.platformBase ?? normalizeOptionalString$
|
|
8713
|
+
deviceName: snapshot.runtime?.deviceName ?? normalizeOptionalString$5(config.remote.deviceName) ?? hostname(),
|
|
8714
|
+
platformBase: snapshot.runtime?.platformBase ?? normalizeOptionalString$5(config.remote.platformApiBase) ?? normalizeOptionalString$5(config.providers.nextclaw?.apiBase) ?? null
|
|
8192
8715
|
};
|
|
8193
8716
|
}
|
|
8194
8717
|
async status(opts = {}) {
|
|
@@ -8214,8 +8737,8 @@ var RemoteCommands = class {
|
|
|
8214
8737
|
const snapshot = resolveNextclawRemoteStatusSnapshot(config);
|
|
8215
8738
|
const localOrigin = snapshot.runtime?.localOrigin ?? this.deps.currentLocalOrigin ?? resolveConfiguredLocalOrigin(config);
|
|
8216
8739
|
const localUi = await probeLocalUi(localOrigin);
|
|
8217
|
-
const token = normalizeOptionalString$
|
|
8218
|
-
const platformApiBase = normalizeOptionalString$
|
|
8740
|
+
const token = normalizeOptionalString$5(config.providers.nextclaw?.apiKey);
|
|
8741
|
+
const platformApiBase = normalizeOptionalString$5(config.remote.platformApiBase) ?? normalizeOptionalString$5(config.providers.nextclaw?.apiBase);
|
|
8219
8742
|
const checks = [
|
|
8220
8743
|
{
|
|
8221
8744
|
name: "remote-enabled",
|
|
@@ -8324,7 +8847,7 @@ var DiagnosticsCommands = class {
|
|
|
8324
8847
|
constructor(deps) {
|
|
8325
8848
|
this.deps = deps;
|
|
8326
8849
|
}
|
|
8327
|
-
async
|
|
8850
|
+
status = async (opts = {}) => {
|
|
8328
8851
|
const report = await this.collectRuntimeStatus({
|
|
8329
8852
|
verbose: Boolean(opts.verbose),
|
|
8330
8853
|
fix: Boolean(opts.fix)
|
|
@@ -8340,30 +8863,58 @@ var DiagnosticsCommands = class {
|
|
|
8340
8863
|
verbose: Boolean(opts.verbose)
|
|
8341
8864
|
});
|
|
8342
8865
|
process.exitCode = 0;
|
|
8343
|
-
}
|
|
8344
|
-
async
|
|
8866
|
+
};
|
|
8867
|
+
doctor = async (opts = {}) => {
|
|
8345
8868
|
const report = await this.collectRuntimeStatus({
|
|
8346
8869
|
verbose: Boolean(opts.verbose),
|
|
8347
8870
|
fix: Boolean(opts.fix)
|
|
8348
8871
|
});
|
|
8349
|
-
const checkPort = await this.checkPortAvailability(
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
})
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8872
|
+
const checkPort = await this.checkPortAvailability(this.resolveDoctorPortCheckTarget(report));
|
|
8873
|
+
const checks = this.buildDoctorChecks(report, checkPort);
|
|
8874
|
+
const exitCode = this.resolveDoctorExitCode(checks);
|
|
8875
|
+
if (opts.json) {
|
|
8876
|
+
console.log(JSON.stringify({
|
|
8877
|
+
generatedAt: report.generatedAt,
|
|
8878
|
+
checks,
|
|
8879
|
+
status: report,
|
|
8880
|
+
exitCode
|
|
8881
|
+
}, null, 2));
|
|
8882
|
+
process.exitCode = exitCode;
|
|
8883
|
+
return;
|
|
8884
|
+
}
|
|
8885
|
+
printDoctorReport({
|
|
8886
|
+
logo: this.deps.logo,
|
|
8887
|
+
generatedAt: report.generatedAt,
|
|
8888
|
+
checks,
|
|
8889
|
+
recommendations: report.recommendations,
|
|
8890
|
+
verbose: Boolean(opts.verbose),
|
|
8891
|
+
logTail: report.logTail
|
|
8892
|
+
});
|
|
8893
|
+
process.exitCode = exitCode;
|
|
8894
|
+
};
|
|
8895
|
+
resolveDoctorPortCheckTarget = (report) => {
|
|
8896
|
+
const host = report.process.running && report.endpoints.uiUrl ? new URL(report.endpoints.uiUrl).hostname : "127.0.0.1";
|
|
8897
|
+
try {
|
|
8898
|
+
const base = report.process.running && report.endpoints.uiUrl ? report.endpoints.uiUrl : report.endpoints.configuredUiUrl;
|
|
8899
|
+
return {
|
|
8900
|
+
host,
|
|
8901
|
+
port: Number(new URL(base).port || 80)
|
|
8902
|
+
};
|
|
8903
|
+
} catch {
|
|
8904
|
+
return {
|
|
8905
|
+
host,
|
|
8906
|
+
port: 55667
|
|
8907
|
+
};
|
|
8908
|
+
}
|
|
8909
|
+
};
|
|
8910
|
+
buildDoctorChecks = (report, checkPort) => {
|
|
8911
|
+
const providerConfigured = report.providers.some((provider) => provider.configured);
|
|
8912
|
+
return [
|
|
8913
|
+
{
|
|
8914
|
+
name: "config-file",
|
|
8915
|
+
status: report.configExists ? "pass" : "fail",
|
|
8916
|
+
detail: report.configPath
|
|
8917
|
+
},
|
|
8367
8918
|
{
|
|
8368
8919
|
name: "workspace-dir",
|
|
8369
8920
|
status: report.workspaceExists ? "pass" : "warn",
|
|
@@ -8376,12 +8927,12 @@ var DiagnosticsCommands = class {
|
|
|
8376
8927
|
},
|
|
8377
8928
|
{
|
|
8378
8929
|
name: "service-health",
|
|
8379
|
-
status: report.process.running ? report.health.managed.state === "ok" ? "pass" : "fail" :
|
|
8930
|
+
status: report.process.running ? report.health.managed.state === "ok" ? "pass" : "fail" : "warn",
|
|
8380
8931
|
detail: report.process.running ? `${report.health.managed.state}: ${report.health.managed.detail}` : `${report.health.configured.state}: ${report.health.configured.detail}`
|
|
8381
8932
|
},
|
|
8382
8933
|
{
|
|
8383
8934
|
name: "ui-port-availability",
|
|
8384
|
-
status: report.process.running
|
|
8935
|
+
status: report.process.running || checkPort.available ? "pass" : "fail",
|
|
8385
8936
|
detail: report.process.running ? "managed by running service" : checkPort.available ? "available" : checkPort.detail
|
|
8386
8937
|
},
|
|
8387
8938
|
{
|
|
@@ -8390,40 +8941,23 @@ var DiagnosticsCommands = class {
|
|
|
8390
8941
|
detail: providerConfigured ? "at least one provider configured" : "no provider api key configured"
|
|
8391
8942
|
}
|
|
8392
8943
|
];
|
|
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) {
|
|
8944
|
+
};
|
|
8945
|
+
resolveDoctorExitCode = (checks) => {
|
|
8946
|
+
if (checks.some((check) => check.status === "fail")) return 1;
|
|
8947
|
+
if (checks.some((check) => check.status === "warn")) return 1;
|
|
8948
|
+
return 0;
|
|
8949
|
+
};
|
|
8950
|
+
collectRuntimeStatus = async (params) => {
|
|
8417
8951
|
const configPath = getConfigPath();
|
|
8418
8952
|
const config = loadConfig();
|
|
8419
8953
|
const workspacePath = getWorkspacePath(config.agents.defaults.workspace);
|
|
8420
|
-
const serviceStatePath =
|
|
8954
|
+
const serviceStatePath = managedServiceStateStore.path;
|
|
8421
8955
|
const fixActions = [];
|
|
8422
|
-
let serviceState =
|
|
8956
|
+
let serviceState = managedServiceStateStore.read();
|
|
8423
8957
|
if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
|
|
8424
|
-
|
|
8958
|
+
managedServiceStateStore.clear();
|
|
8425
8959
|
fixActions.push("Cleared stale service state file.");
|
|
8426
|
-
serviceState =
|
|
8960
|
+
serviceState = managedServiceStateStore.read();
|
|
8427
8961
|
}
|
|
8428
8962
|
const managedByState = Boolean(serviceState);
|
|
8429
8963
|
const running = Boolean(serviceState && isProcessRunning(serviceState.pid));
|
|
@@ -8459,7 +8993,7 @@ var DiagnosticsCommands = class {
|
|
|
8459
8993
|
issues,
|
|
8460
8994
|
recommendations
|
|
8461
8995
|
});
|
|
8462
|
-
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ??
|
|
8996
|
+
const logTail = params.verbose ? this.readLogTail(serviceState?.logPath ?? resolveAppLogPath("service"), 25) : [];
|
|
8463
8997
|
const level = running ? managedHealth.state === "ok" ? issues.length > 0 ? "degraded" : "healthy" : "degraded" : "stopped";
|
|
8464
8998
|
return {
|
|
8465
8999
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8497,8 +9031,8 @@ var DiagnosticsCommands = class {
|
|
|
8497
9031
|
level,
|
|
8498
9032
|
exitCode: 0
|
|
8499
9033
|
};
|
|
8500
|
-
}
|
|
8501
|
-
async
|
|
9034
|
+
};
|
|
9035
|
+
probeApiHealth = async (url, timeoutMs = 1500) => {
|
|
8502
9036
|
const controller = new AbortController();
|
|
8503
9037
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8504
9038
|
try {
|
|
@@ -8529,8 +9063,8 @@ var DiagnosticsCommands = class {
|
|
|
8529
9063
|
} finally {
|
|
8530
9064
|
clearTimeout(timer);
|
|
8531
9065
|
}
|
|
8532
|
-
}
|
|
8533
|
-
listProviderStatuses(config) {
|
|
9066
|
+
};
|
|
9067
|
+
listProviderStatuses = (config) => {
|
|
8534
9068
|
return listBuiltinProviders().map((spec) => {
|
|
8535
9069
|
const provider = config.providers[spec.name];
|
|
8536
9070
|
const apiKeyRefSet = hasSecretRef(config, `providers.${spec.name}.apiKey`);
|
|
@@ -8555,8 +9089,8 @@ var DiagnosticsCommands = class {
|
|
|
8555
9089
|
detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
|
|
8556
9090
|
};
|
|
8557
9091
|
});
|
|
8558
|
-
}
|
|
8559
|
-
collectRuntimeIssues(params) {
|
|
9092
|
+
};
|
|
9093
|
+
collectRuntimeIssues = (params) => {
|
|
8560
9094
|
if (!existsSync(params.configPath)) {
|
|
8561
9095
|
params.issues.push("Config file is missing.");
|
|
8562
9096
|
params.recommendations.push(`Run ${APP_NAME} init to create config files.`);
|
|
@@ -8571,7 +9105,7 @@ var DiagnosticsCommands = class {
|
|
|
8571
9105
|
}
|
|
8572
9106
|
if (params.running && params.managedHealth.state !== "ok") {
|
|
8573
9107
|
params.issues.push(`Managed service health check failed: ${params.managedHealth.detail}`);
|
|
8574
|
-
params.recommendations.push(`Check logs at ${params.serviceState?.logPath ??
|
|
9108
|
+
params.recommendations.push(`Check logs at ${params.serviceState?.logPath ?? resolveAppLogPath("service")}.`);
|
|
8575
9109
|
}
|
|
8576
9110
|
if (params.running && params.serviceState?.startupState === "degraded" && params.managedHealth.state !== "ok") {
|
|
8577
9111
|
const startupHint = params.serviceState.startupLastProbeError ? ` (${params.serviceState.startupLastProbeError})` : "";
|
|
@@ -8584,8 +9118,8 @@ var DiagnosticsCommands = class {
|
|
|
8584
9118
|
params.recommendations.push("Another process may be occupying the UI port; stop it or use --ui-port with a free port.");
|
|
8585
9119
|
}
|
|
8586
9120
|
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) {
|
|
9121
|
+
};
|
|
9122
|
+
readLogTail = (path, maxLines = 25) => {
|
|
8589
9123
|
if (!existsSync(path)) return [];
|
|
8590
9124
|
try {
|
|
8591
9125
|
const lines = readFileSync(path, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
@@ -8594,8 +9128,8 @@ var DiagnosticsCommands = class {
|
|
|
8594
9128
|
} catch {
|
|
8595
9129
|
return [];
|
|
8596
9130
|
}
|
|
8597
|
-
}
|
|
8598
|
-
async
|
|
9131
|
+
};
|
|
9132
|
+
checkPortAvailability = async (params) => {
|
|
8599
9133
|
return await new Promise((resolve) => {
|
|
8600
9134
|
const server = createServer();
|
|
8601
9135
|
server.once("error", (error) => {
|
|
@@ -8613,7 +9147,34 @@ var DiagnosticsCommands = class {
|
|
|
8613
9147
|
});
|
|
8614
9148
|
});
|
|
8615
9149
|
});
|
|
8616
|
-
}
|
|
9150
|
+
};
|
|
9151
|
+
};
|
|
9152
|
+
//#endregion
|
|
9153
|
+
//#region src/cli/commands/logs.ts
|
|
9154
|
+
var LogsCommands = class {
|
|
9155
|
+
constructor(runtime = getLoggingRuntime()) {
|
|
9156
|
+
this.runtime = runtime;
|
|
9157
|
+
}
|
|
9158
|
+
logsPath = () => {
|
|
9159
|
+
const paths = this.runtime.getPaths();
|
|
9160
|
+
console.log([
|
|
9161
|
+
`Logs directory: ${paths.logsDir}`,
|
|
9162
|
+
`Service log: ${paths.serviceLogPath}`,
|
|
9163
|
+
`Crash log: ${paths.crashLogPath}`,
|
|
9164
|
+
`Archive: ${paths.archiveDir}`
|
|
9165
|
+
].join("\n"));
|
|
9166
|
+
};
|
|
9167
|
+
logsTail = (opts = {}) => {
|
|
9168
|
+
const kind = opts.crash ? "crash" : "service";
|
|
9169
|
+
const rawLines = Number(opts.lines);
|
|
9170
|
+
const lines = Number.isFinite(rawLines) && rawLines > 0 ? Math.floor(rawLines) : 40;
|
|
9171
|
+
const output = this.runtime.tail(kind, lines);
|
|
9172
|
+
if (output.length === 0) {
|
|
9173
|
+
console.log(`No log entries found in ${this.runtime.resolveLogPath(kind)}.`);
|
|
9174
|
+
return;
|
|
9175
|
+
}
|
|
9176
|
+
console.log(output.join("\n"));
|
|
9177
|
+
};
|
|
8617
9178
|
};
|
|
8618
9179
|
//#endregion
|
|
8619
9180
|
//#region src/cli/commands/service-support/runtime/service-port-probe.ts
|
|
@@ -8732,17 +9293,42 @@ async function probeHealthEndpoint(healthUrl) {
|
|
|
8732
9293
|
req.end();
|
|
8733
9294
|
});
|
|
8734
9295
|
}
|
|
9296
|
+
async function inspectUiTarget(params) {
|
|
9297
|
+
const { checkPortAvailabilityFn, healthUrl, host, port, probeHealthEndpointFn } = params;
|
|
9298
|
+
const availability = await (checkPortAvailabilityFn ?? checkPortAvailability)({
|
|
9299
|
+
host,
|
|
9300
|
+
port
|
|
9301
|
+
});
|
|
9302
|
+
if (availability.available) return {
|
|
9303
|
+
state: "available",
|
|
9304
|
+
availabilityDetail: availability.detail,
|
|
9305
|
+
probeError: null
|
|
9306
|
+
};
|
|
9307
|
+
const probe = await (probeHealthEndpointFn ?? probeHealthEndpoint)(healthUrl);
|
|
9308
|
+
if (probe.healthy) return {
|
|
9309
|
+
state: "healthy-existing",
|
|
9310
|
+
availabilityDetail: availability.detail,
|
|
9311
|
+
probeError: null
|
|
9312
|
+
};
|
|
9313
|
+
return {
|
|
9314
|
+
state: "occupied-unhealthy",
|
|
9315
|
+
availabilityDetail: availability.detail,
|
|
9316
|
+
probeError: probe.error
|
|
9317
|
+
};
|
|
9318
|
+
}
|
|
8735
9319
|
async function describeUnmanagedHealthyTargetMessage(params) {
|
|
8736
9320
|
const uiConfig = resolveUiConfig(loadConfig(), params.uiOverrides);
|
|
8737
9321
|
const healthUrl = `${resolveUiApiBase(uiConfig.host, uiConfig.port)}/api/health`;
|
|
8738
|
-
if ((await (
|
|
9322
|
+
if ((await inspectUiTarget({
|
|
8739
9323
|
host: uiConfig.host,
|
|
8740
|
-
port: uiConfig.port
|
|
8741
|
-
|
|
8742
|
-
|
|
9324
|
+
port: uiConfig.port,
|
|
9325
|
+
healthUrl,
|
|
9326
|
+
checkPortAvailabilityFn: params.checkPortAvailabilityFn,
|
|
9327
|
+
probeHealthEndpointFn: params.probeHealthEndpointFn
|
|
9328
|
+
})).state !== "healthy-existing") return null;
|
|
8743
9329
|
return [
|
|
8744
9330
|
`Target UI health: ${healthUrl}`,
|
|
8745
|
-
`A healthy ${APP_NAME} service is already responding on this port, but it is not tracked by ${
|
|
9331
|
+
`A healthy ${APP_NAME} service is already responding on this port, but it is not tracked by ${managedServiceStateStore.path}.`,
|
|
8746
9332
|
`${APP_NAME} restart only stops the background service recorded in managed state; it will not auto-kill Docker or other external listeners.`,
|
|
8747
9333
|
`Fix: stop that external service first or rerun with --ui-port <port>.`
|
|
8748
9334
|
].join("\n");
|
|
@@ -9076,7 +9662,7 @@ function claimManagedRemoteRuntimeOwnership(params) {
|
|
|
9076
9662
|
const managedConflict = detectManagedRemoteOwnershipConflict({
|
|
9077
9663
|
currentPid,
|
|
9078
9664
|
isProcessRunningFn,
|
|
9079
|
-
readServiceStateFn: params.readServiceStateFn ??
|
|
9665
|
+
readServiceStateFn: params.readServiceStateFn ?? managedServiceStateStore.read
|
|
9080
9666
|
});
|
|
9081
9667
|
if (managedConflict) return {
|
|
9082
9668
|
ok: false,
|
|
@@ -9119,7 +9705,7 @@ function createManagedRemoteModuleForUi(params) {
|
|
|
9119
9705
|
});
|
|
9120
9706
|
}
|
|
9121
9707
|
function writeInitialManagedServiceState(params) {
|
|
9122
|
-
|
|
9708
|
+
managedServiceStateStore.write({
|
|
9123
9709
|
pid: params.snapshot.pid,
|
|
9124
9710
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9125
9711
|
uiUrl: params.snapshot.uiUrl,
|
|
@@ -9134,7 +9720,7 @@ function writeInitialManagedServiceState(params) {
|
|
|
9134
9720
|
});
|
|
9135
9721
|
}
|
|
9136
9722
|
function writeReadyManagedServiceState(params) {
|
|
9137
|
-
const currentState =
|
|
9723
|
+
const currentState = managedServiceStateStore.read();
|
|
9138
9724
|
const state = {
|
|
9139
9725
|
pid: params.snapshot.pid,
|
|
9140
9726
|
startedAt: currentState?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9149,7 +9735,7 @@ function writeReadyManagedServiceState(params) {
|
|
|
9149
9735
|
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9150
9736
|
...currentState?.remote ? { remote: currentState.remote } : {}
|
|
9151
9737
|
};
|
|
9152
|
-
|
|
9738
|
+
managedServiceStateStore.write(state);
|
|
9153
9739
|
return state;
|
|
9154
9740
|
}
|
|
9155
9741
|
//#endregion
|
|
@@ -9197,8 +9783,8 @@ function resolveSessionRouteCandidate(params) {
|
|
|
9197
9783
|
function spawnManagedService(params) {
|
|
9198
9784
|
const { appName, config, uiConfig, uiUrl, apiUrl, healthUrl, startupTimeoutMs, resolveStartupTimeoutMs, appendStartupStage, printStartupFailureDiagnostics, resolveServiceLogPath } = params;
|
|
9199
9785
|
const logPath = resolveServiceLogPath();
|
|
9200
|
-
|
|
9201
|
-
|
|
9786
|
+
new FileLogSink({ serviceLogPath: logPath }).ensureReady();
|
|
9787
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
9202
9788
|
const readinessTimeoutMs = resolveStartupTimeoutMs(startupTimeoutMs);
|
|
9203
9789
|
const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
|
|
9204
9790
|
const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);
|
|
@@ -9218,15 +9804,10 @@ function spawnManagedService(params) {
|
|
|
9218
9804
|
appendStartupStage(logPath, `spawning background process: ${cliLaunch.command} ${childArgs.join(" ")}`);
|
|
9219
9805
|
const child = spawn(cliLaunch.command, childArgs, {
|
|
9220
9806
|
env: process.env,
|
|
9221
|
-
stdio:
|
|
9222
|
-
"ignore",
|
|
9223
|
-
logFd,
|
|
9224
|
-
logFd
|
|
9225
|
-
],
|
|
9807
|
+
stdio: "ignore",
|
|
9226
9808
|
detached: true
|
|
9227
9809
|
});
|
|
9228
9810
|
appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
|
|
9229
|
-
closeSync(logFd);
|
|
9230
9811
|
if (!child.pid) {
|
|
9231
9812
|
appendStartupStage(logPath, "spawn failed: child pid missing");
|
|
9232
9813
|
console.error("Error: Failed to start background service.");
|
|
@@ -9371,32 +9952,10 @@ var ServiceFileWatcherRegistry = class {
|
|
|
9371
9952
|
}));
|
|
9372
9953
|
};
|
|
9373
9954
|
};
|
|
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
9955
|
function finalizeLocalUiStartup(params) {
|
|
9397
9956
|
const { setUiEventPublisher, uiConfig, uiStartup } = params;
|
|
9398
9957
|
setUiEventPublisher(uiStartup?.publish);
|
|
9399
|
-
if (uiStartup)
|
|
9958
|
+
if (uiStartup) localUiRuntimeStore.writeCurrentProcess(uiConfig);
|
|
9400
9959
|
}
|
|
9401
9960
|
async function startGatewayRuntimeSupport(params) {
|
|
9402
9961
|
const { cronJobs, cronStorePath, reloadCronStore, remoteModule, startCron, startHeartbeat, watchConfigFile, watcherRegistry } = params;
|
|
@@ -9423,7 +9982,7 @@ function resolveRemoteServiceView(currentUi) {
|
|
|
9423
9982
|
uiUrl: resolveUiApiBase(currentUi.host, currentUi.port),
|
|
9424
9983
|
uiPort: currentUi.port
|
|
9425
9984
|
};
|
|
9426
|
-
const serviceState =
|
|
9985
|
+
const serviceState = managedServiceStateStore.read();
|
|
9427
9986
|
const serviceRunning = Boolean(serviceState && isProcessRunning(serviceState.pid));
|
|
9428
9987
|
return {
|
|
9429
9988
|
running: serviceRunning,
|
|
@@ -9462,7 +10021,7 @@ async function controlCurrentProcessRuntime(action, controller) {
|
|
|
9462
10021
|
};
|
|
9463
10022
|
}
|
|
9464
10023
|
async function controlManagedService(action, deps) {
|
|
9465
|
-
const state =
|
|
10024
|
+
const state = managedServiceStateStore.read();
|
|
9466
10025
|
const running = Boolean(state && isProcessRunning(state.pid));
|
|
9467
10026
|
const currentProcess = Boolean(running && state?.pid === process.pid);
|
|
9468
10027
|
const uiOverrides = resolveManagedUiOverrides();
|
|
@@ -9548,7 +10107,7 @@ function launchManagedSelfControl(params = {}) {
|
|
|
9548
10107
|
"const { spawn } = require(\"node:child_process\");",
|
|
9549
10108
|
"const { rmSync } = require(\"node:fs\");",
|
|
9550
10109
|
`const parentPid = ${process.pid};`,
|
|
9551
|
-
`const serviceStatePath = ${JSON.stringify(
|
|
10110
|
+
`const serviceStatePath = ${JSON.stringify(managedServiceStateStore.path)};`,
|
|
9552
10111
|
`const command = ${JSON.stringify(params.command ?? null)};`,
|
|
9553
10112
|
`const args = ${JSON.stringify(params.args ?? [])};`,
|
|
9554
10113
|
`const cwd = ${JSON.stringify(process.cwd())};`,
|
|
@@ -9597,7 +10156,7 @@ function launchManagedSelfControl(params = {}) {
|
|
|
9597
10156
|
}
|
|
9598
10157
|
//#endregion
|
|
9599
10158
|
//#region src/cli/commands/remote-support/remote-access-host.ts
|
|
9600
|
-
function normalizeOptionalString$
|
|
10159
|
+
function normalizeOptionalString$4(value) {
|
|
9601
10160
|
if (typeof value !== "string") return null;
|
|
9602
10161
|
const trimmed = value.trim();
|
|
9603
10162
|
return trimmed.length > 0 ? trimmed : null;
|
|
@@ -9626,8 +10185,8 @@ var RemoteAccessHost = class {
|
|
|
9626
10185
|
const status = this.deps.remoteCommands.getStatusView();
|
|
9627
10186
|
return {
|
|
9628
10187
|
account: this.readAccountView({
|
|
9629
|
-
token: normalizeOptionalString$
|
|
9630
|
-
apiBase: normalizeOptionalString$
|
|
10188
|
+
token: normalizeOptionalString$4(config.providers.nextclaw?.apiKey),
|
|
10189
|
+
apiBase: normalizeOptionalString$4(config.providers.nextclaw?.apiBase),
|
|
9631
10190
|
platformBase: status.platformBase
|
|
9632
10191
|
}),
|
|
9633
10192
|
settings: {
|
|
@@ -9658,7 +10217,7 @@ var RemoteAccessHost = class {
|
|
|
9658
10217
|
pollBrowserAuth = async (input) => {
|
|
9659
10218
|
const config = loadConfig(getConfigPath());
|
|
9660
10219
|
const result = await this.deps.platformAuthCommands.pollBrowserAuth({
|
|
9661
|
-
apiBase: normalizeOptionalString$
|
|
10220
|
+
apiBase: normalizeOptionalString$4(input.apiBase) ?? normalizeOptionalString$4(config.remote.platformApiBase) ?? normalizeOptionalString$4(config.providers.nextclaw?.apiBase) ?? void 0,
|
|
9662
10221
|
sessionId: input.sessionId
|
|
9663
10222
|
});
|
|
9664
10223
|
if (result.status !== "authorized") return result;
|
|
@@ -9784,51 +10343,71 @@ var AssetPutTool = class {
|
|
|
9784
10343
|
description = "Put a normal file path or base64 bytes into the managed asset store.";
|
|
9785
10344
|
parameters = {
|
|
9786
10345
|
type: "object",
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
10346
|
+
oneOf: [{
|
|
10347
|
+
type: "object",
|
|
10348
|
+
properties: {
|
|
10349
|
+
path: {
|
|
10350
|
+
type: "string",
|
|
10351
|
+
description: "Existing local file path to put into the asset store."
|
|
10352
|
+
},
|
|
10353
|
+
fileName: {
|
|
10354
|
+
type: "string",
|
|
10355
|
+
description: "Optional asset file name override."
|
|
10356
|
+
},
|
|
10357
|
+
mimeType: {
|
|
10358
|
+
type: "string",
|
|
10359
|
+
description: "Optional mime type override."
|
|
10360
|
+
}
|
|
9795
10361
|
},
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
10362
|
+
required: ["path"],
|
|
10363
|
+
additionalProperties: false
|
|
10364
|
+
}, {
|
|
10365
|
+
type: "object",
|
|
10366
|
+
properties: {
|
|
10367
|
+
bytesBase64: {
|
|
10368
|
+
type: "string",
|
|
10369
|
+
description: "Base64 file bytes. Use together with fileName when no source path exists."
|
|
10370
|
+
},
|
|
10371
|
+
fileName: {
|
|
10372
|
+
type: "string",
|
|
10373
|
+
description: "Asset file name. Required when using bytesBase64."
|
|
10374
|
+
},
|
|
10375
|
+
mimeType: {
|
|
10376
|
+
type: "string",
|
|
10377
|
+
description: "Optional mime type override."
|
|
10378
|
+
}
|
|
9799
10379
|
},
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
}
|
|
9804
|
-
}
|
|
10380
|
+
required: ["bytesBase64", "fileName"],
|
|
10381
|
+
additionalProperties: false
|
|
10382
|
+
}]
|
|
9805
10383
|
};
|
|
9806
10384
|
constructor(assetStore, contentBasePath) {
|
|
9807
10385
|
this.assetStore = assetStore;
|
|
9808
10386
|
this.contentBasePath = contentBasePath;
|
|
9809
10387
|
}
|
|
9810
|
-
async
|
|
10388
|
+
execute = async (args) => {
|
|
9811
10389
|
const path = readOptionalString$5(args?.path);
|
|
9812
10390
|
const fileName = readOptionalString$5(args?.fileName);
|
|
9813
10391
|
const mimeType = readOptionalString$5(args?.mimeType);
|
|
9814
10392
|
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 {
|
|
10393
|
+
if (path) return {
|
|
9828
10394
|
ok: true,
|
|
9829
|
-
asset: toAssetPayload(
|
|
10395
|
+
asset: toAssetPayload(await this.assetStore.putPath({
|
|
10396
|
+
path,
|
|
10397
|
+
fileName: fileName ?? void 0,
|
|
10398
|
+
mimeType
|
|
10399
|
+
}), this.contentBasePath)
|
|
9830
10400
|
};
|
|
9831
|
-
|
|
10401
|
+
if (bytes && fileName) return {
|
|
10402
|
+
ok: true,
|
|
10403
|
+
asset: toAssetPayload(await this.assetStore.putBytes({
|
|
10404
|
+
fileName,
|
|
10405
|
+
mimeType,
|
|
10406
|
+
bytes
|
|
10407
|
+
}), this.contentBasePath)
|
|
10408
|
+
};
|
|
10409
|
+
throw new Error("asset_put received invalid arguments after validation.");
|
|
10410
|
+
};
|
|
9832
10411
|
};
|
|
9833
10412
|
var AssetExportTool = class {
|
|
9834
10413
|
name = "asset_export";
|
|
@@ -9845,12 +10424,13 @@ var AssetExportTool = class {
|
|
|
9845
10424
|
description: "Destination file path."
|
|
9846
10425
|
}
|
|
9847
10426
|
},
|
|
9848
|
-
required: ["assetUri", "targetPath"]
|
|
10427
|
+
required: ["assetUri", "targetPath"],
|
|
10428
|
+
additionalProperties: false
|
|
9849
10429
|
};
|
|
9850
10430
|
constructor(assetStore) {
|
|
9851
10431
|
this.assetStore = assetStore;
|
|
9852
10432
|
}
|
|
9853
|
-
async
|
|
10433
|
+
execute = async (args) => {
|
|
9854
10434
|
const assetUri = readOptionalString$5(args?.assetUri);
|
|
9855
10435
|
const targetPath = readOptionalString$5(args?.targetPath);
|
|
9856
10436
|
if (!assetUri || !targetPath) throw new Error("asset_export requires assetUri and targetPath.");
|
|
@@ -9859,7 +10439,7 @@ var AssetExportTool = class {
|
|
|
9859
10439
|
assetUri,
|
|
9860
10440
|
exportedPath: await this.assetStore.export({ uri: assetUri }, resolve(targetPath))
|
|
9861
10441
|
};
|
|
9862
|
-
}
|
|
10442
|
+
};
|
|
9863
10443
|
};
|
|
9864
10444
|
var AssetStatTool = class {
|
|
9865
10445
|
name = "asset_stat";
|
|
@@ -9870,13 +10450,14 @@ var AssetStatTool = class {
|
|
|
9870
10450
|
type: "string",
|
|
9871
10451
|
description: "Managed asset URI to inspect."
|
|
9872
10452
|
} },
|
|
9873
|
-
required: ["assetUri"]
|
|
10453
|
+
required: ["assetUri"],
|
|
10454
|
+
additionalProperties: false
|
|
9874
10455
|
};
|
|
9875
10456
|
constructor(assetStore, contentBasePath) {
|
|
9876
10457
|
this.assetStore = assetStore;
|
|
9877
10458
|
this.contentBasePath = contentBasePath;
|
|
9878
10459
|
}
|
|
9879
|
-
async
|
|
10460
|
+
execute = async (args) => {
|
|
9880
10461
|
const assetUri = readOptionalString$5(args?.assetUri);
|
|
9881
10462
|
if (!assetUri) throw new Error("asset_stat requires assetUri.");
|
|
9882
10463
|
const record = await this.assetStore.statRecord(assetUri);
|
|
@@ -9891,7 +10472,7 @@ var AssetStatTool = class {
|
|
|
9891
10472
|
ok: true,
|
|
9892
10473
|
asset: toAssetPayload(record, this.contentBasePath)
|
|
9893
10474
|
};
|
|
9894
|
-
}
|
|
10475
|
+
};
|
|
9895
10476
|
};
|
|
9896
10477
|
function createAssetTools(params) {
|
|
9897
10478
|
const contentBasePath = params.contentBasePath ?? "/api/ncp/assets/content";
|
|
@@ -10042,7 +10623,7 @@ function toLegacyMessages(messages, options = {}) {
|
|
|
10042
10623
|
}
|
|
10043
10624
|
//#endregion
|
|
10044
10625
|
//#region src/cli/commands/ncp/context/nextclaw-ncp-session-preferences.ts
|
|
10045
|
-
function normalizeOptionalString$
|
|
10626
|
+
function normalizeOptionalString$3(value) {
|
|
10046
10627
|
if (typeof value !== "string") return;
|
|
10047
10628
|
const trimmed = value.trim();
|
|
10048
10629
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -10055,7 +10636,7 @@ function readMetadataModel(metadata) {
|
|
|
10055
10636
|
metadata.session_model
|
|
10056
10637
|
];
|
|
10057
10638
|
for (const candidate of candidates) {
|
|
10058
|
-
const normalized = normalizeOptionalString$
|
|
10639
|
+
const normalized = normalizeOptionalString$3(candidate);
|
|
10059
10640
|
if (normalized) return normalized;
|
|
10060
10641
|
}
|
|
10061
10642
|
return null;
|
|
@@ -10082,7 +10663,7 @@ function resolveEffectiveModel(params) {
|
|
|
10082
10663
|
if (params.requestMetadata.clear_model === true || params.requestMetadata.reset_model === true) delete params.session.metadata.preferred_model;
|
|
10083
10664
|
const inboundModel = readMetadataModel(params.requestMetadata);
|
|
10084
10665
|
if (inboundModel) params.session.metadata.preferred_model = inboundModel;
|
|
10085
|
-
return normalizeOptionalString$
|
|
10666
|
+
return normalizeOptionalString$3(params.session.metadata.preferred_model) ?? params.fallbackModel;
|
|
10086
10667
|
}
|
|
10087
10668
|
function syncSessionThinkingPreference(params) {
|
|
10088
10669
|
if (params.requestMetadata.clear_thinking === true || params.requestMetadata.reset_thinking === true) delete params.session.metadata.preferred_thinking;
|
|
@@ -10094,8 +10675,8 @@ function syncSessionThinkingPreference(params) {
|
|
|
10094
10675
|
if (inboundThinking) params.session.metadata.preferred_thinking = inboundThinking;
|
|
10095
10676
|
}
|
|
10096
10677
|
function resolveSessionChannelContext(params) {
|
|
10097
|
-
const channel = normalizeOptionalString$
|
|
10098
|
-
const chatId = normalizeOptionalString$
|
|
10678
|
+
const channel = normalizeOptionalString$3(params.requestMetadata.channel) ?? normalizeOptionalString$3(params.session.metadata.last_channel) ?? "ui";
|
|
10679
|
+
const chatId = normalizeOptionalString$3(params.requestMetadata.chatId) ?? normalizeOptionalString$3(params.requestMetadata.chat_id) ?? normalizeOptionalString$3(params.session.metadata.last_to) ?? "web-ui";
|
|
10099
10680
|
params.session.metadata.last_channel = channel;
|
|
10100
10681
|
params.session.metadata.last_to = chatId;
|
|
10101
10682
|
return {
|
|
@@ -10739,7 +11320,7 @@ var NextclawNcpContextBuilder = class {
|
|
|
10739
11320
|
accountId: accountId ?? null
|
|
10740
11321
|
});
|
|
10741
11322
|
const toolDefinitions = this.options.toolRegistry.getToolDefinitions();
|
|
10742
|
-
const additionalSystemSections = [buildSessionOrchestrationSection()];
|
|
11323
|
+
const additionalSystemSections = [buildSessionOrchestrationSection(), buildMinimalSystemExecutionPrompt(effectiveModel)].filter(Boolean);
|
|
10743
11324
|
const contextBuilder = new ContextBuilder(effectiveWorkspace, config.agents.context, {
|
|
10744
11325
|
hostWorkspace: profile.workspace,
|
|
10745
11326
|
sessionProjectRoot: readSessionProjectRoot(session.metadata)
|
|
@@ -11302,12 +11883,6 @@ function extractSessionMessageText(message) {
|
|
|
11302
11883
|
if (parts.length === 0) return;
|
|
11303
11884
|
return parts.join("\n\n");
|
|
11304
11885
|
}
|
|
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
11886
|
function readParentSessionId(metadata) {
|
|
11312
11887
|
return readOptionalString$1(metadata?.["parent_session_id"]) ?? void 0;
|
|
11313
11888
|
}
|
|
@@ -11527,8 +12102,6 @@ var SessionRequestBroker = class {
|
|
|
11527
12102
|
task
|
|
11528
12103
|
});
|
|
11529
12104
|
if (streamedMessage) return streamedMessage;
|
|
11530
|
-
const fallbackMessage = findLatestAssistantMessage(await backend.listSessionMessages(request.targetSessionId));
|
|
11531
|
-
if (fallbackMessage) return fallbackMessage;
|
|
11532
12105
|
throw new Error("Session request completed without a final reply.");
|
|
11533
12106
|
};
|
|
11534
12107
|
appendRequestEvents = (request, type) => {
|
|
@@ -11710,33 +12283,151 @@ var SessionRequestDeliveryService = class {
|
|
|
11710
12283
|
};
|
|
11711
12284
|
};
|
|
11712
12285
|
//#endregion
|
|
12286
|
+
//#region src/cli/commands/shared/llm-usage-observer.ts
|
|
12287
|
+
var LlmUsageObserver = class {
|
|
12288
|
+
constructor(recorder, source) {
|
|
12289
|
+
this.recorder = recorder;
|
|
12290
|
+
this.source = source;
|
|
12291
|
+
}
|
|
12292
|
+
observe = (params) => {
|
|
12293
|
+
return this.recorder.record({
|
|
12294
|
+
source: this.source,
|
|
12295
|
+
model: params.model ?? null,
|
|
12296
|
+
usage: params.usage
|
|
12297
|
+
});
|
|
12298
|
+
};
|
|
12299
|
+
};
|
|
12300
|
+
var ObservedProviderManager = class extends ProviderManager {
|
|
12301
|
+
constructor(delegate, observer) {
|
|
12302
|
+
super(delegate.get(null));
|
|
12303
|
+
this.delegate = delegate;
|
|
12304
|
+
this.observer = observer;
|
|
12305
|
+
}
|
|
12306
|
+
get(model) {
|
|
12307
|
+
return this.delegate.get(model);
|
|
12308
|
+
}
|
|
12309
|
+
set(next) {
|
|
12310
|
+
this.delegate.set(next);
|
|
12311
|
+
}
|
|
12312
|
+
setConfig(nextConfig) {
|
|
12313
|
+
this.delegate.setConfig(nextConfig);
|
|
12314
|
+
}
|
|
12315
|
+
async chat(params) {
|
|
12316
|
+
const response = await this.delegate.chat(params);
|
|
12317
|
+
this.observer.observe({
|
|
12318
|
+
model: params.model ?? null,
|
|
12319
|
+
usage: response.usage
|
|
12320
|
+
});
|
|
12321
|
+
return response;
|
|
12322
|
+
}
|
|
12323
|
+
async *chatStream(params) {
|
|
12324
|
+
for await (const event of this.delegate.chatStream(params)) {
|
|
12325
|
+
if (event.type === "done") this.observer.observe({
|
|
12326
|
+
model: params.model ?? null,
|
|
12327
|
+
usage: event.response.usage
|
|
12328
|
+
});
|
|
12329
|
+
yield event;
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12332
|
+
};
|
|
12333
|
+
//#endregion
|
|
11713
12334
|
//#region src/cli/commands/ncp/runtime/ui-ncp-agent-handle.ts
|
|
11714
12335
|
function createUiNcpAgentHandle(params) {
|
|
12336
|
+
const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, applyExtensionRegistry, applyMcpConfig, dispose, assetStore } = params;
|
|
11715
12337
|
return {
|
|
11716
12338
|
basePath: "/api/ncp/agent",
|
|
11717
|
-
agentClientEndpoint: createAgentClientFromServer(
|
|
11718
|
-
streamProvider:
|
|
11719
|
-
|
|
12339
|
+
agentClientEndpoint: createAgentClientFromServer(backend),
|
|
12340
|
+
streamProvider: backend,
|
|
12341
|
+
runApi: backend,
|
|
12342
|
+
sessionApi: backend,
|
|
11720
12343
|
listSessionTypes: (describeParams) => {
|
|
11721
|
-
|
|
11722
|
-
return
|
|
12344
|
+
refreshPluginRuntimeRegistrations();
|
|
12345
|
+
return runtimeRegistry.listSessionTypes(describeParams);
|
|
11723
12346
|
},
|
|
11724
12347
|
assetApi: {
|
|
11725
|
-
put: (input) =>
|
|
12348
|
+
put: (input) => assetStore.putBytes({
|
|
11726
12349
|
fileName: input.fileName,
|
|
11727
12350
|
mimeType: input.mimeType,
|
|
11728
12351
|
bytes: input.bytes,
|
|
11729
12352
|
createdAt: input.createdAt
|
|
11730
12353
|
}),
|
|
11731
|
-
stat: (uri) =>
|
|
11732
|
-
resolveContentPath: (uri) =>
|
|
12354
|
+
stat: (uri) => assetStore.statRecord(uri),
|
|
12355
|
+
resolveContentPath: (uri) => assetStore.resolveContentPath(uri)
|
|
11733
12356
|
},
|
|
11734
|
-
applyExtensionRegistry
|
|
11735
|
-
applyMcpConfig
|
|
11736
|
-
dispose
|
|
12357
|
+
applyExtensionRegistry,
|
|
12358
|
+
applyMcpConfig,
|
|
12359
|
+
dispose
|
|
11737
12360
|
};
|
|
11738
12361
|
}
|
|
11739
12362
|
//#endregion
|
|
12363
|
+
//#region src/cli/runtime-state/llm-usage-record.ts
|
|
12364
|
+
var LlmUsageRecordFactory = class {
|
|
12365
|
+
create = (params) => {
|
|
12366
|
+
const { observedAt, source, model, usage: rawUsage } = params;
|
|
12367
|
+
const usage = this.sanitizeUsage(rawUsage);
|
|
12368
|
+
return {
|
|
12369
|
+
version: 1,
|
|
12370
|
+
observedAt: observedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12371
|
+
source,
|
|
12372
|
+
model: this.normalizeModel(model),
|
|
12373
|
+
usage,
|
|
12374
|
+
summary: this.buildSummary(usage)
|
|
12375
|
+
};
|
|
12376
|
+
};
|
|
12377
|
+
sanitizeUsage = (usage) => {
|
|
12378
|
+
const next = {};
|
|
12379
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
12380
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) continue;
|
|
12381
|
+
next[key] = Math.floor(value);
|
|
12382
|
+
}
|
|
12383
|
+
return next;
|
|
12384
|
+
};
|
|
12385
|
+
buildSummary = (usage) => {
|
|
12386
|
+
const promptTokens = usage.prompt_tokens ?? usage.input_tokens ?? 0;
|
|
12387
|
+
const completionTokens = usage.completion_tokens ?? usage.output_tokens ?? 0;
|
|
12388
|
+
const totalTokens = usage.total_tokens ?? promptTokens + completionTokens;
|
|
12389
|
+
const cacheMetricKeys = Object.keys(usage).filter((key) => key.endsWith("cached_tokens"));
|
|
12390
|
+
const cachedTokens = cacheMetricKeys.reduce((max, key) => Math.max(max, usage[key] ?? 0), 0);
|
|
12391
|
+
return {
|
|
12392
|
+
promptTokens,
|
|
12393
|
+
completionTokens,
|
|
12394
|
+
totalTokens,
|
|
12395
|
+
cachedTokens,
|
|
12396
|
+
cacheHit: cachedTokens > 0,
|
|
12397
|
+
cacheMetricKeys
|
|
12398
|
+
};
|
|
12399
|
+
};
|
|
12400
|
+
normalizeModel = (value) => {
|
|
12401
|
+
if (typeof value !== "string") return null;
|
|
12402
|
+
const trimmed = value.trim();
|
|
12403
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
12404
|
+
};
|
|
12405
|
+
};
|
|
12406
|
+
const llmUsageRecordFactory = new LlmUsageRecordFactory();
|
|
12407
|
+
//#endregion
|
|
12408
|
+
//#region src/cli/commands/shared/llm-usage-recorder.ts
|
|
12409
|
+
var LlmUsageRecorder = class {
|
|
12410
|
+
constructor(deps = {}) {
|
|
12411
|
+
this.deps = deps;
|
|
12412
|
+
}
|
|
12413
|
+
record = (params) => {
|
|
12414
|
+
const record = this.recordFactory.create(params);
|
|
12415
|
+
this.snapshotStore.write(record);
|
|
12416
|
+
this.historyStore.append(record);
|
|
12417
|
+
return record;
|
|
12418
|
+
};
|
|
12419
|
+
get snapshotStore() {
|
|
12420
|
+
return this.deps.snapshotStore ?? llmUsageSnapshotStore;
|
|
12421
|
+
}
|
|
12422
|
+
get historyStore() {
|
|
12423
|
+
return this.deps.historyStore ?? llmUsageHistoryStore;
|
|
12424
|
+
}
|
|
12425
|
+
get recordFactory() {
|
|
12426
|
+
return this.deps.recordFactory ?? llmUsageRecordFactory;
|
|
12427
|
+
}
|
|
12428
|
+
};
|
|
12429
|
+
const llmUsageRecorder = new LlmUsageRecorder();
|
|
12430
|
+
//#endregion
|
|
11740
12431
|
//#region src/cli/commands/ncp/create-ui-ncp-agent.ts
|
|
11741
12432
|
const CODEX_RUNTIME_KIND = "codex";
|
|
11742
12433
|
const CODEX_DIRECT_RUNTIME_BACKEND = "codex-sdk";
|
|
@@ -11801,6 +12492,7 @@ async function createMcpRuntimeSupport(getConfig) {
|
|
|
11801
12492
|
};
|
|
11802
12493
|
}
|
|
11803
12494
|
function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker) {
|
|
12495
|
+
const observedProviderManager = new ObservedProviderManager(params.providerManager, new LlmUsageObserver(llmUsageRecorder, "ui-ncp"));
|
|
11804
12496
|
return ({ stateManager, sessionMetadata, setSessionMetadata }) => {
|
|
11805
12497
|
const reasoningNormalizationMode = resolveNativeReasoningNormalizationMode({
|
|
11806
12498
|
config: params.getConfig(),
|
|
@@ -11809,7 +12501,7 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
11809
12501
|
if (reasoningNormalizationMode !== "off" && readAssistantReasoningNormalizationModeFromMetadata(sessionMetadata) !== reasoningNormalizationMode) setSessionMetadata(writeAssistantReasoningNormalizationModeToMetadata(sessionMetadata, reasoningNormalizationMode));
|
|
11810
12502
|
const toolRegistry = new NextclawNcpToolRegistry({
|
|
11811
12503
|
bus: params.bus,
|
|
11812
|
-
providerManager:
|
|
12504
|
+
providerManager: observedProviderManager,
|
|
11813
12505
|
sessionManager: params.sessionManager,
|
|
11814
12506
|
cronService: params.cronService,
|
|
11815
12507
|
gatewayController: params.gatewayController,
|
|
@@ -11827,7 +12519,7 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
11827
12519
|
resolveMessageToolHints: params.resolveMessageToolHints,
|
|
11828
12520
|
assetStore
|
|
11829
12521
|
}),
|
|
11830
|
-
llmApi: new ProviderManagerNcpLLMApi(
|
|
12522
|
+
llmApi: new ProviderManagerNcpLLMApi(observedProviderManager),
|
|
11831
12523
|
toolRegistry,
|
|
11832
12524
|
stateManager,
|
|
11833
12525
|
reasoningNormalizationMode
|
|
@@ -11905,7 +12597,10 @@ async function createUiNcpAgent(params) {
|
|
|
11905
12597
|
onSessionRunStatusChanged: params.onSessionRunStatusChanged,
|
|
11906
12598
|
createRuntime: (runtimeParams) => {
|
|
11907
12599
|
pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
|
|
11908
|
-
return runtimeRegistry.createRuntime(
|
|
12600
|
+
return runtimeRegistry.createRuntime({
|
|
12601
|
+
...runtimeParams,
|
|
12602
|
+
resolveAssetContentPath: (assetUri) => assetStore.resolveContentPath(assetUri)
|
|
12603
|
+
});
|
|
11909
12604
|
}
|
|
11910
12605
|
});
|
|
11911
12606
|
await backend.start();
|
|
@@ -12372,430 +13067,92 @@ var ConfigReloader = class {
|
|
|
12372
13067
|
}
|
|
12373
13068
|
};
|
|
12374
13069
|
//#endregion
|
|
12375
|
-
//#region src/cli/commands/
|
|
12376
|
-
function
|
|
12377
|
-
|
|
13070
|
+
//#region src/cli/commands/service-support/gateway/service-cron-job-handler.ts
|
|
13071
|
+
function normalizeOptionalString$2(value) {
|
|
13072
|
+
if (typeof value !== "string") return;
|
|
13073
|
+
return value.trim() || void 0;
|
|
12378
13074
|
}
|
|
12379
|
-
function
|
|
12380
|
-
|
|
12381
|
-
|
|
13075
|
+
function buildCronSessionMetadata(params) {
|
|
13076
|
+
const { job, agentId, accountId } = params;
|
|
13077
|
+
const channel = normalizeOptionalString$2(job.payload.channel) ?? "cli";
|
|
13078
|
+
const chatId = normalizeOptionalString$2(job.payload.to) ?? "direct";
|
|
13079
|
+
const metadata = {
|
|
13080
|
+
agentId,
|
|
13081
|
+
agent_id: agentId,
|
|
13082
|
+
channel,
|
|
13083
|
+
chatId,
|
|
13084
|
+
chat_id: chatId,
|
|
13085
|
+
label: job.name,
|
|
13086
|
+
cron_job_id: job.id,
|
|
13087
|
+
cron_job_name: job.name,
|
|
13088
|
+
session_origin: "cron"
|
|
13089
|
+
};
|
|
13090
|
+
if (accountId) {
|
|
13091
|
+
metadata.accountId = accountId;
|
|
13092
|
+
metadata.account_id = accountId;
|
|
13093
|
+
}
|
|
13094
|
+
return metadata;
|
|
12382
13095
|
}
|
|
12383
|
-
function
|
|
12384
|
-
|
|
12385
|
-
|
|
13096
|
+
function buildCronUserMessage(params) {
|
|
13097
|
+
const { sessionId, content, metadata } = params;
|
|
13098
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13099
|
+
return {
|
|
13100
|
+
id: `${sessionId}:user:cron:${timestamp}`,
|
|
13101
|
+
sessionId,
|
|
13102
|
+
role: "user",
|
|
13103
|
+
status: "final",
|
|
13104
|
+
timestamp,
|
|
13105
|
+
parts: [{
|
|
13106
|
+
type: "text",
|
|
13107
|
+
text: content
|
|
13108
|
+
}],
|
|
13109
|
+
metadata: structuredClone(metadata)
|
|
13110
|
+
};
|
|
12386
13111
|
}
|
|
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
|
-
}));
|
|
13112
|
+
function extractMessageText(message) {
|
|
13113
|
+
return message.parts.flatMap((part) => {
|
|
13114
|
+
if (part.type === "text" || part.type === "rich-text") return [part.text];
|
|
13115
|
+
return [];
|
|
13116
|
+
}).map((text) => text.trim()).filter((text) => text.length > 0).join("\n\n");
|
|
12406
13117
|
}
|
|
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;
|
|
13118
|
+
async function runJobOverNcp(params) {
|
|
13119
|
+
const { agent, sessionId, message, metadata, missingCompletedMessageError, runErrorMessage } = params;
|
|
13120
|
+
let completedMessage;
|
|
13121
|
+
for await (const event of agent.runApi.send({
|
|
13122
|
+
sessionId,
|
|
13123
|
+
message,
|
|
13124
|
+
metadata
|
|
13125
|
+
})) {
|
|
13126
|
+
if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
|
|
13127
|
+
if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? runErrorMessage);
|
|
13128
|
+
if (event.type === NcpEventType.MessageCompleted) completedMessage = event.payload.message;
|
|
12619
13129
|
}
|
|
12620
|
-
|
|
12621
|
-
|
|
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;
|
|
12766
|
-
}
|
|
12767
|
-
return value;
|
|
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
|
-
};
|
|
13130
|
+
if (!completedMessage) throw new Error(missingCompletedMessageError);
|
|
13131
|
+
return extractMessageText(completedMessage);
|
|
12787
13132
|
}
|
|
12788
13133
|
function createCronJobHandler(params) {
|
|
12789
13134
|
return async (job) => {
|
|
12790
|
-
const
|
|
12791
|
-
|
|
12792
|
-
const
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
13135
|
+
const ncpAgent = params.resolveNcpAgent();
|
|
13136
|
+
if (!ncpAgent) throw new Error("NCP agent is not ready for cron execution.");
|
|
13137
|
+
const accountId = normalizeOptionalString$2(job.payload.accountId);
|
|
13138
|
+
const agentId = normalizeOptionalString$2(job.payload.agentId) ?? "main";
|
|
13139
|
+
const sessionId = `cron:${job.id}`;
|
|
13140
|
+
const metadata = buildCronSessionMetadata({
|
|
13141
|
+
job,
|
|
13142
|
+
agentId,
|
|
13143
|
+
accountId
|
|
13144
|
+
});
|
|
13145
|
+
const response = await runJobOverNcp({
|
|
13146
|
+
agent: ncpAgent,
|
|
13147
|
+
sessionId,
|
|
13148
|
+
message: buildCronUserMessage({
|
|
13149
|
+
sessionId,
|
|
13150
|
+
content: job.payload.message,
|
|
13151
|
+
metadata
|
|
13152
|
+
}),
|
|
12797
13153
|
metadata,
|
|
12798
|
-
|
|
13154
|
+
missingCompletedMessageError: "cron job completed without a final assistant message",
|
|
13155
|
+
runErrorMessage: "cron job failed"
|
|
12799
13156
|
});
|
|
12800
13157
|
if (job.payload.deliver && job.payload.to) await params.bus.publishOutbound({
|
|
12801
13158
|
channel: job.payload.channel ?? "cli",
|
|
@@ -12807,21 +13164,81 @@ function createCronJobHandler(params) {
|
|
|
12807
13164
|
return response;
|
|
12808
13165
|
};
|
|
12809
13166
|
}
|
|
13167
|
+
function buildHeartbeatMetadata(params) {
|
|
13168
|
+
return {
|
|
13169
|
+
agentId: params.agentId,
|
|
13170
|
+
agent_id: params.agentId,
|
|
13171
|
+
channel: "cli",
|
|
13172
|
+
chatId: "direct",
|
|
13173
|
+
chat_id: "direct",
|
|
13174
|
+
label: "heartbeat",
|
|
13175
|
+
session_origin: "heartbeat"
|
|
13176
|
+
};
|
|
13177
|
+
}
|
|
13178
|
+
function buildHeartbeatUserMessage(params) {
|
|
13179
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13180
|
+
return {
|
|
13181
|
+
id: `heartbeat:user:${timestamp}`,
|
|
13182
|
+
sessionId: "heartbeat",
|
|
13183
|
+
role: "user",
|
|
13184
|
+
status: "final",
|
|
13185
|
+
timestamp,
|
|
13186
|
+
parts: [{
|
|
13187
|
+
type: "text",
|
|
13188
|
+
text: params.content
|
|
13189
|
+
}],
|
|
13190
|
+
metadata: structuredClone(params.metadata)
|
|
13191
|
+
};
|
|
13192
|
+
}
|
|
13193
|
+
function createHeartbeatJobHandler(params) {
|
|
13194
|
+
return async (prompt) => {
|
|
13195
|
+
const ncpAgent = params.resolveNcpAgent();
|
|
13196
|
+
if (!ncpAgent) throw new Error("NCP agent is not ready for heartbeat execution.");
|
|
13197
|
+
const metadata = buildHeartbeatMetadata({ agentId: params.resolveAgentId() });
|
|
13198
|
+
return await runJobOverNcp({
|
|
13199
|
+
agent: ncpAgent,
|
|
13200
|
+
sessionId: "heartbeat",
|
|
13201
|
+
message: buildHeartbeatUserMessage({
|
|
13202
|
+
content: prompt,
|
|
13203
|
+
metadata
|
|
13204
|
+
}),
|
|
13205
|
+
metadata,
|
|
13206
|
+
missingCompletedMessageError: "heartbeat execution completed without a final assistant message",
|
|
13207
|
+
runErrorMessage: "heartbeat execution failed"
|
|
13208
|
+
});
|
|
13209
|
+
};
|
|
13210
|
+
}
|
|
12810
13211
|
//#endregion
|
|
12811
13212
|
//#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$
|
|
13213
|
+
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
13214
|
function applyGatewayCapabilityState(gateway, next) {
|
|
12814
13215
|
gateway.pluginRegistry = next.pluginRegistry;
|
|
12815
13216
|
gateway.pluginChannelBindings = next.pluginChannelBindings;
|
|
12816
13217
|
gateway.extensionRegistry = next.extensionRegistry;
|
|
12817
13218
|
}
|
|
13219
|
+
function normalizeAgentId(value) {
|
|
13220
|
+
return (value ?? "").trim().toLowerCase() || "main";
|
|
13221
|
+
}
|
|
13222
|
+
function createGatewayHeartbeat(state, params) {
|
|
13223
|
+
const handleHeartbeat = createHeartbeatJobHandler({
|
|
13224
|
+
resolveNcpAgent: () => params.getLiveUiNcpAgent?.() ?? null,
|
|
13225
|
+
resolveAgentId: () => normalizeAgentId(resolveDefaultAgentProfileId$1(resolveConfigSecrets$3(loadConfig$3(), { configPath: state.runtimeConfigPath })))
|
|
13226
|
+
});
|
|
13227
|
+
return new HeartbeatService(state.workspace, async (promptText) => await handleHeartbeat(promptText), 1800, true);
|
|
13228
|
+
}
|
|
13229
|
+
function createGatewayCronJobHandler(params) {
|
|
13230
|
+
return createCronJobHandler({
|
|
13231
|
+
resolveNcpAgent: () => params.getLiveUiNcpAgent?.() ?? null,
|
|
13232
|
+
bus: params.bus
|
|
13233
|
+
});
|
|
13234
|
+
}
|
|
12818
13235
|
function createGatewayShellContext(params) {
|
|
12819
13236
|
const runtimeConfigPath = getConfigPath$2();
|
|
12820
13237
|
const config = resolveConfigSecrets$3(loadConfig$3(), { configPath: runtimeConfigPath });
|
|
12821
13238
|
const workspace = getWorkspacePath$2(config.agents.defaults.workspace);
|
|
12822
13239
|
const homeDir = getDataDir$1();
|
|
12823
13240
|
const cronStorePath = join(getDataDir$1(), "cron", "jobs.json");
|
|
12824
|
-
const sessionManager = measureStartupSync("service.gateway_shell_context.session_manager", () => new SessionManager$
|
|
13241
|
+
const sessionManager = measureStartupSync("service.gateway_shell_context.session_manager", () => new SessionManager$2({
|
|
12825
13242
|
workspace,
|
|
12826
13243
|
homeDir
|
|
12827
13244
|
}));
|
|
@@ -12842,10 +13259,11 @@ function createGatewayShellContext(params) {
|
|
|
12842
13259
|
};
|
|
12843
13260
|
}
|
|
12844
13261
|
function createGatewayStartupContext(params) {
|
|
13262
|
+
const { shellContext: providedShellContext, uiOverrides, allowMissingProvider, uiStaticDir, initialPluginRegistry, makeProvider, makeMissingProvider, requestRestart, getLiveUiNcpAgent } = params;
|
|
12845
13263
|
const state = {};
|
|
12846
|
-
const shellContext =
|
|
12847
|
-
uiOverrides
|
|
12848
|
-
uiStaticDir
|
|
13264
|
+
const shellContext = providedShellContext ?? createGatewayShellContext({
|
|
13265
|
+
uiOverrides,
|
|
13266
|
+
uiStaticDir
|
|
12849
13267
|
});
|
|
12850
13268
|
state.runtimeConfigPath = shellContext.runtimeConfigPath;
|
|
12851
13269
|
state.config = shellContext.config;
|
|
@@ -12855,14 +13273,14 @@ function createGatewayStartupContext(params) {
|
|
|
12855
13273
|
state.uiConfig = shellContext.uiConfig;
|
|
12856
13274
|
state.uiStaticDir = shellContext.uiStaticDir;
|
|
12857
13275
|
state.remoteModule = shellContext.remoteModule;
|
|
12858
|
-
state.pluginRegistry =
|
|
13276
|
+
state.pluginRegistry = initialPluginRegistry ?? measureStartupSync("service.gateway_context.load_plugin_registry", () => loadPluginRegistry(state.config, state.workspace));
|
|
12859
13277
|
state.pluginChannelBindings = measureStartupSync("service.gateway_context.get_plugin_channel_bindings", () => getPluginChannelBindings(state.pluginRegistry));
|
|
12860
13278
|
state.extensionRegistry = measureStartupSync("service.gateway_context.to_extension_registry", () => toExtensionRegistry(state.pluginRegistry));
|
|
12861
13279
|
logPluginDiagnostics(state.pluginRegistry);
|
|
12862
13280
|
state.bus = new MessageBus$2();
|
|
12863
|
-
const provider =
|
|
13281
|
+
const provider = allowMissingProvider === true ? makeProvider(state.config, { allowMissing: true }) : makeProvider(state.config);
|
|
12864
13282
|
state.providerManager = measureStartupSync("service.gateway_context.provider_manager", () => new ProviderManager$1({
|
|
12865
|
-
defaultProvider: provider ??
|
|
13283
|
+
defaultProvider: provider ?? makeMissingProvider(state.config),
|
|
12866
13284
|
config: state.config
|
|
12867
13285
|
}));
|
|
12868
13286
|
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 +13291,12 @@ function createGatewayStartupContext(params) {
|
|
|
12873
13291
|
bus: state.bus,
|
|
12874
13292
|
sessionManager: state.sessionManager,
|
|
12875
13293
|
providerManager: state.providerManager,
|
|
12876
|
-
makeProvider: (nextConfig) =>
|
|
13294
|
+
makeProvider: (nextConfig) => makeProvider(nextConfig, { allowMissing: true }) ?? makeMissingProvider(nextConfig),
|
|
12877
13295
|
loadConfig: () => resolveConfigSecrets$3(loadConfig$3(), { configPath: state.runtimeConfigPath }),
|
|
12878
13296
|
resolveChannelConfig: (nextConfig) => resolveChannelConfigView(nextConfig, state.pluginChannelBindings),
|
|
12879
13297
|
getExtensionChannels: () => state.extensionRegistry.channels,
|
|
12880
13298
|
onRestartRequired: (paths) => {
|
|
12881
|
-
|
|
13299
|
+
requestRestart({
|
|
12882
13300
|
reason: `config reload requires restart: ${paths.join(", ")}`,
|
|
12883
13301
|
manualMessage: `Config changes require restart: ${paths.join(", ")}`,
|
|
12884
13302
|
strategy: "background-service-or-manual"
|
|
@@ -12895,7 +13313,7 @@ function createGatewayStartupContext(params) {
|
|
|
12895
13313
|
getConfigPath: getConfigPath$2,
|
|
12896
13314
|
saveConfig: saveConfig$1,
|
|
12897
13315
|
requestRestart: async (options) => {
|
|
12898
|
-
await
|
|
13316
|
+
await requestRestart({
|
|
12899
13317
|
reason: options?.reason ?? "gateway tool restart",
|
|
12900
13318
|
manualMessage: "Restart the gateway to apply changes.",
|
|
12901
13319
|
strategy: "background-service-or-exit",
|
|
@@ -12904,37 +13322,356 @@ function createGatewayStartupContext(params) {
|
|
|
12904
13322
|
});
|
|
12905
13323
|
}
|
|
12906
13324
|
}));
|
|
12907
|
-
state.
|
|
13325
|
+
state.cron.onJob = createGatewayCronJobHandler({
|
|
12908
13326
|
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
|
|
13327
|
+
getLiveUiNcpAgent
|
|
12929
13328
|
});
|
|
12930
|
-
state.heartbeat =
|
|
12931
|
-
content: promptText,
|
|
12932
|
-
sessionKey: "heartbeat",
|
|
12933
|
-
agentId: state.runtimePool.primaryAgentId
|
|
12934
|
-
}), 1800, true);
|
|
13329
|
+
state.heartbeat = createGatewayHeartbeat(state, { getLiveUiNcpAgent });
|
|
12935
13330
|
return state;
|
|
12936
13331
|
}
|
|
12937
13332
|
//#endregion
|
|
13333
|
+
//#region src/cli/commands/ncp/runtime/nextclaw-ncp-runner.ts
|
|
13334
|
+
function normalizeOptionalString$1(value) {
|
|
13335
|
+
if (typeof value !== "string") return;
|
|
13336
|
+
return value.trim() || void 0;
|
|
13337
|
+
}
|
|
13338
|
+
function resolveAttachmentName(attachment) {
|
|
13339
|
+
const explicitName = normalizeOptionalString$1(attachment.name);
|
|
13340
|
+
if (explicitName) return explicitName;
|
|
13341
|
+
const explicitPath = normalizeOptionalString$1(attachment.path);
|
|
13342
|
+
if (explicitPath) return basename(explicitPath);
|
|
13343
|
+
const explicitUrl = normalizeOptionalString$1(attachment.url);
|
|
13344
|
+
if (explicitUrl) try {
|
|
13345
|
+
return basename(new URL(explicitUrl).pathname) || "asset.bin";
|
|
13346
|
+
} catch {
|
|
13347
|
+
return basename(explicitUrl) || "asset.bin";
|
|
13348
|
+
}
|
|
13349
|
+
return "asset.bin";
|
|
13350
|
+
}
|
|
13351
|
+
async function attachmentToPart(attachment, assetApi) {
|
|
13352
|
+
const assetUri = normalizeOptionalString$1(attachment.assetUri);
|
|
13353
|
+
if (assetUri) return createFilePartFromAssetUri(attachment, assetUri);
|
|
13354
|
+
const remoteUrl = normalizeOptionalString$1(attachment.url);
|
|
13355
|
+
const localPath = normalizeOptionalString$1(attachment.path);
|
|
13356
|
+
if (localPath) return await createFilePartFromLocalPath(attachment, localPath, assetApi);
|
|
13357
|
+
if (remoteUrl) return createFilePartFromRemoteUrl(attachment, remoteUrl);
|
|
13358
|
+
throw new Error(`Unsupported attachment payload for "${resolveAttachmentName(attachment)}".`);
|
|
13359
|
+
}
|
|
13360
|
+
function createBaseFilePart(attachment) {
|
|
13361
|
+
return {
|
|
13362
|
+
...attachment.name ? { name: attachment.name } : {},
|
|
13363
|
+
...attachment.mimeType ? { mimeType: attachment.mimeType } : {},
|
|
13364
|
+
...typeof attachment.size === "number" ? { sizeBytes: attachment.size } : {}
|
|
13365
|
+
};
|
|
13366
|
+
}
|
|
13367
|
+
function createFilePartFromAssetUri(attachment, assetUri) {
|
|
13368
|
+
return {
|
|
13369
|
+
type: "file",
|
|
13370
|
+
assetUri,
|
|
13371
|
+
...createBaseFilePart(attachment)
|
|
13372
|
+
};
|
|
13373
|
+
}
|
|
13374
|
+
async function createFilePartFromLocalPath(attachment, localPath, assetApi) {
|
|
13375
|
+
if (!assetApi) throw new Error("NCP asset api is unavailable for local attachments.");
|
|
13376
|
+
const fileName = resolveAttachmentName(attachment);
|
|
13377
|
+
const bytes = await readFile(localPath);
|
|
13378
|
+
return {
|
|
13379
|
+
type: "file",
|
|
13380
|
+
assetUri: (await assetApi.put({
|
|
13381
|
+
fileName,
|
|
13382
|
+
mimeType: attachment.mimeType ?? null,
|
|
13383
|
+
bytes
|
|
13384
|
+
})).uri,
|
|
13385
|
+
name: attachment.name ?? fileName,
|
|
13386
|
+
...attachment.mimeType ? { mimeType: attachment.mimeType } : {},
|
|
13387
|
+
...typeof attachment.size === "number" ? { sizeBytes: attachment.size } : {}
|
|
13388
|
+
};
|
|
13389
|
+
}
|
|
13390
|
+
function createFilePartFromRemoteUrl(attachment, remoteUrl) {
|
|
13391
|
+
return {
|
|
13392
|
+
type: "file",
|
|
13393
|
+
url: remoteUrl,
|
|
13394
|
+
name: attachment.name ?? resolveAttachmentName(attachment),
|
|
13395
|
+
...attachment.mimeType ? { mimeType: attachment.mimeType } : {},
|
|
13396
|
+
...typeof attachment.size === "number" ? { sizeBytes: attachment.size } : {}
|
|
13397
|
+
};
|
|
13398
|
+
}
|
|
13399
|
+
async function buildUserMessageParts(params) {
|
|
13400
|
+
const parts = [];
|
|
13401
|
+
if (params.content.length > 0) parts.push({
|
|
13402
|
+
type: "text",
|
|
13403
|
+
text: params.content
|
|
13404
|
+
});
|
|
13405
|
+
for (const attachment of params.attachments ?? []) parts.push(await attachmentToPart(attachment, params.assetApi));
|
|
13406
|
+
return parts;
|
|
13407
|
+
}
|
|
13408
|
+
async function buildNcpUserMessage(params) {
|
|
13409
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13410
|
+
return {
|
|
13411
|
+
id: `${params.sessionId}:user:${timestamp}`,
|
|
13412
|
+
sessionId: params.sessionId,
|
|
13413
|
+
role: "user",
|
|
13414
|
+
status: "final",
|
|
13415
|
+
timestamp,
|
|
13416
|
+
parts: await buildUserMessageParts({
|
|
13417
|
+
content: params.content,
|
|
13418
|
+
attachments: params.attachments,
|
|
13419
|
+
assetApi: params.assetApi
|
|
13420
|
+
}),
|
|
13421
|
+
metadata: structuredClone(params.metadata ?? {})
|
|
13422
|
+
};
|
|
13423
|
+
}
|
|
13424
|
+
async function runPromptOverNcp(params) {
|
|
13425
|
+
const message = await buildNcpUserMessage({
|
|
13426
|
+
sessionId: params.sessionId,
|
|
13427
|
+
content: params.content,
|
|
13428
|
+
attachments: params.attachments,
|
|
13429
|
+
metadata: params.metadata,
|
|
13430
|
+
assetApi: params.agent.assetApi
|
|
13431
|
+
});
|
|
13432
|
+
let completedMessage;
|
|
13433
|
+
for await (const event of params.agent.runApi.send({
|
|
13434
|
+
sessionId: params.sessionId,
|
|
13435
|
+
message,
|
|
13436
|
+
metadata: params.metadata
|
|
13437
|
+
}, { ...params.abortSignal ? { signal: params.abortSignal } : {} })) {
|
|
13438
|
+
params.onEvent?.(event);
|
|
13439
|
+
if (event.type === NcpEventType.MessageTextDelta) {
|
|
13440
|
+
params.onAssistantDelta?.(event.payload.delta);
|
|
13441
|
+
continue;
|
|
13442
|
+
}
|
|
13443
|
+
if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
|
|
13444
|
+
if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? params.runErrorMessage ?? "NCP run failed.");
|
|
13445
|
+
if (event.type === NcpEventType.MessageCompleted) completedMessage = event.payload.message;
|
|
13446
|
+
}
|
|
13447
|
+
if (!completedMessage) throw new Error(params.missingCompletedMessageError ?? "NCP run completed without a final assistant message.");
|
|
13448
|
+
return {
|
|
13449
|
+
text: extractTextFromNcpMessage(completedMessage),
|
|
13450
|
+
completedMessage
|
|
13451
|
+
};
|
|
13452
|
+
}
|
|
13453
|
+
//#endregion
|
|
13454
|
+
//#region src/cli/commands/ncp/runtime/nextclaw-ncp-dispatch.ts
|
|
13455
|
+
function normalizeOptionalString(value) {
|
|
13456
|
+
if (typeof value !== "string") return;
|
|
13457
|
+
return value.trim() || void 0;
|
|
13458
|
+
}
|
|
13459
|
+
function requireNcpAgent(resolveNcpAgent, purpose) {
|
|
13460
|
+
const agent = resolveNcpAgent?.() ?? null;
|
|
13461
|
+
if (!agent) throw new Error(`NCP agent is not ready for ${purpose}.`);
|
|
13462
|
+
return agent;
|
|
13463
|
+
}
|
|
13464
|
+
function createDirectInboundMessage(params) {
|
|
13465
|
+
return {
|
|
13466
|
+
channel: params.channel ?? "cli",
|
|
13467
|
+
senderId: "user",
|
|
13468
|
+
chatId: params.chatId ?? "direct",
|
|
13469
|
+
content: params.content,
|
|
13470
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
13471
|
+
attachments: params.attachments ?? [],
|
|
13472
|
+
metadata: structuredClone(params.metadata ?? {})
|
|
13473
|
+
};
|
|
13474
|
+
}
|
|
13475
|
+
function buildRunMetadata(params) {
|
|
13476
|
+
return {
|
|
13477
|
+
...params.message.metadata ?? {},
|
|
13478
|
+
...params.metadata ?? {},
|
|
13479
|
+
channel: params.message.channel,
|
|
13480
|
+
chatId: params.message.chatId,
|
|
13481
|
+
chat_id: params.message.chatId,
|
|
13482
|
+
accountId: params.route.accountId,
|
|
13483
|
+
account_id: params.route.accountId,
|
|
13484
|
+
agentId: params.route.agentId,
|
|
13485
|
+
agent_id: params.route.agentId,
|
|
13486
|
+
sessionKey: params.route.sessionKey,
|
|
13487
|
+
session_key: params.route.sessionKey,
|
|
13488
|
+
senderId: params.message.senderId,
|
|
13489
|
+
sender_id: params.message.senderId
|
|
13490
|
+
};
|
|
13491
|
+
}
|
|
13492
|
+
function parseCommandOptionValue(type, rawValue) {
|
|
13493
|
+
const value = rawValue.trim();
|
|
13494
|
+
if (!value) return;
|
|
13495
|
+
if (type === "number") {
|
|
13496
|
+
const parsed = Number(value);
|
|
13497
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
13498
|
+
}
|
|
13499
|
+
if (type === "boolean") {
|
|
13500
|
+
const lowered = value.toLowerCase();
|
|
13501
|
+
if ([
|
|
13502
|
+
"1",
|
|
13503
|
+
"true",
|
|
13504
|
+
"yes",
|
|
13505
|
+
"on"
|
|
13506
|
+
].includes(lowered)) return true;
|
|
13507
|
+
if ([
|
|
13508
|
+
"0",
|
|
13509
|
+
"false",
|
|
13510
|
+
"no",
|
|
13511
|
+
"off"
|
|
13512
|
+
].includes(lowered)) return false;
|
|
13513
|
+
return;
|
|
13514
|
+
}
|
|
13515
|
+
return value;
|
|
13516
|
+
}
|
|
13517
|
+
function parseCommandArgsFromText(commandName, rawTail, specs) {
|
|
13518
|
+
if (!rawTail) return {};
|
|
13519
|
+
const options = specs.find((item) => item.name.trim().toLowerCase() === commandName)?.options;
|
|
13520
|
+
if (!options || options.length === 0) return {};
|
|
13521
|
+
const tokens = rawTail.split(/\s+/).filter(Boolean);
|
|
13522
|
+
const args = {};
|
|
13523
|
+
let cursor = 0;
|
|
13524
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
13525
|
+
if (cursor >= tokens.length) break;
|
|
13526
|
+
const option = options[index];
|
|
13527
|
+
const isLastOption = index === options.length - 1;
|
|
13528
|
+
const rawValue = isLastOption ? tokens.slice(cursor).join(" ") : tokens[cursor];
|
|
13529
|
+
cursor += isLastOption ? tokens.length - cursor : 1;
|
|
13530
|
+
const parsedValue = parseCommandOptionValue(option.type, rawValue);
|
|
13531
|
+
if (parsedValue !== void 0) args[option.name] = parsedValue;
|
|
13532
|
+
}
|
|
13533
|
+
return args;
|
|
13534
|
+
}
|
|
13535
|
+
async function executeSlashCommandMaybe(params) {
|
|
13536
|
+
const trimmed = params.rawContent.trim();
|
|
13537
|
+
if (!trimmed.startsWith("/")) return null;
|
|
13538
|
+
const registry = new CommandRegistry(params.config, params.sessionManager);
|
|
13539
|
+
const executeText = registry.executeText;
|
|
13540
|
+
if (typeof executeText === "function") return (await executeText.call(registry, params.rawContent, {
|
|
13541
|
+
channel: params.channel,
|
|
13542
|
+
chatId: params.chatId,
|
|
13543
|
+
senderId: "user",
|
|
13544
|
+
sessionKey: params.sessionKey
|
|
13545
|
+
}))?.content ?? null;
|
|
13546
|
+
const commandRaw = trimmed.slice(1).trim();
|
|
13547
|
+
if (!commandRaw) return null;
|
|
13548
|
+
const [nameToken, ...restTokens] = commandRaw.split(/\s+/);
|
|
13549
|
+
const commandName = nameToken.trim().toLowerCase();
|
|
13550
|
+
if (!commandName) return null;
|
|
13551
|
+
const args = parseCommandArgsFromText(commandName, restTokens.join(" ").trim(), registry.listSlashCommands());
|
|
13552
|
+
return (await registry.execute(commandName, args, {
|
|
13553
|
+
channel: params.channel,
|
|
13554
|
+
chatId: params.chatId,
|
|
13555
|
+
senderId: "user",
|
|
13556
|
+
sessionKey: params.sessionKey
|
|
13557
|
+
}))?.content ?? null;
|
|
13558
|
+
}
|
|
13559
|
+
function resolveDirectRoute(params) {
|
|
13560
|
+
const message = createDirectInboundMessage(params);
|
|
13561
|
+
const forcedAgentId = normalizeOptionalString(params.agentId) ?? parseAgentScopedSessionKey(params.sessionKey)?.agentId ?? void 0;
|
|
13562
|
+
return {
|
|
13563
|
+
message,
|
|
13564
|
+
route: new AgentRouteResolver(params.config).resolveInbound({
|
|
13565
|
+
message,
|
|
13566
|
+
forcedAgentId,
|
|
13567
|
+
sessionKeyOverride: params.sessionKey
|
|
13568
|
+
})
|
|
13569
|
+
};
|
|
13570
|
+
}
|
|
13571
|
+
function formatUserFacingError(error, maxChars = 320) {
|
|
13572
|
+
const normalized = (error instanceof Error ? error.message || error.name || "Unknown error" : String(error ?? "Unknown error")).replace(/\s+/g, " ").trim();
|
|
13573
|
+
if (!normalized) return "Unknown error";
|
|
13574
|
+
if (normalized.length <= maxChars) return normalized;
|
|
13575
|
+
return `${normalized.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
|
|
13576
|
+
}
|
|
13577
|
+
async function dispatchPromptOverNcp(params) {
|
|
13578
|
+
const { message, route } = resolveDirectRoute({
|
|
13579
|
+
config: params.config,
|
|
13580
|
+
content: params.content,
|
|
13581
|
+
sessionKey: params.sessionKey,
|
|
13582
|
+
channel: params.channel,
|
|
13583
|
+
chatId: params.chatId,
|
|
13584
|
+
attachments: params.attachments,
|
|
13585
|
+
metadata: params.metadata,
|
|
13586
|
+
agentId: params.agentId
|
|
13587
|
+
});
|
|
13588
|
+
const commandResult = await executeSlashCommandMaybe({
|
|
13589
|
+
config: params.config,
|
|
13590
|
+
sessionManager: params.sessionManager,
|
|
13591
|
+
rawContent: params.content,
|
|
13592
|
+
channel: message.channel,
|
|
13593
|
+
chatId: message.chatId,
|
|
13594
|
+
sessionKey: route.sessionKey
|
|
13595
|
+
});
|
|
13596
|
+
if (commandResult) return commandResult;
|
|
13597
|
+
return (await runPromptOverNcp({
|
|
13598
|
+
agent: requireNcpAgent(params.resolveNcpAgent, "direct dispatch"),
|
|
13599
|
+
sessionId: route.sessionKey,
|
|
13600
|
+
content: params.content,
|
|
13601
|
+
attachments: params.attachments,
|
|
13602
|
+
metadata: buildRunMetadata({
|
|
13603
|
+
message,
|
|
13604
|
+
route
|
|
13605
|
+
}),
|
|
13606
|
+
abortSignal: params.abortSignal,
|
|
13607
|
+
onAssistantDelta: params.onAssistantDelta,
|
|
13608
|
+
missingCompletedMessageError: `session "${route.sessionKey}" completed without a final assistant message`,
|
|
13609
|
+
runErrorMessage: `session "${route.sessionKey}" failed`
|
|
13610
|
+
})).text;
|
|
13611
|
+
}
|
|
13612
|
+
async function runGatewayInboundLoop(params) {
|
|
13613
|
+
while (true) {
|
|
13614
|
+
const message = await params.bus.consumeInbound();
|
|
13615
|
+
try {
|
|
13616
|
+
const explicitSessionKey = normalizeOptionalString(message.metadata.session_key_override);
|
|
13617
|
+
const forcedAgentId = normalizeOptionalString(message.metadata.target_agent_id);
|
|
13618
|
+
const route = new AgentRouteResolver(params.getConfig()).resolveInbound({
|
|
13619
|
+
message,
|
|
13620
|
+
forcedAgentId,
|
|
13621
|
+
sessionKeyOverride: explicitSessionKey
|
|
13622
|
+
});
|
|
13623
|
+
const agent = requireNcpAgent(params.resolveNcpAgent, "gateway dispatch");
|
|
13624
|
+
if (message.channel !== "system") await params.bus.publishOutbound(createAssistantStreamResetControlMessage(message));
|
|
13625
|
+
const result = await runPromptOverNcp({
|
|
13626
|
+
agent,
|
|
13627
|
+
sessionId: route.sessionKey,
|
|
13628
|
+
content: message.content,
|
|
13629
|
+
attachments: message.attachments,
|
|
13630
|
+
metadata: buildRunMetadata({
|
|
13631
|
+
message,
|
|
13632
|
+
route
|
|
13633
|
+
}),
|
|
13634
|
+
onAssistantDelta: message.channel !== "system" ? (delta) => {
|
|
13635
|
+
if (!delta) return;
|
|
13636
|
+
params.bus.publishOutbound(createAssistantStreamDeltaControlMessage(message, delta));
|
|
13637
|
+
} : void 0,
|
|
13638
|
+
missingCompletedMessageError: `session "${route.sessionKey}" completed without a final assistant message`,
|
|
13639
|
+
runErrorMessage: `session "${route.sessionKey}" failed`
|
|
13640
|
+
});
|
|
13641
|
+
if (message.channel === "system") {
|
|
13642
|
+
params.onSystemSessionUpdated?.({
|
|
13643
|
+
sessionKey: route.sessionKey,
|
|
13644
|
+
message
|
|
13645
|
+
});
|
|
13646
|
+
continue;
|
|
13647
|
+
}
|
|
13648
|
+
if (!result.text.trim()) {
|
|
13649
|
+
await params.bus.publishOutbound(createTypingStopControlMessage(message));
|
|
13650
|
+
continue;
|
|
13651
|
+
}
|
|
13652
|
+
await params.bus.publishOutbound({
|
|
13653
|
+
channel: message.channel,
|
|
13654
|
+
chatId: message.chatId,
|
|
13655
|
+
content: result.text,
|
|
13656
|
+
media: [],
|
|
13657
|
+
metadata: buildRunMetadata({
|
|
13658
|
+
message,
|
|
13659
|
+
route,
|
|
13660
|
+
metadata: result.completedMessage.metadata
|
|
13661
|
+
})
|
|
13662
|
+
});
|
|
13663
|
+
} catch (error) {
|
|
13664
|
+
await params.bus.publishOutbound({
|
|
13665
|
+
channel: message.channel,
|
|
13666
|
+
chatId: message.chatId,
|
|
13667
|
+
content: `Sorry, I encountered an error: ${formatUserFacingError(error)}`,
|
|
13668
|
+
media: [],
|
|
13669
|
+
metadata: {}
|
|
13670
|
+
});
|
|
13671
|
+
}
|
|
13672
|
+
}
|
|
13673
|
+
}
|
|
13674
|
+
//#endregion
|
|
12938
13675
|
//#region src/cli/commands/service-support/session/service-deferred-ncp-agent.ts
|
|
12939
13676
|
const DEFAULT_BASE_PATH = "/api/ncp/agent";
|
|
12940
13677
|
const DEFERRED_NCP_AGENT_UNAVAILABLE = "ncp agent unavailable during startup";
|
|
@@ -13020,13 +13757,13 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
|
|
|
13020
13757
|
}
|
|
13021
13758
|
//#endregion
|
|
13022
13759
|
//#region src/cli/commands/service-support/gateway/service-gateway-startup.ts
|
|
13023
|
-
function
|
|
13024
|
-
|
|
13760
|
+
function createSystemSessionUpdatedPublisher(params) {
|
|
13761
|
+
return ({ sessionKey }) => {
|
|
13025
13762
|
params.publishUiEvent?.({
|
|
13026
13763
|
type: "session.updated",
|
|
13027
13764
|
payload: { sessionKey }
|
|
13028
13765
|
});
|
|
13029
|
-
}
|
|
13766
|
+
};
|
|
13030
13767
|
}
|
|
13031
13768
|
async function startUiShell(params) {
|
|
13032
13769
|
logStartupTrace("service.start_ui_shell.begin");
|
|
@@ -13067,54 +13804,90 @@ async function startUiShell(params) {
|
|
|
13067
13804
|
};
|
|
13068
13805
|
}
|
|
13069
13806
|
async function startDeferredGatewayStartup(params) {
|
|
13807
|
+
const { uiStartup, deferredNcpSessionService, bus, sessionManager, providerManager, cronService, gatewayController, getConfig, getExtensionRegistry, resolveMessageToolHints, hydrateCapabilities, startPluginGateways, startChannels, wakeFromRestartSentinel, onNcpAgentReady, publishSessionChange } = params;
|
|
13070
13808
|
logStartupTrace("service.deferred_startup.begin");
|
|
13071
|
-
|
|
13809
|
+
try {
|
|
13072
13810
|
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:
|
|
13811
|
+
bus,
|
|
13812
|
+
providerManager,
|
|
13813
|
+
sessionManager,
|
|
13814
|
+
cronService,
|
|
13815
|
+
gatewayController,
|
|
13816
|
+
getConfig,
|
|
13817
|
+
getExtensionRegistry,
|
|
13818
|
+
onSessionUpdated: publishSessionChange,
|
|
13081
13819
|
onSessionRunStatusChanged: (payload) => {
|
|
13082
|
-
|
|
13820
|
+
uiStartup?.publish({
|
|
13083
13821
|
type: "session.run-status",
|
|
13084
13822
|
payload
|
|
13085
13823
|
});
|
|
13086
13824
|
},
|
|
13087
|
-
resolveMessageToolHints: ({ channel, accountId }) =>
|
|
13825
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolveMessageToolHints({
|
|
13088
13826
|
channel,
|
|
13089
13827
|
accountId
|
|
13090
13828
|
})
|
|
13091
13829
|
}));
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13830
|
+
deferredNcpSessionService.activate(ncpAgent.sessionApi);
|
|
13831
|
+
onNcpAgentReady(ncpAgent);
|
|
13832
|
+
if (uiStartup) {
|
|
13833
|
+
uiStartup.deferredNcpAgent.activate(ncpAgent);
|
|
13834
|
+
console.log("✓ UI NCP agent: ready");
|
|
13835
|
+
} else console.log("✓ Service NCP agent: ready");
|
|
13096
13836
|
} catch (error) {
|
|
13097
13837
|
console.error(`UI NCP agent startup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
13098
13838
|
}
|
|
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",
|
|
13839
|
+
if (hydrateCapabilities) await measureStartupAsync("service.deferred_startup.hydrate_capabilities", hydrateCapabilities);
|
|
13840
|
+
await measureStartupAsync("service.deferred_startup.start_plugin_gateways", startPluginGateways);
|
|
13841
|
+
await measureStartupAsync("service.deferred_startup.start_channels", startChannels);
|
|
13842
|
+
await measureStartupAsync("service.deferred_startup.wake_restart_sentinel", wakeFromRestartSentinel);
|
|
13103
13843
|
console.log("✓ Deferred startup: plugin gateways and channels settled");
|
|
13104
13844
|
logStartupTrace("service.deferred_startup.end");
|
|
13105
13845
|
}
|
|
13106
13846
|
async function runGatewayRuntimeLoop(params) {
|
|
13107
13847
|
let startupTask = null;
|
|
13108
13848
|
try {
|
|
13109
|
-
const
|
|
13849
|
+
const runtimeLoopTask = params.runRuntimeLoop();
|
|
13110
13850
|
startupTask = params.startDeferredStartup();
|
|
13111
13851
|
startupTask.catch(params.onDeferredStartupError);
|
|
13112
|
-
await
|
|
13852
|
+
await runtimeLoopTask;
|
|
13113
13853
|
} finally {
|
|
13114
13854
|
if (startupTask) await startupTask.catch(() => void 0);
|
|
13115
13855
|
await params.cleanup();
|
|
13116
13856
|
}
|
|
13117
13857
|
}
|
|
13858
|
+
async function runConfiguredGatewayRuntime(params) {
|
|
13859
|
+
const onSystemSessionUpdated = createSystemSessionUpdatedPublisher({ publishUiEvent: params.publishUiEvent });
|
|
13860
|
+
logStartupTrace("service.start_gateway.runtime_loop_begin");
|
|
13861
|
+
await runGatewayRuntimeLoop({
|
|
13862
|
+
runRuntimeLoop: () => runGatewayInboundLoop({
|
|
13863
|
+
bus: params.gateway.bus,
|
|
13864
|
+
sessionManager: params.gateway.sessionManager,
|
|
13865
|
+
getConfig: params.getConfig,
|
|
13866
|
+
resolveNcpAgent: params.getLiveUiNcpAgent,
|
|
13867
|
+
onSystemSessionUpdated: ({ sessionKey }) => onSystemSessionUpdated({ sessionKey })
|
|
13868
|
+
}),
|
|
13869
|
+
startDeferredStartup: () => startDeferredGatewayStartup({
|
|
13870
|
+
uiStartup: params.uiStartup,
|
|
13871
|
+
deferredNcpSessionService: params.deferredNcpSessionService,
|
|
13872
|
+
bus: params.gateway.bus,
|
|
13873
|
+
sessionManager: params.gateway.sessionManager,
|
|
13874
|
+
providerManager: params.gateway.providerManager,
|
|
13875
|
+
cronService: params.gateway.cron,
|
|
13876
|
+
gatewayController: params.gateway.gatewayController,
|
|
13877
|
+
getConfig: params.getConfig,
|
|
13878
|
+
getExtensionRegistry: params.getExtensionRegistry,
|
|
13879
|
+
resolveMessageToolHints: params.resolveMessageToolHints,
|
|
13880
|
+
hydrateCapabilities: params.deferredStartupHooks.hydrateCapabilities,
|
|
13881
|
+
startPluginGateways: params.deferredStartupHooks.startPluginGateways,
|
|
13882
|
+
startChannels: params.deferredStartupHooks.startChannels,
|
|
13883
|
+
wakeFromRestartSentinel: params.deferredStartupHooks.wakeFromRestartSentinel,
|
|
13884
|
+
onNcpAgentReady: params.deferredStartupHooks.onNcpAgentReady,
|
|
13885
|
+
publishSessionChange: params.publishSessionChange
|
|
13886
|
+
}),
|
|
13887
|
+
onDeferredStartupError: params.onDeferredStartupError,
|
|
13888
|
+
cleanup: params.cleanup
|
|
13889
|
+
});
|
|
13890
|
+
}
|
|
13118
13891
|
//#endregion
|
|
13119
13892
|
//#region src/cli/commands/ncp/session/ncp-session-realtime-change.ts
|
|
13120
13893
|
function toNcpSessionRealtimeEvent(change) {
|
|
@@ -13242,6 +14015,10 @@ function createDeferredUiNcpSessionService(fallbackService) {
|
|
|
13242
14015
|
}
|
|
13243
14016
|
//#endregion
|
|
13244
14017
|
//#region src/cli/commands/service-support/session/service-ncp-session-realtime-bridge.ts
|
|
14018
|
+
function formatBackgroundTaskError(error) {
|
|
14019
|
+
if (error instanceof Error) return error.stack ?? error.message;
|
|
14020
|
+
return String(error);
|
|
14021
|
+
}
|
|
13245
14022
|
function createLatestOnlySessionChangePublisher(publishSessionChange) {
|
|
13246
14023
|
const inFlightTasks = /* @__PURE__ */ new Map();
|
|
13247
14024
|
const rerunKeys = /* @__PURE__ */ new Set();
|
|
@@ -13272,7 +14049,9 @@ function createServiceNcpSessionRealtimeBridge(params) {
|
|
|
13272
14049
|
let publishSessionChange = async (_sessionKey) => {};
|
|
13273
14050
|
let scheduleSessionChange = async (_sessionKey) => {};
|
|
13274
14051
|
const deferredSessionService = createDeferredUiNcpSessionService(new UiSessionService(params.sessionManager, { onSessionUpdated: (sessionKey) => {
|
|
13275
|
-
scheduleSessionChange(sessionKey)
|
|
14052
|
+
scheduleSessionChange(sessionKey).catch((error) => {
|
|
14053
|
+
console.error(`[session-realtime] failed to publish session change for ${sessionKey}: ${formatBackgroundTaskError(error)}`);
|
|
14054
|
+
});
|
|
13276
14055
|
} }));
|
|
13277
14056
|
const publishLatestSessionChange = async (sessionKey) => {
|
|
13278
14057
|
await createNcpSessionRealtimeChangePublisher({
|
|
@@ -13297,35 +14076,26 @@ function createServiceNcpSessionRealtimeBridge(params) {
|
|
|
13297
14076
|
//#endregion
|
|
13298
14077
|
//#region src/cli/commands/plugin/plugin-registry-loader.ts
|
|
13299
14078
|
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
|
-
};
|
|
14079
|
+
return getAppLogger("plugin.registry_loader");
|
|
13306
14080
|
}
|
|
13307
14081
|
function withDevFirstPartyPluginPaths(config) {
|
|
13308
|
-
|
|
13309
|
-
return {
|
|
13310
|
-
workspaceExtensionsDir,
|
|
13311
|
-
configWithDevPluginPaths: applyDevFirstPartyPluginLoadPaths(config, workspaceExtensionsDir)
|
|
13312
|
-
};
|
|
14082
|
+
return resolveDevPluginLoadingContext(config, resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR));
|
|
13313
14083
|
}
|
|
13314
14084
|
async function loadPluginRegistryProgressively(config, workspaceDir, options = {}) {
|
|
13315
|
-
const {
|
|
14085
|
+
const { configWithDevPluginOverrides, excludedRoots } = withDevFirstPartyPluginPaths(config);
|
|
13316
14086
|
return await loadOpenClawPluginsProgressively({
|
|
13317
|
-
config:
|
|
14087
|
+
config: configWithDevPluginOverrides,
|
|
13318
14088
|
workspaceDir,
|
|
13319
|
-
excludeRoots:
|
|
14089
|
+
excludeRoots: excludedRoots,
|
|
13320
14090
|
...buildReservedPluginLoadOptions(),
|
|
13321
14091
|
onPluginProcessed: options.onPluginProcessed,
|
|
13322
14092
|
logger: createPluginLogger()
|
|
13323
14093
|
});
|
|
13324
14094
|
}
|
|
13325
14095
|
function discoverPluginRegistryStatus(config, workspaceDir) {
|
|
13326
|
-
const {
|
|
14096
|
+
const { configWithDevPluginOverrides } = withDevFirstPartyPluginPaths(config);
|
|
13327
14097
|
return discoverPluginStatusReport({
|
|
13328
|
-
config:
|
|
14098
|
+
config: configWithDevPluginOverrides,
|
|
13329
14099
|
workspaceDir
|
|
13330
14100
|
});
|
|
13331
14101
|
}
|
|
@@ -13335,7 +14105,6 @@ function createEmptyPluginRegistry() {
|
|
|
13335
14105
|
tools: [],
|
|
13336
14106
|
channels: [],
|
|
13337
14107
|
providers: [],
|
|
13338
|
-
engines: [],
|
|
13339
14108
|
ncpAgentRuntimes: [],
|
|
13340
14109
|
diagnostics: [],
|
|
13341
14110
|
resolvedTools: []
|
|
@@ -13554,8 +14323,6 @@ async function hydrateServiceCapabilities(params) {
|
|
|
13554
14323
|
params.state.extensionRegistry = nextExtensionRegistry;
|
|
13555
14324
|
params.state.pluginChannelBindings = nextPluginChannelBindings;
|
|
13556
14325
|
params.state.pluginUiMetadata = nextPluginUiMetadata;
|
|
13557
|
-
params.gateway.runtimePool.applyExtensionRegistry(nextExtensionRegistry);
|
|
13558
|
-
params.gateway.runtimePool.applyRuntimeConfig(nextConfig);
|
|
13559
14326
|
params.getLiveUiNcpAgent()?.applyExtensionRegistry?.(nextExtensionRegistry);
|
|
13560
14327
|
if (shouldRebuildChannels) await params.gateway.reloader.rebuildChannels(nextConfig, { start: false });
|
|
13561
14328
|
params.uiStartup?.publish({
|
|
@@ -13579,7 +14346,7 @@ async function hydrateServiceCapabilities(params) {
|
|
|
13579
14346
|
//#endregion
|
|
13580
14347
|
//#region src/cli/commands/service-support/plugin/service-plugin-runtime-bridge.ts
|
|
13581
14348
|
function installPluginRuntimeBridge(params) {
|
|
13582
|
-
const {
|
|
14349
|
+
const { dispatchPrompt, runtimeConfigPath, getPluginChannelBindings } = params;
|
|
13583
14350
|
setPluginRuntimeBridge({
|
|
13584
14351
|
loadConfig: () => toPluginConfigView$1(resolveConfigSecrets(loadConfig(), { configPath: runtimeConfigPath }), getPluginChannelBindings()),
|
|
13585
14352
|
writeConfigFile: async (nextConfigView) => {
|
|
@@ -13591,7 +14358,7 @@ function installPluginRuntimeBridge(params) {
|
|
|
13591
14358
|
if (!request) return;
|
|
13592
14359
|
try {
|
|
13593
14360
|
await dispatcherOptions.onReplyStart?.();
|
|
13594
|
-
const response = await
|
|
14361
|
+
const response = await dispatchPrompt(request);
|
|
13595
14362
|
const replyText = typeof response === "string" ? response : String(response ?? "");
|
|
13596
14363
|
if (replyText.trim()) await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
|
|
13597
14364
|
} catch (error) {
|
|
@@ -13723,7 +14490,6 @@ function applyGatewayRuntimeCapabilityState(params) {
|
|
|
13723
14490
|
params.state.pluginChannelBindings = params.next.pluginChannelBindings;
|
|
13724
14491
|
}
|
|
13725
14492
|
function configureGatewayPluginRuntime(params) {
|
|
13726
|
-
params.gateway.reloader.setApplyAgentRuntimeConfig((nextConfig) => params.gateway.runtimePool.applyRuntimeConfig(nextConfig));
|
|
13727
14493
|
params.gateway.reloader.setReloadPlugins(async ({ config: nextConfig, changedPaths }) => {
|
|
13728
14494
|
const result = await reloadServicePlugins({
|
|
13729
14495
|
nextConfig,
|
|
@@ -13746,9 +14512,7 @@ function configureGatewayPluginRuntime(params) {
|
|
|
13746
14512
|
});
|
|
13747
14513
|
params.state.pluginUiMetadata = getPluginUiMetadataFromRegistry(result.pluginRegistry);
|
|
13748
14514
|
params.state.pluginGatewayHandles = result.pluginGatewayHandles;
|
|
13749
|
-
params.gateway.runtimePool.applyExtensionRegistry(result.extensionRegistry);
|
|
13750
14515
|
params.getLiveUiNcpAgent()?.applyExtensionRegistry?.(result.extensionRegistry);
|
|
13751
|
-
params.gateway.runtimePool.applyRuntimeConfig(nextConfig);
|
|
13752
14516
|
if (result.restartChannels) console.log("Config reload: plugin channel gateways restarted.");
|
|
13753
14517
|
return { restartChannels: result.restartChannels };
|
|
13754
14518
|
});
|
|
@@ -13756,7 +14520,12 @@ function configureGatewayPluginRuntime(params) {
|
|
|
13756
14520
|
await params.getLiveUiNcpAgent()?.applyMcpConfig?.(nextConfig);
|
|
13757
14521
|
});
|
|
13758
14522
|
installPluginRuntimeBridge({
|
|
13759
|
-
|
|
14523
|
+
dispatchPrompt: async (request) => await dispatchPromptOverNcp({
|
|
14524
|
+
config: resolveConfigSecrets$2(loadConfig$2(), { configPath: params.gateway.runtimeConfigPath }),
|
|
14525
|
+
sessionManager: params.gateway.sessionManager,
|
|
14526
|
+
resolveNcpAgent: () => params.getLiveUiNcpAgent(),
|
|
14527
|
+
...request
|
|
14528
|
+
}),
|
|
13760
14529
|
runtimeConfigPath: params.gateway.runtimeConfigPath,
|
|
13761
14530
|
getPluginChannelBindings: () => params.state.pluginChannelBindings
|
|
13762
14531
|
});
|
|
@@ -13796,8 +14565,26 @@ function createDeferredGatewayStartupHooks(params) {
|
|
|
13796
14565
|
};
|
|
13797
14566
|
}
|
|
13798
14567
|
//#endregion
|
|
14568
|
+
//#region src/cli/commands/service-support/gateway/service-gateway-runtime-lifecycle.ts
|
|
14569
|
+
function handleGatewayDeferredStartupError(params) {
|
|
14570
|
+
const message = params.error instanceof Error ? params.error.message : String(params.error);
|
|
14571
|
+
params.bootstrapStatus.markError(message);
|
|
14572
|
+
if (params.bootstrapStatus.getStatus().pluginHydration.state === "running") params.bootstrapStatus.markPluginHydrationError(message);
|
|
14573
|
+
console.error(`Deferred startup failed: ${params.error instanceof Error ? params.error.message : String(params.error)}`);
|
|
14574
|
+
}
|
|
14575
|
+
async function cleanupGatewayRuntime(params) {
|
|
14576
|
+
localUiRuntimeStore.clearIfOwnedByProcess();
|
|
14577
|
+
await params.fileWatchers.clear();
|
|
14578
|
+
params.resetRuntimeState();
|
|
14579
|
+
params.clearRealtimeBridge();
|
|
14580
|
+
await params.uiStartup?.deferredNcpAgent.close();
|
|
14581
|
+
await params.remoteModule?.stop();
|
|
14582
|
+
await stopPluginChannelGateways(params.runtimeState?.pluginGatewayHandles ?? []);
|
|
14583
|
+
setPluginRuntimeBridge(null);
|
|
14584
|
+
}
|
|
14585
|
+
//#endregion
|
|
13799
14586
|
//#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;
|
|
14587
|
+
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
14588
|
function createSkillsLoader(workspace) {
|
|
13802
14589
|
const ctor = NextclawCore.SkillsLoader;
|
|
13803
14590
|
if (!ctor) return null;
|
|
@@ -13807,10 +14594,14 @@ var ServiceCommands = class {
|
|
|
13807
14594
|
applyLiveConfigReload = null;
|
|
13808
14595
|
liveUiNcpAgent = null;
|
|
13809
14596
|
fileWatchers = new ServiceFileWatcherRegistry();
|
|
14597
|
+
loggingRuntime = NextclawCore.getLoggingRuntime();
|
|
14598
|
+
serviceLogger = this.loggingRuntime.getLogger("service");
|
|
14599
|
+
loggingInstalled = false;
|
|
13810
14600
|
constructor(deps) {
|
|
13811
14601
|
this.deps = deps;
|
|
13812
14602
|
}
|
|
13813
14603
|
startGateway = async (options = {}) => {
|
|
14604
|
+
this.ensureRuntimeLoggingInstalled();
|
|
13814
14605
|
logStartupTrace("service.start_gateway.begin");
|
|
13815
14606
|
await this.fileWatchers.clear();
|
|
13816
14607
|
this.applyLiveConfigReload = null;
|
|
@@ -13871,9 +14662,11 @@ var ServiceCommands = class {
|
|
|
13871
14662
|
initialPluginRegistry: createEmptyPluginRegistry(),
|
|
13872
14663
|
makeProvider: (config, providerOptions) => providerOptions?.allowMissing === true ? this.makeProvider(config, { allowMissing: true }) : this.makeProvider(config),
|
|
13873
14664
|
makeMissingProvider: (config) => this.makeMissingProvider(config),
|
|
13874
|
-
requestRestart: (params) => this.deps.requestRestart(params)
|
|
14665
|
+
requestRestart: (params) => this.deps.requestRestart(params),
|
|
14666
|
+
getLiveUiNcpAgent: () => this.liveUiNcpAgent
|
|
13875
14667
|
}));
|
|
13876
14668
|
this.applyLiveConfigReload = gateway.applyLiveConfigReload;
|
|
14669
|
+
const loadGatewayConfig = () => resolveConfigSecrets$1(loadConfig$1(), { configPath: gateway.runtimeConfigPath });
|
|
13877
14670
|
const gatewayRuntimeState = createGatewayRuntimeState(gateway);
|
|
13878
14671
|
runtimeState = gatewayRuntimeState;
|
|
13879
14672
|
uiStartup?.publish({
|
|
@@ -13889,10 +14682,6 @@ var ServiceCommands = class {
|
|
|
13889
14682
|
state: gatewayRuntimeState,
|
|
13890
14683
|
getLiveUiNcpAgent: () => this.liveUiNcpAgent
|
|
13891
14684
|
});
|
|
13892
|
-
wireSystemSessionUpdatedPublisher({
|
|
13893
|
-
runtimePool: gateway.runtimePool,
|
|
13894
|
-
publishUiEvent: uiStartup?.publish
|
|
13895
|
-
});
|
|
13896
14685
|
console.log("✓ Capability hydration: scheduled in background");
|
|
13897
14686
|
await measureStartupAsync("service.start_gateway_support_services", async () => await startGatewayRuntimeSupport({
|
|
13898
14687
|
cronJobs: gateway.cron.status().jobs,
|
|
@@ -13922,49 +14711,37 @@ var ServiceCommands = class {
|
|
|
13922
14711
|
sessionManager: gateway.sessionManager
|
|
13923
14712
|
})
|
|
13924
14713
|
});
|
|
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
|
|
14714
|
+
await runConfiguredGatewayRuntime({
|
|
14715
|
+
uiStartup,
|
|
14716
|
+
gateway,
|
|
14717
|
+
deferredNcpSessionService: ncpSessionRealtimeBridge.deferredSessionService,
|
|
14718
|
+
getConfig: loadGatewayConfig,
|
|
14719
|
+
getExtensionRegistry: () => gatewayRuntimeState.extensionRegistry,
|
|
14720
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
14721
|
+
registry: gatewayRuntimeState.pluginRegistry,
|
|
14722
|
+
channel,
|
|
14723
|
+
cfg: loadGatewayConfig(),
|
|
14724
|
+
accountId
|
|
13950
14725
|
}),
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
this.
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14726
|
+
deferredStartupHooks: deferredGatewayStartupHooks,
|
|
14727
|
+
getLiveUiNcpAgent: () => this.liveUiNcpAgent,
|
|
14728
|
+
publishSessionChange: ncpSessionRealtimeBridge.publishSessionChange,
|
|
14729
|
+
publishUiEvent: uiStartup?.publish,
|
|
14730
|
+
onDeferredStartupError: (error) => handleGatewayDeferredStartupError({
|
|
14731
|
+
bootstrapStatus,
|
|
14732
|
+
error
|
|
14733
|
+
}),
|
|
14734
|
+
cleanup: async () => await cleanupGatewayRuntime({
|
|
14735
|
+
fileWatchers: this.fileWatchers,
|
|
14736
|
+
resetRuntimeState: () => {
|
|
14737
|
+
this.applyLiveConfigReload = null;
|
|
14738
|
+
this.liveUiNcpAgent = null;
|
|
14739
|
+
},
|
|
14740
|
+
clearRealtimeBridge: () => ncpSessionRealtimeBridge.clear(),
|
|
14741
|
+
uiStartup,
|
|
14742
|
+
remoteModule: gateway.remoteModule,
|
|
14743
|
+
runtimeState
|
|
14744
|
+
})
|
|
13968
14745
|
});
|
|
13969
14746
|
logStartupTrace("service.start_gateway.end");
|
|
13970
14747
|
};
|
|
@@ -14068,7 +14845,7 @@ var ServiceCommands = class {
|
|
|
14068
14845
|
if (binding.host !== uiConfig.host || binding.port !== uiConfig.port) {
|
|
14069
14846
|
console.log(`Detected running service UI bind (${binding.host}:${binding.port}); enforcing (${uiConfig.host}:${uiConfig.port})...`);
|
|
14070
14847
|
await this.stopService();
|
|
14071
|
-
const stateAfterStop =
|
|
14848
|
+
const stateAfterStop = managedServiceStateStore.read();
|
|
14072
14849
|
if (stateAfterStop && isProcessRunning(stateAfterStop.pid)) {
|
|
14073
14850
|
process.exitCode = 1;
|
|
14074
14851
|
console.error("Error: Failed to stop running service while enforcing public UI exposure.");
|
|
@@ -14083,12 +14860,14 @@ var ServiceCommands = class {
|
|
|
14083
14860
|
return true;
|
|
14084
14861
|
};
|
|
14085
14862
|
startService = async (options) => {
|
|
14863
|
+
this.loggingRuntime.ensureReady();
|
|
14864
|
+
const { open, startupTimeoutMs, uiOverrides } = options;
|
|
14086
14865
|
const config = loadConfig$1();
|
|
14087
|
-
const uiConfig = resolveUiConfig(config,
|
|
14866
|
+
const uiConfig = resolveUiConfig(config, uiOverrides);
|
|
14088
14867
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
14089
14868
|
const apiUrl = `${uiUrl}/api`;
|
|
14090
14869
|
const staticDir = resolveUiStaticDir();
|
|
14091
|
-
const existing =
|
|
14870
|
+
const existing = managedServiceStateStore.read();
|
|
14092
14871
|
if (existing && isProcessRunning(existing.pid)) {
|
|
14093
14872
|
await this.handleExistingManagedService({
|
|
14094
14873
|
existing,
|
|
@@ -14097,7 +14876,7 @@ var ServiceCommands = class {
|
|
|
14097
14876
|
});
|
|
14098
14877
|
return;
|
|
14099
14878
|
}
|
|
14100
|
-
if (existing)
|
|
14879
|
+
if (existing) managedServiceStateStore.clear();
|
|
14101
14880
|
if (!staticDir) {
|
|
14102
14881
|
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
14882
|
return;
|
|
@@ -14112,6 +14891,40 @@ var ServiceCommands = class {
|
|
|
14112
14891
|
process.exitCode = 1, console.error(`Error: Cannot start ${APP_NAME$1} because UI port ${uiConfig.port} is already occupied.`), console.error(portPreflight.message);
|
|
14113
14892
|
return;
|
|
14114
14893
|
}
|
|
14894
|
+
if (portPreflight.reusedExistingHealthyTarget) {
|
|
14895
|
+
await this.reuseExistingHealthyStartTarget({
|
|
14896
|
+
uiConfig,
|
|
14897
|
+
uiUrl,
|
|
14898
|
+
apiUrl,
|
|
14899
|
+
open
|
|
14900
|
+
});
|
|
14901
|
+
return;
|
|
14902
|
+
}
|
|
14903
|
+
await this.startNewManagedServiceTarget({
|
|
14904
|
+
config,
|
|
14905
|
+
uiConfig,
|
|
14906
|
+
uiUrl,
|
|
14907
|
+
apiUrl,
|
|
14908
|
+
healthUrl,
|
|
14909
|
+
startupTimeoutMs
|
|
14910
|
+
});
|
|
14911
|
+
if (open) openBrowser(uiUrl);
|
|
14912
|
+
};
|
|
14913
|
+
reuseExistingHealthyStartTarget = async (params) => {
|
|
14914
|
+
const { apiUrl, open, uiConfig, uiUrl } = params;
|
|
14915
|
+
console.log(`✓ ${APP_NAME$1} is already serving the target UI/API port`);
|
|
14916
|
+
console.log(`UI: ${uiUrl}`);
|
|
14917
|
+
console.log(`API: ${apiUrl}`);
|
|
14918
|
+
console.warn([
|
|
14919
|
+
`Warning: The healthy listener on ${uiConfig.port} is not tracked by ${managedServiceStateStore.path}.`,
|
|
14920
|
+
"This start call reused the existing runtime instead of spawning another one.",
|
|
14921
|
+
"Use the owning process or port-level tools to stop it; managed stop/restart will not control it automatically."
|
|
14922
|
+
].join(" "));
|
|
14923
|
+
await this.printPublicUiUrls(uiConfig.host, uiConfig.port);
|
|
14924
|
+
if (open) openBrowser(uiUrl);
|
|
14925
|
+
};
|
|
14926
|
+
startNewManagedServiceTarget = async (params) => {
|
|
14927
|
+
const { apiUrl, config, healthUrl, startupTimeoutMs, uiConfig, uiUrl } = params;
|
|
14115
14928
|
const startup = spawnManagedService({
|
|
14116
14929
|
appName: APP_NAME$1,
|
|
14117
14930
|
config,
|
|
@@ -14119,13 +14932,14 @@ var ServiceCommands = class {
|
|
|
14119
14932
|
uiUrl,
|
|
14120
14933
|
apiUrl,
|
|
14121
14934
|
healthUrl,
|
|
14122
|
-
startupTimeoutMs
|
|
14935
|
+
startupTimeoutMs,
|
|
14123
14936
|
resolveStartupTimeoutMs: this.resolveStartupTimeoutMs,
|
|
14124
14937
|
appendStartupStage: this.appendStartupStage,
|
|
14125
14938
|
printStartupFailureDiagnostics: this.printStartupFailureDiagnostics,
|
|
14126
14939
|
resolveServiceLogPath
|
|
14127
14940
|
});
|
|
14128
14941
|
if (!startup) {
|
|
14942
|
+
this.serviceLogger.fatal("managed service startup aborted", { reason: "child_process_not_created" });
|
|
14129
14943
|
process.exitCode = 1;
|
|
14130
14944
|
return;
|
|
14131
14945
|
}
|
|
@@ -14141,22 +14955,27 @@ var ServiceCommands = class {
|
|
|
14141
14955
|
waitForBackgroundServiceReady: this.waitForBackgroundServiceReady,
|
|
14142
14956
|
isProcessRunning
|
|
14143
14957
|
});
|
|
14144
|
-
if (!readiness.ready) {
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
|
|
14958
|
+
if (!readiness.ready && !isProcessRunning(startup.snapshot.pid)) {
|
|
14959
|
+
process.exitCode = 1;
|
|
14960
|
+
managedServiceStateStore.clear();
|
|
14961
|
+
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
14962
|
+
this.appendStartupStage(startup.logPath, `startup failed: process exited before ready.${hint}`);
|
|
14963
|
+
this.serviceLogger.fatal("managed service exited before readiness completed", {
|
|
14964
|
+
uiUrl,
|
|
14965
|
+
apiUrl,
|
|
14966
|
+
healthUrl,
|
|
14967
|
+
logPath: startup.logPath,
|
|
14968
|
+
...readiness.lastProbeError ? { lastProbeError: readiness.lastProbeError } : {}
|
|
14969
|
+
});
|
|
14970
|
+
console.error(`Error: Failed to start background service. Check logs: ${startup.logPath}.${hint}`);
|
|
14971
|
+
this.printStartupFailureDiagnostics({
|
|
14972
|
+
uiUrl,
|
|
14973
|
+
apiUrl,
|
|
14974
|
+
healthUrl,
|
|
14975
|
+
logPath: startup.logPath,
|
|
14976
|
+
lastProbeError: readiness.lastProbeError
|
|
14977
|
+
});
|
|
14978
|
+
return;
|
|
14160
14979
|
}
|
|
14161
14980
|
startup.child.unref();
|
|
14162
14981
|
await reportManagedServiceStart({
|
|
@@ -14174,17 +14993,16 @@ var ServiceCommands = class {
|
|
|
14174
14993
|
printPublicUiUrls: this.printPublicUiUrls,
|
|
14175
14994
|
printServiceControlHints: this.printServiceControlHints
|
|
14176
14995
|
});
|
|
14177
|
-
if (options.open) openBrowser(uiUrl);
|
|
14178
14996
|
};
|
|
14179
14997
|
stopService = async () => {
|
|
14180
|
-
const state =
|
|
14998
|
+
const state = managedServiceStateStore.read();
|
|
14181
14999
|
if (!state) {
|
|
14182
|
-
console.log("No running service found.");
|
|
15000
|
+
console.log("No running background service found.");
|
|
14183
15001
|
return;
|
|
14184
15002
|
}
|
|
14185
15003
|
if (!isProcessRunning(state.pid)) {
|
|
14186
15004
|
console.log("Service is not running. Cleaning up state.");
|
|
14187
|
-
|
|
15005
|
+
managedServiceStateStore.clear();
|
|
14188
15006
|
return;
|
|
14189
15007
|
}
|
|
14190
15008
|
console.log(`Stopping ${APP_NAME$1} (PID ${state.pid})...`);
|
|
@@ -14203,7 +15021,8 @@ var ServiceCommands = class {
|
|
|
14203
15021
|
}
|
|
14204
15022
|
await waitForExit(state.pid, 2e3);
|
|
14205
15023
|
}
|
|
14206
|
-
|
|
15024
|
+
managedServiceStateStore.clear();
|
|
15025
|
+
localUiRuntimeStore.clearIfOwnedByProcess(state.pid);
|
|
14207
15026
|
console.log(`✓ ${APP_NAME$1} stopped`);
|
|
14208
15027
|
};
|
|
14209
15028
|
waitForBackgroundServiceReady = async (params) => {
|
|
@@ -14243,14 +15062,14 @@ var ServiceCommands = class {
|
|
|
14243
15062
|
};
|
|
14244
15063
|
appendStartupStage = (logPath, message) => {
|
|
14245
15064
|
try {
|
|
14246
|
-
|
|
15065
|
+
this.serviceLogger.child("startup").info(message, { logPath });
|
|
14247
15066
|
} catch (error) {
|
|
14248
15067
|
const detail = error instanceof Error ? error.message : String(error);
|
|
14249
15068
|
console.error(`Warning: failed to write startup diagnostics log (${logPath}): ${detail}`);
|
|
14250
15069
|
}
|
|
14251
15070
|
};
|
|
14252
15071
|
printStartupFailureDiagnostics = (params) => {
|
|
14253
|
-
const statePath =
|
|
15072
|
+
const statePath = managedServiceStateStore.path;
|
|
14254
15073
|
const lines = [
|
|
14255
15074
|
"Startup diagnostics:",
|
|
14256
15075
|
`- UI URL: ${params.uiUrl}`,
|
|
@@ -14264,20 +15083,22 @@ var ServiceCommands = class {
|
|
|
14264
15083
|
};
|
|
14265
15084
|
checkUiPortPreflight = async (params) => {
|
|
14266
15085
|
const { healthUrl, host, port } = params;
|
|
14267
|
-
const
|
|
15086
|
+
const target = await inspectUiTarget({
|
|
14268
15087
|
host,
|
|
14269
|
-
port
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
}
|
|
15088
|
+
port,
|
|
15089
|
+
healthUrl
|
|
15090
|
+
});
|
|
15091
|
+
if (target.state === "available") return {
|
|
15092
|
+
ok: true,
|
|
15093
|
+
reusedExistingHealthyTarget: false
|
|
15094
|
+
};
|
|
15095
|
+
if (target.state === "healthy-existing") return {
|
|
15096
|
+
ok: true,
|
|
15097
|
+
reusedExistingHealthyTarget: true
|
|
15098
|
+
};
|
|
15099
|
+
const lines = [`Port probe: ${target.availabilityDetail}`];
|
|
15100
|
+
if (target.probeError) lines.push(`Health probe: ${target.probeError}`);
|
|
15101
|
+
lines.push("The port is occupied by a process that does not answer as a healthy NextClaw HTTP server.");
|
|
14281
15102
|
lines.push(`Fix: free port ${port} or start NextClaw with another port via --ui-port <port>.`);
|
|
14282
15103
|
lines.push(`Inspect locally with: ss -ltnp | grep ${port} || lsof -iTCP:${port} -sTCP:LISTEN -n -P`);
|
|
14283
15104
|
return {
|
|
@@ -14334,6 +15155,17 @@ var ServiceCommands = class {
|
|
|
14334
15155
|
console.log("Service controls:");
|
|
14335
15156
|
console.log(` - Check status: ${APP_NAME$1} status`);
|
|
14336
15157
|
console.log(` - If you need to stop the service, run: ${APP_NAME$1} stop`);
|
|
15158
|
+
console.log(` - View log paths: ${APP_NAME$1} logs path`);
|
|
15159
|
+
console.log(` - Tail recent logs: ${APP_NAME$1} logs tail`);
|
|
15160
|
+
};
|
|
15161
|
+
ensureRuntimeLoggingInstalled = () => {
|
|
15162
|
+
if (this.loggingInstalled) return;
|
|
15163
|
+
NextclawCore.configureAppLogging({
|
|
15164
|
+
installConsoleMirror: true,
|
|
15165
|
+
installProcessCrashMonitor: true
|
|
15166
|
+
});
|
|
15167
|
+
this.serviceLogger.info("runtime logging ready", { startupId: this.loggingRuntime.getStartupId() });
|
|
15168
|
+
this.loggingInstalled = true;
|
|
14337
15169
|
};
|
|
14338
15170
|
installBuiltinMarketplaceSkill = (slug, _force) => {
|
|
14339
15171
|
if (!(createSkillsLoader(getWorkspacePath$1(loadConfig$1().agents.defaults.workspace))?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin")) return null;
|
|
@@ -14538,8 +15370,7 @@ var WorkspaceManager = class {
|
|
|
14538
15370
|
}
|
|
14539
15371
|
};
|
|
14540
15372
|
//#endregion
|
|
14541
|
-
//#region src/cli/
|
|
14542
|
-
const LOGO = "🤖";
|
|
15373
|
+
//#region src/cli/commands/agent/cli-agent-runner.ts
|
|
14543
15374
|
const EXIT_COMMANDS = new Set([
|
|
14544
15375
|
"exit",
|
|
14545
15376
|
"quit",
|
|
@@ -14547,6 +15378,91 @@ const EXIT_COMMANDS = new Set([
|
|
|
14547
15378
|
"/quit",
|
|
14548
15379
|
":q"
|
|
14549
15380
|
]);
|
|
15381
|
+
function buildCliSharedMetadata(opts) {
|
|
15382
|
+
return typeof opts.model === "string" && opts.model.trim() ? { model: opts.model.trim() } : {};
|
|
15383
|
+
}
|
|
15384
|
+
function createCliHistoryInterface() {
|
|
15385
|
+
const historyFile = join(getDataDir(), "history", "cli_history");
|
|
15386
|
+
mkdirSync(resolve(historyFile, ".."), { recursive: true });
|
|
15387
|
+
const history = existsSync(historyFile) ? readFileSync(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
15388
|
+
const rl = createInterface({
|
|
15389
|
+
input: process.stdin,
|
|
15390
|
+
output: process.stdout
|
|
15391
|
+
});
|
|
15392
|
+
rl.on("close", () => {
|
|
15393
|
+
writeFileSync(historyFile, history.concat(rl.history ?? []).join("\n"));
|
|
15394
|
+
process.exit(0);
|
|
15395
|
+
});
|
|
15396
|
+
return rl;
|
|
15397
|
+
}
|
|
15398
|
+
async function runCliInteractiveLoop(params) {
|
|
15399
|
+
console.log(`${params.logo} Interactive mode (type exit or Ctrl+C to quit)\n`);
|
|
15400
|
+
const rl = createCliHistoryInterface();
|
|
15401
|
+
let running = true;
|
|
15402
|
+
while (running) {
|
|
15403
|
+
const trimmed = (await prompt(rl, "You: ")).trim();
|
|
15404
|
+
if (!trimmed) continue;
|
|
15405
|
+
if (EXIT_COMMANDS.has(trimmed.toLowerCase())) {
|
|
15406
|
+
rl.close();
|
|
15407
|
+
running = false;
|
|
15408
|
+
break;
|
|
15409
|
+
}
|
|
15410
|
+
printAgentResponse(await dispatchPromptOverNcp({
|
|
15411
|
+
config: params.config,
|
|
15412
|
+
sessionManager: params.sessionManager,
|
|
15413
|
+
resolveNcpAgent: () => params.ncpAgent,
|
|
15414
|
+
sessionKey: params.sessionKey,
|
|
15415
|
+
content: trimmed,
|
|
15416
|
+
metadata: params.metadata
|
|
15417
|
+
}));
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
async function runCliAgentCommand(params) {
|
|
15421
|
+
const bus = new MessageBus();
|
|
15422
|
+
const sessionManager = new SessionManager({
|
|
15423
|
+
workspace: params.workspace,
|
|
15424
|
+
homeDir: getDataDir()
|
|
15425
|
+
});
|
|
15426
|
+
const ncpAgent = await createUiNcpAgent({
|
|
15427
|
+
bus,
|
|
15428
|
+
providerManager: params.providerManager,
|
|
15429
|
+
sessionManager,
|
|
15430
|
+
getConfig: params.loadResolvedConfig,
|
|
15431
|
+
getExtensionRegistry: () => params.extensionRegistry,
|
|
15432
|
+
resolveMessageToolHints: ({ channel, accountId }) => params.resolveMessageToolHints({
|
|
15433
|
+
channel,
|
|
15434
|
+
accountId
|
|
15435
|
+
})
|
|
15436
|
+
});
|
|
15437
|
+
try {
|
|
15438
|
+
const sessionKey = params.opts.session ?? "cli:default";
|
|
15439
|
+
const sharedMetadata = buildCliSharedMetadata(params.opts);
|
|
15440
|
+
if (params.opts.message) {
|
|
15441
|
+
printAgentResponse(await dispatchPromptOverNcp({
|
|
15442
|
+
config: params.config,
|
|
15443
|
+
sessionManager,
|
|
15444
|
+
resolveNcpAgent: () => ncpAgent,
|
|
15445
|
+
sessionKey,
|
|
15446
|
+
content: params.opts.message,
|
|
15447
|
+
metadata: sharedMetadata
|
|
15448
|
+
}));
|
|
15449
|
+
return;
|
|
15450
|
+
}
|
|
15451
|
+
await runCliInteractiveLoop({
|
|
15452
|
+
logo: params.logo,
|
|
15453
|
+
config: params.config,
|
|
15454
|
+
sessionManager,
|
|
15455
|
+
ncpAgent,
|
|
15456
|
+
sessionKey,
|
|
15457
|
+
metadata: sharedMetadata
|
|
15458
|
+
});
|
|
15459
|
+
} finally {
|
|
15460
|
+
await ncpAgent.dispose?.();
|
|
15461
|
+
}
|
|
15462
|
+
}
|
|
15463
|
+
//#endregion
|
|
15464
|
+
//#region src/cli/runtime.ts
|
|
15465
|
+
const LOGO = "🤖";
|
|
14550
15466
|
const FORCED_PUBLIC_UI_HOST = "0.0.0.0";
|
|
14551
15467
|
var CliRuntime = class {
|
|
14552
15468
|
logo;
|
|
@@ -14566,6 +15482,7 @@ var CliRuntime = class {
|
|
|
14566
15482
|
remoteCommands;
|
|
14567
15483
|
remote;
|
|
14568
15484
|
diagnosticsCommands;
|
|
15485
|
+
logsCommands;
|
|
14569
15486
|
constructor(options = {}) {
|
|
14570
15487
|
logStartupTrace("cli.runtime.constructor.begin");
|
|
14571
15488
|
this.logo = options.logo ?? "🤖";
|
|
@@ -14598,8 +15515,9 @@ var CliRuntime = class {
|
|
|
14598
15515
|
hasRunningManagedService: hasRunningNextclawManagedService
|
|
14599
15516
|
}));
|
|
14600
15517
|
this.diagnosticsCommands = measureStartupSync("cli.runtime.diagnostics_commands", () => new DiagnosticsCommands({ logo: this.logo }));
|
|
15518
|
+
this.logsCommands = measureStartupSync("cli.runtime.logs_commands", () => new LogsCommands());
|
|
14601
15519
|
this.restartCoordinator = measureStartupSync("cli.runtime.restart_coordinator", () => new RestartCoordinator({
|
|
14602
|
-
readServiceState,
|
|
15520
|
+
readServiceState: managedServiceStateStore.read,
|
|
14603
15521
|
isProcessRunning,
|
|
14604
15522
|
currentPid: () => process.pid,
|
|
14605
15523
|
restartBackgroundService: async (reason) => this.restartBackgroundService(reason),
|
|
@@ -14619,7 +15537,7 @@ var CliRuntime = class {
|
|
|
14619
15537
|
restartBackgroundService = async (reason) => {
|
|
14620
15538
|
if (this.serviceRestartTask) return this.serviceRestartTask;
|
|
14621
15539
|
this.serviceRestartTask = (async () => {
|
|
14622
|
-
const state =
|
|
15540
|
+
const state = managedServiceStateStore.read();
|
|
14623
15541
|
if (!state || !isProcessRunning(state.pid) || state.pid === process.pid) return false;
|
|
14624
15542
|
const uiHost = FORCED_PUBLIC_UI_HOST;
|
|
14625
15543
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 55667;
|
|
@@ -14645,7 +15563,7 @@ var CliRuntime = class {
|
|
|
14645
15563
|
const strategy = params.strategy ?? "background-service-or-manual";
|
|
14646
15564
|
if (strategy !== "background-service-or-exit" && strategy !== "exit-process") return;
|
|
14647
15565
|
if (this.selfRelaunchArmed) return;
|
|
14648
|
-
const state =
|
|
15566
|
+
const state = managedServiceStateStore.read();
|
|
14649
15567
|
if (!state || state.pid !== process.pid) return;
|
|
14650
15568
|
const uiPort = typeof state.uiPort === "number" && Number.isFinite(state.uiPort) ? state.uiPort : 55667;
|
|
14651
15569
|
const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
|
|
@@ -14655,7 +15573,7 @@ var CliRuntime = class {
|
|
|
14655
15573
|
"--ui-port",
|
|
14656
15574
|
String(uiPort)
|
|
14657
15575
|
];
|
|
14658
|
-
const serviceStatePath =
|
|
15576
|
+
const serviceStatePath = managedServiceStateStore.path;
|
|
14659
15577
|
const helperScript = [
|
|
14660
15578
|
"const { spawnSync } = require(\"node:child_process\");",
|
|
14661
15579
|
"const { readFileSync } = require(\"node:fs\");",
|
|
@@ -14840,13 +15758,13 @@ var CliRuntime = class {
|
|
|
14840
15758
|
uiPort: opts.uiPort,
|
|
14841
15759
|
forcedPublicHost: FORCED_PUBLIC_UI_HOST
|
|
14842
15760
|
});
|
|
14843
|
-
const state =
|
|
15761
|
+
const state = managedServiceStateStore.read();
|
|
14844
15762
|
if (state && isProcessRunning(state.pid)) {
|
|
14845
15763
|
console.log(`Restarting ${APP_NAME}...`);
|
|
14846
15764
|
await this.serviceCommands.stopService();
|
|
14847
15765
|
} else {
|
|
14848
15766
|
if (state) {
|
|
14849
|
-
|
|
15767
|
+
managedServiceStateStore.clear();
|
|
14850
15768
|
console.log("Service state was stale and has been cleaned up.");
|
|
14851
15769
|
}
|
|
14852
15770
|
const unmanagedHealthyServiceMessage = await describeUnmanagedHealthyTargetMessage({ uiOverrides });
|
|
@@ -14888,22 +15806,19 @@ var CliRuntime = class {
|
|
|
14888
15806
|
}
|
|
14889
15807
|
});
|
|
14890
15808
|
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,
|
|
15809
|
+
const provider = this.serviceCommands.createProvider(config) ?? this.serviceCommands.createMissingProvider(config);
|
|
15810
|
+
const providerManager = this.createObservedProviderManager(new ProviderManager({
|
|
15811
|
+
defaultProvider: provider,
|
|
15812
|
+
config
|
|
15813
|
+
}), "cli-agent");
|
|
15814
|
+
await runCliAgentCommand({
|
|
15815
|
+
logo: this.logo,
|
|
15816
|
+
opts,
|
|
14905
15817
|
config,
|
|
15818
|
+
workspace,
|
|
15819
|
+
providerManager,
|
|
14906
15820
|
extensionRegistry,
|
|
15821
|
+
loadResolvedConfig: () => resolveConfigSecrets(loadConfig(), { configPath }),
|
|
14907
15822
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
14908
15823
|
registry: pluginRegistry,
|
|
14909
15824
|
channel,
|
|
@@ -14911,43 +15826,6 @@ var CliRuntime = class {
|
|
|
14911
15826
|
accountId
|
|
14912
15827
|
})
|
|
14913
15828
|
});
|
|
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
15829
|
} finally {
|
|
14952
15830
|
setPluginRuntimeBridge(null);
|
|
14953
15831
|
}
|
|
@@ -14984,7 +15862,7 @@ var CliRuntime = class {
|
|
|
14984
15862
|
console.log(`✓ Update complete (${result.strategy})`);
|
|
14985
15863
|
if (versionAfter === versionBefore) console.log(`Version unchanged: ${versionBefore}`);
|
|
14986
15864
|
else console.log(`Version updated: ${versionBefore} -> ${versionAfter}`);
|
|
14987
|
-
const state =
|
|
15865
|
+
const state = managedServiceStateStore.read();
|
|
14988
15866
|
if (state && isProcessRunning(state.pid)) console.log(`Tip: restart ${APP_NAME} to apply the update.`);
|
|
14989
15867
|
};
|
|
14990
15868
|
agentsList = (opts = {}) => {
|
|
@@ -15092,6 +15970,12 @@ var CliRuntime = class {
|
|
|
15092
15970
|
doctor = async (opts = {}) => {
|
|
15093
15971
|
await this.diagnosticsCommands.doctor(opts);
|
|
15094
15972
|
};
|
|
15973
|
+
logsPath = () => {
|
|
15974
|
+
this.logsCommands.logsPath();
|
|
15975
|
+
};
|
|
15976
|
+
logsTail = (opts = {}) => {
|
|
15977
|
+
this.logsCommands.logsTail(opts);
|
|
15978
|
+
};
|
|
15095
15979
|
skillsInstall = async (options) => {
|
|
15096
15980
|
const config = loadConfig();
|
|
15097
15981
|
const workdir = resolveSkillsInstallWorkdir({
|
|
@@ -15119,6 +16003,7 @@ var CliRuntime = class {
|
|
|
15119
16003
|
console.log(` Alias: ${result.slug}`);
|
|
15120
16004
|
console.log(` Files: ${result.fileCount}`);
|
|
15121
16005
|
};
|
|
16006
|
+
createObservedProviderManager = (providerManager, source) => new ObservedProviderManager(providerManager, new LlmUsageObserver(llmUsageRecorder, source));
|
|
15122
16007
|
};
|
|
15123
16008
|
//#endregion
|
|
15124
16009
|
//#region src/cli/register-agents-commands.ts
|
|
@@ -15135,6 +16020,7 @@ function registerAgentsCommands(program, runtime) {
|
|
|
15135
16020
|
logStartupTrace("cli.index.module_loaded");
|
|
15136
16021
|
const program = new Command();
|
|
15137
16022
|
const runtime = measureStartupSync("cli.runtime.construct", () => new CliRuntime({ logo: LOGO }));
|
|
16023
|
+
const llmUsageCommands = new LlmUsageCommands();
|
|
15138
16024
|
program.name(APP_NAME).description(`${LOGO} ${APP_NAME} - ${APP_TAGLINE}`).version(getPackageVersion$1(), "-v, --version", "show version");
|
|
15139
16025
|
program.command("onboard").description(`Initialize ${APP_NAME} configuration and workspace`).action(async () => runtime.onboard());
|
|
15140
16026
|
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 +16089,10 @@ cron.command("disable <jobId>").action(async (jobId) => runtime.cronEnable(jobId
|
|
|
15203
16089
|
cron.command("run <jobId>").option("-f, --force", "Run even if disabled").action(async (jobId, opts) => runtime.cronRun(jobId, opts));
|
|
15204
16090
|
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
16091
|
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));
|
|
16092
|
+
const logs = program.command("logs").description("Inspect local runtime logs");
|
|
16093
|
+
logs.command("path").description("Show local log file paths").action(() => runtime.logsPath());
|
|
16094
|
+
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));
|
|
16095
|
+
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
16096
|
program.parseAsync(process.argv);
|
|
15207
16097
|
//#endregion
|
|
15208
16098
|
export {};
|