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.
Files changed (90) hide show
  1. package/dist/cli/app/index.js +1212 -473
  2. package/dist/cli/launcher/index.d.ts +1 -0
  3. package/dist/cli/launcher/index.js +51 -0
  4. package/dist/npm-runtime-update-state.store-DaF0iDY9.js +463 -0
  5. package/package.json +16 -11
  6. package/resources/USAGE.md +11 -7
  7. package/resources/update-bundle-public.pem +3 -0
  8. package/templates/AGENTS.md +0 -4
  9. package/ui-dist/assets/{api-BurjmW4A.js → api-C51456xV.js} +1 -1
  10. package/ui-dist/assets/{app-manager-provider-DhxUmyTv.js → app-manager-provider-D_cKqqRG.js} +1 -1
  11. package/ui-dist/assets/{app-navigation.config-Bpd16Pem.js → app-navigation.config-Dve1W20Y.js} +1 -1
  12. package/ui-dist/assets/{book-open-CVEuA0y5.js → book-open-B4mOKdz8.js} +1 -1
  13. package/ui-dist/assets/{channels-list-page-BqhqaBf1.js → channels-list-page-WJ7d4zMI.js} +1 -1
  14. package/ui-dist/assets/chat-BxA-mw53.js +58 -0
  15. package/ui-dist/assets/chat-page-DLFTPfmu.js +1 -0
  16. package/ui-dist/assets/{chunk-JZWAC4HX-24FLdHl7.js → chunk-JZWAC4HX-ptDyT_1C.js} +1 -1
  17. package/ui-dist/assets/{config-split-page-BGjVACdO.js → config-split-page-B3PRA_AV.js} +1 -1
  18. package/ui-dist/assets/{createLucideIcon-PPrXCGK8.js → createLucideIcon-C_GFKVuW.js} +1 -1
  19. package/ui-dist/assets/{desktop-update-config-fMLlSStv.js → desktop-update-config-CzGi43xw.js} +1 -1
  20. package/ui-dist/assets/{dialog-CTCX7oLf.js → dialog-BHcaU6NE.js} +1 -1
  21. package/ui-dist/assets/{dist-FL5e8mMi.js → dist-DtBFqZ6_.js} +1 -1
  22. package/ui-dist/assets/{doc-browser-fyn7eDTp.js → doc-browser-CoKIUCJj.js} +1 -1
  23. package/ui-dist/assets/{doc-browser-C02neCIE.js → doc-browser-CwgI7ipB.js} +1 -1
  24. package/ui-dist/assets/doc-browser-DYKpRqe-.js +1 -0
  25. package/ui-dist/assets/{doc-browser-context-C-WPOji4.js → doc-browser-context-Dib9sS83.js} +1 -1
  26. package/ui-dist/assets/{es2015-BNy4R8AC.js → es2015-BlNhrQUG.js} +1 -1
  27. package/ui-dist/assets/{external-link-BNtqJE01.js → external-link-DP2IJ7AM.js} +1 -1
  28. package/ui-dist/assets/{folder-QyJHVUNz.js → folder-BPwc278w.js} +1 -1
  29. package/ui-dist/assets/{hash-BGYUE-zr.js → hash-CvcvtMBq.js} +1 -1
  30. package/ui-dist/assets/i18n-BnNAQpVM.js +1 -0
  31. package/ui-dist/assets/index-CAYF44Dz.js +2 -0
  32. package/ui-dist/assets/index-mRmSAB-e.css +1 -0
  33. package/ui-dist/assets/{key-round-DenCfA2w.js → key-round-BQXmPSxD.js} +1 -1
  34. package/ui-dist/assets/loader-circle-C6gg2m2a.js +1 -0
  35. package/ui-dist/assets/{logo-badge-CKAxvQFc.js → logo-badge-uB4SwANR.js} +1 -1
  36. package/ui-dist/assets/{logos-CqXnaJIm.js → logos-BcELLmYh.js} +1 -1
  37. package/ui-dist/assets/marketplace-page-0sEdt5sA.js +1 -0
  38. package/ui-dist/assets/{marketplace-page-XnDa2ulT.js → marketplace-page-DiqqX25V.js} +1 -1
  39. package/ui-dist/assets/mcp-marketplace-page-B8vmu9xe.js +1 -0
  40. package/ui-dist/assets/{mcp-marketplace-page-BArKWcRZ.js → mcp-marketplace-page-C_akqPwv.js} +1 -1
  41. package/ui-dist/assets/message-square-CLVODA23.js +1 -0
  42. package/ui-dist/assets/{model-config-ByeL6Toe.js → model-config-B0L43HTL.js} +1 -1
  43. package/ui-dist/assets/{notice-card-D00-02yg.js → notice-card-C9PFAR67.js} +1 -1
  44. package/ui-dist/assets/play-DeNVUA5C.js +1 -0
  45. package/ui-dist/assets/plus-BptLViq1.js +1 -0
  46. package/ui-dist/assets/{popover-AmJkxio3.js → popover-B8msg2FQ.js} +1 -1
  47. package/ui-dist/assets/{provider-scoped-model-input-CfFJsJp-.js → provider-scoped-model-input-DeAo2Y65.js} +1 -1
  48. package/ui-dist/assets/{providers-list-HMQzW2WV.js → providers-list-5_VShcn7.js} +1 -1
  49. package/ui-dist/assets/{refresh-ccw-B-dhb3yS.js → refresh-ccw-CeG203yU.js} +1 -1
  50. package/ui-dist/assets/remote-pzp4oLcL.js +1 -0
  51. package/ui-dist/assets/{rotate-cw-BWqAG3Fv.js → rotate-cw-F7aThvYj.js} +1 -1
  52. package/ui-dist/assets/{runtime-config-page-N4FP6H0M.js → runtime-config-page-B-y_0HIS.js} +1 -1
  53. package/ui-dist/assets/{save-DpdkGieJ.js → save-7ztImRj7.js} +1 -1
  54. package/ui-dist/assets/{search-CQUdr7j_.js → search-DZSNKEGp.js} +1 -1
  55. package/ui-dist/assets/{search-config-B62TY-z2.js → search-config-DJTm9Fno.js} +1 -1
  56. package/ui-dist/assets/{secrets-config-YCsGd1am.js → secrets-config-DKFeFii1.js} +1 -1
  57. package/ui-dist/assets/{select-DVUtSFHZ.js → select-DRDejPLk.js} +1 -1
  58. package/ui-dist/assets/{sessions-config-page-BKN-XdKr.js → sessions-config-page-CZGqS32n.js} +1 -1
  59. package/ui-dist/assets/{setting-row-Cb5-lFs-.js → setting-row-BcF6eTW0.js} +1 -1
  60. package/ui-dist/assets/{settings-DgtZZlnF.js → settings-DjvNMJde.js} +1 -1
  61. package/ui-dist/assets/skeleton-5Mg6vZHN.js +1 -0
  62. package/ui-dist/assets/{sparkles-DNSCyDhL.js → sparkles-CyDTgTM4.js} +1 -1
  63. package/ui-dist/assets/{status-dot-X_j51OfA.js → status-dot-aQU9Mia4.js} +1 -1
  64. package/ui-dist/assets/{tabs-custom-CcWmekaF.js → tabs-custom-C4P7g4vR.js} +1 -1
  65. package/ui-dist/assets/{tag-chip-fdbK2wE6.js → tag-chip-CVIqyMv7.js} +1 -1
  66. package/ui-dist/assets/{theme-provider-WTWq_jYq.js → theme-provider-dHqcWU-j.js} +1 -1
  67. package/ui-dist/assets/{tooltip-BkZCQcKw.js → tooltip-C6VPreZ7.js} +1 -1
  68. package/ui-dist/assets/{trash-2-CqciSCsg.js → trash-2-C1cdqL6V.js} +1 -1
  69. package/ui-dist/assets/{use-config-CyvhbRhf.js → use-config-DFja1sda.js} +1 -1
  70. package/ui-dist/assets/{use-confirm-dialog-DSrb9205.js → use-confirm-dialog-DvIbSUX3.js} +1 -1
  71. package/ui-dist/assets/{use-infinite-scroll-loader-DmowtyTI.js → use-infinite-scroll-loader-D8h0k-iL.js} +1 -1
  72. package/ui-dist/assets/{use-viewport-layout-CaALCA51.js → use-viewport-layout-D-pjxsyz.js} +1 -1
  73. package/ui-dist/assets/x-BjMO7v8q.js +1 -0
  74. package/ui-dist/index.html +39 -39
  75. package/templates/HEARTBEAT.md +0 -5
  76. package/ui-dist/assets/chat-D4KecKjB.js +0 -60
  77. package/ui-dist/assets/chat-page-Cc7n80lW.js +0 -1
  78. package/ui-dist/assets/doc-browser-COj7x090.js +0 -1
  79. package/ui-dist/assets/i18n-CM4y8Mw9.js +0 -1
  80. package/ui-dist/assets/index-CtVSzMPM.js +0 -2
  81. package/ui-dist/assets/index-N3hjuljD.css +0 -1
  82. package/ui-dist/assets/loader-circle-R23uEPkM.js +0 -1
  83. package/ui-dist/assets/marketplace-page-mF-M5mku.js +0 -1
  84. package/ui-dist/assets/mcp-marketplace-page-DBUcIIHJ.js +0 -1
  85. package/ui-dist/assets/message-square-Dm34zD6k.js +0 -1
  86. package/ui-dist/assets/play-ul4L6MWm.js +0 -1
  87. package/ui-dist/assets/plus-D14303DH.js +0 -1
  88. package/ui-dist/assets/remote-B4ELSd3u.js +0 -1
  89. package/ui-dist/assets/skeleton-BCPi52jT.js +0 -1
  90. package/ui-dist/assets/x-tYcSDsrY.js +0 -1
@@ -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, createExternalCommandEnv, createGlobalTypedEventBus, createTypedEventKey, createTypingStopControlMessage, diffConfigPaths, expandHome, findEffectiveAgentProfile, getAppLogger, getConfigPath, getDataDir, getDataPath, getLoggingRuntime, getLogsPath, getPackageVersion, getWorkspacePath, hasSecretRef, loadConfig, normalizeInlineSecretRefs, parseAgentScopedSessionKey, parseThinkingLevel, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveAppLogPath, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveLocalUiBaseUrl, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, saveConfig, toDisposable, updateAgentProfile } from "@nextclaw/core";
5
+ 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, isIP } from "node:net";
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$9(value) {
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$9(config.remote.platformApiBase) ?? (typeof nextclawProvider?.apiBase === "string" ? nextclawProvider.apiBase.trim() : "");
267
- const rawApiBase = normalizeOptionalString$9(opts.apiBase) ?? configuredApiBase;
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$9(opts.localOrigin);
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$9(opts.name) ?? normalizeOptionalString$9(config.remote.deviceName) ?? hostname();
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$8(value) {
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$8(remote.deviceName) ? { deviceName: normalizeOptionalString$8(remote.deviceName) } : {},
4505
- ...normalizeOptionalString$8(remote.platformApiBase) ? { platformBase: normalizeOptionalString$8(remote.platformApiBase) } : {},
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$8(params.config.remote.deviceName) ?? normalizeOptionalString$8(params.fallbackDeviceName) ?? hostname()
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/shared/utils/self-update-report.utils.ts
5123
- function reportSelfUpdateResult(params) {
5124
- const { appName, currentVersion, result, readInstalledVersion } = params;
5125
- const printSteps = () => {
5126
- for (const step of result.steps) {
5127
- console.log(`- ${step.cmd} ${step.args.join(" ")} (code ${step.code ?? "?"})`);
5128
- if (step.stderr) console.log(` stderr: ${step.stderr}`);
5129
- if (step.stdout) console.log(` stdout: ${step.stdout}`);
5130
- }
5131
- };
5132
- if (!result.ok) {
5133
- console.error(`Update failed: ${result.error ?? "unknown error"}`);
5134
- if (result.steps.length > 0) printSteps();
5135
- return {
5136
- ok: false,
5137
- shouldSuggestRestart: false
5138
- };
5139
- }
5140
- if (result.strategy === "noop") {
5141
- console.log(`✓ ${appName} is already up to date (${result.latestVersion ?? currentVersion})`);
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
- ok: true,
5144
- shouldSuggestRestart: false
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/shared/utils/cli.utils.ts
5158
- function resolveUiConfig(config, overrides) {
5159
- return {
5160
- ...config.ui ?? {
5161
- enabled: false,
5162
- host: "127.0.0.1",
5163
- port: 55667,
5164
- open: false
5165
- },
5166
- ...overrides ?? {}
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
- function resolveUiApiBase(host, port) {
5170
- return resolveLocalUiBaseUrl({
5171
- host,
5172
- port
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
- if (!response.ok) return null;
5189
- const text = (await response.text()).trim();
5190
- return isIP(text) ? text : null;
5191
- } catch {
5192
- return null;
5193
- } finally {
5194
- clearTimeout(timer);
5195
- }
5196
- }
5197
- async function resolvePublicIp(timeoutMs = 1500) {
5198
- for (const endpoint of PUBLIC_IP_CHECK_URLS) {
5199
- const candidate = await fetchPublicIpFrom(endpoint, timeoutMs);
5200
- if (candidate) return candidate;
5201
- }
5202
- return null;
5203
- }
5204
- function resolveServiceLogPath() {
5205
- return resolve(getLogsPath(), "service.log");
5206
- }
5207
- function isProcessRunning(pid) {
5208
- try {
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
- const parent = resolve(current, "..");
5236
- if (parent === current) break;
5237
- current = parent;
5238
- }
5239
- return null;
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
- return null;
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
- function getPackageVersion$1() {
5319
- const cliDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
5320
- return findNearestPackageManifest(cliDir, "nextclaw")?.version ?? findNearestPackageManifest(cliDir)?.version ?? getPackageVersion();
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 printAgentResponse(response) {
5323
- console.log("\n" + response + "\n");
5477
+ function normalizeChannel(value) {
5478
+ return typeof value === "string" && value.trim().toLowerCase() === "beta" ? "beta" : "stable";
5324
5479
  }
5325
- async function prompt(rl, question) {
5326
- rl.setPrompt(question);
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
- //#endregion
5333
- //#region src/cli/shared/services/update/self-update.service.ts
5334
- const DEFAULT_TIMEOUT_MS = 20 * 6e4;
5335
- function runSelfUpdate(options = {}) {
5336
- const steps = [];
5337
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5338
- const updateCommand = options.updateCommand ?? process.env.NEXTCLAW_UPDATE_COMMAND?.trim();
5339
- const packageName = options.packageName ?? "nextclaw";
5340
- const currentVersion = options.currentVersion?.trim() || null;
5341
- const resolveShellCommand = (command) => {
5342
- if (process.platform === "win32") return {
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
- const runStep = (cmd, args, cwd) => {
5357
- const result = spawnSync(cmd, args, {
5358
- cwd,
5359
- env: createExternalCommandEnv(process.env),
5360
- encoding: "utf-8",
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
- const parseLatestVersion = (raw) => {
5382
- const trimmed = raw.trim();
5383
- if (!trimmed) return null;
5384
- try {
5385
- const parsed = JSON.parse(trimmed);
5386
- return typeof parsed === "string" && parsed.trim() ? parsed.trim() : null;
5387
- } catch {
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
- if (updateCommand) {
5392
- const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
5393
- const shellCommand = resolveShellCommand(updateCommand);
5394
- if (!runStep(shellCommand.cmd, shellCommand.args, cwd).ok) return {
5395
- ok: false,
5396
- error: "update command failed",
5397
- strategy: "command",
5398
- steps
5399
- };
5400
- return {
5401
- ok: true,
5402
- strategy: "command",
5403
- steps
5404
- };
5405
- }
5406
- const npmExecutable = findExecutableOnPath("npm");
5407
- if (npmExecutable) {
5408
- const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
5409
- const latestVersionStep = runStep(npmExecutable, [
5410
- "view",
5411
- packageName,
5412
- "version",
5413
- "--json"
5414
- ], cwd);
5415
- const latestVersion = latestVersionStep.ok ? parseLatestVersion(latestVersionStep.stdout) : null;
5416
- if (latestVersion && currentVersion && latestVersion === currentVersion) return {
5417
- ok: true,
5418
- strategy: "noop",
5419
- latestVersion,
5420
- steps
5421
- };
5422
- if (!runStep(npmExecutable, [
5423
- "i",
5424
- "-g",
5425
- packageName
5426
- ], cwd).ok) return {
5427
- ok: false,
5428
- error: `npm install -g ${packageName} failed`,
5429
- strategy: "npm",
5430
- latestVersion: latestVersion ?? void 0,
5431
- steps
5432
- };
5433
- return {
5434
- ok: true,
5435
- strategy: "npm",
5436
- latestVersion: latestVersion ?? void 0,
5437
- steps
5438
- };
5439
- }
5440
- return {
5441
- ok: false,
5442
- error: "no update strategy available",
5443
- strategy: "none",
5444
- steps
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 reasoningContent = message.parts.filter((part) => part.type === "reasoning").map((part) => part.text).join("");
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 (reasoningContent.length > 0) assistantMessage.reasoning_content = reasoningContent;
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
- const accountId = readAccountIdForHints(requestMetadata, session.metadata);
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 = _options?.sessionMessages ?? [];
9667
+ const sessionMessages = projectNcpMessagesWithContextCompaction({
9668
+ sessionId: input.sessionId,
9669
+ sessionMessages: _options?.sessionMessages ?? []
9670
+ });
9277
9671
  const messages = contextBuilder.buildMessages({
9278
- history: toLegacyMessages([...sessionMessages], { assetStore: this.options.assetStore }),
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: this.inputBudgetPruner.prune({
9296
- messages,
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 timestamp = ensureIsoTimestamp(params.message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
9381
- const replyTo = typeof params.message.reply_to === "string" && params.message.reply_to.trim().length > 0 ? params.message.reply_to.trim() : void 0;
9382
- const storedParts = readStoredNcpParts(params.message);
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: createMessageId(params.sessionId, params.index, "assistant", timestamp),
9385
- sessionId: params.sessionId,
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(params.message.tool_calls);
9393
- const parts = [...contentToParts(params.message.content)];
9394
- const reasoning = normalizeString(params.message.reasoning_content);
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: reasoning
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: createMessageId(params.sessionId, params.index, "assistant", timestamp),
9408
- sessionId: params.sessionId,
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 timestamp = ensureIsoTimestamp(params.message.timestamp, (/* @__PURE__ */ new Date()).toISOString());
9418
- const storedParts = readStoredNcpParts(params.message);
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: createMessageId(params.sessionId, params.index, params.role, timestamp),
9421
- sessionId: params.sessionId,
9422
- role: params.role,
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: createMessageId(params.sessionId, params.index, params.role, timestamp),
9429
- sessionId: params.sessionId,
9430
- role: params.role,
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(params.message.content)
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
- return new DefaultNcpAgentRuntime({
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, startHeartbeat, watchConfigFile, watcherRegistry } = params;
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
- const result = runSelfUpdate({ timeoutMs: params.timeoutMs });
14808
- if (!result.ok) return {
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: result.error ?? "update failed",
14811
- steps: result.steps,
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: result.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: result.strategy,
14840
- steps: result.steps,
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, HeartbeatService, loadConfig: loadConfig$3, MessageBus: MessageBus$2, ProviderManager: ProviderManager$1, resolveConfigSecrets: resolveConfigSecrets$3, resolveDefaultAgentProfileId: resolveDefaultAgentProfileId$1, saveConfig: saveConfig$1, SessionManager: SessionManager$2 } = NextclawCore;
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
- if (params.patch.metadata === null) return {};
15568
- if (params.patch.metadata) return structuredClone(params.patch.metadata);
15569
- return structuredClone(params.existingMetadata ?? {});
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
- let publishUiEvent = params.publishUiEvent;
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 deferredSessionService = createDeferredUiNcpSessionService(new UiSessionService(params.sessionManager, { onSessionUpdated: (sessionKey) => {
15680
- scheduleSessionChange(sessionKey).catch((error) => {
15681
- console.error(`[session-realtime] failed to publish session change for ${sessionKey}: ${formatBackgroundTaskError(error)}`);
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 ncpSessionRealtimeBridge = createServiceNcpSessionRealtimeBridge({ sessionManager: shellContext.sessionManager });
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: () => resolveConfigSecrets$1(loadConfig$1(), { configPath: shellContext.runtimeConfigPath }),
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 report = reportSelfUpdateResult({
19721
- appName: APP_NAME,
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 (report.shouldSuggestRestart && state && isProcessRunning(state.pid)) console.log(`Tip: restart ${APP_NAME} to apply the update.`);
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(`Update ${APP_NAME}`).option("--timeout <ms>", "Update command timeout in milliseconds").action(async (opts) => runtime.update(opts));
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");