nextclaw 0.18.10 → 0.18.12-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/app/index.js +1212 -473
- package/dist/cli/launcher/index.d.ts +1 -0
- package/dist/cli/launcher/index.js +51 -0
- package/dist/npm-runtime-update-state.store-DaF0iDY9.js +463 -0
- package/package.json +16 -11
- package/resources/USAGE.md +11 -7
- package/resources/update-bundle-public.pem +3 -0
- package/templates/AGENTS.md +0 -4
- package/ui-dist/assets/{api-BurjmW4A.js → api-C51456xV.js} +1 -1
- package/ui-dist/assets/{app-manager-provider-DhxUmyTv.js → app-manager-provider-D_cKqqRG.js} +1 -1
- package/ui-dist/assets/{app-navigation.config-Bpd16Pem.js → app-navigation.config-Dve1W20Y.js} +1 -1
- package/ui-dist/assets/{book-open-CVEuA0y5.js → book-open-B4mOKdz8.js} +1 -1
- package/ui-dist/assets/{channels-list-page-BqhqaBf1.js → channels-list-page-WJ7d4zMI.js} +1 -1
- package/ui-dist/assets/chat-BxA-mw53.js +58 -0
- package/ui-dist/assets/chat-page-DLFTPfmu.js +1 -0
- package/ui-dist/assets/{chunk-JZWAC4HX-24FLdHl7.js → chunk-JZWAC4HX-ptDyT_1C.js} +1 -1
- package/ui-dist/assets/{config-split-page-BGjVACdO.js → config-split-page-B3PRA_AV.js} +1 -1
- package/ui-dist/assets/{createLucideIcon-PPrXCGK8.js → createLucideIcon-C_GFKVuW.js} +1 -1
- package/ui-dist/assets/{desktop-update-config-fMLlSStv.js → desktop-update-config-CzGi43xw.js} +1 -1
- package/ui-dist/assets/{dialog-CTCX7oLf.js → dialog-BHcaU6NE.js} +1 -1
- package/ui-dist/assets/{dist-FL5e8mMi.js → dist-DtBFqZ6_.js} +1 -1
- package/ui-dist/assets/{doc-browser-fyn7eDTp.js → doc-browser-CoKIUCJj.js} +1 -1
- package/ui-dist/assets/{doc-browser-C02neCIE.js → doc-browser-CwgI7ipB.js} +1 -1
- package/ui-dist/assets/doc-browser-DYKpRqe-.js +1 -0
- package/ui-dist/assets/{doc-browser-context-C-WPOji4.js → doc-browser-context-Dib9sS83.js} +1 -1
- package/ui-dist/assets/{es2015-BNy4R8AC.js → es2015-BlNhrQUG.js} +1 -1
- package/ui-dist/assets/{external-link-BNtqJE01.js → external-link-DP2IJ7AM.js} +1 -1
- package/ui-dist/assets/{folder-QyJHVUNz.js → folder-BPwc278w.js} +1 -1
- package/ui-dist/assets/{hash-BGYUE-zr.js → hash-CvcvtMBq.js} +1 -1
- package/ui-dist/assets/i18n-BnNAQpVM.js +1 -0
- package/ui-dist/assets/index-CAYF44Dz.js +2 -0
- package/ui-dist/assets/index-mRmSAB-e.css +1 -0
- package/ui-dist/assets/{key-round-DenCfA2w.js → key-round-BQXmPSxD.js} +1 -1
- package/ui-dist/assets/loader-circle-C6gg2m2a.js +1 -0
- package/ui-dist/assets/{logo-badge-CKAxvQFc.js → logo-badge-uB4SwANR.js} +1 -1
- package/ui-dist/assets/{logos-CqXnaJIm.js → logos-BcELLmYh.js} +1 -1
- package/ui-dist/assets/marketplace-page-0sEdt5sA.js +1 -0
- package/ui-dist/assets/{marketplace-page-XnDa2ulT.js → marketplace-page-DiqqX25V.js} +1 -1
- package/ui-dist/assets/mcp-marketplace-page-B8vmu9xe.js +1 -0
- package/ui-dist/assets/{mcp-marketplace-page-BArKWcRZ.js → mcp-marketplace-page-C_akqPwv.js} +1 -1
- package/ui-dist/assets/message-square-CLVODA23.js +1 -0
- package/ui-dist/assets/{model-config-ByeL6Toe.js → model-config-B0L43HTL.js} +1 -1
- package/ui-dist/assets/{notice-card-D00-02yg.js → notice-card-C9PFAR67.js} +1 -1
- package/ui-dist/assets/play-DeNVUA5C.js +1 -0
- package/ui-dist/assets/plus-BptLViq1.js +1 -0
- package/ui-dist/assets/{popover-AmJkxio3.js → popover-B8msg2FQ.js} +1 -1
- package/ui-dist/assets/{provider-scoped-model-input-CfFJsJp-.js → provider-scoped-model-input-DeAo2Y65.js} +1 -1
- package/ui-dist/assets/{providers-list-HMQzW2WV.js → providers-list-5_VShcn7.js} +1 -1
- package/ui-dist/assets/{refresh-ccw-B-dhb3yS.js → refresh-ccw-CeG203yU.js} +1 -1
- package/ui-dist/assets/remote-pzp4oLcL.js +1 -0
- package/ui-dist/assets/{rotate-cw-BWqAG3Fv.js → rotate-cw-F7aThvYj.js} +1 -1
- package/ui-dist/assets/{runtime-config-page-N4FP6H0M.js → runtime-config-page-B-y_0HIS.js} +1 -1
- package/ui-dist/assets/{save-DpdkGieJ.js → save-7ztImRj7.js} +1 -1
- package/ui-dist/assets/{search-CQUdr7j_.js → search-DZSNKEGp.js} +1 -1
- package/ui-dist/assets/{search-config-B62TY-z2.js → search-config-DJTm9Fno.js} +1 -1
- package/ui-dist/assets/{secrets-config-YCsGd1am.js → secrets-config-DKFeFii1.js} +1 -1
- package/ui-dist/assets/{select-DVUtSFHZ.js → select-DRDejPLk.js} +1 -1
- package/ui-dist/assets/{sessions-config-page-BKN-XdKr.js → sessions-config-page-CZGqS32n.js} +1 -1
- package/ui-dist/assets/{setting-row-Cb5-lFs-.js → setting-row-BcF6eTW0.js} +1 -1
- package/ui-dist/assets/{settings-DgtZZlnF.js → settings-DjvNMJde.js} +1 -1
- package/ui-dist/assets/skeleton-5Mg6vZHN.js +1 -0
- package/ui-dist/assets/{sparkles-DNSCyDhL.js → sparkles-CyDTgTM4.js} +1 -1
- package/ui-dist/assets/{status-dot-X_j51OfA.js → status-dot-aQU9Mia4.js} +1 -1
- package/ui-dist/assets/{tabs-custom-CcWmekaF.js → tabs-custom-C4P7g4vR.js} +1 -1
- package/ui-dist/assets/{tag-chip-fdbK2wE6.js → tag-chip-CVIqyMv7.js} +1 -1
- package/ui-dist/assets/{theme-provider-WTWq_jYq.js → theme-provider-dHqcWU-j.js} +1 -1
- package/ui-dist/assets/{tooltip-BkZCQcKw.js → tooltip-C6VPreZ7.js} +1 -1
- package/ui-dist/assets/{trash-2-CqciSCsg.js → trash-2-C1cdqL6V.js} +1 -1
- package/ui-dist/assets/{use-config-CyvhbRhf.js → use-config-DFja1sda.js} +1 -1
- package/ui-dist/assets/{use-confirm-dialog-DSrb9205.js → use-confirm-dialog-DvIbSUX3.js} +1 -1
- package/ui-dist/assets/{use-infinite-scroll-loader-DmowtyTI.js → use-infinite-scroll-loader-D8h0k-iL.js} +1 -1
- package/ui-dist/assets/{use-viewport-layout-CaALCA51.js → use-viewport-layout-D-pjxsyz.js} +1 -1
- package/ui-dist/assets/x-BjMO7v8q.js +1 -0
- package/ui-dist/index.html +39 -39
- package/templates/HEARTBEAT.md +0 -5
- package/ui-dist/assets/chat-D4KecKjB.js +0 -60
- package/ui-dist/assets/chat-page-Cc7n80lW.js +0 -1
- package/ui-dist/assets/doc-browser-COj7x090.js +0 -1
- package/ui-dist/assets/i18n-CM4y8Mw9.js +0 -1
- package/ui-dist/assets/index-CtVSzMPM.js +0 -2
- package/ui-dist/assets/index-N3hjuljD.css +0 -1
- package/ui-dist/assets/loader-circle-R23uEPkM.js +0 -1
- package/ui-dist/assets/marketplace-page-mF-M5mku.js +0 -1
- package/ui-dist/assets/mcp-marketplace-page-DBUcIIHJ.js +0 -1
- package/ui-dist/assets/message-square-Dm34zD6k.js +0 -1
- package/ui-dist/assets/play-ul4L6MWm.js +0 -1
- package/ui-dist/assets/plus-D14303DH.js +0 -1
- package/ui-dist/assets/remote-B4ELSd3u.js +0 -1
- package/ui-dist/assets/skeleton-BCPi52jT.js +0 -1
- package/ui-dist/assets/x-tYcSDsrY.js +0 -1
package/dist/cli/app/index.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { _ as waitForExit, a as findExecutableOnPath, c as isProcessRunning, d as prompt, f as resolvePublicIp, g as resolveUiStaticDir, h as resolveUiConfig, i as NpmRuntimeBundleLayoutStore, l as openBrowser, m as resolveUiApiBase, n as NpmRuntimeBundleService, o as getPackageVersion$1, p as resolveServiceLogPath, r as compareNpmRuntimeVersions, s as isLoopbackHost, t as NpmRuntimeUpdateStateStore, u as printAgentResponse } from "../../npm-runtime-update-state.store-DaF0iDY9.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import * as NextclawCore from "@nextclaw/core";
|
|
4
|
-
import { APP_NAME, APP_TAGLINE, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, FileLogSink, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderManager, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage,
|
|
5
|
+
import { APP_NAME, APP_TAGLINE, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, ChannelManager, CommandRegistry, ConfigSchema, ContextBuilder, CronService, CronTool, DEFAULT_WORKSPACE_DIR, DEFAULT_WORKSPACE_PATH, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, FileLogSink, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderManager, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionsHistoryTool, SessionsListTool, SkillsLoader, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildConfigSchema, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createGlobalTypedEventBus, createTypedEventKey, createTypingStopControlMessage, diffConfigPaths, expandHome, findEffectiveAgentProfile, getAppLogger, getConfigPath, getDataDir, getDataPath, getLoggingRuntime, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveAppLogPath, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveLocalUiBaseUrl, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
|
|
5
6
|
import { Command } from "commander";
|
|
6
7
|
import fs, { appendFileSync, closeSync, constants, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
-
import path, { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, win32 } from "node:path";
|
|
8
|
+
import path, { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
8
9
|
import { homedir, hostname, platform } from "node:os";
|
|
9
10
|
import { ensureUiBridgeSecret, startUiServer } from "@nextclaw/server";
|
|
10
11
|
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";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
import { spawn, spawnSync } from "node:child_process";
|
|
13
|
-
import { createServer
|
|
14
|
+
import { createServer } from "node:net";
|
|
15
|
+
import { access, readFile, rm } from "node:fs/promises";
|
|
16
|
+
import { createHash, createPublicKey, randomUUID, verify } from "node:crypto";
|
|
17
|
+
import JSZip from "jszip";
|
|
18
|
+
import { UpdateManifestReader, serializeUnsignedUpdateManifest } from "@nextclaw/kernel/update-contract";
|
|
14
19
|
import { BUILTIN_CHANNEL_PLUGIN_IDS, builtinProviderIds, listBuiltinProviders } from "@nextclaw/runtime";
|
|
15
20
|
import { createInterface } from "node:readline";
|
|
16
21
|
import { McpDoctorFacade, McpMutationService, McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
|
|
17
|
-
import { access, readFile } from "node:fs/promises";
|
|
18
22
|
import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
|
|
19
23
|
import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRuntime } from "@nextclaw/nextclaw-ncp-runtime-stdio-client";
|
|
20
24
|
import { buildHermesAcpBridgeLaunchEnv, isHermesAcpRuntimeConfig } from "@nextclaw/nextclaw-hermes-acp-bridge";
|
|
@@ -22,7 +26,6 @@ import { DefaultNcpAgentRuntime, LocalAssetStore, buildAssetContentPath, buildNc
|
|
|
22
26
|
import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
|
|
23
27
|
import { NCP_INTERNAL_VISIBILITY_METADATA_KEY, NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
|
|
24
28
|
import { DefaultNcpAgentBackend, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
|
|
25
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
26
29
|
import { setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
27
30
|
import { request } from "node:http";
|
|
28
31
|
import { request as request$1 } from "node:https";
|
|
@@ -165,7 +168,7 @@ function maskToken(value) {
|
|
|
165
168
|
if (value.length <= 12) return "<redacted>";
|
|
166
169
|
return `${value.slice(0, 6)}...${value.slice(-4)}`;
|
|
167
170
|
}
|
|
168
|
-
function normalizeOptionalString$
|
|
171
|
+
function normalizeOptionalString$10(value) {
|
|
169
172
|
if (typeof value !== "string") return;
|
|
170
173
|
const trimmed = value.trim();
|
|
171
174
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -263,8 +266,8 @@ var RemotePlatformClient = class {
|
|
|
263
266
|
if (tokenState.reason === "missing") throw new Error("NextClaw platform token is missing. Run \"nextclaw login\" first.");
|
|
264
267
|
if (tokenState.reason === "expired") throw new Error("NextClaw platform token expired. Run \"nextclaw login\" or browser sign-in again.");
|
|
265
268
|
if (tokenState.reason === "malformed") throw new Error("NextClaw platform token is invalid. Run \"nextclaw login\" again.");
|
|
266
|
-
const configuredApiBase = normalizeOptionalString$
|
|
267
|
-
const rawApiBase = normalizeOptionalString$
|
|
269
|
+
const configuredApiBase = normalizeOptionalString$10(config.remote.platformApiBase) ?? (typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "");
|
|
270
|
+
const rawApiBase = normalizeOptionalString$10(opts.apiBase) ?? configuredApiBase;
|
|
268
271
|
if (!rawApiBase) throw new Error("Platform API base is missing. Pass --api-base, run nextclaw login, or set remote.platformApiBase.");
|
|
269
272
|
return {
|
|
270
273
|
platformBase: this.deps.resolvePlatformBase(rawApiBase),
|
|
@@ -273,14 +276,14 @@ var RemotePlatformClient = class {
|
|
|
273
276
|
};
|
|
274
277
|
}
|
|
275
278
|
resolveLocalOrigin(config, opts) {
|
|
276
|
-
const explicitOrigin = normalizeOptionalString$
|
|
279
|
+
const explicitOrigin = normalizeOptionalString$10(opts.localOrigin);
|
|
277
280
|
if (explicitOrigin) return explicitOrigin.replace(/\/$/, "");
|
|
278
281
|
const state = this.deps.readManagedServiceState?.();
|
|
279
282
|
if (state && this.deps.isProcessRunning?.(state.pid) && Number.isFinite(state.uiPort)) return `http://127.0.0.1:${state.uiPort}`;
|
|
280
283
|
return `http://127.0.0.1:${typeof config.ui?.port === "number" && Number.isFinite(config.ui.port) ? config.ui.port : 55667}`;
|
|
281
284
|
}
|
|
282
285
|
resolveDisplayName(config, opts) {
|
|
283
|
-
return normalizeOptionalString$
|
|
286
|
+
return normalizeOptionalString$10(opts.name) ?? normalizeOptionalString$10(config.remote.deviceName) ?? hostname();
|
|
284
287
|
}
|
|
285
288
|
};
|
|
286
289
|
//#endregion
|
|
@@ -4490,7 +4493,7 @@ var RemoteConnector = class {
|
|
|
4490
4493
|
};
|
|
4491
4494
|
//#endregion
|
|
4492
4495
|
//#region ../nextclaw-remote/src/remote-status-store.ts
|
|
4493
|
-
function normalizeOptionalString$
|
|
4496
|
+
function normalizeOptionalString$9(value) {
|
|
4494
4497
|
if (typeof value !== "string") return;
|
|
4495
4498
|
const trimmed = value.trim();
|
|
4496
4499
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
@@ -4501,8 +4504,8 @@ function buildConfiguredRemoteState(config) {
|
|
|
4501
4504
|
enabled: Boolean(remote.enabled),
|
|
4502
4505
|
mode: "service",
|
|
4503
4506
|
state: remote.enabled ? "disconnected" : "disabled",
|
|
4504
|
-
...normalizeOptionalString$
|
|
4505
|
-
...normalizeOptionalString$
|
|
4507
|
+
...normalizeOptionalString$9(remote.deviceName) ? { deviceName: normalizeOptionalString$9(remote.deviceName) } : {},
|
|
4508
|
+
...normalizeOptionalString$9(remote.platformApiBase) ? { platformBase: normalizeOptionalString$9(remote.platformApiBase) } : {},
|
|
4506
4509
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4507
4510
|
};
|
|
4508
4511
|
}
|
|
@@ -4515,7 +4518,7 @@ function resolveRemoteStatusSnapshot(params) {
|
|
|
4515
4518
|
configuredEnabled: true,
|
|
4516
4519
|
runtime: {
|
|
4517
4520
|
...buildConfiguredRemoteState(params.config),
|
|
4518
|
-
deviceName: normalizeOptionalString$
|
|
4521
|
+
deviceName: normalizeOptionalString$9(params.config.remote.deviceName) ?? normalizeOptionalString$9(params.fallbackDeviceName) ?? hostname()
|
|
4519
4522
|
}
|
|
4520
4523
|
};
|
|
4521
4524
|
return {
|
|
@@ -5119,331 +5122,471 @@ async function measureStartupAsync(step, fn, fields) {
|
|
|
5119
5122
|
}
|
|
5120
5123
|
}
|
|
5121
5124
|
//#endregion
|
|
5122
|
-
//#region src/cli/
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5125
|
+
//#region src/cli/launcher/npm-runtime-update.manager.ts
|
|
5126
|
+
var NpmRuntimeUpdateManager = class {
|
|
5127
|
+
launcherVersion;
|
|
5128
|
+
now;
|
|
5129
|
+
availableManifest = null;
|
|
5130
|
+
constructor(options) {
|
|
5131
|
+
this.options = options;
|
|
5132
|
+
this.launcherVersion = options.launcherVersion ?? getPackageVersion$1();
|
|
5133
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
5134
|
+
this.options.layout.ensureLauncherDirs();
|
|
5135
|
+
this.syncStateFromCurrentPointer();
|
|
5136
|
+
}
|
|
5137
|
+
getSnapshot = () => this.toSnapshotFromState(this.options.stateStore.read(), { status: this.options.stateStore.read().downloadedVersion ? "downloaded" : "idle" });
|
|
5138
|
+
run = async (options = {}) => {
|
|
5139
|
+
if (options.apply) return this.applyDownloadedUpdate();
|
|
5140
|
+
const checkedSnapshot = await this.checkForUpdate();
|
|
5141
|
+
if (options.checkOnly || checkedSnapshot.status !== "update-available") return checkedSnapshot;
|
|
5142
|
+
if (options.download === false) return checkedSnapshot;
|
|
5143
|
+
return await this.downloadUpdate(options.onProgress);
|
|
5144
|
+
};
|
|
5145
|
+
checkForUpdate = async () => {
|
|
5146
|
+
const manifestUrl = this.options.resolveManifestUrl(this.options.channel);
|
|
5147
|
+
if (!this.options.updateService.hasSignatureVerifier()) return this.toSnapshotFromState(this.options.stateStore.read(), {
|
|
5148
|
+
status: "blocked",
|
|
5149
|
+
installationKind: "npm-runtime-bundle",
|
|
5150
|
+
blockReason: "signature-verification-unavailable",
|
|
5151
|
+
recoveryCommand: "Set NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY or NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY_PATH",
|
|
5152
|
+
errorMessage: "Runtime bundle updates require a configured update public key."
|
|
5153
|
+
});
|
|
5154
|
+
if (!manifestUrl) return this.toSnapshotFromState(this.options.stateStore.read(), {
|
|
5155
|
+
status: "blocked",
|
|
5156
|
+
installationKind: "npm-runtime-bundle",
|
|
5157
|
+
blockReason: "unsupported-installation",
|
|
5158
|
+
recoveryCommand: "Set NEXTCLAW_UPDATE_MANIFEST_URL or NEXTCLAW_UPDATE_MANIFEST_BASE_URL",
|
|
5159
|
+
errorMessage: "Runtime bundle update manifest URL is not configured."
|
|
5160
|
+
});
|
|
5161
|
+
const checkedAt = this.now().toISOString();
|
|
5162
|
+
const state = this.options.stateStore.update((current) => ({
|
|
5163
|
+
...current,
|
|
5164
|
+
channel: this.options.channel,
|
|
5165
|
+
lastUpdateCheckAt: checkedAt
|
|
5166
|
+
}));
|
|
5167
|
+
const availableUpdate = await this.options.updateService.checkForUpdate(manifestUrl, state.currentVersion, state.badVersions);
|
|
5168
|
+
return this.toSnapshotAfterCheck(availableUpdate, this.options.stateStore.read());
|
|
5169
|
+
};
|
|
5170
|
+
downloadUpdate = async (onProgress) => {
|
|
5171
|
+
const manifest = this.availableManifest ?? await this.ensureAvailableManifest();
|
|
5172
|
+
const downloaded = await this.options.updateService.downloadAndInstallUpdate(manifest, onProgress);
|
|
5173
|
+
const state = this.options.stateStore.update((current) => ({
|
|
5174
|
+
...current,
|
|
5175
|
+
downloadedVersion: downloaded.downloadedVersion,
|
|
5176
|
+
downloadedReleaseNotesUrl: downloaded.manifest.releaseNotesUrl
|
|
5177
|
+
}));
|
|
5178
|
+
await this.options.bundleService.pruneRetainedArtifacts();
|
|
5179
|
+
return this.toSnapshotFromState(state, {
|
|
5180
|
+
status: "downloaded",
|
|
5181
|
+
availableVersion: downloaded.downloadedVersion,
|
|
5182
|
+
downloadedVersion: downloaded.downloadedVersion,
|
|
5183
|
+
minimumHostVersion: downloaded.manifest.minimumLauncherVersion,
|
|
5184
|
+
releaseNotesUrl: downloaded.manifest.releaseNotesUrl,
|
|
5185
|
+
canApplyInApp: true,
|
|
5186
|
+
requiresRestart: false
|
|
5187
|
+
});
|
|
5188
|
+
};
|
|
5189
|
+
applyDownloadedUpdate = () => {
|
|
5190
|
+
const downloadedVersion = this.options.stateStore.read().downloadedVersion?.trim();
|
|
5191
|
+
if (!downloadedVersion) throw new Error("No downloaded npm runtime update is ready to apply.");
|
|
5192
|
+
this.options.bundleService.activateVersion(downloadedVersion);
|
|
5193
|
+
this.availableManifest = null;
|
|
5194
|
+
return this.toSnapshotFromState(this.options.stateStore.read(), {
|
|
5195
|
+
status: "restart-required",
|
|
5196
|
+
availableVersion: null,
|
|
5197
|
+
downloadedVersion: null,
|
|
5198
|
+
releaseNotesUrl: null,
|
|
5199
|
+
canApplyInApp: false,
|
|
5200
|
+
requiresRestart: true
|
|
5201
|
+
});
|
|
5202
|
+
};
|
|
5203
|
+
ensureAvailableManifest = async () => {
|
|
5204
|
+
const snapshot = await this.checkForUpdate();
|
|
5205
|
+
if (!this.availableManifest) {
|
|
5206
|
+
if (snapshot.downloadedVersion) throw new Error(`Version ${snapshot.downloadedVersion} has already been downloaded and is ready to apply.`);
|
|
5207
|
+
throw new Error("No npm runtime update is currently available.");
|
|
5208
|
+
}
|
|
5209
|
+
return this.availableManifest;
|
|
5210
|
+
};
|
|
5211
|
+
toSnapshotAfterCheck = (availableUpdate, state) => {
|
|
5212
|
+
if (state.downloadedVersion) {
|
|
5213
|
+
this.availableManifest = availableUpdate?.kind === "runtime-bundle-update" ? availableUpdate.manifest : this.availableManifest;
|
|
5214
|
+
return this.toSnapshotFromState(state, {
|
|
5215
|
+
status: "downloaded",
|
|
5216
|
+
availableVersion: availableUpdate?.kind === "runtime-bundle-update" ? availableUpdate.manifest.latestVersion : state.downloadedVersion,
|
|
5217
|
+
minimumHostVersion: availableUpdate?.kind === "runtime-bundle-update" ? availableUpdate.manifest.minimumLauncherVersion : null,
|
|
5218
|
+
canApplyInApp: true,
|
|
5219
|
+
requiresRestart: false
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
if (!availableUpdate) {
|
|
5223
|
+
this.availableManifest = null;
|
|
5224
|
+
return this.toSnapshotFromState(state, {
|
|
5225
|
+
status: "up-to-date",
|
|
5226
|
+
availableVersion: null,
|
|
5227
|
+
downloadedVersion: null,
|
|
5228
|
+
releaseNotesUrl: null
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
5231
|
+
if (availableUpdate.kind === "host-update-required") {
|
|
5232
|
+
this.availableManifest = null;
|
|
5233
|
+
return this.toSnapshotFromState(state, {
|
|
5234
|
+
status: "blocked",
|
|
5235
|
+
availableVersion: availableUpdate.manifest.latestVersion,
|
|
5236
|
+
minimumHostVersion: availableUpdate.manifest.minimumLauncherVersion,
|
|
5237
|
+
releaseNotesUrl: availableUpdate.manifest.releaseNotesUrl,
|
|
5238
|
+
blockReason: "host-too-old",
|
|
5239
|
+
recoveryCommand: "npm install -g nextclaw@latest",
|
|
5240
|
+
errorMessage: `NextClaw npm launcher ${this.launcherVersion} is too old for runtime bundle ${availableUpdate.manifest.latestVersion}.`
|
|
5241
|
+
});
|
|
5242
|
+
}
|
|
5243
|
+
if (availableUpdate.kind === "quarantined-bad-version") {
|
|
5244
|
+
this.availableManifest = null;
|
|
5245
|
+
return this.toSnapshotFromState(state, {
|
|
5246
|
+
status: "failed",
|
|
5247
|
+
availableVersion: availableUpdate.manifest.latestVersion,
|
|
5248
|
+
minimumHostVersion: availableUpdate.manifest.minimumLauncherVersion,
|
|
5249
|
+
releaseNotesUrl: availableUpdate.manifest.releaseNotesUrl,
|
|
5250
|
+
errorMessage: `Version ${availableUpdate.manifest.latestVersion} was quarantined after a failed launch.`
|
|
5251
|
+
});
|
|
5252
|
+
}
|
|
5253
|
+
this.availableManifest = availableUpdate.manifest;
|
|
5254
|
+
return this.toSnapshotFromState(state, {
|
|
5255
|
+
status: "update-available",
|
|
5256
|
+
availableVersion: availableUpdate.manifest.latestVersion,
|
|
5257
|
+
minimumHostVersion: availableUpdate.manifest.minimumLauncherVersion,
|
|
5258
|
+
releaseNotesUrl: availableUpdate.manifest.releaseNotesUrl
|
|
5259
|
+
});
|
|
5260
|
+
};
|
|
5261
|
+
syncStateFromCurrentPointer = () => {
|
|
5262
|
+
const currentPointer = this.options.layout.readCurrentPointer();
|
|
5263
|
+
if (!currentPointer) return;
|
|
5264
|
+
this.options.stateStore.update((state) => ({
|
|
5265
|
+
...state,
|
|
5266
|
+
currentVersion: state.currentVersion ?? currentPointer.version
|
|
5267
|
+
}));
|
|
5268
|
+
};
|
|
5269
|
+
toSnapshotFromState = (state, patch) => {
|
|
5270
|
+
const hasDownloadedVersion = Boolean(state.downloadedVersion);
|
|
5271
|
+
const { status } = patch;
|
|
5142
5272
|
return {
|
|
5143
|
-
|
|
5144
|
-
|
|
5273
|
+
installationKind: "npm-runtime-bundle",
|
|
5274
|
+
channel: state.channel,
|
|
5275
|
+
hostVersion: this.launcherVersion,
|
|
5276
|
+
currentVersion: state.currentVersion,
|
|
5277
|
+
availableVersion: null,
|
|
5278
|
+
downloadedVersion: state.downloadedVersion,
|
|
5279
|
+
minimumHostVersion: null,
|
|
5280
|
+
releaseNotesUrl: state.downloadedReleaseNotesUrl,
|
|
5281
|
+
lastCheckedAt: state.lastUpdateCheckAt,
|
|
5282
|
+
progress: null,
|
|
5283
|
+
canAutoDownload: state.updatePreferences.autoDownload,
|
|
5284
|
+
canApplyInApp: hasDownloadedVersion,
|
|
5285
|
+
requiresRestart: false,
|
|
5286
|
+
blockReason: null,
|
|
5287
|
+
recoveryCommand: null,
|
|
5288
|
+
errorMessage: null,
|
|
5289
|
+
preferences: { ...state.updatePreferences },
|
|
5290
|
+
...patch,
|
|
5291
|
+
status
|
|
5145
5292
|
};
|
|
5146
|
-
}
|
|
5147
|
-
const versionAfter = result.latestVersion ?? readInstalledVersion();
|
|
5148
|
-
console.log(`✓ Update complete (${result.strategy})`);
|
|
5149
|
-
if (versionAfter === currentVersion) console.log(`Version unchanged: ${currentVersion}`);
|
|
5150
|
-
else console.log(`Version updated: ${currentVersion} -> ${versionAfter}`);
|
|
5151
|
-
return {
|
|
5152
|
-
ok: true,
|
|
5153
|
-
shouldSuggestRestart: true
|
|
5154
5293
|
};
|
|
5155
|
-
}
|
|
5294
|
+
};
|
|
5156
5295
|
//#endregion
|
|
5157
|
-
//#region src/cli/
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5296
|
+
//#region src/cli/launcher/npm-runtime-update.service.ts
|
|
5297
|
+
var NpmRuntimeUpdateService = class {
|
|
5298
|
+
platform;
|
|
5299
|
+
arch;
|
|
5300
|
+
fetchImpl;
|
|
5301
|
+
manifestReader;
|
|
5302
|
+
bundlePublicKey;
|
|
5303
|
+
now;
|
|
5304
|
+
constructor(options) {
|
|
5305
|
+
this.options = options;
|
|
5306
|
+
this.platform = options.platform ?? process.platform;
|
|
5307
|
+
this.arch = options.arch ?? process.arch;
|
|
5308
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
5309
|
+
this.manifestReader = options.manifestReader ?? new UpdateManifestReader();
|
|
5310
|
+
this.bundlePublicKey = this.parseBundlePublicKey(options.bundlePublicKey);
|
|
5311
|
+
this.now = options.now ?? Date.now;
|
|
5312
|
+
}
|
|
5313
|
+
hasSignatureVerifier = () => Boolean(this.bundlePublicKey);
|
|
5314
|
+
checkForUpdate = async (manifestUrl, currentVersion, badVersions = []) => {
|
|
5315
|
+
const manifest = await this.fetchManifest(manifestUrl);
|
|
5316
|
+
if (currentVersion && compareNpmRuntimeVersions(manifest.latestVersion, currentVersion) <= 0) return null;
|
|
5317
|
+
if (compareNpmRuntimeVersions(this.options.launcherVersion, manifest.minimumLauncherVersion) < 0) return {
|
|
5318
|
+
kind: "host-update-required",
|
|
5319
|
+
manifest
|
|
5320
|
+
};
|
|
5321
|
+
if (badVersions.includes(manifest.latestVersion)) return {
|
|
5322
|
+
kind: "quarantined-bad-version",
|
|
5323
|
+
manifest
|
|
5324
|
+
};
|
|
5325
|
+
return {
|
|
5326
|
+
kind: "runtime-bundle-update",
|
|
5327
|
+
manifest
|
|
5328
|
+
};
|
|
5167
5329
|
};
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
}
|
|
5175
|
-
function isLoopbackHost(host) {
|
|
5176
|
-
const normalized = host.trim().toLowerCase();
|
|
5177
|
-
return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
|
|
5178
|
-
}
|
|
5179
|
-
const PUBLIC_IP_CHECK_URLS = ["https://api.ipify.org", "https://ifconfig.me/ip"];
|
|
5180
|
-
async function fetchPublicIpFrom(url, timeoutMs) {
|
|
5181
|
-
const controller = new AbortController();
|
|
5182
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
5183
|
-
try {
|
|
5184
|
-
const response = await fetch(url, {
|
|
5185
|
-
signal: controller.signal,
|
|
5186
|
-
headers: { Accept: "text/plain" }
|
|
5330
|
+
downloadAndInstallUpdate = async (manifest, reportProgress) => {
|
|
5331
|
+
const bytes = await this.downloadBundleBytes(manifest, reportProgress);
|
|
5332
|
+
const stagingRoot = join(this.options.layout.getStagingDir(), `download-${manifest.latestVersion}-${this.now()}`);
|
|
5333
|
+
await rm(stagingRoot, {
|
|
5334
|
+
recursive: true,
|
|
5335
|
+
force: true
|
|
5187
5336
|
});
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
process.kill(pid, 0);
|
|
5210
|
-
return true;
|
|
5211
|
-
} catch {
|
|
5212
|
-
return false;
|
|
5213
|
-
}
|
|
5214
|
-
}
|
|
5215
|
-
async function waitForExit(pid, timeoutMs) {
|
|
5216
|
-
const start = Date.now();
|
|
5217
|
-
while (Date.now() - start < timeoutMs) {
|
|
5218
|
-
if (!isProcessRunning(pid)) return true;
|
|
5219
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
5220
|
-
}
|
|
5221
|
-
return !isProcessRunning(pid);
|
|
5222
|
-
}
|
|
5223
|
-
function findNearestPackageManifest(startDir, expectedName) {
|
|
5224
|
-
let current = resolve(startDir);
|
|
5225
|
-
while (current.length > 0) {
|
|
5226
|
-
const pkgPath = join(current, "package.json");
|
|
5227
|
-
if (existsSync(pkgPath)) try {
|
|
5228
|
-
const raw = readFileSync(pkgPath, "utf-8");
|
|
5229
|
-
const parsed = JSON.parse(raw);
|
|
5230
|
-
if (!expectedName || parsed.name === expectedName) return {
|
|
5231
|
-
rootDir: current,
|
|
5232
|
-
version: typeof parsed.version === "string" ? parsed.version : void 0
|
|
5337
|
+
mkdirSync(stagingRoot, { recursive: true });
|
|
5338
|
+
try {
|
|
5339
|
+
const zip = await JSZip.loadAsync(bytes);
|
|
5340
|
+
const entries = Object.values(zip.files);
|
|
5341
|
+
await Promise.all(entries.map(async (entry) => {
|
|
5342
|
+
const targetPath = resolve(stagingRoot, entry.name);
|
|
5343
|
+
const resolvedStagingRoot = resolve(stagingRoot);
|
|
5344
|
+
if (targetPath !== resolvedStagingRoot && !targetPath.startsWith(`${resolvedStagingRoot}${sep}`)) throw new Error(`runtime bundle archive contains invalid path: ${entry.name}`);
|
|
5345
|
+
if (entry.dir) {
|
|
5346
|
+
mkdirSync(targetPath, { recursive: true });
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
5350
|
+
writeFileSync(targetPath, Buffer.from(await entry.async("uint8array")));
|
|
5351
|
+
}));
|
|
5352
|
+
const bundleRoot = this.findBundleRoot(stagingRoot);
|
|
5353
|
+
const installedBundle = await this.options.bundleService.installFromDirectory(bundleRoot);
|
|
5354
|
+
return {
|
|
5355
|
+
manifest,
|
|
5356
|
+
downloadedVersion: installedBundle.manifest.bundleVersion,
|
|
5357
|
+
bundleDirectory: installedBundle.bundleDirectory
|
|
5233
5358
|
};
|
|
5234
|
-
} catch {
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
}
|
|
5241
|
-
function resolveUiStaticDir(importMetaUrl = import.meta.url) {
|
|
5242
|
-
if (process.env.NEXTCLAW_DISABLE_STATIC_UI === "1") return null;
|
|
5243
|
-
const envDir = process.env.NEXTCLAW_UI_STATIC_DIR;
|
|
5244
|
-
if (envDir) return existsSync(join(envDir, "index.html")) ? envDir : null;
|
|
5245
|
-
const pkgRoot = findNearestPackageManifest(resolve(fileURLToPath(new URL(".", importMetaUrl))), "nextclaw")?.rootDir;
|
|
5246
|
-
if (!pkgRoot) return null;
|
|
5247
|
-
const bundledDir = join(pkgRoot, "ui-dist");
|
|
5248
|
-
return existsSync(join(bundledDir, "index.html")) ? bundledDir : null;
|
|
5249
|
-
}
|
|
5250
|
-
function openBrowser(url) {
|
|
5251
|
-
const platform = process.platform;
|
|
5252
|
-
let command;
|
|
5253
|
-
let args;
|
|
5254
|
-
if (platform === "darwin") {
|
|
5255
|
-
command = "open";
|
|
5256
|
-
args = [url];
|
|
5257
|
-
} else if (platform === "win32") {
|
|
5258
|
-
command = "cmd";
|
|
5259
|
-
args = [
|
|
5260
|
-
"/c",
|
|
5261
|
-
"start",
|
|
5262
|
-
"",
|
|
5263
|
-
url
|
|
5264
|
-
];
|
|
5265
|
-
} else {
|
|
5266
|
-
command = "xdg-open";
|
|
5267
|
-
args = [url];
|
|
5268
|
-
}
|
|
5269
|
-
if (!findExecutableOnPath(command)) return false;
|
|
5270
|
-
try {
|
|
5271
|
-
spawn(command, args, {
|
|
5272
|
-
stdio: "ignore",
|
|
5273
|
-
detached: true,
|
|
5274
|
-
env: createExternalCommandEnv(process.env)
|
|
5275
|
-
}).unref();
|
|
5276
|
-
return true;
|
|
5277
|
-
} catch {
|
|
5278
|
-
return false;
|
|
5279
|
-
}
|
|
5280
|
-
}
|
|
5281
|
-
function normalizePathEntries(rawPath, platform) {
|
|
5282
|
-
const delimiter = platform === "win32" ? ";" : ":";
|
|
5283
|
-
return rawPath.split(delimiter).map((entry) => entry.trim().replace(/^"+|"+$/g, "")).filter(Boolean);
|
|
5284
|
-
}
|
|
5285
|
-
function normalizeWindowsPathExt(rawPathExt) {
|
|
5286
|
-
const source = rawPathExt && rawPathExt.trim().length > 0 ? rawPathExt : ".COM;.EXE;.BAT;.CMD";
|
|
5287
|
-
const unique = /* @__PURE__ */ new Set();
|
|
5288
|
-
for (const ext of source.split(";")) {
|
|
5289
|
-
const trimmed = ext.trim();
|
|
5290
|
-
if (!trimmed) continue;
|
|
5291
|
-
const normalized = trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
|
|
5292
|
-
unique.add(normalized.toUpperCase());
|
|
5293
|
-
}
|
|
5294
|
-
return [...unique];
|
|
5295
|
-
}
|
|
5296
|
-
function hasFileExtension(binary) {
|
|
5297
|
-
return binary.lastIndexOf(".") > Math.max(binary.lastIndexOf("/"), binary.lastIndexOf("\\"));
|
|
5298
|
-
}
|
|
5299
|
-
function findExecutableOnPath(binary, env = process.env, platform = process.platform) {
|
|
5300
|
-
const target = binary.trim();
|
|
5301
|
-
if (!target) return null;
|
|
5302
|
-
if (target.includes("/") || target.includes("\\")) return existsSync(target) ? target : null;
|
|
5303
|
-
const rawPath = env.PATH ?? env.Path ?? env.path ?? "";
|
|
5304
|
-
if (!rawPath.trim()) return null;
|
|
5305
|
-
const entries = normalizePathEntries(rawPath, platform);
|
|
5306
|
-
if (entries.length === 0) return null;
|
|
5307
|
-
for (const dir of entries) {
|
|
5308
|
-
const direct = join(dir, target);
|
|
5309
|
-
if (existsSync(direct)) return direct;
|
|
5310
|
-
if (platform !== "win32" || hasFileExtension(target)) continue;
|
|
5311
|
-
for (const ext of normalizeWindowsPathExt(env.PATHEXT)) {
|
|
5312
|
-
const withExt = join(dir, `${target}${ext}`);
|
|
5313
|
-
if (existsSync(withExt)) return withExt;
|
|
5359
|
+
} catch (error) {
|
|
5360
|
+
await rm(stagingRoot, {
|
|
5361
|
+
recursive: true,
|
|
5362
|
+
force: true
|
|
5363
|
+
});
|
|
5364
|
+
throw error;
|
|
5314
5365
|
}
|
|
5315
|
-
}
|
|
5316
|
-
|
|
5366
|
+
};
|
|
5367
|
+
downloadBundleBytes = async (manifest, reportProgress) => {
|
|
5368
|
+
const response = await this.fetchBundle(manifest.bundleUrl);
|
|
5369
|
+
const bytes = await this.readResponseBytes(response, reportProgress);
|
|
5370
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
5371
|
+
if (sha256 !== manifest.bundleSha256) throw new Error(`runtime bundle sha256 mismatch: expected ${manifest.bundleSha256} but got ${sha256}`);
|
|
5372
|
+
this.assertBundleSignature(manifest, bytes);
|
|
5373
|
+
return bytes;
|
|
5374
|
+
};
|
|
5375
|
+
fetchManifest = async (manifestUrl) => {
|
|
5376
|
+
if (manifestUrl.startsWith("file://")) {
|
|
5377
|
+
const manifest = this.manifestReader.parse(JSON.parse(readFileSync(fileURLToPath(manifestUrl), "utf8")), manifestUrl);
|
|
5378
|
+
return this.verifyManifest(manifest, manifestUrl);
|
|
5379
|
+
}
|
|
5380
|
+
const response = await this.fetchImpl(manifestUrl);
|
|
5381
|
+
if (!response.ok) throw new Error(`runtime update manifest request failed with status ${response.status}`);
|
|
5382
|
+
const manifest = this.manifestReader.parse(await response.json(), manifestUrl);
|
|
5383
|
+
return this.verifyManifest(manifest, manifestUrl);
|
|
5384
|
+
};
|
|
5385
|
+
verifyManifest = (manifest, manifestUrl) => {
|
|
5386
|
+
if (manifest.hostKind && manifest.hostKind !== "npm-runtime-bundle") throw new Error(`runtime update manifest hostKind mismatch: expected npm-runtime-bundle but got ${manifest.hostKind}`);
|
|
5387
|
+
if (manifest.channel !== "stable" && manifest.channel !== "beta") throw new Error(`runtime update manifest channel is unsupported: ${manifest.channel}`);
|
|
5388
|
+
if (manifest.platform !== this.platform) throw new Error(`runtime update manifest platform mismatch: expected ${this.platform} but got ${manifest.platform}`);
|
|
5389
|
+
if (manifest.arch !== this.arch) throw new Error(`runtime update manifest arch mismatch: expected ${this.arch} but got ${manifest.arch}`);
|
|
5390
|
+
this.assertManifestSignature(manifest);
|
|
5391
|
+
return manifest;
|
|
5392
|
+
};
|
|
5393
|
+
fetchBundle = async (bundleUrl) => {
|
|
5394
|
+
if (bundleUrl.startsWith("file://")) {
|
|
5395
|
+
const bytes = readFileSync(fileURLToPath(bundleUrl));
|
|
5396
|
+
return new Response(bytes, {
|
|
5397
|
+
status: 200,
|
|
5398
|
+
headers: { "content-length": String(bytes.byteLength) }
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
const response = await this.fetchImpl(bundleUrl);
|
|
5402
|
+
if (!response.ok) throw new Error(`runtime bundle download failed with status ${response.status}`);
|
|
5403
|
+
return response;
|
|
5404
|
+
};
|
|
5405
|
+
readResponseBytes = async (response, reportProgress) => {
|
|
5406
|
+
const totalBytes = this.readContentLength(response);
|
|
5407
|
+
if (!response.body) {
|
|
5408
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
5409
|
+
this.reportDownloadProgress(reportProgress, bytes.byteLength, totalBytes ?? bytes.byteLength);
|
|
5410
|
+
return bytes;
|
|
5411
|
+
}
|
|
5412
|
+
const reader = response.body.getReader();
|
|
5413
|
+
const chunks = [];
|
|
5414
|
+
let downloadedBytes = 0;
|
|
5415
|
+
this.reportDownloadProgress(reportProgress, downloadedBytes, totalBytes);
|
|
5416
|
+
while (true) {
|
|
5417
|
+
const next = await reader.read();
|
|
5418
|
+
if (next.done) break;
|
|
5419
|
+
chunks.push(next.value);
|
|
5420
|
+
downloadedBytes += next.value.byteLength;
|
|
5421
|
+
this.reportDownloadProgress(reportProgress, downloadedBytes, totalBytes);
|
|
5422
|
+
}
|
|
5423
|
+
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
|
|
5424
|
+
};
|
|
5425
|
+
readContentLength = (response) => {
|
|
5426
|
+
const raw = response.headers.get("content-length");
|
|
5427
|
+
if (!raw) return null;
|
|
5428
|
+
const parsed = Number(raw);
|
|
5429
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
5430
|
+
};
|
|
5431
|
+
reportDownloadProgress = (reportProgress, downloadedBytes, totalBytes) => {
|
|
5432
|
+
reportProgress?.({
|
|
5433
|
+
downloadedBytes,
|
|
5434
|
+
totalBytes,
|
|
5435
|
+
percent: totalBytes && totalBytes > 0 ? Math.min(100, Math.round(downloadedBytes / totalBytes * 100)) : null
|
|
5436
|
+
});
|
|
5437
|
+
};
|
|
5438
|
+
findBundleRoot = (stagingRoot) => {
|
|
5439
|
+
if (existsSync(join(stagingRoot, "manifest.json"))) return stagingRoot;
|
|
5440
|
+
const entries = readDirectoryNames(stagingRoot).filter((entry) => existsSync(join(stagingRoot, entry, "manifest.json")));
|
|
5441
|
+
if (entries.length === 1) return join(stagingRoot, entries[0]);
|
|
5442
|
+
throw new Error(`runtime bundle archive does not contain exactly one manifest root under ${stagingRoot}`);
|
|
5443
|
+
};
|
|
5444
|
+
parseBundlePublicKey = (publicKey) => {
|
|
5445
|
+
const normalizedKey = publicKey?.trim();
|
|
5446
|
+
if (!normalizedKey) return null;
|
|
5447
|
+
const pemOrEscapedPem = normalizedKey.replaceAll("\\n", "\n");
|
|
5448
|
+
if (pemOrEscapedPem.includes("BEGIN PUBLIC KEY")) return createPublicKey(pemOrEscapedPem);
|
|
5449
|
+
return createPublicKey({
|
|
5450
|
+
key: Buffer.from(normalizedKey, "base64"),
|
|
5451
|
+
format: "der",
|
|
5452
|
+
type: "spki"
|
|
5453
|
+
});
|
|
5454
|
+
};
|
|
5455
|
+
assertManifestSignature = (manifest) => {
|
|
5456
|
+
if (!this.bundlePublicKey) throw new Error("runtime update manifest signature verification requires bundlePublicKey");
|
|
5457
|
+
const signature = Buffer.from(manifest.manifestSignature, "base64");
|
|
5458
|
+
if (!verify(null, Buffer.from(serializeUnsignedUpdateManifest(manifest)), this.bundlePublicKey, signature)) throw new Error(`runtime update manifest signature verification failed for ${manifest.latestVersion}`);
|
|
5459
|
+
};
|
|
5460
|
+
assertBundleSignature = (manifest, bytes) => {
|
|
5461
|
+
if (!this.bundlePublicKey) throw new Error("runtime bundle signature verification requires bundlePublicKey");
|
|
5462
|
+
const signature = Buffer.from(manifest.bundleSignature, "base64");
|
|
5463
|
+
if (!verify(null, bytes, this.bundlePublicKey, signature)) throw new Error(`runtime bundle signature verification failed for ${manifest.latestVersion}`);
|
|
5464
|
+
};
|
|
5465
|
+
};
|
|
5466
|
+
function readDirectoryNames(directory) {
|
|
5467
|
+
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
5317
5468
|
}
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5469
|
+
//#endregion
|
|
5470
|
+
//#region src/cli/launcher/npm-runtime-update-source.service.ts
|
|
5471
|
+
const DEFAULT_NPM_RUNTIME_UPDATE_BASE_URL = "https://Peiiii.github.io/nextclaw/npm-runtime-updates";
|
|
5472
|
+
function normalizeOptionalString$8(value) {
|
|
5473
|
+
if (typeof value !== "string") return null;
|
|
5474
|
+
const trimmed = value.trim();
|
|
5475
|
+
return trimmed ? trimmed : null;
|
|
5321
5476
|
}
|
|
5322
|
-
function
|
|
5323
|
-
|
|
5477
|
+
function normalizeChannel(value) {
|
|
5478
|
+
return typeof value === "string" && value.trim().toLowerCase() === "beta" ? "beta" : "stable";
|
|
5324
5479
|
}
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
rl.prompt();
|
|
5328
|
-
return new Promise((resolve) => {
|
|
5329
|
-
rl.once("line", (line) => resolve(line));
|
|
5330
|
-
});
|
|
5480
|
+
function resolvePackagedPublicKeyPath() {
|
|
5481
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "../../..", "resources", "update-bundle-public.pem");
|
|
5331
5482
|
}
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
cmd: process.env.ComSpec || "cmd.exe",
|
|
5344
|
-
args: [
|
|
5345
|
-
"/d",
|
|
5346
|
-
"/s",
|
|
5347
|
-
"/c",
|
|
5348
|
-
command
|
|
5349
|
-
]
|
|
5350
|
-
};
|
|
5351
|
-
return {
|
|
5352
|
-
cmd: process.env.SHELL || "sh",
|
|
5353
|
-
args: ["-c", command]
|
|
5354
|
-
};
|
|
5483
|
+
var NpmRuntimeUpdateSourceService = class {
|
|
5484
|
+
env;
|
|
5485
|
+
platform;
|
|
5486
|
+
arch;
|
|
5487
|
+
constructor(options = {}) {
|
|
5488
|
+
this.env = options.env ?? process.env;
|
|
5489
|
+
this.platform = options.platform ?? process.platform;
|
|
5490
|
+
this.arch = options.arch ?? process.arch;
|
|
5491
|
+
}
|
|
5492
|
+
resolveChannel = (explicitChannel) => {
|
|
5493
|
+
return normalizeChannel(explicitChannel ?? this.env.NEXTCLAW_UPDATE_CHANNEL);
|
|
5355
5494
|
};
|
|
5356
|
-
|
|
5357
|
-
const
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
timeout: timeoutMs,
|
|
5362
|
-
stdio: "pipe"
|
|
5363
|
-
});
|
|
5364
|
-
const stdout = (result.stdout ?? "").toString().slice(0, 4e3);
|
|
5365
|
-
const stderr = (result.stderr ?? "").toString().slice(0, 4e3);
|
|
5366
|
-
steps.push({
|
|
5367
|
-
cmd,
|
|
5368
|
-
args,
|
|
5369
|
-
cwd,
|
|
5370
|
-
code: result.status,
|
|
5371
|
-
stdout,
|
|
5372
|
-
stderr
|
|
5373
|
-
});
|
|
5374
|
-
return {
|
|
5375
|
-
ok: result.status === 0,
|
|
5376
|
-
code: result.status,
|
|
5377
|
-
stdout,
|
|
5378
|
-
stderr
|
|
5379
|
-
};
|
|
5495
|
+
resolveManifestUrl = (channel, explicitManifestUrl) => {
|
|
5496
|
+
const manifestUrl = normalizeOptionalString$8(explicitManifestUrl) ?? normalizeOptionalString$8(this.env.NEXTCLAW_UPDATE_MANIFEST_URL);
|
|
5497
|
+
if (manifestUrl) return manifestUrl;
|
|
5498
|
+
const baseUrl = normalizeOptionalString$8(this.env.NEXTCLAW_UPDATE_MANIFEST_BASE_URL) ?? DEFAULT_NPM_RUNTIME_UPDATE_BASE_URL;
|
|
5499
|
+
return new URL(`${channel}/manifest-${channel}-${this.platform}-${this.arch}.json`, `${baseUrl.replace(/\/+$/, "")}/`).toString();
|
|
5380
5500
|
};
|
|
5381
|
-
|
|
5382
|
-
const
|
|
5383
|
-
if (
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
return trimmed;
|
|
5501
|
+
resolveBundlePublicKey = () => {
|
|
5502
|
+
const explicitPublicKey = normalizeOptionalString$8(this.env.NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY);
|
|
5503
|
+
if (explicitPublicKey) return explicitPublicKey;
|
|
5504
|
+
const publicKeyPath = normalizeOptionalString$8(this.env.NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY_PATH);
|
|
5505
|
+
if (!publicKeyPath || !existsSync(publicKeyPath)) {
|
|
5506
|
+
const packagedPublicKeyPath = resolvePackagedPublicKeyPath();
|
|
5507
|
+
return existsSync(packagedPublicKeyPath) ? readFileSync(packagedPublicKeyPath, "utf8").trim() : null;
|
|
5389
5508
|
}
|
|
5509
|
+
return readFileSync(publicKeyPath, "utf8").trim();
|
|
5390
5510
|
};
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
const
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5511
|
+
};
|
|
5512
|
+
//#endregion
|
|
5513
|
+
//#region src/cli/launcher/npm-runtime-update-command.service.ts
|
|
5514
|
+
var NpmRuntimeUpdateCommandService = class {
|
|
5515
|
+
run = async (opts) => {
|
|
5516
|
+
const snapshot = await this.runManaged(opts);
|
|
5517
|
+
if (opts.json) console.log(JSON.stringify(snapshot, null, 2));
|
|
5518
|
+
else this.printSnapshot(snapshot);
|
|
5519
|
+
return snapshot;
|
|
5520
|
+
};
|
|
5521
|
+
runManaged = async (opts) => {
|
|
5522
|
+
const source = new NpmRuntimeUpdateSourceService();
|
|
5523
|
+
const channel = source.resolveChannel(opts.channel);
|
|
5524
|
+
const manifestUrl = source.resolveManifestUrl(channel, opts.manifestUrl);
|
|
5525
|
+
const layout = new NpmRuntimeBundleLayoutStore();
|
|
5526
|
+
const stateStore = new NpmRuntimeUpdateStateStore(layout.getStatePath());
|
|
5527
|
+
const launcherVersion = getPackageVersion$1();
|
|
5528
|
+
const bundleService = new NpmRuntimeBundleService({
|
|
5529
|
+
layout,
|
|
5530
|
+
stateStore,
|
|
5531
|
+
launcherVersion
|
|
5532
|
+
});
|
|
5533
|
+
return await new NpmRuntimeUpdateManager({
|
|
5534
|
+
layout,
|
|
5535
|
+
stateStore,
|
|
5536
|
+
bundleService,
|
|
5537
|
+
updateService: new NpmRuntimeUpdateService({
|
|
5538
|
+
layout,
|
|
5539
|
+
bundleService,
|
|
5540
|
+
launcherVersion,
|
|
5541
|
+
bundlePublicKey: source.resolveBundlePublicKey() ?? void 0
|
|
5542
|
+
}),
|
|
5543
|
+
resolveManifestUrl: () => manifestUrl,
|
|
5544
|
+
launcherVersion,
|
|
5545
|
+
channel
|
|
5546
|
+
}).run({
|
|
5547
|
+
apply: Boolean(opts.apply),
|
|
5548
|
+
checkOnly: Boolean(opts.check),
|
|
5549
|
+
download: opts.download === void 0 ? true : Boolean(opts.download),
|
|
5550
|
+
onProgress: opts.json ? void 0 : this.printProgress
|
|
5551
|
+
});
|
|
5552
|
+
};
|
|
5553
|
+
printProgress = (progress) => {
|
|
5554
|
+
const percent = progress.percent === null ? "" : ` ${progress.percent}%`;
|
|
5555
|
+
const total = progress.totalBytes === null ? "" : ` / ${progress.totalBytes} bytes`;
|
|
5556
|
+
process.stdout.write(`\rDownloading${percent} (${progress.downloadedBytes} bytes${total})`);
|
|
5557
|
+
if (progress.percent === 100) process.stdout.write("\n");
|
|
5558
|
+
};
|
|
5559
|
+
printSnapshot = (snapshot) => {
|
|
5560
|
+
if (snapshot.status === "blocked") {
|
|
5561
|
+
console.error(`Update blocked: ${snapshot.errorMessage ?? snapshot.blockReason ?? "unknown reason"}`);
|
|
5562
|
+
if (snapshot.recoveryCommand) console.error(`Recovery: ${snapshot.recoveryCommand}`);
|
|
5563
|
+
return;
|
|
5564
|
+
}
|
|
5565
|
+
if (snapshot.status === "failed") {
|
|
5566
|
+
console.error(`Update failed: ${snapshot.errorMessage ?? "unknown error"}`);
|
|
5567
|
+
return;
|
|
5568
|
+
}
|
|
5569
|
+
if (snapshot.status === "up-to-date") {
|
|
5570
|
+
console.log(`NextClaw runtime is already up to date (${snapshot.currentVersion ?? snapshot.hostVersion ?? "unknown"}).`);
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
if (snapshot.status === "downloaded") {
|
|
5574
|
+
console.log(`Runtime update downloaded: ${snapshot.downloadedVersion ?? snapshot.availableVersion}`);
|
|
5575
|
+
console.log("Run `nextclaw update --apply` to switch to the downloaded runtime.");
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
5578
|
+
if (snapshot.status === "restart-required") {
|
|
5579
|
+
console.log(`Runtime update applied: ${snapshot.currentVersion}`);
|
|
5580
|
+
console.log("Restart the running NextClaw service or start a new CLI process to use it.");
|
|
5581
|
+
return;
|
|
5582
|
+
}
|
|
5583
|
+
if (snapshot.status === "update-available") {
|
|
5584
|
+
console.log(`Runtime update available: ${snapshot.currentVersion ?? "none"} -> ${snapshot.availableVersion}`);
|
|
5585
|
+
return;
|
|
5586
|
+
}
|
|
5587
|
+
console.log(`Update status: ${snapshot.status}`);
|
|
5445
5588
|
};
|
|
5446
|
-
}
|
|
5589
|
+
};
|
|
5447
5590
|
//#endregion
|
|
5448
5591
|
//#region src/cli/shared/stores/managed-service-state.store.ts
|
|
5449
5592
|
var ManagedServiceStateStore = class {
|
|
@@ -8527,7 +8670,8 @@ function buildLegacyUserContent(parts, options = {}) {
|
|
|
8527
8670
|
}
|
|
8528
8671
|
function buildLegacyAssistantMessages(message, timestamp) {
|
|
8529
8672
|
const textContent = extractTextFromNcpMessage(message);
|
|
8530
|
-
const
|
|
8673
|
+
const reasoningParts = message.parts.filter((part) => part.type === "reasoning");
|
|
8674
|
+
const reasoningContent = reasoningParts.map((part) => part.text).join("");
|
|
8531
8675
|
const toolInvocations = message.parts.filter((part) => part.type === "tool-invocation");
|
|
8532
8676
|
const assistantMessage = {
|
|
8533
8677
|
role: "assistant",
|
|
@@ -8537,7 +8681,7 @@ function buildLegacyAssistantMessages(message, timestamp) {
|
|
|
8537
8681
|
ncp_parts: structuredClone(message.parts)
|
|
8538
8682
|
};
|
|
8539
8683
|
if (typeof message.metadata?.reply_to === "string" && message.metadata.reply_to.trim().length > 0) assistantMessage.reply_to = message.metadata.reply_to.trim();
|
|
8540
|
-
if (
|
|
8684
|
+
if (reasoningParts.length > 0) assistantMessage.reasoning_content = reasoningContent;
|
|
8541
8685
|
if (toolInvocations.length > 0) assistantMessage.tool_calls = toolInvocations.map((toolInvocation, index) => ({
|
|
8542
8686
|
id: toolInvocation.toolCallId ?? `${message.id}:tool:${index}`,
|
|
8543
8687
|
type: "function",
|
|
@@ -8563,11 +8707,12 @@ function buildLegacyAssistantMessages(message, timestamp) {
|
|
|
8563
8707
|
}
|
|
8564
8708
|
function buildLegacyNonAssistantMessage(message, timestamp, options) {
|
|
8565
8709
|
return {
|
|
8566
|
-
role: message.role === "service" ? "system" : message.role,
|
|
8710
|
+
role: message.role === "service" && options.serviceRole === "system" ? "system" : message.role,
|
|
8567
8711
|
content: buildLegacyUserContent(message.parts, options),
|
|
8568
8712
|
timestamp,
|
|
8569
8713
|
ncp_message_id: message.id,
|
|
8570
|
-
ncp_parts: structuredClone(message.parts)
|
|
8714
|
+
ncp_parts: structuredClone(message.parts),
|
|
8715
|
+
...message.metadata ? { ncp_metadata: structuredClone(message.metadata) } : {}
|
|
8571
8716
|
};
|
|
8572
8717
|
}
|
|
8573
8718
|
function toLegacyMessages(messages, options = {}) {
|
|
@@ -8718,6 +8863,225 @@ function buildCurrentTurnState(params) {
|
|
|
8718
8863
|
};
|
|
8719
8864
|
}
|
|
8720
8865
|
//#endregion
|
|
8866
|
+
//#region src/cli/commands/ncp/context/context-compaction-timeline-message.utils.ts
|
|
8867
|
+
const NEXTCLAW_TIMELINE_KIND_METADATA_KEY = "nextclaw_timeline_kind";
|
|
8868
|
+
const CONTEXT_COMPACTION_TIMELINE_KIND = "context_compaction";
|
|
8869
|
+
function readTimelineMetadata(message) {
|
|
8870
|
+
const rawMetadata = message?.ncp_metadata;
|
|
8871
|
+
if (!rawMetadata || typeof rawMetadata !== "object" || Array.isArray(rawMetadata)) return null;
|
|
8872
|
+
const metadata = rawMetadata;
|
|
8873
|
+
if (metadata["nextclaw_timeline_kind"] !== "context_compaction") return null;
|
|
8874
|
+
const checkpoint = metadata.checkpoint && typeof metadata.checkpoint === "object" && !Array.isArray(metadata.checkpoint) ? metadata.checkpoint : null;
|
|
8875
|
+
if (!checkpoint) return null;
|
|
8876
|
+
return {
|
|
8877
|
+
[NEXTCLAW_TIMELINE_KIND_METADATA_KEY]: CONTEXT_COMPACTION_TIMELINE_KIND,
|
|
8878
|
+
checkpoint
|
|
8879
|
+
};
|
|
8880
|
+
}
|
|
8881
|
+
function buildTimelineMessage(checkpoint) {
|
|
8882
|
+
return {
|
|
8883
|
+
role: "service",
|
|
8884
|
+
content: checkpoint.status === "compressing" ? "正在压缩较早上下文" : "较早上下文已自动压缩",
|
|
8885
|
+
timestamp: checkpoint.updatedAt,
|
|
8886
|
+
ncp_parts: [{
|
|
8887
|
+
type: "text",
|
|
8888
|
+
text: checkpoint.status === "compressing" ? "正在压缩较早上下文" : "较早上下文已自动压缩"
|
|
8889
|
+
}],
|
|
8890
|
+
ncp_metadata: {
|
|
8891
|
+
[NEXTCLAW_TIMELINE_KIND_METADATA_KEY]: CONTEXT_COMPACTION_TIMELINE_KIND,
|
|
8892
|
+
checkpoint
|
|
8893
|
+
}
|
|
8894
|
+
};
|
|
8895
|
+
}
|
|
8896
|
+
function buildContextCompactionTimelineNcpMessage(params) {
|
|
8897
|
+
const { checkpoint, sessionId } = params;
|
|
8898
|
+
const text = checkpoint.status === "compressing" ? "正在压缩较早上下文" : "较早上下文已自动压缩";
|
|
8899
|
+
return {
|
|
8900
|
+
id: `${sessionId}:service:context-compaction:${checkpoint.id}`,
|
|
8901
|
+
sessionId,
|
|
8902
|
+
role: "service",
|
|
8903
|
+
status: "final",
|
|
8904
|
+
timestamp: checkpoint.updatedAt,
|
|
8905
|
+
parts: [{
|
|
8906
|
+
type: "text",
|
|
8907
|
+
text
|
|
8908
|
+
}],
|
|
8909
|
+
metadata: {
|
|
8910
|
+
[NEXTCLAW_TIMELINE_KIND_METADATA_KEY]: CONTEXT_COMPACTION_TIMELINE_KIND,
|
|
8911
|
+
checkpoint
|
|
8912
|
+
}
|
|
8913
|
+
};
|
|
8914
|
+
}
|
|
8915
|
+
function upsertContextCompactionTimelineMessage(params) {
|
|
8916
|
+
const { checkpoint, insertBeforeMessageIds = [], session } = params;
|
|
8917
|
+
const nextMessage = buildTimelineMessage(checkpoint);
|
|
8918
|
+
for (let index = session.events.length - 1; index >= 0; index -= 1) {
|
|
8919
|
+
const event = session.events[index];
|
|
8920
|
+
const metadata = readTimelineMetadata(event?.data?.message && typeof event.data.message === "object" && !Array.isArray(event.data.message) ? event.data.message : null);
|
|
8921
|
+
if (!metadata) continue;
|
|
8922
|
+
event.timestamp = checkpoint.updatedAt;
|
|
8923
|
+
event.data.message = nextMessage;
|
|
8924
|
+
session.messages = session.messages.map((item) => {
|
|
8925
|
+
return readTimelineMetadata(item)?.checkpoint.id === metadata.checkpoint.id ? nextMessage : item;
|
|
8926
|
+
});
|
|
8927
|
+
session.updatedAt = new Date(checkpoint.updatedAt);
|
|
8928
|
+
return;
|
|
8929
|
+
}
|
|
8930
|
+
const event = {
|
|
8931
|
+
seq: session.nextSeq,
|
|
8932
|
+
type: "message.service",
|
|
8933
|
+
timestamp: checkpoint.updatedAt,
|
|
8934
|
+
data: { message: nextMessage }
|
|
8935
|
+
};
|
|
8936
|
+
session.nextSeq += 1;
|
|
8937
|
+
const insertionIds = new Set(insertBeforeMessageIds);
|
|
8938
|
+
const messageIndex = session.messages.findIndex((message) => typeof message.ncp_message_id === "string" && insertionIds.has(message.ncp_message_id));
|
|
8939
|
+
if (messageIndex >= 0) session.messages.splice(messageIndex, 0, nextMessage);
|
|
8940
|
+
else session.messages.push(nextMessage);
|
|
8941
|
+
const eventIndex = session.events.findIndex((candidateEvent) => {
|
|
8942
|
+
const message = candidateEvent?.data?.message && typeof candidateEvent.data.message === "object" && !Array.isArray(candidateEvent.data.message) ? candidateEvent.data.message : null;
|
|
8943
|
+
return typeof message?.ncp_message_id === "string" && insertionIds.has(message.ncp_message_id);
|
|
8944
|
+
});
|
|
8945
|
+
if (eventIndex >= 0) session.events.splice(eventIndex, 0, event);
|
|
8946
|
+
else session.events.push(event);
|
|
8947
|
+
session.updatedAt = new Date(checkpoint.updatedAt);
|
|
8948
|
+
}
|
|
8949
|
+
function isContextCompactionTimelineMessage(message) {
|
|
8950
|
+
return message?.metadata?.[NEXTCLAW_TIMELINE_KIND_METADATA_KEY] === CONTEXT_COMPACTION_TIMELINE_KIND;
|
|
8951
|
+
}
|
|
8952
|
+
//#endregion
|
|
8953
|
+
//#region src/cli/commands/ncp/context/context-compaction.service.ts
|
|
8954
|
+
const CONTEXT_COMPACTION_METADATA_KEY = "last_context_compaction";
|
|
8955
|
+
const MIN_MESSAGES_TO_COMPACT = 8;
|
|
8956
|
+
const RECENT_TAIL_MESSAGES = 6;
|
|
8957
|
+
function createCheckpointId(createdAt, coveredMessageCount) {
|
|
8958
|
+
return `ctx-${createdAt.replace(/[^0-9]/g, "").slice(0, 14)}-${coveredMessageCount}`;
|
|
8959
|
+
}
|
|
8960
|
+
function isSystemMessage(message) {
|
|
8961
|
+
return message?.role === "system";
|
|
8962
|
+
}
|
|
8963
|
+
var ContextCompactionService = class {
|
|
8964
|
+
inputBudgetPruner = new InputBudgetPruner();
|
|
8965
|
+
prepareForModelInput = (params) => {
|
|
8966
|
+
const { compactionThresholdTokens, contextTokens, messages } = params;
|
|
8967
|
+
const originalEstimate = this.inputBudgetPruner.estimate({
|
|
8968
|
+
messages,
|
|
8969
|
+
contextTokens
|
|
8970
|
+
});
|
|
8971
|
+
const thresholdTokens = compactionThresholdTokens ?? originalEstimate.budgetTokens;
|
|
8972
|
+
if (originalEstimate.estimatedTokens < thresholdTokens) return null;
|
|
8973
|
+
const { coveredMessages, keptMessages } = this.splitMessages(messages);
|
|
8974
|
+
if (coveredMessages.length < MIN_MESSAGES_TO_COMPACT) return null;
|
|
8975
|
+
return {
|
|
8976
|
+
messages,
|
|
8977
|
+
coveredMessages,
|
|
8978
|
+
keptMessages,
|
|
8979
|
+
originalEstimatedTokens: originalEstimate.estimatedTokens
|
|
8980
|
+
};
|
|
8981
|
+
};
|
|
8982
|
+
compactForModelInput = async (params) => {
|
|
8983
|
+
const { compactionThresholdTokens, contextTokens, generateSummary, messages, now } = params;
|
|
8984
|
+
const plan = this.prepareForModelInput({
|
|
8985
|
+
messages,
|
|
8986
|
+
contextTokens,
|
|
8987
|
+
compactionThresholdTokens
|
|
8988
|
+
});
|
|
8989
|
+
if (!plan) return {
|
|
8990
|
+
messages,
|
|
8991
|
+
checkpoint: null
|
|
8992
|
+
};
|
|
8993
|
+
return await this.compactPreparedForModelInput({
|
|
8994
|
+
contextTokens,
|
|
8995
|
+
generateSummary,
|
|
8996
|
+
now,
|
|
8997
|
+
plan
|
|
8998
|
+
});
|
|
8999
|
+
};
|
|
9000
|
+
compactPreparedForModelInput = async (params) => {
|
|
9001
|
+
const { contextTokens, generateSummary, now, plan } = params;
|
|
9002
|
+
const { coveredMessages, keptMessages, messages, originalEstimatedTokens } = plan;
|
|
9003
|
+
const createdAt = (now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
9004
|
+
const summary = await generateSummary({ messages: coveredMessages.map((message) => structuredClone(message)) });
|
|
9005
|
+
const checkpointMessage = {
|
|
9006
|
+
role: "user",
|
|
9007
|
+
content: summary
|
|
9008
|
+
};
|
|
9009
|
+
const recentHistory = keptMessages.slice(0, -1);
|
|
9010
|
+
const currentTurn = keptMessages.at(-1);
|
|
9011
|
+
const projectedMessages = [
|
|
9012
|
+
isSystemMessage(messages[0]) ? messages[0] : null,
|
|
9013
|
+
checkpointMessage,
|
|
9014
|
+
...recentHistory,
|
|
9015
|
+
currentTurn
|
|
9016
|
+
].filter((message) => Boolean(message));
|
|
9017
|
+
const projectedEstimate = this.inputBudgetPruner.estimate({
|
|
9018
|
+
messages: projectedMessages,
|
|
9019
|
+
contextTokens
|
|
9020
|
+
});
|
|
9021
|
+
return {
|
|
9022
|
+
messages: projectedMessages,
|
|
9023
|
+
checkpoint: {
|
|
9024
|
+
version: 1,
|
|
9025
|
+
id: createCheckpointId(createdAt, coveredMessages.length),
|
|
9026
|
+
status: "compressed",
|
|
9027
|
+
summary,
|
|
9028
|
+
coveredMessageCount: coveredMessages.length,
|
|
9029
|
+
coveredSessionMessageCount: coveredMessages.length,
|
|
9030
|
+
originalEstimatedTokens,
|
|
9031
|
+
projectedEstimatedTokens: projectedEstimate.estimatedTokens,
|
|
9032
|
+
createdAt,
|
|
9033
|
+
updatedAt: createdAt
|
|
9034
|
+
}
|
|
9035
|
+
};
|
|
9036
|
+
};
|
|
9037
|
+
splitMessages = (messages) => {
|
|
9038
|
+
const historyStartIndex = isSystemMessage(messages[0]) ? 1 : 0;
|
|
9039
|
+
const history = messages.slice(historyStartIndex, -1);
|
|
9040
|
+
const currentTurn = messages.at(-1);
|
|
9041
|
+
const tailStart = Math.max(0, history.length - RECENT_TAIL_MESSAGES);
|
|
9042
|
+
return {
|
|
9043
|
+
coveredMessages: history.slice(0, tailStart),
|
|
9044
|
+
keptMessages: [...history.slice(tailStart), ...currentTurn ? [currentTurn] : []]
|
|
9045
|
+
};
|
|
9046
|
+
};
|
|
9047
|
+
};
|
|
9048
|
+
function readCompressedContextCompactionCheckpoint(value) {
|
|
9049
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
9050
|
+
const checkpoint = value;
|
|
9051
|
+
return checkpoint.version === 1 && checkpoint.status === "compressed" && typeof checkpoint.summary === "string" ? checkpoint : null;
|
|
9052
|
+
}
|
|
9053
|
+
//#endregion
|
|
9054
|
+
//#region src/cli/commands/ncp/context/context-compaction-projection.utils.ts
|
|
9055
|
+
function readCompactionSummary(message) {
|
|
9056
|
+
const metadata = message.metadata;
|
|
9057
|
+
if (metadata?.["nextclaw_timeline_kind"] !== "context_compaction") return null;
|
|
9058
|
+
return readCompressedContextCompactionCheckpoint(metadata.checkpoint)?.summary ?? null;
|
|
9059
|
+
}
|
|
9060
|
+
function projectNcpMessagesWithContextCompaction(params) {
|
|
9061
|
+
const { sessionId, sessionMessages } = params;
|
|
9062
|
+
let checkpointIndex = -1;
|
|
9063
|
+
let summary = "";
|
|
9064
|
+
for (let index = sessionMessages.length - 1; index >= 0; index -= 1) {
|
|
9065
|
+
const candidateSummary = readCompactionSummary(sessionMessages[index]);
|
|
9066
|
+
if (!candidateSummary) continue;
|
|
9067
|
+
checkpointIndex = index;
|
|
9068
|
+
summary = candidateSummary;
|
|
9069
|
+
break;
|
|
9070
|
+
}
|
|
9071
|
+
if (checkpointIndex < 0) return sessionMessages.filter((message) => !readCompactionSummary(message)).map((message) => structuredClone(message));
|
|
9072
|
+
return [{
|
|
9073
|
+
id: `${sessionId}:context-compaction-summary:${sessionMessages[checkpointIndex]?.id ?? "latest"}`,
|
|
9074
|
+
sessionId,
|
|
9075
|
+
role: "user",
|
|
9076
|
+
status: "final",
|
|
9077
|
+
timestamp: sessionMessages[checkpointIndex]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9078
|
+
parts: [{
|
|
9079
|
+
type: "text",
|
|
9080
|
+
text: summary
|
|
9081
|
+
}]
|
|
9082
|
+
}, ...sessionMessages.slice(checkpointIndex + 1).filter((message) => !readCompactionSummary(message)).map((message) => structuredClone(message))];
|
|
9083
|
+
}
|
|
9084
|
+
//#endregion
|
|
8721
9085
|
//#region src/cli/commands/ncp/session-request/session-request.tool.ts
|
|
8722
9086
|
function readRequiredString$1(params, key) {
|
|
8723
9087
|
const value = params[key];
|
|
@@ -9131,14 +9495,14 @@ function resolveRequestedToolNames(metadata) {
|
|
|
9131
9495
|
if (!Array.isArray(rawValue)) return [];
|
|
9132
9496
|
return Array.from(new Set(rawValue.map((item) => normalizeString(item)).filter((item) => Boolean(item))));
|
|
9133
9497
|
}
|
|
9134
|
-
function readRequestedAgentId(metadata) {
|
|
9498
|
+
function readRequestedAgentId$1(metadata) {
|
|
9135
9499
|
return normalizeString(metadata.agent_id)?.toLowerCase() ?? normalizeString(metadata.agentId)?.toLowerCase() ?? null;
|
|
9136
9500
|
}
|
|
9137
9501
|
function resolveAgentProfile(params) {
|
|
9138
9502
|
const { config, requestMetadata, storedAgentId } = params;
|
|
9139
9503
|
const { agents: { defaults }, search: searchConfig, tools: { restrictToWorkspace, exec: { timeout: execTimeoutSeconds } } } = config;
|
|
9140
9504
|
const defaultAgentId = resolveDefaultAgentProfileId(config);
|
|
9141
|
-
const profile = findEffectiveAgentProfile(config, normalizeString(storedAgentId)?.toLowerCase() ?? readRequestedAgentId(requestMetadata) ?? defaultAgentId) ?? findEffectiveAgentProfile(config, defaultAgentId);
|
|
9505
|
+
const profile = findEffectiveAgentProfile(config, normalizeString(storedAgentId)?.toLowerCase() ?? readRequestedAgentId$1(requestMetadata) ?? defaultAgentId) ?? findEffectiveAgentProfile(config, defaultAgentId);
|
|
9142
9506
|
if (!profile) throw new Error(`default agent profile not found: ${defaultAgentId}`);
|
|
9143
9507
|
return {
|
|
9144
9508
|
agentId: profile.id,
|
|
@@ -9204,6 +9568,16 @@ var NextclawNcpContextBuilder = class {
|
|
|
9204
9568
|
this.options = options;
|
|
9205
9569
|
}
|
|
9206
9570
|
prepare = (input, _options) => {
|
|
9571
|
+
const runContext = this.prepareRunContext(input);
|
|
9572
|
+
const modelMessages = this.buildModelMessages(input, _options, runContext);
|
|
9573
|
+
return {
|
|
9574
|
+
messages: this.pruneModelMessages(runContext, modelMessages).messages,
|
|
9575
|
+
tools: buildRequestedOpenAiTools(modelMessages.toolDefinitions, runContext.requestedToolNames),
|
|
9576
|
+
model: runContext.effectiveModel,
|
|
9577
|
+
thinkingLevel: runContext.runtimeThinking
|
|
9578
|
+
};
|
|
9579
|
+
};
|
|
9580
|
+
prepareRunContext = (input) => {
|
|
9207
9581
|
const config = this.options.getConfig();
|
|
9208
9582
|
const requestMetadata = mergeInputMetadata(input);
|
|
9209
9583
|
const session = this.options.sessionManager.getOrCreate(input.sessionId);
|
|
@@ -9230,7 +9604,6 @@ var NextclawNcpContextBuilder = class {
|
|
|
9230
9604
|
requestMetadata
|
|
9231
9605
|
});
|
|
9232
9606
|
const requestedSkills = REQUESTED_SKILLS_METADATA_READER.readSelection(requestMetadata);
|
|
9233
|
-
const requestedToolNames = resolveRequestedToolNames(requestMetadata);
|
|
9234
9607
|
const currentTurn = buildCurrentTurnState({
|
|
9235
9608
|
input,
|
|
9236
9609
|
currentModel: effectiveModel,
|
|
@@ -9260,7 +9633,25 @@ var NextclawNcpContextBuilder = class {
|
|
|
9260
9633
|
searchConfig: profile.searchConfig,
|
|
9261
9634
|
workspace: effectiveWorkspace
|
|
9262
9635
|
});
|
|
9263
|
-
|
|
9636
|
+
return {
|
|
9637
|
+
accountId: readAccountIdForHints(requestMetadata, session.metadata),
|
|
9638
|
+
channel,
|
|
9639
|
+
chatId,
|
|
9640
|
+
config,
|
|
9641
|
+
currentTurn,
|
|
9642
|
+
effectiveModel,
|
|
9643
|
+
effectiveWorkspace,
|
|
9644
|
+
profile,
|
|
9645
|
+
requestMetadata,
|
|
9646
|
+
requestedSkills,
|
|
9647
|
+
requestedToolNames: resolveRequestedToolNames(requestMetadata),
|
|
9648
|
+
runtimeThinking,
|
|
9649
|
+
session,
|
|
9650
|
+
sessionKey: input.sessionId
|
|
9651
|
+
};
|
|
9652
|
+
};
|
|
9653
|
+
buildModelMessages = (input, _options, runContext) => {
|
|
9654
|
+
const { accountId, channel, chatId, config, currentTurn, effectiveModel, effectiveWorkspace, profile, requestedSkills, runtimeThinking, session } = runContext;
|
|
9264
9655
|
const messageToolHints = this.options.resolveMessageToolHints?.({
|
|
9265
9656
|
sessionKey: input.sessionId,
|
|
9266
9657
|
channel,
|
|
@@ -9273,9 +9664,15 @@ var NextclawNcpContextBuilder = class {
|
|
|
9273
9664
|
hostWorkspace: profile.workspace,
|
|
9274
9665
|
sessionProjectRoot: readSessionProjectRoot(session.metadata)
|
|
9275
9666
|
});
|
|
9276
|
-
const sessionMessages =
|
|
9667
|
+
const sessionMessages = projectNcpMessagesWithContextCompaction({
|
|
9668
|
+
sessionId: input.sessionId,
|
|
9669
|
+
sessionMessages: _options?.sessionMessages ?? []
|
|
9670
|
+
});
|
|
9277
9671
|
const messages = contextBuilder.buildMessages({
|
|
9278
|
-
history: toLegacyMessages([...sessionMessages], {
|
|
9672
|
+
history: toLegacyMessages([...sessionMessages], {
|
|
9673
|
+
assetStore: this.options.assetStore,
|
|
9674
|
+
serviceRole: "system"
|
|
9675
|
+
}),
|
|
9279
9676
|
currentMessage: "",
|
|
9280
9677
|
attachments: [],
|
|
9281
9678
|
channel,
|
|
@@ -9292,18 +9689,19 @@ var NextclawNcpContextBuilder = class {
|
|
|
9292
9689
|
content: currentTurn.currentUserContent
|
|
9293
9690
|
};
|
|
9294
9691
|
return {
|
|
9295
|
-
messages
|
|
9296
|
-
|
|
9297
|
-
contextTokens: profile.contextTokens
|
|
9298
|
-
}).messages,
|
|
9299
|
-
tools: buildRequestedOpenAiTools(toolDefinitions, requestedToolNames),
|
|
9300
|
-
model: effectiveModel,
|
|
9301
|
-
thinkingLevel: runtimeThinking
|
|
9692
|
+
messages,
|
|
9693
|
+
toolDefinitions
|
|
9302
9694
|
};
|
|
9303
9695
|
};
|
|
9696
|
+
pruneModelMessages = (runContext, modelMessages) => {
|
|
9697
|
+
return this.inputBudgetPruner.prune({
|
|
9698
|
+
messages: modelMessages.messages,
|
|
9699
|
+
contextTokens: runContext.profile.contextTokens
|
|
9700
|
+
});
|
|
9701
|
+
};
|
|
9304
9702
|
};
|
|
9305
9703
|
//#endregion
|
|
9306
|
-
//#region src/cli/commands/ncp/session/nextclaw-agent-session-message-adapter.ts
|
|
9704
|
+
//#region src/cli/commands/ncp/session/nextclaw-agent-session-message-adapter.utils.ts
|
|
9307
9705
|
function tryParseJson(value) {
|
|
9308
9706
|
try {
|
|
9309
9707
|
return JSON.parse(value);
|
|
@@ -9366,6 +9764,10 @@ function parseLegacyToolCalls(value) {
|
|
|
9366
9764
|
function createMessageId(sessionId, index, role, timestamp) {
|
|
9367
9765
|
return `${sessionId}:${role.trim().toLowerCase() || "message"}:${index}:${timestamp}`;
|
|
9368
9766
|
}
|
|
9767
|
+
function resolveMessageId(params) {
|
|
9768
|
+
const { index, message, role, sessionId, timestamp } = params;
|
|
9769
|
+
return normalizeString(message.ncp_message_id) ?? createMessageId(sessionId, index, role, timestamp);
|
|
9770
|
+
}
|
|
9369
9771
|
function isNcpMessagePart(value) {
|
|
9370
9772
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.type === "string";
|
|
9371
9773
|
}
|
|
@@ -9376,25 +9778,36 @@ function readStoredNcpParts(message) {
|
|
|
9376
9778
|
if (parts.length !== rawParts.length) return null;
|
|
9377
9779
|
return structuredClone(parts);
|
|
9378
9780
|
}
|
|
9781
|
+
function readStoredNcpMetadata(message) {
|
|
9782
|
+
const rawMetadata = message.ncp_metadata;
|
|
9783
|
+
if (!rawMetadata || typeof rawMetadata !== "object" || Array.isArray(rawMetadata)) return;
|
|
9784
|
+
return structuredClone(rawMetadata);
|
|
9785
|
+
}
|
|
9379
9786
|
function buildAssistantMessage(params) {
|
|
9380
|
-
const
|
|
9381
|
-
const
|
|
9382
|
-
const
|
|
9787
|
+
const { index, message, sessionId } = params;
|
|
9788
|
+
const timestamp = ensureIsoTimestamp(message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
|
|
9789
|
+
const replyTo = typeof message.reply_to === "string" && message.reply_to.trim().length > 0 ? message.reply_to.trim() : void 0;
|
|
9790
|
+
const storedParts = readStoredNcpParts(message);
|
|
9383
9791
|
if (storedParts) return sanitizeAssistantReplyTags({
|
|
9384
|
-
id:
|
|
9385
|
-
|
|
9792
|
+
id: resolveMessageId({
|
|
9793
|
+
index,
|
|
9794
|
+
message,
|
|
9795
|
+
role: "assistant",
|
|
9796
|
+
sessionId,
|
|
9797
|
+
timestamp
|
|
9798
|
+
}),
|
|
9799
|
+
sessionId,
|
|
9386
9800
|
role: "assistant",
|
|
9387
9801
|
status: "final",
|
|
9388
9802
|
timestamp,
|
|
9389
9803
|
parts: storedParts,
|
|
9390
9804
|
metadata: replyTo ? { reply_to: replyTo } : void 0
|
|
9391
9805
|
});
|
|
9392
|
-
const toolCalls = parseLegacyToolCalls(
|
|
9393
|
-
const parts = [...contentToParts(
|
|
9394
|
-
|
|
9395
|
-
if (reasoning) parts.push({
|
|
9806
|
+
const toolCalls = parseLegacyToolCalls(message.tool_calls);
|
|
9807
|
+
const parts = [...contentToParts(message.content)];
|
|
9808
|
+
if (typeof message.reasoning_content === "string") parts.push({
|
|
9396
9809
|
type: "reasoning",
|
|
9397
|
-
text:
|
|
9810
|
+
text: message.reasoning_content
|
|
9398
9811
|
});
|
|
9399
9812
|
for (const toolCall of toolCalls) parts.push({
|
|
9400
9813
|
type: "tool-invocation",
|
|
@@ -9404,8 +9817,14 @@ function buildAssistantMessage(params) {
|
|
|
9404
9817
|
args: tryParseJson(toolCall.function.arguments)
|
|
9405
9818
|
});
|
|
9406
9819
|
return sanitizeAssistantReplyTags({
|
|
9407
|
-
id:
|
|
9408
|
-
|
|
9820
|
+
id: resolveMessageId({
|
|
9821
|
+
index,
|
|
9822
|
+
message,
|
|
9823
|
+
role: "assistant",
|
|
9824
|
+
sessionId,
|
|
9825
|
+
timestamp
|
|
9826
|
+
}),
|
|
9827
|
+
sessionId,
|
|
9409
9828
|
role: "assistant",
|
|
9410
9829
|
status: "final",
|
|
9411
9830
|
timestamp,
|
|
@@ -9414,23 +9833,39 @@ function buildAssistantMessage(params) {
|
|
|
9414
9833
|
});
|
|
9415
9834
|
}
|
|
9416
9835
|
function buildGenericMessage(params) {
|
|
9417
|
-
const
|
|
9418
|
-
const
|
|
9836
|
+
const { index, message, role, sessionId } = params;
|
|
9837
|
+
const timestamp = ensureIsoTimestamp(message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
|
|
9838
|
+
const storedParts = readStoredNcpParts(message);
|
|
9839
|
+
const storedMetadata = readStoredNcpMetadata(message);
|
|
9419
9840
|
if (storedParts) return {
|
|
9420
|
-
id:
|
|
9421
|
-
|
|
9422
|
-
|
|
9841
|
+
id: resolveMessageId({
|
|
9842
|
+
index,
|
|
9843
|
+
message,
|
|
9844
|
+
role,
|
|
9845
|
+
sessionId,
|
|
9846
|
+
timestamp
|
|
9847
|
+
}),
|
|
9848
|
+
sessionId,
|
|
9849
|
+
role,
|
|
9423
9850
|
status: "final",
|
|
9424
9851
|
timestamp,
|
|
9425
|
-
parts: storedParts
|
|
9852
|
+
parts: storedParts,
|
|
9853
|
+
...storedMetadata ? { metadata: storedMetadata } : {}
|
|
9426
9854
|
};
|
|
9427
9855
|
return {
|
|
9428
|
-
id:
|
|
9429
|
-
|
|
9430
|
-
|
|
9856
|
+
id: resolveMessageId({
|
|
9857
|
+
index,
|
|
9858
|
+
message,
|
|
9859
|
+
role,
|
|
9860
|
+
sessionId,
|
|
9861
|
+
timestamp
|
|
9862
|
+
}),
|
|
9863
|
+
sessionId,
|
|
9864
|
+
role,
|
|
9431
9865
|
status: "final",
|
|
9432
9866
|
timestamp,
|
|
9433
|
-
parts: contentToParts(
|
|
9867
|
+
parts: contentToParts(message.content),
|
|
9868
|
+
...storedMetadata ? { metadata: storedMetadata } : {}
|
|
9434
9869
|
};
|
|
9435
9870
|
}
|
|
9436
9871
|
function attachToolResult(target, toolCallId, result, toolName, resultContentItems) {
|
|
@@ -11215,7 +11650,293 @@ var LlmUsageRecorder = class {
|
|
|
11215
11650
|
};
|
|
11216
11651
|
const llmUsageRecorder = new LlmUsageRecorder();
|
|
11217
11652
|
//#endregion
|
|
11653
|
+
//#region src/cli/commands/ncp/context/context-window-snapshot.utils.ts
|
|
11654
|
+
function buildContextWindowSnapshot(params) {
|
|
11655
|
+
const { checkpoint, compactedUsedContextTokens, droppedHistoryCount, prunedUsedContextTokens, totalContextTokens, truncatedSystemPrompt, truncatedToolResultCount, truncatedUserMessage, usedContextTokens } = params;
|
|
11656
|
+
return {
|
|
11657
|
+
version: 1,
|
|
11658
|
+
usedContextTokens,
|
|
11659
|
+
totalContextTokens,
|
|
11660
|
+
prunedUsedContextTokens,
|
|
11661
|
+
availableContextTokens: Math.max(0, totalContextTokens - usedContextTokens),
|
|
11662
|
+
droppedHistoryCount,
|
|
11663
|
+
truncatedToolResultCount,
|
|
11664
|
+
truncatedSystemPrompt,
|
|
11665
|
+
truncatedUserMessage,
|
|
11666
|
+
compacted: Boolean(checkpoint),
|
|
11667
|
+
...checkpoint ? {
|
|
11668
|
+
checkpointId: checkpoint.id,
|
|
11669
|
+
compactedMessageCount: checkpoint.coveredMessageCount,
|
|
11670
|
+
compactedUsedContextTokens
|
|
11671
|
+
} : { compactedMessageCount: 0 },
|
|
11672
|
+
updatedAt: (params.now ?? /* @__PURE__ */ new Date()).toISOString()
|
|
11673
|
+
};
|
|
11674
|
+
}
|
|
11675
|
+
function buildCompressingCompactionCheckpoint(previousValue) {
|
|
11676
|
+
const previous = previousValue && typeof previousValue === "object" && !Array.isArray(previousValue) ? previousValue : null;
|
|
11677
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11678
|
+
return {
|
|
11679
|
+
version: 1,
|
|
11680
|
+
id: typeof previous?.id === "string" ? previous.id : `ctx-${Date.now()}`,
|
|
11681
|
+
status: "compressing",
|
|
11682
|
+
summary: typeof previous?.summary === "string" && previous.summary.trim().length > 0 ? previous.summary : "Compressing earlier context for the next model request.",
|
|
11683
|
+
coveredMessageCount: typeof previous?.coveredMessageCount === "number" ? previous.coveredMessageCount : 0,
|
|
11684
|
+
coveredSessionMessageCount: typeof previous?.coveredSessionMessageCount === "number" ? previous.coveredSessionMessageCount : 0,
|
|
11685
|
+
originalEstimatedTokens: typeof previous?.originalEstimatedTokens === "number" ? previous.originalEstimatedTokens : 0,
|
|
11686
|
+
projectedEstimatedTokens: typeof previous?.projectedEstimatedTokens === "number" ? previous.projectedEstimatedTokens : 0,
|
|
11687
|
+
createdAt: typeof previous?.createdAt === "string" ? previous.createdAt : now,
|
|
11688
|
+
updatedAt: now
|
|
11689
|
+
};
|
|
11690
|
+
}
|
|
11691
|
+
//#endregion
|
|
11692
|
+
//#region src/cli/commands/ncp/context/context-compaction-preflight.service.ts
|
|
11693
|
+
const DEFAULT_COMPACTION_TRIGGER_RATIO = .8;
|
|
11694
|
+
const SUMMARY_MAX_TOKENS = 4e3;
|
|
11695
|
+
const SUMMARY_SOURCE_MAX_CHARS = 12e4;
|
|
11696
|
+
function readRequestedAgentId(metadata) {
|
|
11697
|
+
return normalizeString(metadata.agent_id)?.toLowerCase() ?? normalizeString(metadata.agentId)?.toLowerCase() ?? null;
|
|
11698
|
+
}
|
|
11699
|
+
function resolveCompactionProfile(params) {
|
|
11700
|
+
const { config, requestMetadata, storedAgentId } = params;
|
|
11701
|
+
const { defaults } = config.agents;
|
|
11702
|
+
const defaultAgentId = resolveDefaultAgentProfileId(config);
|
|
11703
|
+
const profile = findEffectiveAgentProfile(config, normalizeString(storedAgentId)?.toLowerCase() ?? readRequestedAgentId(requestMetadata) ?? defaultAgentId) ?? findEffectiveAgentProfile(config, defaultAgentId);
|
|
11704
|
+
return {
|
|
11705
|
+
contextTokens: profile?.contextTokens ?? defaults.contextTokens,
|
|
11706
|
+
model: profile?.model ?? defaults.model
|
|
11707
|
+
};
|
|
11708
|
+
}
|
|
11709
|
+
function mergeInputMessages(params) {
|
|
11710
|
+
const messages = params.sessionMessages.map((message) => structuredClone(message));
|
|
11711
|
+
const seen = new Set(messages.map((message) => message.id));
|
|
11712
|
+
for (const message of params.inputMessages) {
|
|
11713
|
+
if (seen.has(message.id)) continue;
|
|
11714
|
+
messages.push(structuredClone(message));
|
|
11715
|
+
}
|
|
11716
|
+
return messages;
|
|
11717
|
+
}
|
|
11718
|
+
function stringifyCompactionSource(messages) {
|
|
11719
|
+
const json = JSON.stringify(messages, null, 2);
|
|
11720
|
+
if (json.length <= SUMMARY_SOURCE_MAX_CHARS) return json;
|
|
11721
|
+
return `${json.slice(0, SUMMARY_SOURCE_MAX_CHARS).trimEnd()}\n[truncated_source]`;
|
|
11722
|
+
}
|
|
11723
|
+
var ContextCompactionPreflightService = class {
|
|
11724
|
+
compactionService = new ContextCompactionService();
|
|
11725
|
+
inputBudgetPruner = new InputBudgetPruner();
|
|
11726
|
+
constructor(options) {
|
|
11727
|
+
this.options = options;
|
|
11728
|
+
}
|
|
11729
|
+
run = async (params) => {
|
|
11730
|
+
const beginResult = this.begin(params);
|
|
11731
|
+
if (!beginResult) return null;
|
|
11732
|
+
if (!beginResult.pendingCompaction) return beginResult;
|
|
11733
|
+
return await this.finish(beginResult.pendingCompaction);
|
|
11734
|
+
};
|
|
11735
|
+
preview = (params) => {
|
|
11736
|
+
const { contextWindowOwner, requestMetadata, sessionId, sessionMessages } = params;
|
|
11737
|
+
if (contextWindowOwner === "runtime") return null;
|
|
11738
|
+
const session = this.options.sessionManager.getIfExists(sessionId);
|
|
11739
|
+
const metadata = session?.metadata ?? requestMetadata;
|
|
11740
|
+
const profile = resolveCompactionProfile({
|
|
11741
|
+
config: this.options.getConfig(),
|
|
11742
|
+
requestMetadata,
|
|
11743
|
+
storedAgentId: session?.agentId
|
|
11744
|
+
});
|
|
11745
|
+
const existingCheckpoint = readCompressedContextCompactionCheckpoint(metadata[CONTEXT_COMPACTION_METADATA_KEY]);
|
|
11746
|
+
return this.buildContextWindowSnapshotForMessages({
|
|
11747
|
+
checkpoint: existingCheckpoint,
|
|
11748
|
+
contextTokens: profile.contextTokens,
|
|
11749
|
+
sessionId,
|
|
11750
|
+
sessionMessages
|
|
11751
|
+
});
|
|
11752
|
+
};
|
|
11753
|
+
begin = (params) => {
|
|
11754
|
+
const { contextWindowOwner, inputMessages, requestMetadata, sessionId, sessionMessages } = params;
|
|
11755
|
+
if (contextWindowOwner === "runtime") return null;
|
|
11756
|
+
const session = this.options.sessionManager.getOrCreate(sessionId);
|
|
11757
|
+
const profile = resolveCompactionProfile({
|
|
11758
|
+
config: this.options.getConfig(),
|
|
11759
|
+
requestMetadata,
|
|
11760
|
+
storedAgentId: session.agentId
|
|
11761
|
+
});
|
|
11762
|
+
const { contextTokens } = profile;
|
|
11763
|
+
const ncpMessages = mergeInputMessages({
|
|
11764
|
+
inputMessages,
|
|
11765
|
+
sessionMessages
|
|
11766
|
+
});
|
|
11767
|
+
const modelCandidateMessages = ncpMessages.filter((message) => !isContextCompactionTimelineMessage(message));
|
|
11768
|
+
const existingCheckpoint = readCompressedContextCompactionCheckpoint(session.metadata[CONTEXT_COMPACTION_METADATA_KEY]);
|
|
11769
|
+
const messages = toLegacyMessages(existingCheckpoint ? projectNcpMessagesWithContextCompaction({
|
|
11770
|
+
sessionId,
|
|
11771
|
+
sessionMessages: ncpMessages
|
|
11772
|
+
}) : modelCandidateMessages);
|
|
11773
|
+
const plan = existingCheckpoint ? null : this.compactionService.prepareForModelInput({
|
|
11774
|
+
messages,
|
|
11775
|
+
contextTokens,
|
|
11776
|
+
compactionThresholdTokens: Math.floor(contextTokens * DEFAULT_COMPACTION_TRIGGER_RATIO)
|
|
11777
|
+
});
|
|
11778
|
+
const pruned = this.inputBudgetPruner.prune({
|
|
11779
|
+
messages,
|
|
11780
|
+
contextTokens
|
|
11781
|
+
});
|
|
11782
|
+
const checkpoint = plan ? {
|
|
11783
|
+
...buildCompressingCompactionCheckpoint(session.metadata[CONTEXT_COMPACTION_METADATA_KEY]),
|
|
11784
|
+
coveredMessageCount: plan.coveredMessages.length,
|
|
11785
|
+
coveredSessionMessageCount: plan.coveredMessages.length,
|
|
11786
|
+
originalEstimatedTokens: plan.originalEstimatedTokens,
|
|
11787
|
+
projectedEstimatedTokens: pruned.estimatedTokens
|
|
11788
|
+
} : existingCheckpoint;
|
|
11789
|
+
const contextWindow = buildContextWindowSnapshot({
|
|
11790
|
+
usedContextTokens: pruned.estimatedTokens,
|
|
11791
|
+
totalContextTokens: contextTokens,
|
|
11792
|
+
prunedUsedContextTokens: pruned.estimatedTokens,
|
|
11793
|
+
droppedHistoryCount: pruned.droppedHistoryCount,
|
|
11794
|
+
truncatedToolResultCount: pruned.truncatedToolResultCount,
|
|
11795
|
+
truncatedSystemPrompt: pruned.truncatedSystemPrompt,
|
|
11796
|
+
truncatedUserMessage: pruned.truncatedUserMessage,
|
|
11797
|
+
checkpoint,
|
|
11798
|
+
compactedUsedContextTokens: checkpoint ? pruned.estimatedTokens : void 0
|
|
11799
|
+
});
|
|
11800
|
+
if (plan && checkpoint) {
|
|
11801
|
+
session.metadata[CONTEXT_COMPACTION_METADATA_KEY] = checkpoint;
|
|
11802
|
+
upsertContextCompactionTimelineMessage({
|
|
11803
|
+
session,
|
|
11804
|
+
checkpoint,
|
|
11805
|
+
insertBeforeMessageIds: inputMessages.map((message) => message.id)
|
|
11806
|
+
});
|
|
11807
|
+
}
|
|
11808
|
+
this.options.sessionManager.save(session);
|
|
11809
|
+
return {
|
|
11810
|
+
contextWindow,
|
|
11811
|
+
metadata: structuredClone(session.metadata),
|
|
11812
|
+
sessionMessages: toNcpMessages(sessionId, session.messages),
|
|
11813
|
+
timelineMessage: plan && checkpoint ? buildContextCompactionTimelineNcpMessage({
|
|
11814
|
+
sessionId,
|
|
11815
|
+
checkpoint
|
|
11816
|
+
}) : null,
|
|
11817
|
+
pendingCompaction: plan && checkpoint ? {
|
|
11818
|
+
checkpoint,
|
|
11819
|
+
contextTokens,
|
|
11820
|
+
inputMessageIds: inputMessages.map((message) => message.id),
|
|
11821
|
+
model: profile.model,
|
|
11822
|
+
plan,
|
|
11823
|
+
sessionId
|
|
11824
|
+
} : null
|
|
11825
|
+
};
|
|
11826
|
+
};
|
|
11827
|
+
buildContextWindowSnapshotForMessages = (params) => {
|
|
11828
|
+
const { checkpoint, contextTokens, sessionId, sessionMessages } = params;
|
|
11829
|
+
const modelCandidateMessages = sessionMessages.filter((message) => !isContextCompactionTimelineMessage(message));
|
|
11830
|
+
const messages = toLegacyMessages(checkpoint ? projectNcpMessagesWithContextCompaction({
|
|
11831
|
+
sessionId,
|
|
11832
|
+
sessionMessages
|
|
11833
|
+
}) : modelCandidateMessages);
|
|
11834
|
+
const pruned = this.inputBudgetPruner.prune({
|
|
11835
|
+
messages,
|
|
11836
|
+
contextTokens
|
|
11837
|
+
});
|
|
11838
|
+
return buildContextWindowSnapshot({
|
|
11839
|
+
usedContextTokens: pruned.estimatedTokens,
|
|
11840
|
+
totalContextTokens: contextTokens,
|
|
11841
|
+
prunedUsedContextTokens: pruned.estimatedTokens,
|
|
11842
|
+
droppedHistoryCount: pruned.droppedHistoryCount,
|
|
11843
|
+
truncatedToolResultCount: pruned.truncatedToolResultCount,
|
|
11844
|
+
truncatedSystemPrompt: pruned.truncatedSystemPrompt,
|
|
11845
|
+
truncatedUserMessage: pruned.truncatedUserMessage,
|
|
11846
|
+
checkpoint,
|
|
11847
|
+
compactedUsedContextTokens: checkpoint ? pruned.estimatedTokens : void 0
|
|
11848
|
+
});
|
|
11849
|
+
};
|
|
11850
|
+
finish = async (pending) => {
|
|
11851
|
+
const compacted = await this.compactionService.compactPreparedForModelInput({
|
|
11852
|
+
contextTokens: pending.contextTokens,
|
|
11853
|
+
plan: pending.plan,
|
|
11854
|
+
generateSummary: async ({ messages }) => await this.generateSummary({
|
|
11855
|
+
messages,
|
|
11856
|
+
model: pending.model
|
|
11857
|
+
})
|
|
11858
|
+
});
|
|
11859
|
+
const generatedCheckpoint = compacted.checkpoint;
|
|
11860
|
+
if (!generatedCheckpoint) throw new Error("context compaction pending work did not produce a checkpoint");
|
|
11861
|
+
const checkpoint = {
|
|
11862
|
+
...generatedCheckpoint,
|
|
11863
|
+
id: pending.checkpoint.id,
|
|
11864
|
+
createdAt: pending.checkpoint.createdAt,
|
|
11865
|
+
status: "compressed"
|
|
11866
|
+
};
|
|
11867
|
+
const pruned = this.inputBudgetPruner.prune({
|
|
11868
|
+
messages: compacted.messages,
|
|
11869
|
+
contextTokens: pending.contextTokens
|
|
11870
|
+
});
|
|
11871
|
+
const contextWindow = buildContextWindowSnapshot({
|
|
11872
|
+
usedContextTokens: pruned.estimatedTokens,
|
|
11873
|
+
totalContextTokens: pending.contextTokens,
|
|
11874
|
+
prunedUsedContextTokens: pruned.estimatedTokens,
|
|
11875
|
+
droppedHistoryCount: pruned.droppedHistoryCount,
|
|
11876
|
+
truncatedToolResultCount: pruned.truncatedToolResultCount,
|
|
11877
|
+
truncatedSystemPrompt: pruned.truncatedSystemPrompt,
|
|
11878
|
+
truncatedUserMessage: pruned.truncatedUserMessage,
|
|
11879
|
+
checkpoint,
|
|
11880
|
+
compactedUsedContextTokens: pruned.estimatedTokens
|
|
11881
|
+
});
|
|
11882
|
+
const session = this.options.sessionManager.getOrCreate(pending.sessionId);
|
|
11883
|
+
session.metadata[CONTEXT_COMPACTION_METADATA_KEY] = checkpoint;
|
|
11884
|
+
upsertContextCompactionTimelineMessage({
|
|
11885
|
+
session,
|
|
11886
|
+
checkpoint,
|
|
11887
|
+
insertBeforeMessageIds: pending.inputMessageIds
|
|
11888
|
+
});
|
|
11889
|
+
this.options.sessionManager.save(session);
|
|
11890
|
+
return {
|
|
11891
|
+
contextWindow,
|
|
11892
|
+
metadata: structuredClone(session.metadata),
|
|
11893
|
+
sessionMessages: toNcpMessages(pending.sessionId, session.messages),
|
|
11894
|
+
timelineMessage: buildContextCompactionTimelineNcpMessage({
|
|
11895
|
+
sessionId: pending.sessionId,
|
|
11896
|
+
checkpoint
|
|
11897
|
+
})
|
|
11898
|
+
};
|
|
11899
|
+
};
|
|
11900
|
+
generateSummary = async (params) => {
|
|
11901
|
+
if (!this.options.providerManager) throw new Error("context compaction summary generation requires a provider manager");
|
|
11902
|
+
const { messages, model } = params;
|
|
11903
|
+
const summary = (await this.options.providerManager.chat({
|
|
11904
|
+
model,
|
|
11905
|
+
maxTokens: SUMMARY_MAX_TOKENS,
|
|
11906
|
+
messages: [{
|
|
11907
|
+
role: "system",
|
|
11908
|
+
content: [
|
|
11909
|
+
"You are NextClaw's context compactor for a coding agent session.",
|
|
11910
|
+
"Create a dense, structured summary that will replace earlier conversation history in a future model request.",
|
|
11911
|
+
"Preserve user goals, explicit instructions, decisions, files touched or inspected, code changes, commands run, test results, failures, blockers, and exact next steps.",
|
|
11912
|
+
"Do not invent facts. If something is uncertain, mark it as uncertain.",
|
|
11913
|
+
"Return Markdown only. Start with '# Compressed Earlier Context'."
|
|
11914
|
+
].join("\n")
|
|
11915
|
+
}, {
|
|
11916
|
+
role: "user",
|
|
11917
|
+
content: [
|
|
11918
|
+
"Compress these earlier runtime messages into a reusable checkpoint summary.",
|
|
11919
|
+
"",
|
|
11920
|
+
"Messages JSON:",
|
|
11921
|
+
stringifyCompactionSource(messages)
|
|
11922
|
+
].join("\n")
|
|
11923
|
+
}]
|
|
11924
|
+
})).content?.trim();
|
|
11925
|
+
if (!summary) throw new Error("context compaction summary is empty");
|
|
11926
|
+
return summary;
|
|
11927
|
+
};
|
|
11928
|
+
};
|
|
11929
|
+
//#endregion
|
|
11218
11930
|
//#region src/cli/commands/ncp/features/runtime/create-ui-ncp-agent.service.ts
|
|
11931
|
+
function createContextWindowUpdatedEvent(params) {
|
|
11932
|
+
return {
|
|
11933
|
+
type: NcpEventType.ContextWindowUpdated,
|
|
11934
|
+
payload: {
|
|
11935
|
+
sessionId: params.sessionId,
|
|
11936
|
+
contextWindow: params.contextWindow
|
|
11937
|
+
}
|
|
11938
|
+
};
|
|
11939
|
+
}
|
|
11219
11940
|
function isRecord$2(value) {
|
|
11220
11941
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11221
11942
|
}
|
|
@@ -11271,7 +11992,7 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
11271
11992
|
...sessionSearchRuntimeSupport.createAdditionalTools({ currentSessionId: context.sessionId })
|
|
11272
11993
|
]
|
|
11273
11994
|
});
|
|
11274
|
-
|
|
11995
|
+
const runtime = new DefaultNcpAgentRuntime({
|
|
11275
11996
|
contextBuilder: new NextclawNcpContextBuilder({
|
|
11276
11997
|
sessionManager: params.sessionManager,
|
|
11277
11998
|
toolRegistry,
|
|
@@ -11284,7 +12005,67 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
|
|
|
11284
12005
|
stateManager,
|
|
11285
12006
|
reasoningNormalizationMode
|
|
11286
12007
|
});
|
|
12008
|
+
const contextCompactionPreflight = new ContextCompactionPreflightService({
|
|
12009
|
+
getConfig: params.getConfig,
|
|
12010
|
+
providerManager: observedProviderManager,
|
|
12011
|
+
sessionManager: params.sessionManager
|
|
12012
|
+
});
|
|
12013
|
+
return { run: async function* (input, options) {
|
|
12014
|
+
const beginResult = contextCompactionPreflight.begin({
|
|
12015
|
+
contextWindowOwner: "nextclaw",
|
|
12016
|
+
inputMessages: input.messages,
|
|
12017
|
+
requestMetadata: input.metadata ?? {},
|
|
12018
|
+
sessionId: input.sessionId,
|
|
12019
|
+
sessionMessages: stateManager.getSnapshot().messages
|
|
12020
|
+
});
|
|
12021
|
+
if (beginResult) {
|
|
12022
|
+
setSessionMetadata(beginResult.metadata);
|
|
12023
|
+
params.onSessionUpdated?.(input.sessionId);
|
|
12024
|
+
yield* publishPreflightResult({
|
|
12025
|
+
input,
|
|
12026
|
+
result: beginResult,
|
|
12027
|
+
stateManager
|
|
12028
|
+
});
|
|
12029
|
+
if (beginResult.pendingCompaction) {
|
|
12030
|
+
const finishResult = await contextCompactionPreflight.finish(beginResult.pendingCompaction);
|
|
12031
|
+
setSessionMetadata(finishResult.metadata);
|
|
12032
|
+
params.onSessionUpdated?.(input.sessionId);
|
|
12033
|
+
yield* publishPreflightResult({
|
|
12034
|
+
input,
|
|
12035
|
+
result: finishResult,
|
|
12036
|
+
stateManager
|
|
12037
|
+
});
|
|
12038
|
+
}
|
|
12039
|
+
}
|
|
12040
|
+
yield* runtime.run(input, options);
|
|
12041
|
+
} };
|
|
12042
|
+
};
|
|
12043
|
+
}
|
|
12044
|
+
async function* publishPreflightResult(params) {
|
|
12045
|
+
const { input, result, stateManager } = params;
|
|
12046
|
+
const contextWindowEvent = createContextWindowUpdatedEvent({
|
|
12047
|
+
contextWindow: result.contextWindow,
|
|
12048
|
+
sessionId: input.sessionId
|
|
12049
|
+
});
|
|
12050
|
+
await stateManager.dispatch(contextWindowEvent);
|
|
12051
|
+
yield contextWindowEvent;
|
|
12052
|
+
if (!result.timelineMessage) return;
|
|
12053
|
+
const activeRun = stateManager.getSnapshot().activeRun;
|
|
12054
|
+
stateManager.hydrate({
|
|
12055
|
+
sessionId: input.sessionId,
|
|
12056
|
+
messages: result.sessionMessages,
|
|
12057
|
+
activeRun,
|
|
12058
|
+
contextWindow: result.contextWindow
|
|
12059
|
+
});
|
|
12060
|
+
const timelineEvent = {
|
|
12061
|
+
type: NcpEventType.MessageSent,
|
|
12062
|
+
payload: {
|
|
12063
|
+
sessionId: input.sessionId,
|
|
12064
|
+
message: result.timelineMessage
|
|
12065
|
+
}
|
|
11287
12066
|
};
|
|
12067
|
+
await stateManager.dispatch(timelineEvent);
|
|
12068
|
+
yield timelineEvent;
|
|
11288
12069
|
}
|
|
11289
12070
|
function createResolveOpenAiToolsForRuntime(params) {
|
|
11290
12071
|
const { assetStore, bus, cronService, gatewayController, getConfig, getExtensionRegistry, providerManager, resolveMessageToolHints, sessionCreationService, sessionManager, sessionRequestBroker, sessionSearchRuntimeSupport, toolRegistryAdapter } = params;
|
|
@@ -11358,10 +12139,20 @@ var UiNcpAgentRuntimeService = class {
|
|
|
11358
12139
|
this.assertNotDisposed();
|
|
11359
12140
|
if (this.handle) return this.handle;
|
|
11360
12141
|
this.registerCoreRuntimes();
|
|
12142
|
+
const contextWindowPreview = new ContextCompactionPreflightService({
|
|
12143
|
+
getConfig: this.params.getConfig,
|
|
12144
|
+
sessionManager: this.params.sessionManager
|
|
12145
|
+
});
|
|
11361
12146
|
this.backend = new DefaultNcpAgentBackend({
|
|
11362
12147
|
endpointId: "nextclaw-ui-agent",
|
|
11363
12148
|
sessionStore: this.sessionStore,
|
|
11364
12149
|
onSessionRunStatusChanged: this.params.onSessionRunStatusChanged,
|
|
12150
|
+
resolveSessionContextWindow: ({ messages, metadata, sessionId }) => contextWindowPreview.preview({
|
|
12151
|
+
contextWindowOwner: "nextclaw",
|
|
12152
|
+
requestMetadata: metadata,
|
|
12153
|
+
sessionId,
|
|
12154
|
+
sessionMessages: messages
|
|
12155
|
+
}),
|
|
11365
12156
|
createRuntime: (runtimeParams) => {
|
|
11366
12157
|
this.pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
|
|
11367
12158
|
this.refreshConfiguredRuntimeEntries();
|
|
@@ -13991,11 +14782,9 @@ function logPluginGatewayDiagnostics(diagnostics) {
|
|
|
13991
14782
|
}
|
|
13992
14783
|
async function startGatewaySupportServices(params) {
|
|
13993
14784
|
if (params.cronJobs > 0) console.log(`✓ Cron: ${params.cronJobs} scheduled jobs`);
|
|
13994
|
-
console.log("✓ Heartbeat: every 30m");
|
|
13995
14785
|
params.remoteModule?.start();
|
|
13996
14786
|
params.watchConfigFile();
|
|
13997
14787
|
await params.startCron();
|
|
13998
|
-
await params.startHeartbeat();
|
|
13999
14788
|
}
|
|
14000
14789
|
function watchCronStoreFile(params) {
|
|
14001
14790
|
const cronStorePath = resolve(params.cronStorePath);
|
|
@@ -14059,13 +14848,12 @@ function finalizeLocalUiStartup(params) {
|
|
|
14059
14848
|
if (uiStartup) localUiRuntimeStore.writeCurrentProcess(uiConfig);
|
|
14060
14849
|
}
|
|
14061
14850
|
async function startGatewayRuntimeSupport(params) {
|
|
14062
|
-
const { cronJobs, cronStorePath, reloadCronStore, remoteModule, startCron,
|
|
14851
|
+
const { cronJobs, cronStorePath, reloadCronStore, remoteModule, startCron, watchConfigFile, watcherRegistry } = params;
|
|
14063
14852
|
await startGatewaySupportServices({
|
|
14064
14853
|
cronJobs,
|
|
14065
14854
|
remoteModule,
|
|
14066
14855
|
watchConfigFile,
|
|
14067
|
-
startCron
|
|
14068
|
-
startHeartbeat
|
|
14856
|
+
startCron
|
|
14069
14857
|
});
|
|
14070
14858
|
watcherRegistry.remember(watchCronStoreFile({
|
|
14071
14859
|
cronStorePath,
|
|
@@ -14804,11 +15592,14 @@ var GatewayControllerImpl = class {
|
|
|
14804
15592
|
};
|
|
14805
15593
|
updateRun = async (params) => {
|
|
14806
15594
|
const versionBefore = getPackageVersion$1();
|
|
14807
|
-
|
|
14808
|
-
|
|
15595
|
+
params.timeoutMs;
|
|
15596
|
+
const updateCommand = new NpmRuntimeUpdateCommandService();
|
|
15597
|
+
const downloadedSnapshot = await updateCommand.runManaged({ download: true });
|
|
15598
|
+
const snapshot = downloadedSnapshot.status === "downloaded" ? await updateCommand.runManaged({ apply: true }) : downloadedSnapshot;
|
|
15599
|
+
if (snapshot.status === "blocked" || snapshot.status === "failed") return {
|
|
14809
15600
|
ok: false,
|
|
14810
|
-
error:
|
|
14811
|
-
|
|
15601
|
+
error: snapshot.errorMessage ?? snapshot.blockReason ?? "update failed",
|
|
15602
|
+
snapshot,
|
|
14812
15603
|
version: {
|
|
14813
15604
|
before: versionBefore,
|
|
14814
15605
|
after: getPackageVersion$1(),
|
|
@@ -14823,7 +15614,7 @@ var GatewayControllerImpl = class {
|
|
|
14823
15614
|
sessionKey: params.sessionKey,
|
|
14824
15615
|
note: params.note,
|
|
14825
15616
|
reason: "update.run",
|
|
14826
|
-
strategy:
|
|
15617
|
+
strategy: "runtime-bundle"
|
|
14827
15618
|
});
|
|
14828
15619
|
await this.requestRestart({
|
|
14829
15620
|
delayMs,
|
|
@@ -14836,8 +15627,8 @@ var GatewayControllerImpl = class {
|
|
|
14836
15627
|
scheduled: true,
|
|
14837
15628
|
delayMs
|
|
14838
15629
|
},
|
|
14839
|
-
strategy:
|
|
14840
|
-
|
|
15630
|
+
strategy: "runtime-bundle",
|
|
15631
|
+
snapshot,
|
|
14841
15632
|
version: {
|
|
14842
15633
|
before: versionBefore,
|
|
14843
15634
|
after: versionAfter,
|
|
@@ -15089,68 +15880,14 @@ function createCronJobHandler(params) {
|
|
|
15089
15880
|
return response;
|
|
15090
15881
|
};
|
|
15091
15882
|
}
|
|
15092
|
-
function buildHeartbeatMetadata(params) {
|
|
15093
|
-
return {
|
|
15094
|
-
agentId: params.agentId,
|
|
15095
|
-
agent_id: params.agentId,
|
|
15096
|
-
channel: "cli",
|
|
15097
|
-
chatId: "direct",
|
|
15098
|
-
chat_id: "direct",
|
|
15099
|
-
label: "heartbeat",
|
|
15100
|
-
session_origin: "heartbeat"
|
|
15101
|
-
};
|
|
15102
|
-
}
|
|
15103
|
-
function buildHeartbeatUserMessage(params) {
|
|
15104
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
15105
|
-
return {
|
|
15106
|
-
id: `heartbeat:user:${timestamp}`,
|
|
15107
|
-
sessionId: "heartbeat",
|
|
15108
|
-
role: "user",
|
|
15109
|
-
status: "final",
|
|
15110
|
-
timestamp,
|
|
15111
|
-
parts: [{
|
|
15112
|
-
type: "text",
|
|
15113
|
-
text: params.content
|
|
15114
|
-
}],
|
|
15115
|
-
metadata: structuredClone(params.metadata)
|
|
15116
|
-
};
|
|
15117
|
-
}
|
|
15118
|
-
function createHeartbeatJobHandler(params) {
|
|
15119
|
-
return async (prompt) => {
|
|
15120
|
-
const ncpAgent = params.resolveNcpAgent();
|
|
15121
|
-
if (!ncpAgent) throw new Error("NCP agent is not ready for heartbeat execution.");
|
|
15122
|
-
const metadata = buildHeartbeatMetadata({ agentId: params.resolveAgentId() });
|
|
15123
|
-
return await runJobOverNcp({
|
|
15124
|
-
agent: ncpAgent,
|
|
15125
|
-
sessionId: "heartbeat",
|
|
15126
|
-
message: buildHeartbeatUserMessage({
|
|
15127
|
-
content: prompt,
|
|
15128
|
-
metadata
|
|
15129
|
-
}),
|
|
15130
|
-
metadata,
|
|
15131
|
-
missingCompletedMessageError: "heartbeat execution completed without a final assistant message",
|
|
15132
|
-
runErrorMessage: "heartbeat execution failed"
|
|
15133
|
-
});
|
|
15134
|
-
};
|
|
15135
|
-
}
|
|
15136
15883
|
//#endregion
|
|
15137
15884
|
//#region src/cli/shared/services/gateway/service-gateway-context.service.ts
|
|
15138
|
-
const { ChannelManager: ChannelManager$1, CronService: CronService$1, getConfigPath: getConfigPath$2, getDataDir: getDataDir$1, getWorkspacePath: getWorkspacePath$2,
|
|
15885
|
+
const { ChannelManager: ChannelManager$1, CronService: CronService$1, getConfigPath: getConfigPath$2, getDataDir: getDataDir$1, getWorkspacePath: getWorkspacePath$2, loadConfig: loadConfig$3, MessageBus: MessageBus$2, ProviderManager: ProviderManager$1, resolveConfigSecrets: resolveConfigSecrets$3, saveConfig: saveConfig$1, SessionManager: SessionManager$2 } = NextclawCore;
|
|
15139
15886
|
function applyGatewayCapabilityState(gateway, next) {
|
|
15140
15887
|
gateway.pluginRegistry = next.pluginRegistry;
|
|
15141
15888
|
gateway.pluginChannelBindings = next.pluginChannelBindings;
|
|
15142
15889
|
gateway.extensionRegistry = next.extensionRegistry;
|
|
15143
15890
|
}
|
|
15144
|
-
function normalizeAgentId(value) {
|
|
15145
|
-
return (value ?? "").trim().toLowerCase() || "main";
|
|
15146
|
-
}
|
|
15147
|
-
function createGatewayHeartbeat(state, params) {
|
|
15148
|
-
const handleHeartbeat = createHeartbeatJobHandler({
|
|
15149
|
-
resolveNcpAgent: () => params.getLiveUiNcpAgent?.() ?? null,
|
|
15150
|
-
resolveAgentId: () => normalizeAgentId(resolveDefaultAgentProfileId$1(resolveConfigSecrets$3(loadConfig$3(), { configPath: state.runtimeConfigPath })))
|
|
15151
|
-
});
|
|
15152
|
-
return new HeartbeatService(state.workspace, async (promptText) => await handleHeartbeat(promptText), 1800, true);
|
|
15153
|
-
}
|
|
15154
15891
|
function createGatewayCronJobHandler(params) {
|
|
15155
15892
|
return createCronJobHandler({
|
|
15156
15893
|
resolveNcpAgent: () => params.getLiveUiNcpAgent?.() ?? null,
|
|
@@ -15253,7 +15990,6 @@ function createGatewayStartupContext(params) {
|
|
|
15253
15990
|
bus: state.bus,
|
|
15254
15991
|
getLiveUiNcpAgent
|
|
15255
15992
|
});
|
|
15256
|
-
state.heartbeat = createGatewayHeartbeat(state, { getLiveUiNcpAgent });
|
|
15257
15993
|
return state;
|
|
15258
15994
|
}
|
|
15259
15995
|
//#endregion
|
|
@@ -15543,7 +16279,7 @@ function createNcpSessionRealtimeChangePublisher(params) {
|
|
|
15543
16279
|
//#endregion
|
|
15544
16280
|
//#region src/cli/commands/ncp/session/ncp-session-summary.ts
|
|
15545
16281
|
function createNcpSessionSummary(params) {
|
|
15546
|
-
const { sessionId, agentId, messages, updatedAt, status, metadata } = params;
|
|
16282
|
+
const { sessionId, agentId, messages, updatedAt, status, metadata, contextWindow } = params;
|
|
15547
16283
|
return {
|
|
15548
16284
|
sessionId,
|
|
15549
16285
|
...agentId ? { agentId } : {},
|
|
@@ -15551,7 +16287,8 @@ function createNcpSessionSummary(params) {
|
|
|
15551
16287
|
updatedAt,
|
|
15552
16288
|
...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
|
|
15553
16289
|
status,
|
|
15554
|
-
...metadata ? { metadata: structuredClone(metadata) } : {}
|
|
16290
|
+
...metadata ? { metadata: structuredClone(metadata) } : {},
|
|
16291
|
+
...contextWindow ? { contextWindow: structuredClone(contextWindow) } : {}
|
|
15555
16292
|
};
|
|
15556
16293
|
}
|
|
15557
16294
|
//#endregion
|
|
@@ -15564,13 +16301,16 @@ function now$1() {
|
|
|
15564
16301
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
15565
16302
|
}
|
|
15566
16303
|
function buildUpdatedMetadata(params) {
|
|
15567
|
-
|
|
15568
|
-
if (
|
|
15569
|
-
return structuredClone(
|
|
16304
|
+
const { existingMetadata, patch } = params;
|
|
16305
|
+
if (patch.metadata === null) return {};
|
|
16306
|
+
if (patch.metadata) return structuredClone(patch.metadata);
|
|
16307
|
+
return structuredClone(existingMetadata ?? {});
|
|
15570
16308
|
}
|
|
15571
16309
|
var UiSessionService = class {
|
|
15572
16310
|
sessionStore;
|
|
16311
|
+
resolveContextWindow;
|
|
15573
16312
|
constructor(sessionManager, options = {}) {
|
|
16313
|
+
this.resolveContextWindow = options.resolveContextWindow;
|
|
15574
16314
|
this.sessionStore = new NextclawAgentSessionStore(sessionManager, { onSessionUpdated: options.onSessionUpdated });
|
|
15575
16315
|
}
|
|
15576
16316
|
listSessions = async (options) => {
|
|
@@ -15597,7 +16337,12 @@ var UiSessionService = class {
|
|
|
15597
16337
|
messages: session.messages,
|
|
15598
16338
|
updatedAt: session.updatedAt,
|
|
15599
16339
|
status: "idle",
|
|
15600
|
-
metadata: session.metadata
|
|
16340
|
+
metadata: session.metadata,
|
|
16341
|
+
contextWindow: this.resolveContextWindow?.({
|
|
16342
|
+
messages: session.messages,
|
|
16343
|
+
metadata: session.metadata ?? {},
|
|
16344
|
+
sessionId
|
|
16345
|
+
}) ?? null
|
|
15601
16346
|
});
|
|
15602
16347
|
};
|
|
15603
16348
|
updateSession = async (sessionId, patch) => {
|
|
@@ -15673,14 +16418,27 @@ function createLatestOnlySessionChangePublisher(publishSessionChange) {
|
|
|
15673
16418
|
};
|
|
15674
16419
|
}
|
|
15675
16420
|
function createServiceNcpSessionRealtimeBridge(params) {
|
|
15676
|
-
|
|
16421
|
+
const { getConfig, publishUiEvent: initialPublishUiEvent, sessionManager } = params;
|
|
16422
|
+
let publishUiEvent = initialPublishUiEvent;
|
|
15677
16423
|
let publishSessionChange = async (_sessionKey) => {};
|
|
15678
16424
|
let scheduleSessionChange = async (_sessionKey) => {};
|
|
15679
|
-
const
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
16425
|
+
const contextWindowPreview = new ContextCompactionPreflightService({
|
|
16426
|
+
getConfig,
|
|
16427
|
+
sessionManager
|
|
16428
|
+
});
|
|
16429
|
+
const deferredSessionService = createDeferredUiNcpSessionService(new UiSessionService(sessionManager, {
|
|
16430
|
+
onSessionUpdated: (sessionKey) => {
|
|
16431
|
+
scheduleSessionChange(sessionKey).catch((error) => {
|
|
16432
|
+
console.error(`[session-realtime] failed to publish session change for ${sessionKey}: ${formatBackgroundTaskError(error)}`);
|
|
16433
|
+
});
|
|
16434
|
+
},
|
|
16435
|
+
resolveContextWindow: ({ messages, metadata, sessionId }) => contextWindowPreview.preview({
|
|
16436
|
+
contextWindowOwner: "nextclaw",
|
|
16437
|
+
requestMetadata: metadata,
|
|
16438
|
+
sessionId,
|
|
16439
|
+
sessionMessages: messages
|
|
16440
|
+
})
|
|
16441
|
+
}));
|
|
15684
16442
|
const publishLatestSessionChange = async (sessionKey) => {
|
|
15685
16443
|
await createNcpSessionRealtimeChangePublisher({
|
|
15686
16444
|
sessionApi: deferredSessionService.service,
|
|
@@ -16240,7 +16998,11 @@ var RuntimeCommandService = class {
|
|
|
16240
16998
|
};
|
|
16241
16999
|
let runtimeState = null;
|
|
16242
17000
|
const bootstrapStatus = createBootstrapStatus(shellContext.config.remote.enabled);
|
|
16243
|
-
const
|
|
17001
|
+
const getRuntimeConfig = () => resolveConfigSecrets$1(loadConfig$1(), { configPath: shellContext.runtimeConfigPath });
|
|
17002
|
+
const ncpSessionRealtimeBridge = createServiceNcpSessionRealtimeBridge({
|
|
17003
|
+
getConfig: getRuntimeConfig,
|
|
17004
|
+
sessionManager: shellContext.sessionManager
|
|
17005
|
+
});
|
|
16244
17006
|
const marketplaceInstaller = new ServiceMarketplaceInstaller({
|
|
16245
17007
|
applyLiveConfigReload,
|
|
16246
17008
|
runCliSubcommand: (args) => this.runCliSubcommand(args),
|
|
@@ -16256,7 +17018,7 @@ var RuntimeCommandService = class {
|
|
|
16256
17018
|
uiConfig: shellContext.uiConfig,
|
|
16257
17019
|
uiStaticDir: shellContext.uiStaticDir,
|
|
16258
17020
|
cronService: shellContext.cron,
|
|
16259
|
-
getConfig:
|
|
17021
|
+
getConfig: getRuntimeConfig,
|
|
16260
17022
|
configPath: getConfigPath$1(),
|
|
16261
17023
|
productVersion: getPackageVersion$1(),
|
|
16262
17024
|
getPluginChannelBindings: () => runtimeState?.pluginChannelBindings ?? [],
|
|
@@ -16318,7 +17080,6 @@ var RuntimeCommandService = class {
|
|
|
16318
17080
|
scheduleReload: (reason) => gateway.reloader.scheduleReload(reason)
|
|
16319
17081
|
}),
|
|
16320
17082
|
startCron: () => gateway.cron.start(),
|
|
16321
|
-
startHeartbeat: () => gateway.heartbeat.start(),
|
|
16322
17083
|
cronStorePath: resolve(join(NextclawCore.getDataDir(), "cron", "jobs.json")),
|
|
16323
17084
|
reloadCronStore: () => gateway.cron.reloadFromStore(),
|
|
16324
17085
|
watcherRegistry: this.fileWatchers
|
|
@@ -18149,10 +18910,6 @@ var WorkspaceManager = class {
|
|
|
18149
18910
|
source: "BOOTSTRAP.md",
|
|
18150
18911
|
target: "BOOTSTRAP.md"
|
|
18151
18912
|
},
|
|
18152
|
-
{
|
|
18153
|
-
source: "HEARTBEAT.md",
|
|
18154
|
-
target: "HEARTBEAT.md"
|
|
18155
|
-
},
|
|
18156
18913
|
{
|
|
18157
18914
|
source: "MEMORY.md",
|
|
18158
18915
|
target: "MEMORY.md"
|
|
@@ -19706,30 +20463,12 @@ var CliRuntime = class {
|
|
|
19706
20463
|
}
|
|
19707
20464
|
};
|
|
19708
20465
|
update = async (opts) => {
|
|
19709
|
-
let timeoutMs;
|
|
19710
|
-
if (opts.timeout !== void 0) {
|
|
19711
|
-
const parsed = Number(opts.timeout);
|
|
19712
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
19713
|
-
console.error("Invalid --timeout value. Provide milliseconds (e.g. 1200000).");
|
|
19714
|
-
process.exit(1);
|
|
19715
|
-
}
|
|
19716
|
-
timeoutMs = parsed;
|
|
19717
|
-
}
|
|
19718
20466
|
const versionBefore = getPackageVersion$1();
|
|
19719
|
-
console.log(`Current version: ${versionBefore}`);
|
|
19720
|
-
const
|
|
19721
|
-
|
|
19722
|
-
currentVersion: versionBefore,
|
|
19723
|
-
result: runSelfUpdate({
|
|
19724
|
-
timeoutMs,
|
|
19725
|
-
cwd: process.cwd(),
|
|
19726
|
-
currentVersion: versionBefore
|
|
19727
|
-
}),
|
|
19728
|
-
readInstalledVersion: getPackageVersion$1
|
|
19729
|
-
});
|
|
19730
|
-
if (!report.ok) process.exit(1);
|
|
20467
|
+
if (!opts.json) console.log(`Current npm launcher version: ${versionBefore}`);
|
|
20468
|
+
const snapshot = await new NpmRuntimeUpdateCommandService().run(opts);
|
|
20469
|
+
if (snapshot.status === "blocked" || snapshot.status === "failed") process.exit(1);
|
|
19731
20470
|
const state = managedServiceStateStore.read();
|
|
19732
|
-
if (
|
|
20471
|
+
if (snapshot.requiresRestart && state && isProcessRunning(state.pid)) console.log(`Tip: restart ${APP_NAME} to apply the update.`);
|
|
19733
20472
|
};
|
|
19734
20473
|
agentsList = (opts = {}) => {
|
|
19735
20474
|
this.agentCommands.agentsList(opts);
|
|
@@ -19915,7 +20654,7 @@ registerServiceCommands({
|
|
|
19915
20654
|
getServiceCommands
|
|
19916
20655
|
});
|
|
19917
20656
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--model <model>", "Session model override for this run").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|
|
19918
|
-
program.command("update").description(`
|
|
20657
|
+
program.command("update").description(`Check, download, or apply ${APP_NAME} runtime updates`).option("--check", "Only check for a runtime update", false).option("--download", "Download an available runtime update without applying it").option("--apply", "Apply the downloaded runtime update", false).option("--channel <channel>", "Update channel (stable or beta)").option("--manifest-url <url>", "Explicit runtime update manifest URL").option("--json", "Output JSON", false).action(async (opts) => runtime.update(opts));
|
|
19919
20658
|
registerSkillsCommands(program, runtime.skillsCommands);
|
|
19920
20659
|
registerAgentsCommands(program, runtime);
|
|
19921
20660
|
const plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
|