nextclaw 0.16.2 → 0.16.3

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 (2) hide show
  1. package/dist/cli/index.js +770 -358
  2. package/package.json +10 -10
package/dist/cli/index.js CHANGED
@@ -7,23 +7,23 @@ import { registerRemoteCommands } from "@nextclaw/remote";
7
7
 
8
8
  // src/cli/runtime.ts
9
9
  import {
10
- loadConfig as loadConfig19,
10
+ loadConfig as loadConfig21,
11
11
  saveConfig as saveConfig11,
12
12
  getConfigPath as getConfigPath11,
13
13
  getDataDir as getDataDir10,
14
- getWorkspacePath as getWorkspacePath12,
14
+ getWorkspacePath as getWorkspacePath13,
15
15
  expandHome as expandHome2,
16
16
  MessageBus as MessageBus3,
17
17
  AgentLoop,
18
18
  ProviderManager as ProviderManager2,
19
- resolveConfigSecrets as resolveConfigSecrets5,
19
+ resolveConfigSecrets as resolveConfigSecrets7,
20
20
  APP_NAME as APP_NAME5,
21
21
  DEFAULT_WORKSPACE_DIR,
22
22
  DEFAULT_WORKSPACE_PATH
23
23
  } from "@nextclaw/core";
24
24
  import { RemoteRuntimeActions } from "@nextclaw/remote";
25
25
  import {
26
- getPluginChannelBindings as getPluginChannelBindings5,
26
+ getPluginChannelBindings as getPluginChannelBindings6,
27
27
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints3,
28
28
  setPluginRuntimeBridge as setPluginRuntimeBridge3
29
29
  } from "@nextclaw/openclaw-compat";
@@ -3985,12 +3985,10 @@ var DiagnosticsCommands = class {
3985
3985
  };
3986
3986
 
3987
3987
  // src/cli/commands/service.ts
3988
- import * as NextclawCore2 from "@nextclaw/core";
3988
+ import * as NextclawCore3 from "@nextclaw/core";
3989
3989
  import {
3990
- getPluginUiMetadataFromRegistry,
3991
3990
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
3992
3991
  setPluginRuntimeBridge as setPluginRuntimeBridge2,
3993
- startPluginChannelGateways as startPluginChannelGateways2,
3994
3992
  stopPluginChannelGateways as stopPluginChannelGateways2
3995
3993
  } from "@nextclaw/openclaw-compat";
3996
3994
  import { appendFileSync, closeSync as closeSync2, cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync5, openSync as openSync2 } from "fs";
@@ -4254,221 +4252,6 @@ var ServiceMarketplaceInstaller = class {
4254
4252
  }
4255
4253
  };
4256
4254
 
4257
- // src/cli/commands/service-plugin-runtime-bridge.ts
4258
- import { loadConfig as loadConfig14, resolveConfigSecrets as resolveConfigSecrets2, saveConfig as saveConfig9 } from "@nextclaw/core";
4259
- import { setPluginRuntimeBridge } from "@nextclaw/openclaw-compat";
4260
- function installPluginRuntimeBridge(params) {
4261
- const { runtimePool, runtimeConfigPath, pluginChannelBindings } = params;
4262
- setPluginRuntimeBridge({
4263
- loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig14(), { configPath: runtimeConfigPath }), pluginChannelBindings),
4264
- writeConfigFile: async (nextConfigView) => {
4265
- if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
4266
- throw new Error("plugin runtime writeConfigFile expects an object config");
4267
- }
4268
- const current = loadConfig14();
4269
- const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
4270
- saveConfig9(next);
4271
- },
4272
- dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
4273
- const request = resolvePluginRuntimeRequest(ctx);
4274
- if (!request) {
4275
- return;
4276
- }
4277
- try {
4278
- await dispatcherOptions.onReplyStart?.();
4279
- const response = await runtimePool.processDirect(request);
4280
- const replyText = typeof response === "string" ? response : String(response ?? "");
4281
- if (replyText.trim()) {
4282
- await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
4283
- }
4284
- } catch (error) {
4285
- dispatcherOptions.onError?.(error);
4286
- throw error;
4287
- }
4288
- }
4289
- });
4290
- }
4291
- function resolvePluginRuntimeRequest(ctx) {
4292
- const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
4293
- const body = typeof ctx.Body === "string" ? ctx.Body : "";
4294
- const content = (bodyForAgent || body).trim();
4295
- const attachments = resolvePluginRuntimeAttachments(ctx);
4296
- if (!content && attachments.length === 0) {
4297
- return null;
4298
- }
4299
- const sessionKey = typeof ctx.SessionKey === "string" && ctx.SessionKey.trim().length > 0 ? ctx.SessionKey : void 0;
4300
- const channel = typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel.trim().length > 0 ? ctx.OriginatingChannel : "cli";
4301
- const chatId = typeof ctx.OriginatingTo === "string" && ctx.OriginatingTo.trim().length > 0 ? ctx.OriginatingTo : typeof ctx.SenderId === "string" && ctx.SenderId.trim().length > 0 ? ctx.SenderId : "direct";
4302
- const agentId = typeof ctx.AgentId === "string" ? ctx.AgentId : void 0;
4303
- const modelOverride = resolveModelOverride(ctx);
4304
- const accountId = typeof ctx.AccountId === "string" && ctx.AccountId.trim().length > 0 ? ctx.AccountId : void 0;
4305
- return {
4306
- content,
4307
- sessionKey,
4308
- channel,
4309
- chatId,
4310
- agentId,
4311
- attachments,
4312
- metadata: {
4313
- ...accountId ? { account_id: accountId } : {},
4314
- ...modelOverride ? { model: modelOverride } : {}
4315
- }
4316
- };
4317
- }
4318
- function resolveModelOverride(ctx) {
4319
- if (typeof ctx.Model === "string" && ctx.Model.trim().length > 0) {
4320
- return ctx.Model.trim();
4321
- }
4322
- if (typeof ctx.AgentModel === "string" && ctx.AgentModel.trim().length > 0) {
4323
- return ctx.AgentModel.trim();
4324
- }
4325
- return void 0;
4326
- }
4327
- function readStringList(value) {
4328
- if (!Array.isArray(value)) {
4329
- return [];
4330
- }
4331
- return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
4332
- }
4333
- function readOptionalString(value) {
4334
- if (typeof value !== "string") {
4335
- return void 0;
4336
- }
4337
- const trimmed = value.trim();
4338
- return trimmed || void 0;
4339
- }
4340
- function resolvePluginRuntimeAttachments(ctx) {
4341
- const mediaPaths = readStringList(ctx.MediaPaths);
4342
- const mediaUrls = readStringList(ctx.MediaUrls);
4343
- const fallbackPath = readOptionalString(ctx.MediaPath);
4344
- const fallbackUrl = readOptionalString(ctx.MediaUrl);
4345
- const mediaTypes = readStringList(ctx.MediaTypes);
4346
- const fallbackType = readOptionalString(ctx.MediaType);
4347
- const entryCount = Math.max(
4348
- mediaPaths.length,
4349
- mediaUrls.length,
4350
- fallbackPath ? 1 : 0,
4351
- fallbackUrl ? 1 : 0
4352
- );
4353
- const attachments = [];
4354
- for (let index = 0; index < entryCount; index += 1) {
4355
- const path2 = mediaPaths[index] ?? (index === 0 ? fallbackPath : void 0);
4356
- const rawUrl = mediaUrls[index] ?? (index === 0 ? fallbackUrl : void 0);
4357
- const url = rawUrl && rawUrl !== path2 ? rawUrl : void 0;
4358
- const mimeType = mediaTypes[index] ?? fallbackType;
4359
- if (!path2 && !url) {
4360
- continue;
4361
- }
4362
- attachments.push({
4363
- path: path2,
4364
- url,
4365
- mimeType,
4366
- source: "plugin-runtime",
4367
- status: path2 ? "ready" : "remote-only"
4368
- });
4369
- }
4370
- return attachments;
4371
- }
4372
-
4373
- // src/cli/commands/service-plugin-reload.ts
4374
- import { getWorkspacePath as getWorkspacePath7 } from "@nextclaw/core";
4375
- import {
4376
- getPluginChannelBindings as getPluginChannelBindings3,
4377
- startPluginChannelGateways,
4378
- stopPluginChannelGateways
4379
- } from "@nextclaw/openclaw-compat";
4380
-
4381
- // src/cli/commands/plugin-reload.ts
4382
- function buildPluginChannelBindingSignature(binding) {
4383
- return `${binding.pluginId}:${binding.channelId}`;
4384
- }
4385
- function buildSortedBindingSignatures(bindings) {
4386
- return bindings.map(buildPluginChannelBindingSignature).sort();
4387
- }
4388
- function buildSortedExtensionChannelIds(channels2) {
4389
- return channels2.map((registration) => registration.channel.id).filter((id) => typeof id === "string" && id.trim().length > 0).sort();
4390
- }
4391
- function areSortedStringListsEqual(left, right) {
4392
- if (left.length !== right.length) {
4393
- return false;
4394
- }
4395
- for (let index = 0; index < left.length; index += 1) {
4396
- if (left[index] !== right[index]) {
4397
- return false;
4398
- }
4399
- }
4400
- return true;
4401
- }
4402
- function readPluginIdFromPluginsEntryPath(path2) {
4403
- const prefix = "plugins.entries.";
4404
- if (!path2.startsWith(prefix)) {
4405
- return null;
4406
- }
4407
- const suffix = path2.slice(prefix.length);
4408
- const [pluginId] = suffix.split(".");
4409
- return pluginId?.trim() ? pluginId.trim() : null;
4410
- }
4411
- function shouldRestartChannelsForPluginReload(params) {
4412
- const currentBindingSignatures = buildSortedBindingSignatures(params.currentPluginChannelBindings);
4413
- const nextBindingSignatures = buildSortedBindingSignatures(params.nextPluginChannelBindings);
4414
- if (!areSortedStringListsEqual(currentBindingSignatures, nextBindingSignatures)) {
4415
- return true;
4416
- }
4417
- const currentExtensionChannelIds = buildSortedExtensionChannelIds(params.currentExtensionChannels);
4418
- const nextExtensionChannelIds = buildSortedExtensionChannelIds(params.nextExtensionChannels);
4419
- if (!areSortedStringListsEqual(currentExtensionChannelIds, nextExtensionChannelIds)) {
4420
- return true;
4421
- }
4422
- const channelPluginIds = /* @__PURE__ */ new Set();
4423
- for (const binding of params.currentPluginChannelBindings) {
4424
- channelPluginIds.add(binding.pluginId);
4425
- }
4426
- for (const binding of params.nextPluginChannelBindings) {
4427
- channelPluginIds.add(binding.pluginId);
4428
- }
4429
- for (const path2 of params.changedPaths) {
4430
- const pluginId = readPluginIdFromPluginsEntryPath(path2);
4431
- if (pluginId && channelPluginIds.has(pluginId)) {
4432
- return true;
4433
- }
4434
- }
4435
- return false;
4436
- }
4437
-
4438
- // src/cli/commands/service-plugin-reload.ts
4439
- async function reloadServicePlugins(params) {
4440
- const nextWorkspace = getWorkspacePath7(params.nextConfig.agents.defaults.workspace);
4441
- const nextPluginRegistry = loadPluginRegistry(params.nextConfig, nextWorkspace);
4442
- const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
4443
- const nextPluginChannelBindings = getPluginChannelBindings3(nextPluginRegistry);
4444
- const shouldRestartChannels = shouldRestartChannelsForPluginReload({
4445
- changedPaths: params.changedPaths,
4446
- currentPluginChannelBindings: params.pluginChannelBindings,
4447
- nextPluginChannelBindings,
4448
- currentExtensionChannels: params.extensionRegistry.channels,
4449
- nextExtensionChannels: nextExtensionRegistry.channels
4450
- });
4451
- logPluginDiagnostics(nextPluginRegistry);
4452
- let pluginGatewayHandles = params.pluginGatewayHandles;
4453
- if (shouldRestartChannels) {
4454
- await stopPluginChannelGateways(pluginGatewayHandles);
4455
- const startedPluginGateways = await startPluginChannelGateways({
4456
- registry: nextPluginRegistry,
4457
- config: params.nextConfig,
4458
- logger: params.pluginGatewayLogger
4459
- });
4460
- pluginGatewayHandles = startedPluginGateways.handles;
4461
- params.logPluginGatewayDiagnostics(startedPluginGateways.diagnostics);
4462
- }
4463
- return {
4464
- pluginRegistry: nextPluginRegistry,
4465
- extensionRegistry: nextExtensionRegistry,
4466
- pluginChannelBindings: nextPluginChannelBindings,
4467
- pluginGatewayHandles,
4468
- restartChannels: shouldRestartChannels
4469
- };
4470
- }
4471
-
4472
4255
  // src/cli/commands/service-startup-support.ts
4473
4256
  var pluginGatewayLogger = {
4474
4257
  info: (message) => console.log(`[plugins] ${message}`),
@@ -4626,7 +4409,7 @@ function tryClaimRemoteOwnershipLock(params) {
4626
4409
  function claimManagedRemoteRuntimeOwnership(params) {
4627
4410
  const lockPath = params.lockPath ?? resolveRemoteOwnershipLockPath();
4628
4411
  const currentPid = params.currentPid ?? process.pid;
4629
- const now = params.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
4412
+ const now3 = params.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
4630
4413
  const isProcessRunningFn = params.isProcessRunningFn ?? isProcessRunning;
4631
4414
  const readServiceStateFn = params.readServiceStateFn ?? readServiceState;
4632
4415
  const managedConflict = detectManagedRemoteOwnershipConflict({
@@ -4646,7 +4429,7 @@ function claimManagedRemoteRuntimeOwnership(params) {
4646
4429
  claim: {
4647
4430
  pid: currentPid,
4648
4431
  localOrigin: params.localOrigin,
4649
- claimedAt: now()
4432
+ claimedAt: now3()
4650
4433
  },
4651
4434
  currentPid,
4652
4435
  isProcessRunningFn
@@ -4714,11 +4497,11 @@ function writeReadyManagedServiceState(params) {
4714
4497
  }
4715
4498
 
4716
4499
  // src/cli/commands/remote-access-host.ts
4717
- import { getConfigPath as getConfigPath8, loadConfig as loadConfig16 } from "@nextclaw/core";
4500
+ import { getConfigPath as getConfigPath8, loadConfig as loadConfig15 } from "@nextclaw/core";
4718
4501
  import { readPlatformSessionTokenState as readPlatformSessionTokenState2 } from "@nextclaw/remote";
4719
4502
 
4720
4503
  // src/cli/commands/remote-access-service-control.ts
4721
- import { getConfigPath as getConfigPath7, loadConfig as loadConfig15 } from "@nextclaw/core";
4504
+ import { getConfigPath as getConfigPath7, loadConfig as loadConfig14 } from "@nextclaw/core";
4722
4505
  import { spawn as spawn2 } from "child_process";
4723
4506
  var FORCED_PUBLIC_UI_HOST = "0.0.0.0";
4724
4507
  function resolveRemoteServiceView(currentUi) {
@@ -4807,7 +4590,7 @@ async function controlManagedService(action, deps) {
4807
4590
  return { accepted: true, action, message: "Managed service restarted." };
4808
4591
  }
4809
4592
  function resolveManagedUiOverrides() {
4810
- const config2 = loadConfig15(getConfigPath7());
4593
+ const config2 = loadConfig14(getConfigPath7());
4811
4594
  const resolved = resolveUiConfig(config2, {
4812
4595
  enabled: true,
4813
4596
  host: FORCED_PUBLIC_UI_HOST,
@@ -4907,7 +4690,7 @@ var RemoteAccessHost = class {
4907
4690
  this.deps = deps;
4908
4691
  }
4909
4692
  getStatus() {
4910
- const config2 = loadConfig16(getConfigPath8());
4693
+ const config2 = loadConfig15(getConfigPath8());
4911
4694
  const status = this.deps.remoteCommands.getStatusView();
4912
4695
  const account = this.readAccountView({
4913
4696
  token: normalizeOptionalString4(config2.providers.nextclaw?.apiKey),
@@ -4944,7 +4727,7 @@ var RemoteAccessHost = class {
4944
4727
  };
4945
4728
  }
4946
4729
  async pollBrowserAuth(input) {
4947
- const config2 = loadConfig16(getConfigPath8());
4730
+ const config2 = loadConfig15(getConfigPath8());
4948
4731
  const result = await this.deps.platformAuthCommands.pollBrowserAuth({
4949
4732
  apiBase: normalizeOptionalString4(input.apiBase) ?? normalizeOptionalString4(config2.remote.platformApiBase) ?? normalizeOptionalString4(config2.providers.nextclaw?.apiBase) ?? void 0,
4950
4733
  sessionId: input.sessionId
@@ -5064,7 +4847,7 @@ import { resolve as resolve11 } from "path";
5064
4847
  import {
5065
4848
  buildAssetContentPath
5066
4849
  } from "@nextclaw/ncp-agent-runtime";
5067
- function readOptionalString2(value) {
4850
+ function readOptionalString(value) {
5068
4851
  if (typeof value !== "string") {
5069
4852
  return null;
5070
4853
  }
@@ -5072,7 +4855,7 @@ function readOptionalString2(value) {
5072
4855
  return trimmed.length > 0 ? trimmed : null;
5073
4856
  }
5074
4857
  function readOptionalBase64Bytes(value) {
5075
- const base64 = readOptionalString2(value);
4858
+ const base64 = readOptionalString(value);
5076
4859
  if (!base64) {
5077
4860
  return null;
5078
4861
  }
@@ -5124,9 +4907,9 @@ var AssetPutTool = class {
5124
4907
  }
5125
4908
  };
5126
4909
  async execute(args) {
5127
- const path2 = readOptionalString2(args?.path);
5128
- const fileName = readOptionalString2(args?.fileName);
5129
- const mimeType = readOptionalString2(args?.mimeType);
4910
+ const path2 = readOptionalString(args?.path);
4911
+ const fileName = readOptionalString(args?.fileName);
4912
+ const mimeType = readOptionalString(args?.mimeType);
5130
4913
  const bytes = readOptionalBase64Bytes(args?.bytesBase64);
5131
4914
  let record;
5132
4915
  if (path2) {
@@ -5171,8 +4954,8 @@ var AssetExportTool = class {
5171
4954
  required: ["assetUri", "targetPath"]
5172
4955
  };
5173
4956
  async execute(args) {
5174
- const assetUri = readOptionalString2(args?.assetUri);
5175
- const targetPath = readOptionalString2(args?.targetPath);
4957
+ const assetUri = readOptionalString(args?.assetUri);
4958
+ const targetPath = readOptionalString(args?.targetPath);
5176
4959
  if (!assetUri || !targetPath) {
5177
4960
  throw new Error("asset_export requires assetUri and targetPath.");
5178
4961
  }
@@ -5202,7 +4985,7 @@ var AssetStatTool = class {
5202
4985
  required: ["assetUri"]
5203
4986
  };
5204
4987
  async execute(args) {
5205
- const assetUri = readOptionalString2(args?.assetUri);
4988
+ const assetUri = readOptionalString(args?.assetUri);
5206
4989
  if (!assetUri) {
5207
4990
  throw new Error("asset_stat requires assetUri.");
5208
4991
  }
@@ -5236,7 +5019,7 @@ import {
5236
5019
  buildToolCatalogEntries,
5237
5020
  ContextBuilder,
5238
5021
  InputBudgetPruner,
5239
- getWorkspacePath as getWorkspacePath8,
5022
+ getWorkspacePath as getWorkspacePath7,
5240
5023
  parseThinkingLevel as parseThinkingLevel2,
5241
5024
  resolveThinkingLevel
5242
5025
  } from "@nextclaw/core";
@@ -5895,7 +5678,7 @@ function resolvePrimaryAgentProfile(config2) {
5895
5678
  const profile = config2.agents.list.find((entry) => entry.id.trim() === configuredDefaultAgentId);
5896
5679
  return {
5897
5680
  agentId: configuredDefaultAgentId,
5898
- workspace: getWorkspacePath8(profile?.workspace ?? config2.agents.defaults.workspace),
5681
+ workspace: getWorkspacePath7(profile?.workspace ?? config2.agents.defaults.workspace),
5899
5682
  model: profile?.model ?? config2.agents.defaults.model,
5900
5683
  maxIterations: profile?.maxToolIterations ?? config2.agents.defaults.maxToolIterations,
5901
5684
  contextTokens: profile?.contextTokens ?? config2.agents.defaults.contextTokens,
@@ -6749,7 +6532,6 @@ function createUiNcpAgentHandle(params) {
6749
6532
  basePath: "/api/ncp/agent",
6750
6533
  agentClientEndpoint: createAgentClientFromServer(params.backend),
6751
6534
  streamProvider: params.backend,
6752
- sessionApi: params.backend,
6753
6535
  listSessionTypes: (describeParams) => {
6754
6536
  params.refreshPluginRuntimeRegistrations();
6755
6537
  return params.runtimeRegistry.listSessionTypes(describeParams);
@@ -6807,9 +6589,96 @@ async function createUiNcpAgent(params) {
6807
6589
  });
6808
6590
  }
6809
6591
 
6592
+ // src/cli/commands/ncp/ui-session-service.ts
6593
+ function applyLimit(items, limit) {
6594
+ if (!Number.isFinite(limit) || typeof limit !== "number" || limit <= 0) {
6595
+ return items;
6596
+ }
6597
+ return items.slice(0, Math.trunc(limit));
6598
+ }
6599
+ function now() {
6600
+ return (/* @__PURE__ */ new Date()).toISOString();
6601
+ }
6602
+ function toSessionSummary(params) {
6603
+ return {
6604
+ sessionId: params.sessionId,
6605
+ messageCount: params.messages.length,
6606
+ updatedAt: params.updatedAt,
6607
+ status: "idle",
6608
+ ...params.metadata ? { metadata: structuredClone(params.metadata) } : {}
6609
+ };
6610
+ }
6611
+ function buildUpdatedMetadata(params) {
6612
+ if (params.patch.metadata === null) {
6613
+ return {};
6614
+ }
6615
+ if (params.patch.metadata) {
6616
+ return structuredClone(params.patch.metadata);
6617
+ }
6618
+ return structuredClone(params.existingMetadata ?? {});
6619
+ }
6620
+ var UiSessionService = class {
6621
+ sessionStore;
6622
+ constructor(sessionManager) {
6623
+ this.sessionStore = new NextclawAgentSessionStore(sessionManager);
6624
+ }
6625
+ async listSessions(options) {
6626
+ const sessions = await this.sessionStore.listSessions();
6627
+ return applyLimit(
6628
+ sessions.map(
6629
+ (session) => toSessionSummary({
6630
+ sessionId: session.sessionId,
6631
+ messages: session.messages,
6632
+ updatedAt: session.updatedAt,
6633
+ metadata: session.metadata
6634
+ })
6635
+ ),
6636
+ options?.limit
6637
+ );
6638
+ }
6639
+ async listSessionMessages(sessionId, options) {
6640
+ const session = await this.sessionStore.getSession(sessionId);
6641
+ if (!session) {
6642
+ return [];
6643
+ }
6644
+ return applyLimit(session.messages.map((message) => structuredClone(message)), options?.limit);
6645
+ }
6646
+ async getSession(sessionId) {
6647
+ const session = await this.sessionStore.getSession(sessionId);
6648
+ if (!session) {
6649
+ return null;
6650
+ }
6651
+ return toSessionSummary({
6652
+ sessionId,
6653
+ messages: session.messages,
6654
+ updatedAt: session.updatedAt,
6655
+ metadata: session.metadata
6656
+ });
6657
+ }
6658
+ async updateSession(sessionId, patch) {
6659
+ const session = await this.sessionStore.getSession(sessionId);
6660
+ if (!session) {
6661
+ return null;
6662
+ }
6663
+ await this.sessionStore.saveSession({
6664
+ sessionId,
6665
+ messages: session.messages.map((message) => structuredClone(message)),
6666
+ updatedAt: now(),
6667
+ metadata: buildUpdatedMetadata({
6668
+ existingMetadata: session.metadata,
6669
+ patch
6670
+ })
6671
+ });
6672
+ return await this.getSession(sessionId);
6673
+ }
6674
+ async deleteSession(sessionId) {
6675
+ await this.sessionStore.deleteSession(sessionId);
6676
+ }
6677
+ };
6678
+
6810
6679
  // src/cli/commands/service-gateway-context.ts
6811
6680
  import * as NextclawCore from "@nextclaw/core";
6812
- import { getPluginChannelBindings as getPluginChannelBindings4, resolvePluginChannelMessageToolHints } from "@nextclaw/openclaw-compat";
6681
+ import { getPluginChannelBindings as getPluginChannelBindings3, resolvePluginChannelMessageToolHints } from "@nextclaw/openclaw-compat";
6813
6682
  import { join as join6 } from "path";
6814
6683
 
6815
6684
  // src/cli/gateway/controller.ts
@@ -7157,7 +7026,7 @@ var ConfigReloader = class {
7157
7026
  console.log("Config reload: MCP servers reloaded.");
7158
7027
  }
7159
7028
  if (plan.restartChannels || reloadPluginsResult?.restartChannels) {
7160
- await this.reloadChannels(nextConfig);
7029
+ await this.reloadChannels(nextConfig, { start: true });
7161
7030
  console.log("Config reload: channels restarted.");
7162
7031
  }
7163
7032
  if (plan.reloadProviders) {
@@ -7207,7 +7076,10 @@ var ConfigReloader = class {
7207
7076
  await this.runReload(reason ?? "gateway tool");
7208
7077
  return "Config reload triggered";
7209
7078
  }
7210
- async reloadChannels(nextConfig) {
7079
+ async rebuildChannels(nextConfig, options = {}) {
7080
+ await this.reloadChannels(nextConfig, { start: options.start ?? true });
7081
+ }
7082
+ async reloadChannels(nextConfig, options) {
7211
7083
  if (this.reloadTask) {
7212
7084
  await this.reloadTask;
7213
7085
  return;
@@ -7221,7 +7093,9 @@ var ConfigReloader = class {
7221
7093
  this.options.sessionManager,
7222
7094
  this.options.getExtensionChannels?.() ?? []
7223
7095
  );
7224
- await this.channels.startAll();
7096
+ if (options.start) {
7097
+ await this.channels.startAll();
7098
+ }
7225
7099
  })();
7226
7100
  try {
7227
7101
  await this.reloadTask;
@@ -7273,7 +7147,7 @@ import {
7273
7147
  createAssistantStreamDeltaControlMessage,
7274
7148
  createAssistantStreamResetControlMessage,
7275
7149
  AgentRouteResolver,
7276
- getWorkspacePath as getWorkspacePath9,
7150
+ getWorkspacePath as getWorkspacePath8,
7277
7151
  parseAgentScopedSessionKey
7278
7152
  } from "@nextclaw/core";
7279
7153
  function normalizeAgentId(value) {
@@ -7318,7 +7192,7 @@ function resolveAgentProfiles(config2) {
7318
7192
  }
7319
7193
  return Array.from(unique.values()).map((entry) => ({
7320
7194
  id: entry.id,
7321
- workspace: getWorkspacePath9(entry.workspace ?? defaults.workspace),
7195
+ workspace: getWorkspacePath8(entry.workspace ?? defaults.workspace),
7322
7196
  model: entry.model ?? defaults.model,
7323
7197
  engine: normalizeEngineKind(entry.engine ?? defaults.engine),
7324
7198
  engineConfig: entry.engineConfig ?? toRecord(defaults.engineConfig),
@@ -7586,7 +7460,7 @@ var GatewayAgentRuntimePool = class {
7586
7460
  const normalizedAgentId = normalizeAgentId(agentId);
7587
7461
  return this.resolvedProfiles.find((profile) => profile.id === normalizedAgentId) ?? this.resolvedProfiles.find((profile) => profile.id === this.defaultAgentId) ?? this.resolvedProfiles[0] ?? {
7588
7462
  id: this.defaultAgentId,
7589
- workspace: getWorkspacePath9(this.options.config.agents.defaults.workspace),
7463
+ workspace: getWorkspacePath8(this.options.config.agents.defaults.workspace),
7590
7464
  model: this.options.config.agents.defaults.model,
7591
7465
  maxIterations: this.options.config.agents.defaults.maxToolIterations,
7592
7466
  contextTokens: this.options.config.agents.defaults.contextTokens,
@@ -7808,31 +7682,36 @@ var {
7808
7682
  CronService: CronService2,
7809
7683
  getConfigPath: getConfigPath9,
7810
7684
  getDataDir: getDataDir8,
7811
- getWorkspacePath: getWorkspacePath10,
7685
+ getWorkspacePath: getWorkspacePath9,
7812
7686
  HeartbeatService,
7813
- loadConfig: loadConfig17,
7687
+ loadConfig: loadConfig16,
7814
7688
  MessageBus,
7815
7689
  ProviderManager,
7816
- resolveConfigSecrets: resolveConfigSecrets3,
7817
- saveConfig: saveConfig10,
7690
+ resolveConfigSecrets: resolveConfigSecrets2,
7691
+ saveConfig: saveConfig9,
7818
7692
  SessionManager
7819
7693
  } = NextclawCore;
7820
7694
  function createGatewayShellContext(params) {
7821
7695
  const runtimeConfigPath = getConfigPath9();
7822
- const config2 = resolveConfigSecrets3(loadConfig17(), { configPath: runtimeConfigPath });
7823
- const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
7696
+ const config2 = resolveConfigSecrets2(loadConfig16(), { configPath: runtimeConfigPath });
7697
+ const workspace = getWorkspacePath9(config2.agents.defaults.workspace);
7824
7698
  const cronStorePath = join6(getDataDir8(), "cron", "jobs.json");
7699
+ const sessionManager = measureStartupSync(
7700
+ "service.gateway_shell_context.session_manager",
7701
+ () => new SessionManager(workspace)
7702
+ );
7825
7703
  const cron2 = new CronService2(cronStorePath);
7826
7704
  const uiConfig = resolveUiConfig(config2, params.uiOverrides);
7827
7705
  const uiStaticDir = params.uiStaticDir === void 0 ? resolveUiStaticDir() : params.uiStaticDir;
7828
7706
  const remoteModule = createManagedRemoteModuleForUi({
7829
- loadConfig: () => resolveConfigSecrets3(loadConfig17(), { configPath: runtimeConfigPath }),
7707
+ loadConfig: () => resolveConfigSecrets2(loadConfig16(), { configPath: runtimeConfigPath }),
7830
7708
  uiConfig
7831
7709
  });
7832
7710
  return {
7833
7711
  runtimeConfigPath,
7834
7712
  config: config2,
7835
7713
  workspace,
7714
+ sessionManager,
7836
7715
  cron: cron2,
7837
7716
  uiConfig,
7838
7717
  uiStaticDir,
@@ -7848,17 +7727,18 @@ function createGatewayStartupContext(params) {
7848
7727
  state.runtimeConfigPath = shellContext.runtimeConfigPath;
7849
7728
  state.config = shellContext.config;
7850
7729
  state.workspace = shellContext.workspace;
7730
+ state.sessionManager = shellContext.sessionManager;
7851
7731
  state.cron = shellContext.cron;
7852
7732
  state.uiConfig = shellContext.uiConfig;
7853
7733
  state.uiStaticDir = shellContext.uiStaticDir;
7854
7734
  state.remoteModule = shellContext.remoteModule;
7855
- state.pluginRegistry = measureStartupSync(
7735
+ state.pluginRegistry = params.initialPluginRegistry ?? measureStartupSync(
7856
7736
  "service.gateway_context.load_plugin_registry",
7857
7737
  () => loadPluginRegistry(state.config, state.workspace)
7858
7738
  );
7859
7739
  state.pluginChannelBindings = measureStartupSync(
7860
7740
  "service.gateway_context.get_plugin_channel_bindings",
7861
- () => getPluginChannelBindings4(state.pluginRegistry)
7741
+ () => getPluginChannelBindings3(state.pluginRegistry)
7862
7742
  );
7863
7743
  state.extensionRegistry = measureStartupSync(
7864
7744
  "service.gateway_context.to_extension_registry",
@@ -7874,10 +7754,6 @@ function createGatewayStartupContext(params) {
7874
7754
  config: state.config
7875
7755
  })
7876
7756
  );
7877
- state.sessionManager = measureStartupSync(
7878
- "service.gateway_context.session_manager",
7879
- () => new SessionManager(state.workspace)
7880
- );
7881
7757
  if (!provider) {
7882
7758
  console.warn(
7883
7759
  "Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set."
@@ -7898,7 +7774,7 @@ function createGatewayStartupContext(params) {
7898
7774
  sessionManager: state.sessionManager,
7899
7775
  providerManager: state.providerManager,
7900
7776
  makeProvider: (nextConfig) => params.makeProvider(nextConfig, { allowMissing: true }) ?? params.makeMissingProvider(nextConfig),
7901
- loadConfig: () => resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7777
+ loadConfig: () => resolveConfigSecrets2(loadConfig16(), { configPath: state.runtimeConfigPath }),
7902
7778
  resolveChannelConfig: (nextConfig) => resolveChannelConfigView(nextConfig, state.pluginChannelBindings),
7903
7779
  getExtensionChannels: () => state.extensionRegistry.channels,
7904
7780
  onRestartRequired: (paths) => {
@@ -7911,7 +7787,7 @@ function createGatewayStartupContext(params) {
7911
7787
  })
7912
7788
  );
7913
7789
  state.applyLiveConfigReload = async () => {
7914
- await state.reloader.applyReloadPlan(resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }));
7790
+ await state.reloader.applyReloadPlan(resolveConfigSecrets2(loadConfig16(), { configPath: state.runtimeConfigPath }));
7915
7791
  };
7916
7792
  state.gatewayController = measureStartupSync(
7917
7793
  "service.gateway_context.gateway_controller",
@@ -7920,7 +7796,7 @@ function createGatewayStartupContext(params) {
7920
7796
  cron: state.cron,
7921
7797
  sessionManager: state.sessionManager,
7922
7798
  getConfigPath: getConfigPath9,
7923
- saveConfig: saveConfig10,
7799
+ saveConfig: saveConfig9,
7924
7800
  requestRestart: async (options) => {
7925
7801
  await params.requestRestart({
7926
7802
  reason: options?.reason ?? "gateway tool restart",
@@ -7949,7 +7825,7 @@ function createGatewayStartupContext(params) {
7949
7825
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
7950
7826
  registry: state.pluginRegistry,
7951
7827
  channel,
7952
- cfg: resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7828
+ cfg: resolveConfigSecrets2(loadConfig16(), { configPath: state.runtimeConfigPath }),
7953
7829
  accountId
7954
7830
  })
7955
7831
  })
@@ -7969,7 +7845,9 @@ function createGatewayStartupContext(params) {
7969
7845
  }
7970
7846
 
7971
7847
  // src/cli/commands/service-gateway-startup.ts
7972
- import { startUiServer } from "@nextclaw/server";
7848
+ import {
7849
+ startUiServer
7850
+ } from "@nextclaw/server";
7973
7851
 
7974
7852
  // src/cli/commands/service-deferred-ncp-agent.ts
7975
7853
  var DEFAULT_BASE_PATH = "/api/ncp/agent";
@@ -8039,7 +7917,6 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8039
7917
  activeAgent = null;
8040
7918
  agent.basePath = basePath;
8041
7919
  agent.streamProvider = void 0;
8042
- agent.sessionApi = void 0;
8043
7920
  agent.listSessionTypes = void 0;
8044
7921
  agent.assetApi = void 0;
8045
7922
  };
@@ -8049,7 +7926,6 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8049
7926
  activeAgent = nextAgent;
8050
7927
  agent.basePath = nextAgent.basePath ?? basePath;
8051
7928
  agent.streamProvider = nextAgent.streamProvider;
8052
- agent.sessionApi = nextAgent.sessionApi;
8053
7929
  agent.listSessionTypes = nextAgent.listSessionTypes;
8054
7930
  agent.assetApi = nextAgent.assetApi;
8055
7931
  },
@@ -8083,8 +7959,10 @@ async function startUiShell(params) {
8083
7959
  cronService: params.cronService,
8084
7960
  marketplace: params.marketplace,
8085
7961
  remoteAccess: params.remoteAccess,
7962
+ getBootstrapStatus: params.getBootstrapStatus,
8086
7963
  getPluginChannelBindings: params.getPluginChannelBindings,
8087
7964
  getPluginUiMetadata: params.getPluginUiMetadata,
7965
+ ncpSessionService: params.ncpSessionService,
8088
7966
  ncpAgent: deferredNcpAgent.agent
8089
7967
  });
8090
7968
  publishUiEvent = uiServer.publish;
@@ -8131,6 +8009,9 @@ async function startDeferredGatewayStartup(params) {
8131
8009
  console.error(`UI NCP agent startup failed: ${error instanceof Error ? error.message : String(error)}`);
8132
8010
  }
8133
8011
  }
8012
+ if (params.hydrateCapabilities) {
8013
+ await measureStartupAsync("service.deferred_startup.hydrate_capabilities", params.hydrateCapabilities);
8014
+ }
8134
8015
  await measureStartupAsync("service.deferred_startup.start_plugin_gateways", params.startPluginGateways);
8135
8016
  await measureStartupAsync("service.deferred_startup.start_channels", params.startChannels);
8136
8017
  await measureStartupAsync("service.deferred_startup.wake_restart_sentinel", params.wakeFromRestartSentinel);
@@ -8152,18 +8033,570 @@ async function runGatewayRuntimeLoop(params) {
8152
8033
  }
8153
8034
  }
8154
8035
 
8155
- // src/cli/commands/service-ui-shell-grace.ts
8156
- import { setTimeout as delay } from "timers/promises";
8157
- var DEFAULT_UI_SHELL_GRACE_MS = 3e3;
8158
- async function waitForUiShellGraceWindow(uiStartup) {
8159
- if (!uiStartup) {
8160
- return;
8161
- }
8036
+ // src/cli/commands/plugin-registry-loader.ts
8037
+ import {
8038
+ discoverPluginStatusReport,
8039
+ loadOpenClawPluginsProgressively
8040
+ } from "@nextclaw/openclaw-compat";
8041
+ function createPluginLogger() {
8042
+ return {
8043
+ info: (message) => console.log(message),
8044
+ warn: (message) => console.warn(message),
8045
+ error: (message) => console.error(message),
8046
+ debug: (message) => console.debug(message)
8047
+ };
8048
+ }
8049
+ function withDevFirstPartyPluginPaths(config2) {
8050
+ const workspaceExtensionsDir = resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR);
8051
+ return {
8052
+ workspaceExtensionsDir,
8053
+ configWithDevPluginPaths: applyDevFirstPartyPluginLoadPaths(config2, workspaceExtensionsDir)
8054
+ };
8055
+ }
8056
+ async function loadPluginRegistryProgressively(config2, workspaceDir, options = {}) {
8057
+ const { workspaceExtensionsDir, configWithDevPluginPaths } = withDevFirstPartyPluginPaths(config2);
8058
+ const excludedRoots = resolveDevFirstPartyPluginInstallRoots(config2, workspaceExtensionsDir);
8059
+ return await loadOpenClawPluginsProgressively({
8060
+ config: configWithDevPluginPaths,
8061
+ workspaceDir,
8062
+ excludeRoots: excludedRoots,
8063
+ ...buildReservedPluginLoadOptions(),
8064
+ onPluginProcessed: options.onPluginProcessed,
8065
+ logger: createPluginLogger()
8066
+ });
8067
+ }
8068
+ function discoverPluginRegistryStatus(config2, workspaceDir) {
8069
+ const { configWithDevPluginPaths } = withDevFirstPartyPluginPaths(config2);
8070
+ return discoverPluginStatusReport({
8071
+ config: configWithDevPluginPaths,
8072
+ workspaceDir
8073
+ });
8074
+ }
8075
+ function createEmptyPluginRegistry() {
8076
+ return {
8077
+ plugins: [],
8078
+ tools: [],
8079
+ channels: [],
8080
+ providers: [],
8081
+ engines: [],
8082
+ ncpAgentRuntimes: [],
8083
+ diagnostics: [],
8084
+ resolvedTools: []
8085
+ };
8086
+ }
8087
+
8088
+ // src/cli/commands/service-gateway-bootstrap.ts
8089
+ import * as NextclawCore2 from "@nextclaw/core";
8090
+ import {
8091
+ getPluginUiMetadataFromRegistry as getPluginUiMetadataFromRegistry2,
8092
+ startPluginChannelGateways as startPluginChannelGateways2
8093
+ } from "@nextclaw/openclaw-compat";
8094
+
8095
+ // src/cli/commands/service-bootstrap-status.ts
8096
+ function now2() {
8097
+ return (/* @__PURE__ */ new Date()).toISOString();
8098
+ }
8099
+ var ServiceBootstrapStatusStore = class {
8100
+ state = {
8101
+ phase: "kernel-starting",
8102
+ pluginHydration: {
8103
+ state: "pending",
8104
+ loadedPluginCount: 0,
8105
+ totalPluginCount: 0
8106
+ },
8107
+ channels: {
8108
+ state: "pending",
8109
+ enabled: []
8110
+ },
8111
+ remote: {
8112
+ state: "pending"
8113
+ }
8114
+ };
8115
+ getStatus() {
8116
+ return {
8117
+ ...this.state,
8118
+ pluginHydration: { ...this.state.pluginHydration },
8119
+ channels: {
8120
+ ...this.state.channels,
8121
+ enabled: [...this.state.channels.enabled]
8122
+ },
8123
+ remote: { ...this.state.remote }
8124
+ };
8125
+ }
8126
+ markShellReady() {
8127
+ this.state.phase = "shell-ready";
8128
+ this.state.shellReadyAt = this.state.shellReadyAt ?? now2();
8129
+ }
8130
+ markPluginHydrationPending() {
8131
+ this.state.pluginHydration = {
8132
+ ...this.state.pluginHydration,
8133
+ state: "pending",
8134
+ loadedPluginCount: 0,
8135
+ totalPluginCount: 0
8136
+ };
8137
+ }
8138
+ markPluginHydrationRunning(params) {
8139
+ this.state.phase = "hydrating-capabilities";
8140
+ this.state.pluginHydration = {
8141
+ ...this.state.pluginHydration,
8142
+ state: "running",
8143
+ loadedPluginCount: 0,
8144
+ totalPluginCount: params.totalPluginCount,
8145
+ startedAt: this.state.pluginHydration.startedAt ?? now2(),
8146
+ completedAt: void 0,
8147
+ error: void 0
8148
+ };
8149
+ }
8150
+ markPluginHydrationProgress(params) {
8151
+ this.state.pluginHydration = {
8152
+ ...this.state.pluginHydration,
8153
+ state: "running",
8154
+ loadedPluginCount: params.loadedPluginCount,
8155
+ totalPluginCount: params.totalPluginCount ?? this.state.pluginHydration.totalPluginCount
8156
+ };
8157
+ }
8158
+ markPluginHydrationReady(params) {
8159
+ this.state.pluginHydration = {
8160
+ ...this.state.pluginHydration,
8161
+ state: "ready",
8162
+ loadedPluginCount: params.loadedPluginCount,
8163
+ totalPluginCount: params.totalPluginCount,
8164
+ completedAt: now2(),
8165
+ error: void 0
8166
+ };
8167
+ }
8168
+ markPluginHydrationError(error) {
8169
+ this.state.phase = "error";
8170
+ this.state.lastError = error;
8171
+ this.state.pluginHydration = {
8172
+ ...this.state.pluginHydration,
8173
+ state: "error",
8174
+ completedAt: now2(),
8175
+ error
8176
+ };
8177
+ }
8178
+ markChannelsPending() {
8179
+ this.state.channels = {
8180
+ state: "pending",
8181
+ enabled: []
8182
+ };
8183
+ }
8184
+ markChannelsReady(enabled) {
8185
+ this.state.channels = {
8186
+ state: "ready",
8187
+ enabled: [...enabled]
8188
+ };
8189
+ if (this.state.pluginHydration.state === "ready") {
8190
+ this.state.phase = "ready";
8191
+ this.state.lastError = void 0;
8192
+ }
8193
+ }
8194
+ markChannelsError(error) {
8195
+ this.state.phase = "error";
8196
+ this.state.lastError = error;
8197
+ this.state.channels = {
8198
+ state: "error",
8199
+ enabled: [...this.state.channels.enabled],
8200
+ error
8201
+ };
8202
+ }
8203
+ setRemoteState(state, message) {
8204
+ this.state.remote = {
8205
+ state,
8206
+ ...message ? { message } : {}
8207
+ };
8208
+ }
8209
+ markReady() {
8210
+ this.state.phase = "ready";
8211
+ this.state.lastError = void 0;
8212
+ }
8213
+ markError(error) {
8214
+ this.state.phase = "error";
8215
+ this.state.lastError = error;
8216
+ }
8217
+ };
8218
+
8219
+ // src/cli/commands/service-capability-hydration.ts
8220
+ import { getWorkspacePath as getWorkspacePath10, loadConfig as loadConfig17, resolveConfigSecrets as resolveConfigSecrets3 } from "@nextclaw/core";
8221
+ import {
8222
+ getPluginChannelBindings as getPluginChannelBindings4,
8223
+ getPluginUiMetadataFromRegistry
8224
+ } from "@nextclaw/openclaw-compat";
8225
+
8226
+ // src/cli/commands/plugin-reload.ts
8227
+ function buildPluginChannelBindingSignature(binding) {
8228
+ return `${binding.pluginId}:${binding.channelId}`;
8229
+ }
8230
+ function buildSortedBindingSignatures(bindings) {
8231
+ return bindings.map(buildPluginChannelBindingSignature).sort();
8232
+ }
8233
+ function buildSortedExtensionChannelIds(channels2) {
8234
+ return channels2.map((registration) => registration.channel.id).filter((id) => typeof id === "string" && id.trim().length > 0).sort();
8235
+ }
8236
+ function areSortedStringListsEqual(left, right) {
8237
+ if (left.length !== right.length) {
8238
+ return false;
8239
+ }
8240
+ for (let index = 0; index < left.length; index += 1) {
8241
+ if (left[index] !== right[index]) {
8242
+ return false;
8243
+ }
8244
+ }
8245
+ return true;
8246
+ }
8247
+ function readPluginIdFromPluginsEntryPath(path2) {
8248
+ const prefix = "plugins.entries.";
8249
+ if (!path2.startsWith(prefix)) {
8250
+ return null;
8251
+ }
8252
+ const suffix = path2.slice(prefix.length);
8253
+ const [pluginId] = suffix.split(".");
8254
+ return pluginId?.trim() ? pluginId.trim() : null;
8255
+ }
8256
+ function shouldRestartChannelsForPluginReload(params) {
8257
+ const currentBindingSignatures = buildSortedBindingSignatures(params.currentPluginChannelBindings);
8258
+ const nextBindingSignatures = buildSortedBindingSignatures(params.nextPluginChannelBindings);
8259
+ if (!areSortedStringListsEqual(currentBindingSignatures, nextBindingSignatures)) {
8260
+ return true;
8261
+ }
8262
+ const currentExtensionChannelIds = buildSortedExtensionChannelIds(params.currentExtensionChannels);
8263
+ const nextExtensionChannelIds = buildSortedExtensionChannelIds(params.nextExtensionChannels);
8264
+ if (!areSortedStringListsEqual(currentExtensionChannelIds, nextExtensionChannelIds)) {
8265
+ return true;
8266
+ }
8267
+ const channelPluginIds = /* @__PURE__ */ new Set();
8268
+ for (const binding of params.currentPluginChannelBindings) {
8269
+ channelPluginIds.add(binding.pluginId);
8270
+ }
8271
+ for (const binding of params.nextPluginChannelBindings) {
8272
+ channelPluginIds.add(binding.pluginId);
8273
+ }
8274
+ for (const path2 of params.changedPaths) {
8275
+ const pluginId = readPluginIdFromPluginsEntryPath(path2);
8276
+ if (pluginId && channelPluginIds.has(pluginId)) {
8277
+ return true;
8278
+ }
8279
+ }
8280
+ return false;
8281
+ }
8282
+
8283
+ // src/cli/commands/service-ui-shell-grace.ts
8284
+ import { setTimeout as delay } from "timers/promises";
8285
+ var DEFAULT_UI_SHELL_GRACE_MS = 3e3;
8286
+ async function waitForUiShellGraceWindow(uiStartup) {
8287
+ if (!uiStartup) {
8288
+ return;
8289
+ }
8162
8290
  await measureStartupAsync("service.ui_shell_grace_window", async () => {
8163
8291
  await delay(DEFAULT_UI_SHELL_GRACE_MS);
8164
8292
  });
8165
8293
  }
8166
8294
 
8295
+ // src/cli/commands/service-capability-hydration.ts
8296
+ function countEnabledPlugins(config2, workspaceDir) {
8297
+ return discoverPluginRegistryStatus(config2, workspaceDir).plugins.filter((plugin) => plugin.enabled).length;
8298
+ }
8299
+ async function hydrateServiceCapabilities(params) {
8300
+ await waitForUiShellGraceWindow(params.uiStartup);
8301
+ const nextConfig = resolveConfigSecrets3(loadConfig17(), { configPath: params.gateway.runtimeConfigPath });
8302
+ const nextWorkspace = getWorkspacePath10(nextConfig.agents.defaults.workspace);
8303
+ const totalPluginCount = countEnabledPlugins(nextConfig, nextWorkspace);
8304
+ let loadedPluginCount = 0;
8305
+ params.bootstrapStatus.markPluginHydrationRunning({
8306
+ totalPluginCount
8307
+ });
8308
+ params.bootstrapStatus.markChannelsPending();
8309
+ try {
8310
+ const nextPluginRegistry = await loadPluginRegistryProgressively(nextConfig, nextWorkspace, {
8311
+ onPluginProcessed: ({ loadedPluginCount: nextCount }) => {
8312
+ loadedPluginCount = nextCount;
8313
+ params.bootstrapStatus.markPluginHydrationProgress({
8314
+ loadedPluginCount: nextCount,
8315
+ totalPluginCount
8316
+ });
8317
+ }
8318
+ });
8319
+ logPluginDiagnostics(nextPluginRegistry);
8320
+ const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
8321
+ const nextPluginChannelBindings = getPluginChannelBindings4(nextPluginRegistry);
8322
+ const nextPluginUiMetadata = getPluginUiMetadataFromRegistry(nextPluginRegistry);
8323
+ const shouldRebuildChannels = shouldRestartChannelsForPluginReload({
8324
+ changedPaths: [],
8325
+ currentPluginChannelBindings: params.state.pluginChannelBindings,
8326
+ nextPluginChannelBindings,
8327
+ currentExtensionChannels: params.state.extensionRegistry.channels,
8328
+ nextExtensionChannels: nextExtensionRegistry.channels
8329
+ });
8330
+ params.state.pluginRegistry = nextPluginRegistry;
8331
+ params.state.extensionRegistry = nextExtensionRegistry;
8332
+ params.state.pluginChannelBindings = nextPluginChannelBindings;
8333
+ params.state.pluginUiMetadata = nextPluginUiMetadata;
8334
+ params.gateway.runtimePool.applyExtensionRegistry(nextExtensionRegistry);
8335
+ params.gateway.runtimePool.applyRuntimeConfig(nextConfig);
8336
+ params.getLiveUiNcpAgent()?.applyExtensionRegistry?.(nextExtensionRegistry);
8337
+ if (shouldRebuildChannels) {
8338
+ await params.gateway.reloader.rebuildChannels(nextConfig, { start: false });
8339
+ }
8340
+ params.uiStartup?.publish({ type: "config.updated", payload: { path: "channels" } });
8341
+ params.uiStartup?.publish({ type: "config.updated", payload: { path: "plugins" } });
8342
+ params.bootstrapStatus.markPluginHydrationReady({
8343
+ loadedPluginCount: loadedPluginCount || totalPluginCount,
8344
+ totalPluginCount
8345
+ });
8346
+ } catch (error) {
8347
+ const message = error instanceof Error ? error.message : String(error);
8348
+ params.bootstrapStatus.markPluginHydrationError(message);
8349
+ throw error;
8350
+ }
8351
+ }
8352
+
8353
+ // src/cli/commands/service-plugin-runtime-bridge.ts
8354
+ import { loadConfig as loadConfig18, resolveConfigSecrets as resolveConfigSecrets4, saveConfig as saveConfig10 } from "@nextclaw/core";
8355
+ import { setPluginRuntimeBridge } from "@nextclaw/openclaw-compat";
8356
+ function installPluginRuntimeBridge(params) {
8357
+ const { runtimePool, runtimeConfigPath, getPluginChannelBindings: getPluginChannelBindings7 } = params;
8358
+ setPluginRuntimeBridge({
8359
+ loadConfig: () => toPluginConfigView(resolveConfigSecrets4(loadConfig18(), { configPath: runtimeConfigPath }), getPluginChannelBindings7()),
8360
+ writeConfigFile: async (nextConfigView) => {
8361
+ if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
8362
+ throw new Error("plugin runtime writeConfigFile expects an object config");
8363
+ }
8364
+ const current = loadConfig18();
8365
+ const next = mergePluginConfigView(current, nextConfigView, getPluginChannelBindings7());
8366
+ saveConfig10(next);
8367
+ },
8368
+ dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
8369
+ const request = resolvePluginRuntimeRequest(ctx);
8370
+ if (!request) {
8371
+ return;
8372
+ }
8373
+ try {
8374
+ await dispatcherOptions.onReplyStart?.();
8375
+ const response = await runtimePool.processDirect(request);
8376
+ const replyText = typeof response === "string" ? response : String(response ?? "");
8377
+ if (replyText.trim()) {
8378
+ await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
8379
+ }
8380
+ } catch (error) {
8381
+ dispatcherOptions.onError?.(error);
8382
+ throw error;
8383
+ }
8384
+ }
8385
+ });
8386
+ }
8387
+ function resolvePluginRuntimeRequest(ctx) {
8388
+ const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
8389
+ const body = typeof ctx.Body === "string" ? ctx.Body : "";
8390
+ const content = (bodyForAgent || body).trim();
8391
+ const attachments = resolvePluginRuntimeAttachments(ctx);
8392
+ if (!content && attachments.length === 0) {
8393
+ return null;
8394
+ }
8395
+ const sessionKey = typeof ctx.SessionKey === "string" && ctx.SessionKey.trim().length > 0 ? ctx.SessionKey : void 0;
8396
+ const channel = typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel.trim().length > 0 ? ctx.OriginatingChannel : "cli";
8397
+ const chatId = typeof ctx.OriginatingTo === "string" && ctx.OriginatingTo.trim().length > 0 ? ctx.OriginatingTo : typeof ctx.SenderId === "string" && ctx.SenderId.trim().length > 0 ? ctx.SenderId : "direct";
8398
+ const agentId = typeof ctx.AgentId === "string" ? ctx.AgentId : void 0;
8399
+ const modelOverride = resolveModelOverride(ctx);
8400
+ const accountId = typeof ctx.AccountId === "string" && ctx.AccountId.trim().length > 0 ? ctx.AccountId : void 0;
8401
+ return {
8402
+ content,
8403
+ sessionKey,
8404
+ channel,
8405
+ chatId,
8406
+ agentId,
8407
+ attachments,
8408
+ metadata: {
8409
+ ...accountId ? { account_id: accountId } : {},
8410
+ ...modelOverride ? { model: modelOverride } : {}
8411
+ }
8412
+ };
8413
+ }
8414
+ function resolveModelOverride(ctx) {
8415
+ if (typeof ctx.Model === "string" && ctx.Model.trim().length > 0) {
8416
+ return ctx.Model.trim();
8417
+ }
8418
+ if (typeof ctx.AgentModel === "string" && ctx.AgentModel.trim().length > 0) {
8419
+ return ctx.AgentModel.trim();
8420
+ }
8421
+ return void 0;
8422
+ }
8423
+ function readStringList(value) {
8424
+ if (!Array.isArray(value)) {
8425
+ return [];
8426
+ }
8427
+ return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
8428
+ }
8429
+ function readOptionalString2(value) {
8430
+ if (typeof value !== "string") {
8431
+ return void 0;
8432
+ }
8433
+ const trimmed = value.trim();
8434
+ return trimmed || void 0;
8435
+ }
8436
+ function resolvePluginRuntimeAttachments(ctx) {
8437
+ const mediaPaths = readStringList(ctx.MediaPaths);
8438
+ const mediaUrls = readStringList(ctx.MediaUrls);
8439
+ const fallbackPath = readOptionalString2(ctx.MediaPath);
8440
+ const fallbackUrl = readOptionalString2(ctx.MediaUrl);
8441
+ const mediaTypes = readStringList(ctx.MediaTypes);
8442
+ const fallbackType = readOptionalString2(ctx.MediaType);
8443
+ const entryCount = Math.max(
8444
+ mediaPaths.length,
8445
+ mediaUrls.length,
8446
+ fallbackPath ? 1 : 0,
8447
+ fallbackUrl ? 1 : 0
8448
+ );
8449
+ const attachments = [];
8450
+ for (let index = 0; index < entryCount; index += 1) {
8451
+ const path2 = mediaPaths[index] ?? (index === 0 ? fallbackPath : void 0);
8452
+ const rawUrl = mediaUrls[index] ?? (index === 0 ? fallbackUrl : void 0);
8453
+ const url = rawUrl && rawUrl !== path2 ? rawUrl : void 0;
8454
+ const mimeType = mediaTypes[index] ?? fallbackType;
8455
+ if (!path2 && !url) {
8456
+ continue;
8457
+ }
8458
+ attachments.push({
8459
+ path: path2,
8460
+ url,
8461
+ mimeType,
8462
+ source: "plugin-runtime",
8463
+ status: path2 ? "ready" : "remote-only"
8464
+ });
8465
+ }
8466
+ return attachments;
8467
+ }
8468
+
8469
+ // src/cli/commands/service-plugin-reload.ts
8470
+ import { getWorkspacePath as getWorkspacePath11 } from "@nextclaw/core";
8471
+ import {
8472
+ getPluginChannelBindings as getPluginChannelBindings5,
8473
+ startPluginChannelGateways,
8474
+ stopPluginChannelGateways
8475
+ } from "@nextclaw/openclaw-compat";
8476
+ async function reloadServicePlugins(params) {
8477
+ const nextWorkspace = getWorkspacePath11(params.nextConfig.agents.defaults.workspace);
8478
+ const nextPluginRegistry = loadPluginRegistry(params.nextConfig, nextWorkspace);
8479
+ const nextExtensionRegistry = toExtensionRegistry(nextPluginRegistry);
8480
+ const nextPluginChannelBindings = getPluginChannelBindings5(nextPluginRegistry);
8481
+ const shouldRestartChannels = shouldRestartChannelsForPluginReload({
8482
+ changedPaths: params.changedPaths,
8483
+ currentPluginChannelBindings: params.pluginChannelBindings,
8484
+ nextPluginChannelBindings,
8485
+ currentExtensionChannels: params.extensionRegistry.channels,
8486
+ nextExtensionChannels: nextExtensionRegistry.channels
8487
+ });
8488
+ logPluginDiagnostics(nextPluginRegistry);
8489
+ let pluginGatewayHandles = params.pluginGatewayHandles;
8490
+ if (shouldRestartChannels) {
8491
+ await stopPluginChannelGateways(pluginGatewayHandles);
8492
+ const startedPluginGateways = await startPluginChannelGateways({
8493
+ registry: nextPluginRegistry,
8494
+ config: params.nextConfig,
8495
+ logger: params.pluginGatewayLogger
8496
+ });
8497
+ pluginGatewayHandles = startedPluginGateways.handles;
8498
+ params.logPluginGatewayDiagnostics(startedPluginGateways.diagnostics);
8499
+ }
8500
+ return {
8501
+ pluginRegistry: nextPluginRegistry,
8502
+ extensionRegistry: nextExtensionRegistry,
8503
+ pluginChannelBindings: nextPluginChannelBindings,
8504
+ pluginGatewayHandles,
8505
+ restartChannels: shouldRestartChannels
8506
+ };
8507
+ }
8508
+
8509
+ // src/cli/commands/service-gateway-bootstrap.ts
8510
+ var { loadConfig: loadConfig19, resolveConfigSecrets: resolveConfigSecrets5 } = NextclawCore2;
8511
+ function createBootstrapStatus(remoteEnabled) {
8512
+ const bootstrapStatus = new ServiceBootstrapStatusStore();
8513
+ bootstrapStatus.markPluginHydrationPending();
8514
+ bootstrapStatus.markChannelsPending();
8515
+ bootstrapStatus.setRemoteState(remoteEnabled ? "pending" : "disabled");
8516
+ return bootstrapStatus;
8517
+ }
8518
+ function createGatewayRuntimeState(gateway) {
8519
+ return {
8520
+ pluginRegistry: gateway.pluginRegistry,
8521
+ extensionRegistry: gateway.extensionRegistry,
8522
+ pluginChannelBindings: gateway.pluginChannelBindings,
8523
+ pluginUiMetadata: getPluginUiMetadataFromRegistry2(gateway.pluginRegistry),
8524
+ pluginGatewayHandles: []
8525
+ };
8526
+ }
8527
+ function configureGatewayPluginRuntime(params) {
8528
+ params.gateway.reloader.setApplyAgentRuntimeConfig((nextConfig) => params.gateway.runtimePool.applyRuntimeConfig(nextConfig));
8529
+ params.gateway.reloader.setReloadPlugins(async ({ config: nextConfig, changedPaths }) => {
8530
+ const result = await reloadServicePlugins({
8531
+ nextConfig,
8532
+ changedPaths,
8533
+ pluginRegistry: params.state.pluginRegistry,
8534
+ extensionRegistry: params.state.extensionRegistry,
8535
+ pluginChannelBindings: params.state.pluginChannelBindings,
8536
+ pluginGatewayHandles: params.state.pluginGatewayHandles,
8537
+ pluginGatewayLogger,
8538
+ logPluginGatewayDiagnostics
8539
+ });
8540
+ params.state.pluginRegistry = result.pluginRegistry;
8541
+ params.state.extensionRegistry = result.extensionRegistry;
8542
+ params.state.pluginChannelBindings = result.pluginChannelBindings;
8543
+ params.state.pluginUiMetadata = getPluginUiMetadataFromRegistry2(result.pluginRegistry);
8544
+ params.state.pluginGatewayHandles = result.pluginGatewayHandles;
8545
+ params.gateway.runtimePool.applyExtensionRegistry(result.extensionRegistry);
8546
+ params.getLiveUiNcpAgent()?.applyExtensionRegistry?.(result.extensionRegistry);
8547
+ params.gateway.runtimePool.applyRuntimeConfig(nextConfig);
8548
+ if (result.restartChannels) {
8549
+ console.log("Config reload: plugin channel gateways restarted.");
8550
+ }
8551
+ return { restartChannels: result.restartChannels };
8552
+ });
8553
+ params.gateway.reloader.setReloadMcp(async ({ config: nextConfig }) => {
8554
+ await params.getLiveUiNcpAgent()?.applyMcpConfig?.(nextConfig);
8555
+ });
8556
+ installPluginRuntimeBridge({
8557
+ runtimePool: params.gateway.runtimePool,
8558
+ runtimeConfigPath: params.gateway.runtimeConfigPath,
8559
+ getPluginChannelBindings: () => params.state.pluginChannelBindings
8560
+ });
8561
+ }
8562
+ function createDeferredGatewayStartupHooks(params) {
8563
+ return {
8564
+ hydrateCapabilities: async () => {
8565
+ await hydrateServiceCapabilities({
8566
+ uiStartup: params.uiStartup,
8567
+ gateway: params.gateway,
8568
+ state: params.state,
8569
+ bootstrapStatus: params.bootstrapStatus,
8570
+ getLiveUiNcpAgent: params.getLiveUiNcpAgent
8571
+ });
8572
+ },
8573
+ startPluginGateways: async () => {
8574
+ const startedPluginGateways = await startPluginChannelGateways2({
8575
+ registry: params.state.pluginRegistry,
8576
+ config: resolveConfigSecrets5(loadConfig19(), { configPath: params.gateway.runtimeConfigPath }),
8577
+ logger: pluginGatewayLogger
8578
+ });
8579
+ params.state.pluginGatewayHandles = startedPluginGateways.handles;
8580
+ logPluginGatewayDiagnostics(startedPluginGateways.diagnostics);
8581
+ },
8582
+ startChannels: async () => {
8583
+ await params.gateway.reloader.getChannels().startAll();
8584
+ const enabledChannels = params.gateway.reloader.getChannels().enabledChannels;
8585
+ if (enabledChannels.length > 0) {
8586
+ console.log(`\u2713 Channels enabled: ${enabledChannels.join(", ")}`);
8587
+ } else {
8588
+ console.log("Warning: No channels enabled");
8589
+ }
8590
+ params.bootstrapStatus.markChannelsReady(enabledChannels);
8591
+ params.bootstrapStatus.markReady();
8592
+ },
8593
+ wakeFromRestartSentinel: params.wakeFromRestartSentinel,
8594
+ onNcpAgentReady: (ncpAgent) => {
8595
+ params.setLiveUiNcpAgent(ncpAgent);
8596
+ }
8597
+ };
8598
+ }
8599
+
8167
8600
  // src/cli/commands/service.ts
8168
8601
  var {
8169
8602
  APP_NAME: APP_NAME3,
@@ -8171,16 +8604,16 @@ var {
8171
8604
  getConfigPath: getConfigPath10,
8172
8605
  getProvider,
8173
8606
  getProviderName,
8174
- getWorkspacePath: getWorkspacePath11,
8607
+ getWorkspacePath: getWorkspacePath12,
8175
8608
  LiteLLMProvider,
8176
- loadConfig: loadConfig18,
8609
+ loadConfig: loadConfig20,
8177
8610
  MessageBus: MessageBus2,
8178
- resolveConfigSecrets: resolveConfigSecrets4,
8611
+ resolveConfigSecrets: resolveConfigSecrets6,
8179
8612
  SessionManager: SessionManager2,
8180
8613
  parseAgentScopedSessionKey: parseAgentScopedSessionKey2
8181
- } = NextclawCore2;
8614
+ } = NextclawCore3;
8182
8615
  function createSkillsLoader(workspace) {
8183
- const ctor = NextclawCore2.SkillsLoader;
8616
+ const ctor = NextclawCore3.SkillsLoader;
8184
8617
  if (!ctor) {
8185
8618
  return null;
8186
8619
  }
@@ -8203,9 +8636,9 @@ var ServiceCommands = class {
8203
8636
  const applyLiveConfigReload = async () => {
8204
8637
  await this.applyLiveConfigReload?.();
8205
8638
  };
8206
- let pluginChannelBindings = [];
8207
- let pluginUiMetadata = [];
8208
- let pluginGatewayHandles = [];
8639
+ let runtimeState = null;
8640
+ const bootstrapStatus = createBootstrapStatus(shellContext.config.remote.enabled);
8641
+ const ncpSessionService = new UiSessionService(shellContext.sessionManager);
8209
8642
  const marketplaceInstaller = new ServiceMarketplaceInstaller({
8210
8643
  applyLiveConfigReload,
8211
8644
  runCliSubcommand: (args) => this.runCliSubcommand(args),
@@ -8223,18 +8656,20 @@ var ServiceCommands = class {
8223
8656
  uiConfig: shellContext.uiConfig,
8224
8657
  uiStaticDir: shellContext.uiStaticDir,
8225
8658
  cronService: shellContext.cron,
8226
- getConfig: () => resolveConfigSecrets4(loadConfig18(), { configPath: shellContext.runtimeConfigPath }),
8659
+ getConfig: () => resolveConfigSecrets6(loadConfig20(), { configPath: shellContext.runtimeConfigPath }),
8227
8660
  configPath: getConfigPath10(),
8228
8661
  productVersion: getPackageVersion(),
8229
- getPluginChannelBindings: () => pluginChannelBindings,
8230
- getPluginUiMetadata: () => pluginUiMetadata,
8662
+ getPluginChannelBindings: () => runtimeState?.pluginChannelBindings ?? [],
8663
+ getPluginUiMetadata: () => runtimeState?.pluginUiMetadata ?? [],
8231
8664
  marketplace: { apiBaseUrl: process.env.NEXTCLAW_MARKETPLACE_API_BASE, installer: marketplaceInstaller },
8232
8665
  remoteAccess,
8666
+ getBootstrapStatus: () => bootstrapStatus.getStatus(),
8233
8667
  openBrowserWindow: shellContext.uiConfig.open,
8234
- applyLiveConfigReload
8668
+ applyLiveConfigReload,
8669
+ ncpSessionService
8235
8670
  })
8236
8671
  );
8237
- await waitForUiShellGraceWindow(uiStartup);
8672
+ bootstrapStatus.markShellReady();
8238
8673
  await waitForNextTick();
8239
8674
  const gateway = measureStartupSync(
8240
8675
  "service.create_gateway_startup_context",
@@ -8243,51 +8678,23 @@ var ServiceCommands = class {
8243
8678
  uiOverrides: options.uiOverrides,
8244
8679
  allowMissingProvider: options.allowMissingProvider,
8245
8680
  uiStaticDir: options.uiStaticDir,
8681
+ initialPluginRegistry: createEmptyPluginRegistry(),
8246
8682
  makeProvider: (config2, providerOptions) => providerOptions?.allowMissing === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2),
8247
8683
  makeMissingProvider: (config2) => this.makeMissingProvider(config2),
8248
8684
  requestRestart: (params) => this.deps.requestRestart(params)
8249
8685
  })
8250
8686
  );
8251
8687
  this.applyLiveConfigReload = gateway.applyLiveConfigReload;
8252
- let { pluginRegistry, extensionRegistry } = gateway;
8253
- pluginChannelBindings = gateway.pluginChannelBindings;
8254
- pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
8688
+ const gatewayRuntimeState = createGatewayRuntimeState(gateway);
8689
+ runtimeState = gatewayRuntimeState;
8255
8690
  uiStartup?.publish({ type: "config.updated", payload: { path: "channels" } });
8256
8691
  uiStartup?.publish({ type: "config.updated", payload: { path: "plugins" } });
8257
- gateway.reloader.setApplyAgentRuntimeConfig((nextConfig) => gateway.runtimePool.applyRuntimeConfig(nextConfig));
8258
- gateway.reloader.setReloadPlugins(async ({ config: nextConfig, changedPaths }) => {
8259
- const result = await reloadServicePlugins({
8260
- nextConfig,
8261
- changedPaths,
8262
- pluginRegistry,
8263
- extensionRegistry,
8264
- pluginChannelBindings,
8265
- pluginGatewayHandles,
8266
- pluginGatewayLogger,
8267
- logPluginGatewayDiagnostics
8268
- });
8269
- pluginRegistry = result.pluginRegistry;
8270
- extensionRegistry = result.extensionRegistry;
8271
- pluginChannelBindings = result.pluginChannelBindings;
8272
- pluginGatewayHandles = result.pluginGatewayHandles;
8273
- gateway.runtimePool.applyExtensionRegistry(result.extensionRegistry);
8274
- this.liveUiNcpAgent?.applyExtensionRegistry?.(result.extensionRegistry);
8275
- gateway.runtimePool.applyRuntimeConfig(nextConfig);
8276
- if (result.restartChannels) {
8277
- console.log("Config reload: plugin channel gateways restarted.");
8278
- }
8279
- return { restartChannels: result.restartChannels };
8280
- });
8281
- gateway.reloader.setReloadMcp(async ({ config: nextConfig }) => {
8282
- await this.liveUiNcpAgent?.applyMcpConfig?.(nextConfig);
8283
- });
8284
- installPluginRuntimeBridge({
8285
- runtimePool: gateway.runtimePool,
8286
- runtimeConfigPath: gateway.runtimeConfigPath,
8287
- pluginChannelBindings
8692
+ configureGatewayPluginRuntime({
8693
+ gateway,
8694
+ state: gatewayRuntimeState,
8695
+ getLiveUiNcpAgent: () => this.liveUiNcpAgent
8288
8696
  });
8289
- if (gateway.reloader.getChannels().enabledChannels.length) console.log(`\u2713 Channels enabled: ${gateway.reloader.getChannels().enabledChannels.join(", ")}`);
8290
- else console.log("Warning: No channels enabled");
8697
+ console.log("\u2713 Capability hydration: scheduled in background");
8291
8698
  await measureStartupAsync(
8292
8699
  "service.start_gateway_support_services",
8293
8700
  async () => await startGatewaySupportServices({
@@ -8298,6 +8705,19 @@ var ServiceCommands = class {
8298
8705
  startHeartbeat: () => gateway.heartbeat.start()
8299
8706
  })
8300
8707
  );
8708
+ const deferredGatewayStartupHooks = createDeferredGatewayStartupHooks({
8709
+ uiStartup,
8710
+ gateway,
8711
+ state: gatewayRuntimeState,
8712
+ bootstrapStatus,
8713
+ getLiveUiNcpAgent: () => this.liveUiNcpAgent,
8714
+ setLiveUiNcpAgent: (ncpAgent) => {
8715
+ this.liveUiNcpAgent = ncpAgent;
8716
+ },
8717
+ wakeFromRestartSentinel: async () => {
8718
+ await this.wakeFromRestartSentinel({ bus: gateway.bus, sessionManager: gateway.sessionManager });
8719
+ }
8720
+ });
8301
8721
  logStartupTrace("service.start_gateway.runtime_loop_begin");
8302
8722
  await runGatewayRuntimeLoop({
8303
8723
  runtimePool: gateway.runtimePool,
@@ -8308,34 +8728,26 @@ var ServiceCommands = class {
8308
8728
  providerManager: gateway.providerManager,
8309
8729
  cronService: gateway.cron,
8310
8730
  gatewayController: gateway.gatewayController,
8311
- getConfig: () => resolveConfigSecrets4(loadConfig18(), { configPath: gateway.runtimeConfigPath }),
8312
- getExtensionRegistry: () => extensionRegistry,
8731
+ getConfig: () => resolveConfigSecrets6(loadConfig20(), { configPath: gateway.runtimeConfigPath }),
8732
+ getExtensionRegistry: () => gatewayRuntimeState.extensionRegistry,
8313
8733
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
8314
- registry: pluginRegistry,
8734
+ registry: gatewayRuntimeState.pluginRegistry,
8315
8735
  channel,
8316
- cfg: resolveConfigSecrets4(loadConfig18(), { configPath: gateway.runtimeConfigPath }),
8736
+ cfg: resolveConfigSecrets6(loadConfig20(), { configPath: gateway.runtimeConfigPath }),
8317
8737
  accountId
8318
8738
  }),
8319
- startPluginGateways: async () => {
8320
- const startedPluginGateways = await startPluginChannelGateways2({
8321
- registry: pluginRegistry,
8322
- config: gateway.config,
8323
- logger: pluginGatewayLogger
8324
- });
8325
- pluginGatewayHandles = startedPluginGateways.handles;
8326
- logPluginGatewayDiagnostics(startedPluginGateways.diagnostics);
8327
- },
8328
- startChannels: async () => {
8329
- await gateway.reloader.getChannels().startAll();
8330
- },
8331
- wakeFromRestartSentinel: async () => {
8332
- await this.wakeFromRestartSentinel({ bus: gateway.bus, sessionManager: gateway.sessionManager });
8333
- },
8334
- onNcpAgentReady: (ncpAgent) => {
8335
- this.liveUiNcpAgent = ncpAgent;
8336
- }
8739
+ hydrateCapabilities: deferredGatewayStartupHooks.hydrateCapabilities,
8740
+ startPluginGateways: deferredGatewayStartupHooks.startPluginGateways,
8741
+ startChannels: deferredGatewayStartupHooks.startChannels,
8742
+ wakeFromRestartSentinel: deferredGatewayStartupHooks.wakeFromRestartSentinel,
8743
+ onNcpAgentReady: deferredGatewayStartupHooks.onNcpAgentReady
8337
8744
  }),
8338
8745
  onDeferredStartupError: (error) => {
8746
+ const message = error instanceof Error ? error.message : String(error);
8747
+ bootstrapStatus.markError(message);
8748
+ if (bootstrapStatus.getStatus().pluginHydration.state === "running") {
8749
+ bootstrapStatus.markPluginHydrationError(message);
8750
+ }
8339
8751
  console.error(`Deferred startup failed: ${error instanceof Error ? error.message : String(error)}`);
8340
8752
  },
8341
8753
  cleanup: async () => {
@@ -8343,7 +8755,7 @@ var ServiceCommands = class {
8343
8755
  this.liveUiNcpAgent = null;
8344
8756
  await uiStartup?.deferredNcpAgent.close();
8345
8757
  await gateway.remoteModule?.stop();
8346
- await stopPluginChannelGateways2(pluginGatewayHandles);
8758
+ await stopPluginChannelGateways2(runtimeState?.pluginGatewayHandles ?? []);
8347
8759
  setPluginRuntimeBridge2(null);
8348
8760
  }
8349
8761
  });
@@ -8478,7 +8890,7 @@ var ServiceCommands = class {
8478
8890
  });
8479
8891
  }
8480
8892
  async runForeground(options) {
8481
- const config2 = loadConfig18();
8893
+ const config2 = loadConfig20();
8482
8894
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
8483
8895
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
8484
8896
  if (options.open) {
@@ -8491,7 +8903,7 @@ var ServiceCommands = class {
8491
8903
  });
8492
8904
  }
8493
8905
  async startService(options) {
8494
- const config2 = loadConfig18();
8906
+ const config2 = loadConfig20();
8495
8907
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
8496
8908
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
8497
8909
  const apiUrl = `${uiUrl}/api`;
@@ -8940,7 +9352,7 @@ var ServiceCommands = class {
8940
9352
  console.log(` - If you need to stop the service, run: ${APP_NAME3} stop`);
8941
9353
  }
8942
9354
  installBuiltinMarketplaceSkill(slug, force) {
8943
- const workspace = getWorkspacePath11(loadConfig18().agents.defaults.workspace);
9355
+ const workspace = getWorkspacePath12(loadConfig20().agents.defaults.workspace);
8944
9356
  const destination = join7(workspace, "skills", slug);
8945
9357
  const destinationSkillFile = join7(destination, "SKILL.md");
8946
9358
  if (existsSync11(destinationSkillFile) && !force) {
@@ -9204,7 +9616,7 @@ function resolveSkillsInstallWorkdir(params) {
9204
9616
  if (params.explicitWorkdir) {
9205
9617
  return expandHome2(params.explicitWorkdir);
9206
9618
  }
9207
- return getWorkspacePath12(params.configuredWorkspace);
9619
+ return getWorkspacePath13(params.configuredWorkspace);
9208
9620
  }
9209
9621
  var CliRuntime = class {
9210
9622
  logo;
@@ -9449,7 +9861,7 @@ var CliRuntime = class {
9449
9861
  const force = Boolean(options.force);
9450
9862
  const configPath = getConfigPath11();
9451
9863
  const createdConfig = initializeConfigIfMissing(configPath);
9452
- const config2 = loadConfig19();
9864
+ const config2 = loadConfig21();
9453
9865
  const workspaceSetting = config2.agents.defaults.workspace;
9454
9866
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir10(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
9455
9867
  const workspaceExisted = existsSync13(workspacePath);
@@ -9576,15 +9988,15 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
9576
9988
  }
9577
9989
  async agent(opts) {
9578
9990
  const configPath = getConfigPath11();
9579
- const config2 = resolveConfigSecrets5(loadConfig19(), { configPath });
9580
- const workspace = getWorkspacePath12(config2.agents.defaults.workspace);
9991
+ const config2 = resolveConfigSecrets7(loadConfig21(), { configPath });
9992
+ const workspace = getWorkspacePath13(config2.agents.defaults.workspace);
9581
9993
  const pluginRegistry = loadPluginRegistry(config2, workspace);
9582
9994
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
9583
9995
  logPluginDiagnostics(pluginRegistry);
9584
- const pluginChannelBindings = getPluginChannelBindings5(pluginRegistry);
9996
+ const pluginChannelBindings = getPluginChannelBindings6(pluginRegistry);
9585
9997
  setPluginRuntimeBridge3({
9586
9998
  loadConfig: () => toPluginConfigView(
9587
- resolveConfigSecrets5(loadConfig19(), { configPath }),
9999
+ resolveConfigSecrets7(loadConfig21(), { configPath }),
9588
10000
  pluginChannelBindings
9589
10001
  ),
9590
10002
  writeConfigFile: async (nextConfigView) => {
@@ -9593,7 +10005,7 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
9593
10005
  "plugin runtime writeConfigFile expects an object config"
9594
10006
  );
9595
10007
  }
9596
- const current = loadConfig19();
10008
+ const current = loadConfig21();
9597
10009
  const next = mergePluginConfigView(
9598
10010
  current,
9599
10011
  nextConfigView,
@@ -9625,7 +10037,7 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
9625
10037
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints3({
9626
10038
  registry: pluginRegistry,
9627
10039
  channel,
9628
- cfg: resolveConfigSecrets5(loadConfig19(), { configPath }),
10040
+ cfg: resolveConfigSecrets7(loadConfig21(), { configPath }),
9629
10041
  accountId
9630
10042
  })
9631
10043
  });
@@ -9820,7 +10232,7 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
9820
10232
  await this.diagnosticsCommands.doctor(opts);
9821
10233
  }
9822
10234
  async skillsInstall(options) {
9823
- const config2 = loadConfig19();
10235
+ const config2 = loadConfig21();
9824
10236
  const workdir = resolveSkillsInstallWorkdir({
9825
10237
  explicitWorkdir: options.workdir,
9826
10238
  configuredWorkspace: config2.agents.defaults.workspace