nextclaw 0.16.0 → 0.16.2

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 (43) hide show
  1. package/dist/cli/index.js +355 -968
  2. package/package.json +8 -8
  3. package/ui-dist/assets/{ChannelsList-BqsOYnXz.js → ChannelsList-CKl1Zg8f.js} +3 -3
  4. package/ui-dist/assets/ChatPage-BJgO27mk.js +37 -0
  5. package/ui-dist/assets/{DocBrowser-BmL0QXBZ.js → DocBrowser-DYRBs4-z.js} +1 -1
  6. package/ui-dist/assets/{LogoBadge-C1HiPZPf.js → LogoBadge-33Qlv3Hg.js} +1 -1
  7. package/ui-dist/assets/MarketplacePage-B8BZVtjV.js +49 -0
  8. package/ui-dist/assets/{McpMarketplacePage-CLHFnNBd.js → McpMarketplacePage-BRuE5fJJ.js} +2 -2
  9. package/ui-dist/assets/{ModelConfig-LQSR58tc.js → ModelConfig-BiFblwO-.js} +1 -1
  10. package/ui-dist/assets/ProvidersList-9goRgHE4.js +1 -0
  11. package/ui-dist/assets/RemoteAccessPage-5vCxZPS6.js +1 -0
  12. package/ui-dist/assets/RuntimeConfig-BmDFHBdW.js +1 -0
  13. package/ui-dist/assets/{SearchConfig-Chzo_JGs.js → SearchConfig-CJx5CKwG.js} +1 -1
  14. package/ui-dist/assets/{SecretsConfig-CEIbjZYA.js → SecretsConfig-B91efXoK.js} +2 -2
  15. package/ui-dist/assets/SessionsConfig-CbFPVmx3.js +2 -0
  16. package/ui-dist/assets/index-BtAuUyww.css +1 -0
  17. package/ui-dist/assets/index-COJomMe9.js +8 -0
  18. package/ui-dist/assets/{label-GACO2RzW.js → label-BnSDpjhL.js} +1 -1
  19. package/ui-dist/assets/ncp-session-adapter-w8ZHprab.js +1 -0
  20. package/ui-dist/assets/{page-layout-DjXaK3A3.js → page-layout-B1RIu5-r.js} +1 -1
  21. package/ui-dist/assets/popover-ChzbCIfO.js +1 -0
  22. package/ui-dist/assets/security-config-eYa6Ovfa.js +1 -0
  23. package/ui-dist/assets/skeleton-D4Eyop0R.js +1 -0
  24. package/ui-dist/assets/{status-dot-IWEBezqb.js → status-dot-CrCw5tkJ.js} +1 -1
  25. package/ui-dist/assets/{switch-DCHAJSrA.js → switch-C3vVTpfU.js} +1 -1
  26. package/ui-dist/assets/tabs-custom-Ilrgt6n1.js +1 -0
  27. package/ui-dist/assets/useConfirmDialog-BeaFLDO8.js +1 -0
  28. package/ui-dist/assets/{vendor-CNhxtHCf.js → vendor-waGu-koL.js} +101 -86
  29. package/ui-dist/index.html +3 -3
  30. package/ui-dist/assets/ChatPage-CJBYKR-Y.js +0 -38
  31. package/ui-dist/assets/MarketplacePage-BIRP0NRS.js +0 -49
  32. package/ui-dist/assets/ProvidersList-CwI-mxah.js +0 -1
  33. package/ui-dist/assets/RemoteAccessPage-Cw5BqZb6.js +0 -1
  34. package/ui-dist/assets/RuntimeConfig-DbowSRAb.js +0 -1
  35. package/ui-dist/assets/SessionsConfig-BR8GfGWL.js +0 -2
  36. package/ui-dist/assets/chat-message-CPG7zxRR.js +0 -3
  37. package/ui-dist/assets/index-j6A_-1b6.js +0 -8
  38. package/ui-dist/assets/index-kaPUhd-8.css +0 -1
  39. package/ui-dist/assets/popover-DTaFiTmU.js +0 -1
  40. package/ui-dist/assets/security-config-Dk-yoKvK.js +0 -1
  41. package/ui-dist/assets/skeleton-Dm2xOBSA.js +0 -1
  42. package/ui-dist/assets/tabs-custom-DKSbDSB9.js +0 -1
  43. package/ui-dist/assets/useConfirmDialog-ByJ8A8n7.js +0 -1
package/dist/cli/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig as loadConfig19,
11
11
  saveConfig as saveConfig11,
12
12
  getConfigPath as getConfigPath11,
13
- getDataDir as getDataDir11,
13
+ getDataDir as getDataDir10,
14
14
  getWorkspacePath as getWorkspacePath12,
15
15
  expandHome as expandHome2,
16
16
  MessageBus as MessageBus3,
@@ -27,8 +27,8 @@ import {
27
27
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints3,
28
28
  setPluginRuntimeBridge as setPluginRuntimeBridge3
29
29
  } from "@nextclaw/openclaw-compat";
30
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
31
- import { join as join10, resolve as resolve14 } from "path";
30
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
31
+ import { join as join9, resolve as resolve14 } from "path";
32
32
  import { createInterface as createInterface3 } from "readline";
33
33
  import { fileURLToPath as fileURLToPath5 } from "url";
34
34
  import { spawn as spawn4 } from "child_process";
@@ -75,9 +75,9 @@ var RestartCoordinator = class {
75
75
  message: "Restart already scheduled; skipping duplicate request."
76
76
  };
77
77
  }
78
- const delay = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
78
+ const delay2 = typeof request.delayMs === "number" && Number.isFinite(request.delayMs) ? Math.max(0, Math.floor(request.delayMs)) : 100;
79
79
  this.exitScheduled = true;
80
- this.deps.scheduleProcessExit(delay, reason);
80
+ this.deps.scheduleProcessExit(delay2, reason);
81
81
  return {
82
82
  status: "exit-scheduled",
83
83
  message: `Restart scheduled (${reason}).`
@@ -197,6 +197,46 @@ function parseSessionKey(sessionKey) {
197
197
  };
198
198
  }
199
199
 
200
+ // src/cli/startup-trace.ts
201
+ var STARTUP_TRACE_ENABLED = process.env.NEXTCLAW_STARTUP_TRACE === "1";
202
+ var STARTUP_TRACE_ORIGIN_MS = Date.now();
203
+ function formatFields(fields) {
204
+ if (!fields) {
205
+ return "";
206
+ }
207
+ const parts = Object.entries(fields).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}=${String(value)}`);
208
+ return parts.length > 0 ? ` ${parts.join(" ")}` : "";
209
+ }
210
+ function logStartupTrace(step, fields) {
211
+ if (!STARTUP_TRACE_ENABLED) {
212
+ return;
213
+ }
214
+ const elapsedMs = Date.now() - STARTUP_TRACE_ORIGIN_MS;
215
+ console.log(`[startup-trace] +${elapsedMs}ms ${step}${formatFields(fields)}`);
216
+ }
217
+ function measureStartupSync(step, fn, fields) {
218
+ const startedAt = Date.now();
219
+ try {
220
+ return fn();
221
+ } finally {
222
+ logStartupTrace(step, {
223
+ ...fields,
224
+ duration_ms: Date.now() - startedAt
225
+ });
226
+ }
227
+ }
228
+ async function measureStartupAsync(step, fn, fields) {
229
+ const startedAt = Date.now();
230
+ try {
231
+ return await fn();
232
+ } finally {
233
+ logStartupTrace(step, {
234
+ ...fields,
235
+ duration_ms: Date.now() - startedAt
236
+ });
237
+ }
238
+ }
239
+
200
240
  // src/cli/skills/marketplace.ts
201
241
  import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
202
242
  import { basename, dirname, isAbsolute, join, relative, resolve as resolve3 } from "path";
@@ -3953,12 +3993,13 @@ import {
3953
3993
  startPluginChannelGateways as startPluginChannelGateways2,
3954
3994
  stopPluginChannelGateways as stopPluginChannelGateways2
3955
3995
  } from "@nextclaw/openclaw-compat";
3956
- import { appendFileSync, closeSync as closeSync2, cpSync as cpSync2, existsSync as existsSync12, mkdirSync as mkdirSync6, openSync as openSync2 } from "fs";
3957
- import { dirname as dirname3, join as join8, resolve as resolve12 } from "path";
3996
+ import { appendFileSync, closeSync as closeSync2, cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync5, openSync as openSync2 } from "fs";
3997
+ import { dirname as dirname3, join as join7, resolve as resolve12 } from "path";
3958
3998
  import { spawn as spawn3 } from "child_process";
3959
3999
  import { request as httpRequest } from "http";
3960
4000
  import { request as httpsRequest } from "https";
3961
4001
  import { createServer as createNetServer2 } from "net";
4002
+ import { setImmediate as waitForNextTick } from "timers/promises";
3962
4003
  import chokidar from "chokidar";
3963
4004
 
3964
4005
  // src/cli/missing-provider.ts
@@ -6893,11 +6934,11 @@ var GatewayControllerImpl = class {
6893
6934
  await this.deps.requestRestart(options);
6894
6935
  return;
6895
6936
  }
6896
- const delay = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
6937
+ const delay2 = typeof options?.delayMs === "number" && Number.isFinite(options.delayMs) ? Math.max(0, options.delayMs) : 100;
6897
6938
  console.log(`Gateway restart requested via tool${options?.reason ? ` (${options.reason})` : ""}.`);
6898
6939
  setTimeout(() => {
6899
6940
  process.exit(0);
6900
- }, delay);
6941
+ }, delay2);
6901
6942
  }
6902
6943
  status() {
6903
6944
  return {
@@ -7776,30 +7817,67 @@ var {
7776
7817
  saveConfig: saveConfig10,
7777
7818
  SessionManager
7778
7819
  } = NextclawCore;
7820
+ function createGatewayShellContext(params) {
7821
+ const runtimeConfigPath = getConfigPath9();
7822
+ const config2 = resolveConfigSecrets3(loadConfig17(), { configPath: runtimeConfigPath });
7823
+ const workspace = getWorkspacePath10(config2.agents.defaults.workspace);
7824
+ const cronStorePath = join6(getDataDir8(), "cron", "jobs.json");
7825
+ const cron2 = new CronService2(cronStorePath);
7826
+ const uiConfig = resolveUiConfig(config2, params.uiOverrides);
7827
+ const uiStaticDir = params.uiStaticDir === void 0 ? resolveUiStaticDir() : params.uiStaticDir;
7828
+ const remoteModule = createManagedRemoteModuleForUi({
7829
+ loadConfig: () => resolveConfigSecrets3(loadConfig17(), { configPath: runtimeConfigPath }),
7830
+ uiConfig
7831
+ });
7832
+ return {
7833
+ runtimeConfigPath,
7834
+ config: config2,
7835
+ workspace,
7836
+ cron: cron2,
7837
+ uiConfig,
7838
+ uiStaticDir,
7839
+ remoteModule
7840
+ };
7841
+ }
7779
7842
  function createGatewayStartupContext(params) {
7780
7843
  const state = {};
7781
- state.runtimeConfigPath = getConfigPath9();
7782
- state.config = resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath });
7783
- state.workspace = getWorkspacePath10(state.config.agents.defaults.workspace);
7784
- state.pluginRegistry = loadPluginRegistry(state.config, state.workspace);
7785
- state.pluginChannelBindings = getPluginChannelBindings4(state.pluginRegistry);
7786
- state.extensionRegistry = toExtensionRegistry(state.pluginRegistry);
7844
+ const shellContext = params.shellContext ?? createGatewayShellContext({
7845
+ uiOverrides: params.uiOverrides,
7846
+ uiStaticDir: params.uiStaticDir
7847
+ });
7848
+ state.runtimeConfigPath = shellContext.runtimeConfigPath;
7849
+ state.config = shellContext.config;
7850
+ state.workspace = shellContext.workspace;
7851
+ state.cron = shellContext.cron;
7852
+ state.uiConfig = shellContext.uiConfig;
7853
+ state.uiStaticDir = shellContext.uiStaticDir;
7854
+ state.remoteModule = shellContext.remoteModule;
7855
+ state.pluginRegistry = measureStartupSync(
7856
+ "service.gateway_context.load_plugin_registry",
7857
+ () => loadPluginRegistry(state.config, state.workspace)
7858
+ );
7859
+ state.pluginChannelBindings = measureStartupSync(
7860
+ "service.gateway_context.get_plugin_channel_bindings",
7861
+ () => getPluginChannelBindings4(state.pluginRegistry)
7862
+ );
7863
+ state.extensionRegistry = measureStartupSync(
7864
+ "service.gateway_context.to_extension_registry",
7865
+ () => toExtensionRegistry(state.pluginRegistry)
7866
+ );
7787
7867
  logPluginDiagnostics(state.pluginRegistry);
7788
7868
  state.bus = new MessageBus();
7789
7869
  const provider = params.allowMissingProvider === true ? params.makeProvider(state.config, { allowMissing: true }) : params.makeProvider(state.config);
7790
- state.providerManager = new ProviderManager({
7791
- defaultProvider: provider ?? params.makeMissingProvider(state.config),
7792
- config: state.config
7793
- });
7794
- state.sessionManager = new SessionManager(state.workspace);
7795
- const cronStorePath = join6(getDataDir8(), "cron", "jobs.json");
7796
- state.cron = new CronService2(cronStorePath);
7797
- state.uiConfig = resolveUiConfig(state.config, params.uiOverrides);
7798
- state.uiStaticDir = params.uiStaticDir === void 0 ? resolveUiStaticDir() : params.uiStaticDir;
7799
- state.remoteModule = createManagedRemoteModuleForUi({
7800
- loadConfig: () => resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7801
- uiConfig: state.uiConfig
7802
- });
7870
+ state.providerManager = measureStartupSync(
7871
+ "service.gateway_context.provider_manager",
7872
+ () => new ProviderManager({
7873
+ defaultProvider: provider ?? params.makeMissingProvider(state.config),
7874
+ config: state.config
7875
+ })
7876
+ );
7877
+ state.sessionManager = measureStartupSync(
7878
+ "service.gateway_context.session_manager",
7879
+ () => new SessionManager(state.workspace)
7880
+ );
7803
7881
  if (!provider) {
7804
7882
  console.warn(
7805
7883
  "Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set."
@@ -7811,62 +7889,71 @@ function createGatewayStartupContext(params) {
7811
7889
  state.sessionManager,
7812
7890
  state.extensionRegistry.channels
7813
7891
  );
7814
- state.reloader = new ConfigReloader({
7815
- initialConfig: state.config,
7816
- channels: channels2,
7817
- bus: state.bus,
7818
- sessionManager: state.sessionManager,
7819
- providerManager: state.providerManager,
7820
- makeProvider: (nextConfig) => params.makeProvider(nextConfig, { allowMissing: true }) ?? params.makeMissingProvider(nextConfig),
7821
- loadConfig: () => resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7822
- resolveChannelConfig: (nextConfig) => resolveChannelConfigView(nextConfig, state.pluginChannelBindings),
7823
- getExtensionChannels: () => state.extensionRegistry.channels,
7824
- onRestartRequired: (paths) => {
7825
- void params.requestRestart({
7826
- reason: `config reload requires restart: ${paths.join(", ")}`,
7827
- manualMessage: `Config changes require restart: ${paths.join(", ")}`,
7828
- strategy: "background-service-or-manual"
7829
- });
7830
- }
7831
- });
7892
+ state.reloader = measureStartupSync(
7893
+ "service.gateway_context.config_reloader",
7894
+ () => new ConfigReloader({
7895
+ initialConfig: state.config,
7896
+ channels: channels2,
7897
+ bus: state.bus,
7898
+ sessionManager: state.sessionManager,
7899
+ providerManager: state.providerManager,
7900
+ makeProvider: (nextConfig) => params.makeProvider(nextConfig, { allowMissing: true }) ?? params.makeMissingProvider(nextConfig),
7901
+ loadConfig: () => resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7902
+ resolveChannelConfig: (nextConfig) => resolveChannelConfigView(nextConfig, state.pluginChannelBindings),
7903
+ getExtensionChannels: () => state.extensionRegistry.channels,
7904
+ onRestartRequired: (paths) => {
7905
+ void params.requestRestart({
7906
+ reason: `config reload requires restart: ${paths.join(", ")}`,
7907
+ manualMessage: `Config changes require restart: ${paths.join(", ")}`,
7908
+ strategy: "background-service-or-manual"
7909
+ });
7910
+ }
7911
+ })
7912
+ );
7832
7913
  state.applyLiveConfigReload = async () => {
7833
7914
  await state.reloader.applyReloadPlan(resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }));
7834
7915
  };
7835
- state.gatewayController = new GatewayControllerImpl({
7836
- reloader: state.reloader,
7837
- cron: state.cron,
7838
- sessionManager: state.sessionManager,
7839
- getConfigPath: getConfigPath9,
7840
- saveConfig: saveConfig10,
7841
- requestRestart: async (options) => {
7842
- await params.requestRestart({
7843
- reason: options?.reason ?? "gateway tool restart",
7844
- manualMessage: "Restart the gateway to apply changes.",
7845
- strategy: "background-service-or-exit",
7846
- delayMs: options?.delayMs,
7847
- silentOnServiceRestart: true
7848
- });
7849
- }
7850
- });
7851
- state.runtimePool = new GatewayAgentRuntimePool({
7852
- bus: state.bus,
7853
- providerManager: state.providerManager,
7854
- sessionManager: state.sessionManager,
7855
- config: state.config,
7856
- cronService: state.cron,
7857
- restrictToWorkspace: state.config.tools.restrictToWorkspace,
7858
- searchConfig: state.config.search,
7859
- execConfig: state.config.tools.exec,
7860
- contextConfig: state.config.agents.context,
7861
- gatewayController: state.gatewayController,
7862
- extensionRegistry: state.extensionRegistry,
7863
- resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
7864
- registry: state.pluginRegistry,
7865
- channel,
7866
- cfg: resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7867
- accountId
7916
+ state.gatewayController = measureStartupSync(
7917
+ "service.gateway_context.gateway_controller",
7918
+ () => new GatewayControllerImpl({
7919
+ reloader: state.reloader,
7920
+ cron: state.cron,
7921
+ sessionManager: state.sessionManager,
7922
+ getConfigPath: getConfigPath9,
7923
+ saveConfig: saveConfig10,
7924
+ requestRestart: async (options) => {
7925
+ await params.requestRestart({
7926
+ reason: options?.reason ?? "gateway tool restart",
7927
+ manualMessage: "Restart the gateway to apply changes.",
7928
+ strategy: "background-service-or-exit",
7929
+ delayMs: options?.delayMs,
7930
+ silentOnServiceRestart: true
7931
+ });
7932
+ }
7868
7933
  })
7869
- });
7934
+ );
7935
+ state.runtimePool = measureStartupSync(
7936
+ "service.gateway_context.runtime_pool",
7937
+ () => new GatewayAgentRuntimePool({
7938
+ bus: state.bus,
7939
+ providerManager: state.providerManager,
7940
+ sessionManager: state.sessionManager,
7941
+ config: state.config,
7942
+ cronService: state.cron,
7943
+ restrictToWorkspace: state.config.tools.restrictToWorkspace,
7944
+ searchConfig: state.config.search,
7945
+ execConfig: state.config.tools.exec,
7946
+ contextConfig: state.config.agents.context,
7947
+ gatewayController: state.gatewayController,
7948
+ extensionRegistry: state.extensionRegistry,
7949
+ resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
7950
+ registry: state.pluginRegistry,
7951
+ channel,
7952
+ cfg: resolveConfigSecrets3(loadConfig17(), { configPath: state.runtimeConfigPath }),
7953
+ accountId
7954
+ })
7955
+ })
7956
+ );
7870
7957
  state.cron.onJob = createCronJobHandler({ runtimePool: state.runtimePool, bus: state.bus });
7871
7958
  state.heartbeat = new HeartbeatService(
7872
7959
  state.workspace,
@@ -7978,755 +8065,13 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
7978
8065
  };
7979
8066
  }
7980
8067
 
7981
- // src/cli/commands/service-ui-chat-runtime.ts
7982
- import { parseAgentScopedSessionKey as parseAgentScopedSessionKey2 } from "@nextclaw/core";
7983
- function createServiceUiChatRuntime(params) {
7984
- return {
7985
- listSessionTypes: async () => {
7986
- const options = params.runtimePool.listAvailableEngineKinds().map((value) => ({
7987
- value,
7988
- label: resolveUiSessionTypeLabel(value)
7989
- }));
7990
- return {
7991
- defaultType: "native",
7992
- options
7993
- };
7994
- },
7995
- getCapabilities: async (request) => {
7996
- const sessionKey = typeof request.sessionKey === "string" && request.sessionKey.trim().length > 0 ? request.sessionKey.trim() : `ui:capability:${Date.now().toString(36)}`;
7997
- const capability = params.runtimePool.supportsTurnAbort({
7998
- sessionKey,
7999
- agentId: typeof request.agentId === "string" ? request.agentId : void 0,
8000
- channel: "ui",
8001
- chatId: "web-ui",
8002
- metadata: {}
8003
- });
8004
- return {
8005
- stopSupported: capability.supported,
8006
- ...capability.reason ? { stopReason: capability.reason } : {}
8007
- };
8008
- },
8009
- processTurn: async (request) => {
8010
- const resolved = resolveChatTurnParams(request);
8011
- const reply = await params.runtimePool.processDirect({
8012
- content: request.message,
8013
- sessionKey: resolved.sessionKey,
8014
- channel: resolved.channel,
8015
- chatId: resolved.chatId,
8016
- agentId: resolved.inferredAgentId,
8017
- metadata: resolved.metadata
8018
- });
8019
- return buildTurnResult({
8020
- reply,
8021
- sessionKey: resolved.sessionKey,
8022
- inferredAgentId: resolved.inferredAgentId,
8023
- model: resolved.model
8024
- });
8025
- },
8026
- startTurnRun: async (request) => {
8027
- return params.runCoordinator.startRun(request);
8028
- },
8029
- listRuns: async (request) => {
8030
- return params.runCoordinator.listRuns(request);
8031
- },
8032
- getRun: async (request) => {
8033
- return params.runCoordinator.getRun(request);
8034
- },
8035
- streamRun: async function* (request) {
8036
- for await (const event of params.runCoordinator.streamRun(request)) {
8037
- yield event;
8038
- }
8039
- },
8040
- stopTurn: async (request) => {
8041
- return await params.runCoordinator.stopRun(request);
8042
- },
8043
- processTurnStream: async function* (request) {
8044
- const run = params.runCoordinator.startRun(request);
8045
- for await (const event of params.runCoordinator.streamRun({ runId: run.runId })) {
8046
- yield event;
8047
- }
8048
- }
8049
- };
8050
- }
8051
- function resolveChatTurnParams(params) {
8052
- const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim().length > 0 ? params.sessionKey.trim() : `ui:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
8053
- const inferredAgentId = typeof params.agentId === "string" && params.agentId.trim().length > 0 ? params.agentId.trim() : parseAgentScopedSessionKey2(sessionKey)?.agentId;
8054
- const model = typeof params.model === "string" && params.model.trim().length > 0 ? params.model.trim() : void 0;
8055
- const metadata = params.metadata && typeof params.metadata === "object" && !Array.isArray(params.metadata) ? { ...params.metadata } : {};
8056
- if (model) {
8057
- metadata.model = model;
8058
- }
8059
- const runId = typeof params.runId === "string" && params.runId.trim().length > 0 ? params.runId.trim() : void 0;
8060
- return {
8061
- runId,
8062
- sessionKey,
8063
- inferredAgentId,
8064
- model,
8065
- metadata,
8066
- channel: typeof params.channel === "string" && params.channel.trim().length > 0 ? params.channel : "ui",
8067
- chatId: typeof params.chatId === "string" && params.chatId.trim().length > 0 ? params.chatId : "web-ui"
8068
- };
8069
- }
8070
- function buildTurnResult(params) {
8071
- return {
8072
- reply: params.reply,
8073
- sessionKey: params.sessionKey,
8074
- ...params.inferredAgentId ? { agentId: params.inferredAgentId } : {},
8075
- ...params.model ? { model: params.model } : {}
8076
- };
8077
- }
8078
- function resolveUiSessionTypeLabel(sessionType) {
8079
- if (sessionType === "native") {
8080
- return "Native";
8081
- }
8082
- return sessionType.trim().split(/[-_]+/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || sessionType;
8083
- }
8084
-
8085
- // src/cli/commands/ui-chat-run-coordinator.ts
8086
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
8087
- import { join as join7 } from "path";
8088
- import {
8089
- getDataDir as getDataDir9,
8090
- parseAgentScopedSessionKey as parseAgentScopedSessionKey3,
8091
- safeFilename
8092
- } from "@nextclaw/core";
8093
- var RUNS_DIR = join7(getDataDir9(), "runs");
8094
- var NON_TERMINAL_STATES = /* @__PURE__ */ new Set(["queued", "running"]);
8095
- var DEFAULT_SESSION_TYPE = "native";
8096
- var SESSION_TYPE_METADATA_KEY = "session_type";
8097
- function createRunId() {
8098
- const now = Date.now().toString(36);
8099
- const rand = Math.random().toString(36).slice(2, 10);
8100
- return `run-${now}-${rand}`;
8101
- }
8102
- function isRecord6(value) {
8103
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
8104
- }
8105
- function readOptionalString3(value) {
8106
- if (typeof value !== "string") {
8107
- return void 0;
8108
- }
8109
- const trimmed = value.trim();
8110
- return trimmed || void 0;
8111
- }
8112
- function isAbortError(error) {
8113
- if (error instanceof DOMException && error.name === "AbortError") {
8114
- return true;
8115
- }
8116
- if (error instanceof Error) {
8117
- if (error.name === "AbortError") {
8118
- return true;
8119
- }
8120
- const message = error.message.toLowerCase();
8121
- if (message.includes("aborted") || message.includes("abort")) {
8122
- return true;
8123
- }
8124
- }
8125
- return false;
8126
- }
8127
- function cloneEvent(event) {
8128
- return JSON.parse(JSON.stringify(event));
8129
- }
8130
- function hasToolSessionEvent(run) {
8131
- for (const item of run.events) {
8132
- if (item.type !== "session_event") {
8133
- continue;
8134
- }
8135
- const message = item.event?.message;
8136
- if (!message || typeof message.role !== "string") {
8137
- continue;
8138
- }
8139
- const role = message.role.trim().toLowerCase();
8140
- if (role === "tool" || role === "tool_result" || role === "toolresult" || role === "function") {
8141
- return true;
8142
- }
8143
- if (role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
8144
- return true;
8145
- }
8146
- }
8147
- return false;
8148
- }
8149
- var UiChatRunCoordinator = class {
8150
- constructor(options) {
8151
- this.options = options;
8152
- mkdirSync5(RUNS_DIR, { recursive: true });
8153
- this.loadPersistedRuns();
8154
- }
8155
- runs = /* @__PURE__ */ new Map();
8156
- sessionRuns = /* @__PURE__ */ new Map();
8157
- startRun(input) {
8158
- const request = this.resolveRequest(input);
8159
- const resolvedSessionType = this.resolveSessionTypeForRun(request.sessionKey, request.metadata);
8160
- request.metadata[SESSION_TYPE_METADATA_KEY] = resolvedSessionType;
8161
- const stopCapability = this.options.runtimePool.supportsTurnAbort({
8162
- sessionKey: request.sessionKey,
8163
- agentId: request.agentId,
8164
- channel: request.channel,
8165
- chatId: request.chatId,
8166
- metadata: request.metadata
8167
- });
8168
- const run = {
8169
- runId: request.runId,
8170
- sessionKey: request.sessionKey,
8171
- ...request.agentId ? { agentId: request.agentId } : {},
8172
- ...request.model ? { model: request.model, requestedModel: request.model } : {},
8173
- state: "queued",
8174
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
8175
- stopSupported: stopCapability.supported,
8176
- ...stopCapability.reason ? { stopReason: stopCapability.reason } : {},
8177
- events: [],
8178
- waiters: /* @__PURE__ */ new Set(),
8179
- abortController: null,
8180
- cancelRequested: false
8181
- };
8182
- this.runs.set(run.runId, run);
8183
- this.bindRunToSession(run.sessionKey, run.runId);
8184
- this.syncSessionRunState(run);
8185
- this.persistRun(run);
8186
- this.emitRunUpdated(run);
8187
- void this.executeRun(run, request);
8188
- return this.toRunView(run);
8189
- }
8190
- getRun(params) {
8191
- const run = this.getRunRecord(params.runId);
8192
- return run ? this.toRunView(run) : null;
8193
- }
8194
- listRuns(params = {}) {
8195
- const sessionKey = readOptionalString3(params.sessionKey);
8196
- const stateFilter = Array.isArray(params.states) && params.states.length > 0 ? new Set(params.states) : null;
8197
- const limit = Number.isFinite(params.limit) ? Math.max(0, Math.trunc(params.limit)) : 0;
8198
- const records = Array.from(this.runs.values()).filter((run) => {
8199
- if (sessionKey && run.sessionKey !== sessionKey) {
8200
- return false;
8201
- }
8202
- if (stateFilter && !stateFilter.has(run.state)) {
8203
- return false;
8204
- }
8205
- return true;
8206
- }).sort((left, right) => Date.parse(right.requestedAt) - Date.parse(left.requestedAt));
8207
- const total = records.length;
8208
- const runs = (limit > 0 ? records.slice(0, limit) : records).map((run) => this.toRunView(run));
8209
- return { runs, total };
8210
- }
8211
- async *streamRun(params) {
8212
- const run = this.getRunRecord(params.runId);
8213
- if (!run) {
8214
- throw new Error(`chat run not found: ${params.runId}`);
8215
- }
8216
- let cursor = Number.isFinite(params.fromEventIndex) ? Math.max(0, Math.trunc(params.fromEventIndex)) : 0;
8217
- while (true) {
8218
- while (cursor < run.events.length) {
8219
- yield cloneEvent(run.events[cursor]);
8220
- cursor += 1;
8221
- }
8222
- if (!NON_TERMINAL_STATES.has(run.state)) {
8223
- return;
8224
- }
8225
- if (params.signal?.aborted) {
8226
- return;
8227
- }
8228
- await this.waitForRunUpdate(run, params.signal);
8229
- }
8230
- }
8231
- async stopRun(params) {
8232
- const runId = readOptionalString3(params.runId) ?? "";
8233
- if (!runId) {
8234
- return {
8235
- stopped: false,
8236
- runId: "",
8237
- reason: "runId is required"
8238
- };
8239
- }
8240
- const run = this.getRunRecord(runId);
8241
- if (!run) {
8242
- return {
8243
- stopped: false,
8244
- runId,
8245
- ...readOptionalString3(params.sessionKey) ? { sessionKey: readOptionalString3(params.sessionKey) } : {},
8246
- reason: "run not found or already completed"
8247
- };
8248
- }
8249
- const requestedSessionKey = readOptionalString3(params.sessionKey);
8250
- if (requestedSessionKey && requestedSessionKey !== run.sessionKey) {
8251
- return {
8252
- stopped: false,
8253
- runId,
8254
- sessionKey: run.sessionKey,
8255
- reason: "session key mismatch"
8256
- };
8257
- }
8258
- if (!run.stopSupported) {
8259
- return {
8260
- stopped: false,
8261
- runId,
8262
- sessionKey: run.sessionKey,
8263
- reason: run.stopReason ?? "run stop is not supported"
8264
- };
8265
- }
8266
- if (!NON_TERMINAL_STATES.has(run.state)) {
8267
- return {
8268
- stopped: false,
8269
- runId,
8270
- sessionKey: run.sessionKey,
8271
- reason: `run already ${run.state}`
8272
- };
8273
- }
8274
- run.cancelRequested = true;
8275
- this.finalizeRunAsStopped(run);
8276
- if (run.abortController) {
8277
- run.abortController.abort(new Error("chat turn stopped by user"));
8278
- }
8279
- return {
8280
- stopped: true,
8281
- runId,
8282
- sessionKey: run.sessionKey
8283
- };
8284
- }
8285
- resolveRequest(input) {
8286
- const message = readOptionalString3(input.message) ?? "";
8287
- const sessionKey = readOptionalString3(input.sessionKey) ?? `ui:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
8288
- const explicitAgentId = readOptionalString3(input.agentId);
8289
- const parsedAgentId = parseAgentScopedSessionKey3(sessionKey)?.agentId;
8290
- const agentId = explicitAgentId ?? readOptionalString3(parsedAgentId);
8291
- const model = readOptionalString3(input.model);
8292
- const metadata = isRecord6(input.metadata) ? { ...input.metadata } : {};
8293
- if (model) {
8294
- metadata.model = model;
8295
- }
8296
- const runId = readOptionalString3(input.runId) ?? createRunId();
8297
- return {
8298
- runId,
8299
- message,
8300
- sessionKey,
8301
- ...agentId ? { agentId } : {},
8302
- ...model ? { model } : {},
8303
- metadata,
8304
- channel: readOptionalString3(input.channel) ?? "ui",
8305
- chatId: readOptionalString3(input.chatId) ?? "web-ui"
8306
- };
8307
- }
8308
- readRequestedSessionType(metadata) {
8309
- const value = readOptionalString3(metadata.session_type) ?? readOptionalString3(metadata.sessionType);
8310
- return value ? value.toLowerCase() : void 0;
8311
- }
8312
- readStoredSessionType(metadata) {
8313
- const value = readOptionalString3(metadata[SESSION_TYPE_METADATA_KEY]);
8314
- return value ? value.toLowerCase() : DEFAULT_SESSION_TYPE;
8315
- }
8316
- countUserMessages(session) {
8317
- return session.messages.reduce((count, message) => {
8318
- const role = typeof message.role === "string" ? message.role.trim().toLowerCase() : "";
8319
- return role === "user" ? count + 1 : count;
8320
- }, 0);
8321
- }
8322
- ensureSessionTypeAvailable(sessionType) {
8323
- const available = new Set(this.options.runtimePool.listAvailableEngineKinds());
8324
- available.add(DEFAULT_SESSION_TYPE);
8325
- if (!available.has(sessionType)) {
8326
- throw new Error(
8327
- `session type "${sessionType}" is unavailable. Re-enable the related plugin or create a native session.`
8328
- );
8329
- }
8330
- }
8331
- resolveSessionTypeForRun(sessionKey, metadata) {
8332
- const requestedType = this.readRequestedSessionType(metadata);
8333
- const session = this.options.sessionManager.getOrCreate(sessionKey);
8334
- const storedType = this.readStoredSessionType(session.metadata);
8335
- const userMessageCount = this.countUserMessages(session);
8336
- const locked = userMessageCount > 0;
8337
- if (locked) {
8338
- if (requestedType && requestedType !== storedType) {
8339
- throw new Error(
8340
- `session type is locked to "${storedType}" after the first user message; create a new session to switch type`
8341
- );
8342
- }
8343
- this.ensureSessionTypeAvailable(storedType);
8344
- if (session.metadata[SESSION_TYPE_METADATA_KEY] !== storedType) {
8345
- session.metadata[SESSION_TYPE_METADATA_KEY] = storedType;
8346
- session.updatedAt = /* @__PURE__ */ new Date();
8347
- this.options.sessionManager.save(session);
8348
- }
8349
- return storedType;
8350
- }
8351
- const nextType = requestedType ?? storedType;
8352
- this.ensureSessionTypeAvailable(nextType);
8353
- if (session.metadata[SESSION_TYPE_METADATA_KEY] !== nextType) {
8354
- session.metadata[SESSION_TYPE_METADATA_KEY] = nextType;
8355
- session.updatedAt = /* @__PURE__ */ new Date();
8356
- this.options.sessionManager.save(session);
8357
- }
8358
- return nextType;
8359
- }
8360
- async executeRun(run, request) {
8361
- if (run.cancelRequested) {
8362
- this.finalizeRunAsStopped(run);
8363
- return;
8364
- }
8365
- this.transitionState(run, "running");
8366
- const abortController = run.stopSupported ? new AbortController() : null;
8367
- run.abortController = abortController;
8368
- const assistantDeltaParts = [];
8369
- try {
8370
- const reply = await this.options.runtimePool.processDirect({
8371
- content: request.message,
8372
- sessionKey: request.sessionKey,
8373
- channel: request.channel,
8374
- chatId: request.chatId,
8375
- agentId: request.agentId,
8376
- metadata: request.metadata,
8377
- ...abortController ? { abortSignal: abortController.signal } : {},
8378
- onAssistantDelta: (delta) => {
8379
- if (run.cancelRequested) {
8380
- return;
8381
- }
8382
- if (typeof delta !== "string" || delta.length === 0) {
8383
- return;
8384
- }
8385
- assistantDeltaParts.push(delta);
8386
- this.pushStreamEvent(run, { type: "delta", delta });
8387
- },
8388
- onSessionEvent: (event) => {
8389
- if (run.cancelRequested) {
8390
- return;
8391
- }
8392
- this.pushStreamEvent(run, {
8393
- type: "session_event",
8394
- event: this.mapSessionEvent(event)
8395
- });
8396
- }
8397
- });
8398
- if (run.cancelRequested) {
8399
- this.finalizeRunAsStopped(run);
8400
- return;
8401
- }
8402
- this.pushStreamEvent(run, {
8403
- type: "final",
8404
- result: {
8405
- reply,
8406
- sessionKey: request.sessionKey,
8407
- ...request.agentId ? { agentId: request.agentId } : {},
8408
- ...request.model ? { model: request.model } : {}
8409
- }
8410
- });
8411
- run.reply = reply;
8412
- this.transitionState(run, "completed");
8413
- } catch (error) {
8414
- const aborted = (abortController?.signal.aborted ?? false) || isAbortError(error);
8415
- if (aborted) {
8416
- if (run.cancelRequested) {
8417
- this.finalizeRunAsStopped(run);
8418
- return;
8419
- }
8420
- const partialReply = assistantDeltaParts.join("");
8421
- const shouldPersistPartialReply = partialReply.trim().length > 0 && !hasToolSessionEvent(run);
8422
- if (shouldPersistPartialReply) {
8423
- this.persistAbortedAssistantReply(run.sessionKey, partialReply);
8424
- }
8425
- this.pushStreamEvent(run, {
8426
- type: "final",
8427
- result: {
8428
- reply: partialReply,
8429
- sessionKey: request.sessionKey,
8430
- ...request.agentId ? { agentId: request.agentId } : {},
8431
- ...request.model ? { model: request.model } : {}
8432
- }
8433
- });
8434
- run.reply = partialReply;
8435
- this.transitionState(run, "aborted", {
8436
- error: abortController?.signal.reason instanceof Error ? abortController.signal.reason.message : readOptionalString3(abortController?.signal.reason)
8437
- });
8438
- return;
8439
- }
8440
- const errorMessage = error instanceof Error ? error.message : String(error);
8441
- this.pushStreamEvent(run, {
8442
- type: "error",
8443
- error: errorMessage
8444
- });
8445
- this.transitionState(run, "failed", { error: errorMessage });
8446
- } finally {
8447
- run.abortController = null;
8448
- this.persistRun(run);
8449
- this.notifyRunWaiters(run);
8450
- }
8451
- }
8452
- transitionState(run, next, options = {}) {
8453
- run.state = next;
8454
- if (next === "running") {
8455
- run.startedAt = (/* @__PURE__ */ new Date()).toISOString();
8456
- }
8457
- if (!NON_TERMINAL_STATES.has(next)) {
8458
- run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
8459
- }
8460
- if (options.error) {
8461
- run.error = options.error;
8462
- } else if (next === "completed") {
8463
- run.error = void 0;
8464
- }
8465
- this.syncSessionRunState(run);
8466
- this.persistRun(run);
8467
- this.emitRunUpdated(run);
8468
- this.notifyRunWaiters(run);
8469
- }
8470
- finalizeRunAsStopped(run) {
8471
- const partialReply = this.collectAssistantReplyFromEvents(run.events);
8472
- if (!this.hasFinalStreamEvent(run.events)) {
8473
- this.pushStreamEvent(run, {
8474
- type: "final",
8475
- result: {
8476
- reply: partialReply,
8477
- sessionKey: run.sessionKey,
8478
- ...run.agentId ? { agentId: run.agentId } : {},
8479
- ...run.model ? { model: run.model } : {}
8480
- }
8481
- });
8482
- }
8483
- run.reply = partialReply;
8484
- const shouldPersistPartialReply = partialReply.trim().length > 0 && !hasToolSessionEvent(run);
8485
- if (shouldPersistPartialReply) {
8486
- this.persistAbortedAssistantReply(run.sessionKey, partialReply);
8487
- }
8488
- if (run.state !== "aborted") {
8489
- this.transitionState(run, "aborted", {
8490
- error: "chat turn stopped by user"
8491
- });
8492
- }
8493
- }
8494
- hasFinalStreamEvent(events) {
8495
- return events.some((event) => event.type === "final");
8496
- }
8497
- collectAssistantReplyFromEvents(events) {
8498
- const chunks = [];
8499
- for (const event of events) {
8500
- if (event.type === "delta" && event.delta.length > 0) {
8501
- chunks.push(event.delta);
8502
- }
8503
- }
8504
- return chunks.join("");
8505
- }
8506
- pushStreamEvent(run, event) {
8507
- run.events.push(event);
8508
- this.persistRun(run);
8509
- this.notifyRunWaiters(run);
8510
- this.emitRunUpdated(run);
8511
- }
8512
- waitForRunUpdate(run, signal) {
8513
- return new Promise((resolve15) => {
8514
- const wake = () => {
8515
- cleanup();
8516
- resolve15();
8517
- };
8518
- const onAbort = () => {
8519
- cleanup();
8520
- resolve15();
8521
- };
8522
- const cleanup = () => {
8523
- run.waiters.delete(wake);
8524
- signal?.removeEventListener("abort", onAbort);
8525
- };
8526
- run.waiters.add(wake);
8527
- signal?.addEventListener("abort", onAbort, { once: true });
8528
- });
8529
- }
8530
- notifyRunWaiters(run) {
8531
- if (run.waiters.size === 0) {
8532
- return;
8533
- }
8534
- const waiters = Array.from(run.waiters);
8535
- run.waiters.clear();
8536
- for (const wake of waiters) {
8537
- wake();
8538
- }
8539
- }
8540
- bindRunToSession(sessionKey, runId) {
8541
- const existing = this.sessionRuns.get(sessionKey);
8542
- if (existing) {
8543
- existing.add(runId);
8544
- return;
8545
- }
8546
- this.sessionRuns.set(sessionKey, /* @__PURE__ */ new Set([runId]));
8547
- }
8548
- syncSessionRunState(run) {
8549
- const session = this.options.sessionManager.getOrCreate(run.sessionKey);
8550
- const metadata = session.metadata;
8551
- metadata.ui_last_run_id = run.runId;
8552
- metadata.ui_last_run_state = run.state;
8553
- metadata.ui_last_run_updated_at = (/* @__PURE__ */ new Date()).toISOString();
8554
- if (NON_TERMINAL_STATES.has(run.state)) {
8555
- metadata.ui_active_run_id = run.runId;
8556
- metadata.ui_run_state = run.state;
8557
- metadata.ui_run_requested_at = run.requestedAt;
8558
- if (run.startedAt) {
8559
- metadata.ui_run_started_at = run.startedAt;
8560
- }
8561
- } else if (metadata.ui_active_run_id === run.runId) {
8562
- delete metadata.ui_active_run_id;
8563
- delete metadata.ui_run_state;
8564
- delete metadata.ui_run_requested_at;
8565
- delete metadata.ui_run_started_at;
8566
- }
8567
- session.updatedAt = /* @__PURE__ */ new Date();
8568
- this.options.sessionManager.save(session);
8569
- }
8570
- persistAbortedAssistantReply(sessionKey, partialReply) {
8571
- const session = this.options.sessionManager.getOrCreate(sessionKey);
8572
- const latest = session.messages.length > 0 ? session.messages[session.messages.length - 1] : null;
8573
- if (latest?.role === "assistant" && typeof latest.content === "string" && latest.content === partialReply) {
8574
- return;
8575
- }
8576
- this.options.sessionManager.addMessage(session, "assistant", partialReply);
8577
- this.options.sessionManager.save(session);
8578
- }
8579
- mapSessionEvent(event) {
8580
- const raw = event.data?.message;
8581
- const messageRecord = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
8582
- const message = messageRecord && typeof messageRecord.role === "string" ? {
8583
- role: messageRecord.role,
8584
- content: messageRecord.content,
8585
- timestamp: typeof messageRecord.timestamp === "string" ? messageRecord.timestamp : event.timestamp,
8586
- ...typeof messageRecord.name === "string" ? { name: messageRecord.name } : {},
8587
- ...typeof messageRecord.tool_call_id === "string" ? { tool_call_id: messageRecord.tool_call_id } : {},
8588
- ...Array.isArray(messageRecord.tool_calls) ? { tool_calls: messageRecord.tool_calls } : {},
8589
- ...typeof messageRecord.reasoning_content === "string" ? { reasoning_content: messageRecord.reasoning_content } : {}
8590
- } : void 0;
8591
- return {
8592
- seq: event.seq,
8593
- type: event.type,
8594
- timestamp: event.timestamp,
8595
- ...message ? { message } : {}
8596
- };
8597
- }
8598
- emitRunUpdated(run) {
8599
- this.options.onRunUpdated?.(this.toRunView(run));
8600
- }
8601
- toRunView(run) {
8602
- return {
8603
- runId: run.runId,
8604
- sessionKey: run.sessionKey,
8605
- ...run.agentId ? { agentId: run.agentId } : {},
8606
- ...run.model ? { model: run.model } : {},
8607
- state: run.state,
8608
- requestedAt: run.requestedAt,
8609
- ...run.startedAt ? { startedAt: run.startedAt } : {},
8610
- ...run.completedAt ? { completedAt: run.completedAt } : {},
8611
- stopSupported: run.stopSupported,
8612
- ...run.stopReason ? { stopReason: run.stopReason } : {},
8613
- ...run.error ? { error: run.error } : {},
8614
- ...typeof run.reply === "string" ? { reply: run.reply } : {},
8615
- eventCount: run.events.length
8616
- };
8617
- }
8618
- getRunPath(runId) {
8619
- return join7(RUNS_DIR, `${safeFilename(runId)}.json`);
8620
- }
8621
- persistRun(run) {
8622
- const persisted = {
8623
- runId: run.runId,
8624
- sessionKey: run.sessionKey,
8625
- ...run.agentId ? { agentId: run.agentId } : {},
8626
- ...run.model ? { model: run.model } : {},
8627
- state: run.state,
8628
- requestedAt: run.requestedAt,
8629
- ...run.startedAt ? { startedAt: run.startedAt } : {},
8630
- ...run.completedAt ? { completedAt: run.completedAt } : {},
8631
- stopSupported: run.stopSupported,
8632
- ...run.stopReason ? { stopReason: run.stopReason } : {},
8633
- ...run.error ? { error: run.error } : {},
8634
- ...typeof run.reply === "string" ? { reply: run.reply } : {},
8635
- events: run.events
8636
- };
8637
- writeFileSync5(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
8638
- `);
8639
- }
8640
- loadPersistedRuns() {
8641
- if (!existsSync11(RUNS_DIR)) {
8642
- return;
8643
- }
8644
- for (const entry of readdirSync2(RUNS_DIR, { withFileTypes: true })) {
8645
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
8646
- continue;
8647
- }
8648
- const path2 = join7(RUNS_DIR, entry.name);
8649
- try {
8650
- const parsed = JSON.parse(readFileSync9(path2, "utf-8"));
8651
- const runId = readOptionalString3(parsed.runId);
8652
- const sessionKey = readOptionalString3(parsed.sessionKey);
8653
- if (!runId || !sessionKey) {
8654
- continue;
8655
- }
8656
- const state = this.normalizeRunState(parsed.state);
8657
- const events = Array.isArray(parsed.events) ? parsed.events : [];
8658
- const run = {
8659
- runId,
8660
- sessionKey,
8661
- ...readOptionalString3(parsed.agentId) ? { agentId: readOptionalString3(parsed.agentId) } : {},
8662
- ...readOptionalString3(parsed.model) ? { model: readOptionalString3(parsed.model) } : {},
8663
- state,
8664
- requestedAt: readOptionalString3(parsed.requestedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
8665
- ...readOptionalString3(parsed.startedAt) ? { startedAt: readOptionalString3(parsed.startedAt) } : {},
8666
- ...readOptionalString3(parsed.completedAt) ? { completedAt: readOptionalString3(parsed.completedAt) } : {},
8667
- stopSupported: Boolean(parsed.stopSupported),
8668
- ...readOptionalString3(parsed.stopReason) ? { stopReason: readOptionalString3(parsed.stopReason) } : {},
8669
- ...readOptionalString3(parsed.error) ? { error: readOptionalString3(parsed.error) } : {},
8670
- ...typeof parsed.reply === "string" ? { reply: parsed.reply } : {},
8671
- events,
8672
- waiters: /* @__PURE__ */ new Set(),
8673
- abortController: null,
8674
- cancelRequested: false
8675
- };
8676
- if (NON_TERMINAL_STATES.has(run.state)) {
8677
- run.state = "failed";
8678
- run.error = run.error ?? "run interrupted by service restart";
8679
- run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
8680
- this.persistRun(run);
8681
- }
8682
- this.runs.set(run.runId, run);
8683
- this.bindRunToSession(run.sessionKey, run.runId);
8684
- } catch {
8685
- }
8686
- }
8687
- }
8688
- normalizeRunState(value) {
8689
- if (value === "queued" || value === "running" || value === "completed" || value === "failed" || value === "aborted") {
8690
- return value;
8691
- }
8692
- return "failed";
8693
- }
8694
- getRunRecord(runId) {
8695
- const normalized = readOptionalString3(runId);
8696
- if (!normalized) {
8697
- return null;
8698
- }
8699
- return this.runs.get(normalized) ?? null;
8700
- }
8701
- };
8702
-
8703
8068
  // src/cli/commands/service-gateway-startup.ts
8704
8069
  async function startUiShell(params) {
8070
+ logStartupTrace("service.start_ui_shell.begin");
8705
8071
  if (!params.uiConfig.enabled) {
8706
8072
  return null;
8707
8073
  }
8708
8074
  let publishUiEvent = null;
8709
- params.runtimePool.setSystemSessionUpdatedHandler(({ sessionKey, message }) => {
8710
- if (!publishUiEvent) {
8711
- return;
8712
- }
8713
- if (!message.chatId.startsWith("ui:")) {
8714
- return;
8715
- }
8716
- publishUiEvent({
8717
- type: "session.updated",
8718
- payload: {
8719
- sessionKey
8720
- }
8721
- });
8722
- });
8723
- const runCoordinator = new UiChatRunCoordinator({
8724
- runtimePool: params.runtimePool,
8725
- sessionManager: params.sessionManager,
8726
- onRunUpdated: (run) => {
8727
- publishUiEvent?.({ type: "run.updated", payload: { run } });
8728
- }
8729
- });
8730
8075
  const deferredNcpAgent = createDeferredUiNcpAgent();
8731
8076
  const uiServer = startUiServer({
8732
8077
  host: params.uiConfig.host,
@@ -8740,11 +8085,7 @@ async function startUiShell(params) {
8740
8085
  remoteAccess: params.remoteAccess,
8741
8086
  getPluginChannelBindings: params.getPluginChannelBindings,
8742
8087
  getPluginUiMetadata: params.getPluginUiMetadata,
8743
- ncpAgent: deferredNcpAgent.agent,
8744
- chatRuntime: createServiceUiChatRuntime({
8745
- runtimePool: params.runtimePool,
8746
- runCoordinator
8747
- })
8088
+ ncpAgent: deferredNcpAgent.agent
8748
8089
  });
8749
8090
  publishUiEvent = uiServer.publish;
8750
8091
  const uiUrl = `http://${uiServer.host}:${uiServer.port}`;
@@ -8755,23 +8096,34 @@ async function startUiShell(params) {
8755
8096
  if (params.openBrowserWindow) {
8756
8097
  openBrowser(uiUrl);
8757
8098
  }
8099
+ logStartupTrace("service.start_ui_shell.ready", {
8100
+ host: uiServer.host,
8101
+ port: uiServer.port
8102
+ });
8758
8103
  return {
8759
- deferredNcpAgent
8104
+ deferredNcpAgent,
8105
+ publish: (event) => {
8106
+ publishUiEvent?.(event);
8107
+ }
8760
8108
  };
8761
8109
  }
8762
8110
  async function startDeferredGatewayStartup(params) {
8111
+ logStartupTrace("service.deferred_startup.begin");
8763
8112
  if (params.uiStartup) {
8764
8113
  try {
8765
- const ncpAgent = await createUiNcpAgent({
8766
- bus: params.bus,
8767
- providerManager: params.providerManager,
8768
- sessionManager: params.sessionManager,
8769
- cronService: params.cronService,
8770
- gatewayController: params.gatewayController,
8771
- getConfig: params.getConfig,
8772
- getExtensionRegistry: params.getExtensionRegistry,
8773
- resolveMessageToolHints: ({ channel, accountId }) => params.resolveMessageToolHints({ channel, accountId })
8774
- });
8114
+ const ncpAgent = await measureStartupAsync(
8115
+ "service.deferred_startup.create_ui_ncp_agent",
8116
+ async () => await createUiNcpAgent({
8117
+ bus: params.bus,
8118
+ providerManager: params.providerManager,
8119
+ sessionManager: params.sessionManager,
8120
+ cronService: params.cronService,
8121
+ gatewayController: params.gatewayController,
8122
+ getConfig: params.getConfig,
8123
+ getExtensionRegistry: params.getExtensionRegistry,
8124
+ resolveMessageToolHints: ({ channel, accountId }) => params.resolveMessageToolHints({ channel, accountId })
8125
+ })
8126
+ );
8775
8127
  params.onNcpAgentReady(ncpAgent);
8776
8128
  params.uiStartup.deferredNcpAgent.activate(ncpAgent);
8777
8129
  console.log("\u2713 UI NCP agent: ready");
@@ -8779,10 +8131,11 @@ async function startDeferredGatewayStartup(params) {
8779
8131
  console.error(`UI NCP agent startup failed: ${error instanceof Error ? error.message : String(error)}`);
8780
8132
  }
8781
8133
  }
8782
- await params.startPluginGateways();
8783
- await params.startChannels();
8784
- await params.wakeFromRestartSentinel();
8134
+ await measureStartupAsync("service.deferred_startup.start_plugin_gateways", params.startPluginGateways);
8135
+ await measureStartupAsync("service.deferred_startup.start_channels", params.startChannels);
8136
+ await measureStartupAsync("service.deferred_startup.wake_restart_sentinel", params.wakeFromRestartSentinel);
8785
8137
  console.log("\u2713 Deferred startup: plugin gateways and channels settled");
8138
+ logStartupTrace("service.deferred_startup.end");
8786
8139
  }
8787
8140
  async function runGatewayRuntimeLoop(params) {
8788
8141
  let startupTask = null;
@@ -8799,6 +8152,18 @@ async function runGatewayRuntimeLoop(params) {
8799
8152
  }
8800
8153
  }
8801
8154
 
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
+ }
8162
+ await measureStartupAsync("service.ui_shell_grace_window", async () => {
8163
+ await delay(DEFAULT_UI_SHELL_GRACE_MS);
8164
+ });
8165
+ }
8166
+
8802
8167
  // src/cli/commands/service.ts
8803
8168
  var {
8804
8169
  APP_NAME: APP_NAME3,
@@ -8812,7 +8177,7 @@ var {
8812
8177
  MessageBus: MessageBus2,
8813
8178
  resolveConfigSecrets: resolveConfigSecrets4,
8814
8179
  SessionManager: SessionManager2,
8815
- parseAgentScopedSessionKey: parseAgentScopedSessionKey4
8180
+ parseAgentScopedSessionKey: parseAgentScopedSessionKey2
8816
8181
  } = NextclawCore2;
8817
8182
  function createSkillsLoader(workspace) {
8818
8183
  const ctor = NextclawCore2.SkillsLoader;
@@ -8828,19 +8193,67 @@ var ServiceCommands = class {
8828
8193
  applyLiveConfigReload = null;
8829
8194
  liveUiNcpAgent = null;
8830
8195
  async startGateway(options = {}) {
8196
+ logStartupTrace("service.start_gateway.begin");
8831
8197
  this.applyLiveConfigReload = null;
8832
8198
  this.liveUiNcpAgent = null;
8833
- const gateway = createGatewayStartupContext({
8834
- uiOverrides: options.uiOverrides,
8835
- allowMissingProvider: options.allowMissingProvider,
8836
- uiStaticDir: options.uiStaticDir,
8837
- makeProvider: (config2, providerOptions) => providerOptions?.allowMissing === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2),
8838
- makeMissingProvider: (config2) => this.makeMissingProvider(config2),
8839
- requestRestart: (params) => this.deps.requestRestart(params)
8840
- });
8841
- this.applyLiveConfigReload = gateway.applyLiveConfigReload;
8842
- let { pluginRegistry, pluginChannelBindings, extensionRegistry } = gateway;
8199
+ const shellContext = measureStartupSync(
8200
+ "service.create_gateway_shell_context",
8201
+ () => createGatewayShellContext({ uiOverrides: options.uiOverrides, uiStaticDir: options.uiStaticDir })
8202
+ );
8203
+ const applyLiveConfigReload = async () => {
8204
+ await this.applyLiveConfigReload?.();
8205
+ };
8206
+ let pluginChannelBindings = [];
8207
+ let pluginUiMetadata = [];
8843
8208
  let pluginGatewayHandles = [];
8209
+ const marketplaceInstaller = new ServiceMarketplaceInstaller({
8210
+ applyLiveConfigReload,
8211
+ runCliSubcommand: (args) => this.runCliSubcommand(args),
8212
+ installBuiltinSkill: (slug, force) => this.installBuiltinMarketplaceSkill(slug, force)
8213
+ }).createInstaller();
8214
+ const remoteAccess = createRemoteAccessHost({
8215
+ serviceCommands: this,
8216
+ requestRestart: this.deps.requestRestart,
8217
+ uiConfig: shellContext.uiConfig,
8218
+ remoteModule: shellContext.remoteModule
8219
+ });
8220
+ const uiStartup = await measureStartupAsync(
8221
+ "service.start_ui_shell",
8222
+ async () => await startUiShell({
8223
+ uiConfig: shellContext.uiConfig,
8224
+ uiStaticDir: shellContext.uiStaticDir,
8225
+ cronService: shellContext.cron,
8226
+ getConfig: () => resolveConfigSecrets4(loadConfig18(), { configPath: shellContext.runtimeConfigPath }),
8227
+ configPath: getConfigPath10(),
8228
+ productVersion: getPackageVersion(),
8229
+ getPluginChannelBindings: () => pluginChannelBindings,
8230
+ getPluginUiMetadata: () => pluginUiMetadata,
8231
+ marketplace: { apiBaseUrl: process.env.NEXTCLAW_MARKETPLACE_API_BASE, installer: marketplaceInstaller },
8232
+ remoteAccess,
8233
+ openBrowserWindow: shellContext.uiConfig.open,
8234
+ applyLiveConfigReload
8235
+ })
8236
+ );
8237
+ await waitForUiShellGraceWindow(uiStartup);
8238
+ await waitForNextTick();
8239
+ const gateway = measureStartupSync(
8240
+ "service.create_gateway_startup_context",
8241
+ () => createGatewayStartupContext({
8242
+ shellContext,
8243
+ uiOverrides: options.uiOverrides,
8244
+ allowMissingProvider: options.allowMissingProvider,
8245
+ uiStaticDir: options.uiStaticDir,
8246
+ makeProvider: (config2, providerOptions) => providerOptions?.allowMissing === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2),
8247
+ makeMissingProvider: (config2) => this.makeMissingProvider(config2),
8248
+ requestRestart: (params) => this.deps.requestRestart(params)
8249
+ })
8250
+ );
8251
+ this.applyLiveConfigReload = gateway.applyLiveConfigReload;
8252
+ let { pluginRegistry, extensionRegistry } = gateway;
8253
+ pluginChannelBindings = gateway.pluginChannelBindings;
8254
+ pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
8255
+ uiStartup?.publish({ type: "config.updated", payload: { path: "channels" } });
8256
+ uiStartup?.publish({ type: "config.updated", payload: { path: "plugins" } });
8844
8257
  gateway.reloader.setApplyAgentRuntimeConfig((nextConfig) => gateway.runtimePool.applyRuntimeConfig(nextConfig));
8845
8258
  gateway.reloader.setReloadPlugins(async ({ config: nextConfig, changedPaths }) => {
8846
8259
  const result = await reloadServicePlugins({
@@ -8873,49 +8286,19 @@ var ServiceCommands = class {
8873
8286
  runtimeConfigPath: gateway.runtimeConfigPath,
8874
8287
  pluginChannelBindings
8875
8288
  });
8876
- if (gateway.reloader.getChannels().enabledChannels.length) {
8877
- console.log(`\u2713 Channels enabled: ${gateway.reloader.getChannels().enabledChannels.join(", ")}`);
8878
- } else {
8879
- console.log("Warning: No channels enabled");
8880
- }
8881
- const marketplaceInstaller = new ServiceMarketplaceInstaller({
8882
- applyLiveConfigReload: this.applyLiveConfigReload ?? void 0,
8883
- runCliSubcommand: (args) => this.runCliSubcommand(args),
8884
- installBuiltinSkill: (slug, force) => this.installBuiltinMarketplaceSkill(slug, force)
8885
- }).createInstaller();
8886
- const remoteAccess = createRemoteAccessHost({
8887
- serviceCommands: this,
8888
- requestRestart: this.deps.requestRestart,
8889
- uiConfig: gateway.uiConfig,
8890
- remoteModule: gateway.remoteModule
8891
- });
8892
- const uiStartup = await startUiShell({
8893
- uiConfig: gateway.uiConfig,
8894
- uiStaticDir: gateway.uiStaticDir,
8895
- cronService: gateway.cron,
8896
- runtimePool: gateway.runtimePool,
8897
- sessionManager: gateway.sessionManager,
8898
- bus: gateway.bus,
8899
- getConfig: () => resolveConfigSecrets4(loadConfig18(), { configPath: gateway.runtimeConfigPath }),
8900
- configPath: getConfigPath10(),
8901
- productVersion: getPackageVersion(),
8902
- getPluginChannelBindings: () => pluginChannelBindings,
8903
- getPluginUiMetadata: () => getPluginUiMetadataFromRegistry(pluginRegistry),
8904
- marketplace: {
8905
- apiBaseUrl: process.env.NEXTCLAW_MARKETPLACE_API_BASE,
8906
- installer: marketplaceInstaller
8907
- },
8908
- remoteAccess,
8909
- openBrowserWindow: gateway.uiConfig.open,
8910
- applyLiveConfigReload: this.applyLiveConfigReload ?? void 0
8911
- });
8912
- await startGatewaySupportServices({
8913
- cronJobs: gateway.cron.status().jobs,
8914
- remoteModule: gateway.remoteModule,
8915
- watchConfigFile: () => this.watchConfigFile(gateway.reloader),
8916
- startCron: () => gateway.cron.start(),
8917
- startHeartbeat: () => gateway.heartbeat.start()
8918
- });
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");
8291
+ await measureStartupAsync(
8292
+ "service.start_gateway_support_services",
8293
+ async () => await startGatewaySupportServices({
8294
+ cronJobs: gateway.cron.status().jobs,
8295
+ remoteModule: gateway.remoteModule,
8296
+ watchConfigFile: () => this.watchConfigFile(gateway.reloader),
8297
+ startCron: () => gateway.cron.start(),
8298
+ startHeartbeat: () => gateway.heartbeat.start()
8299
+ })
8300
+ );
8301
+ logStartupTrace("service.start_gateway.runtime_loop_begin");
8919
8302
  await runGatewayRuntimeLoop({
8920
8303
  runtimePool: gateway.runtimePool,
8921
8304
  startDeferredStartup: () => startDeferredGatewayStartup({
@@ -8964,6 +8347,7 @@ var ServiceCommands = class {
8964
8347
  setPluginRuntimeBridge2(null);
8965
8348
  }
8966
8349
  });
8350
+ logStartupTrace("service.start_gateway.end");
8967
8351
  }
8968
8352
  normalizeOptionalString(value) {
8969
8353
  if (typeof value !== "string") {
@@ -9058,7 +8442,7 @@ var ServiceCommands = class {
9058
8442
  }
9059
8443
  const sessionKey = sentinelSessionKey ?? fallbackSessionKey ?? "cli:default";
9060
8444
  const parsedSession = parseSessionKey(sessionKey);
9061
- const parsedAgentSession = parseAgentScopedSessionKey4(sessionKey);
8445
+ const parsedAgentSession = parseAgentScopedSessionKey2(sessionKey);
9062
8446
  const parsedSessionRoute = parsedSession && parsedSession.channel !== "agent" ? parsedSession : null;
9063
8447
  const context = payload.deliveryContext;
9064
8448
  const channel = this.normalizeOptionalString(context?.channel) ?? parsedSessionRoute?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
@@ -9168,7 +8552,7 @@ var ServiceCommands = class {
9168
8552
  }
9169
8553
  const logPath = resolveServiceLogPath();
9170
8554
  const logDir = resolve12(logPath, "..");
9171
- mkdirSync6(logDir, { recursive: true });
8555
+ mkdirSync5(logDir, { recursive: true });
9172
8556
  const logFd = openSync2(logPath, "a");
9173
8557
  const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
9174
8558
  const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
@@ -9557,9 +8941,9 @@ var ServiceCommands = class {
9557
8941
  }
9558
8942
  installBuiltinMarketplaceSkill(slug, force) {
9559
8943
  const workspace = getWorkspacePath11(loadConfig18().agents.defaults.workspace);
9560
- const destination = join8(workspace, "skills", slug);
9561
- const destinationSkillFile = join8(destination, "SKILL.md");
9562
- if (existsSync12(destinationSkillFile) && !force) {
8944
+ const destination = join7(workspace, "skills", slug);
8945
+ const destinationSkillFile = join7(destination, "SKILL.md");
8946
+ if (existsSync11(destinationSkillFile) && !force) {
9563
8947
  return {
9564
8948
  message: `${slug} is already installed`
9565
8949
  };
@@ -9567,14 +8951,14 @@ var ServiceCommands = class {
9567
8951
  const loader = createSkillsLoader(workspace);
9568
8952
  const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
9569
8953
  if (!builtin) {
9570
- if (existsSync12(destinationSkillFile)) {
8954
+ if (existsSync11(destinationSkillFile)) {
9571
8955
  return {
9572
8956
  message: `${slug} is already installed`
9573
8957
  };
9574
8958
  }
9575
8959
  return null;
9576
8960
  }
9577
- mkdirSync6(join8(workspace, "skills"), { recursive: true });
8961
+ mkdirSync5(join7(workspace, "skills"), { recursive: true });
9578
8962
  cpSync2(dirname3(builtin.path), destination, { recursive: true, force: true });
9579
8963
  return {
9580
8964
  message: `Installed skill: ${slug}`
@@ -9634,11 +9018,11 @@ ${stderr}`.trim();
9634
9018
  };
9635
9019
 
9636
9020
  // src/cli/workspace.ts
9637
- import { cpSync as cpSync3, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync10, readdirSync as readdirSync3, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
9021
+ import { cpSync as cpSync3, existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "fs";
9638
9022
  import { createRequire as createRequire2 } from "module";
9639
- import { dirname as dirname4, join as join9, resolve as resolve13 } from "path";
9023
+ import { dirname as dirname4, join as join8, resolve as resolve13 } from "path";
9640
9024
  import { fileURLToPath as fileURLToPath4 } from "url";
9641
- import { APP_NAME as APP_NAME4, getDataDir as getDataDir10 } from "@nextclaw/core";
9025
+ import { APP_NAME as APP_NAME4, getDataDir as getDataDir9 } from "@nextclaw/core";
9642
9026
  import { spawnSync as spawnSync3 } from "child_process";
9643
9027
  var WorkspaceManager = class {
9644
9028
  constructor(logo) {
@@ -9666,30 +9050,30 @@ var WorkspaceManager = class {
9666
9050
  { source: "memory/MEMORY.md", target: "memory/MEMORY.md" }
9667
9051
  ];
9668
9052
  for (const entry of templateFiles) {
9669
- const filePath = join9(workspace, entry.target);
9670
- if (!force && existsSync13(filePath)) {
9053
+ const filePath = join8(workspace, entry.target);
9054
+ if (!force && existsSync12(filePath)) {
9671
9055
  continue;
9672
9056
  }
9673
- const templatePath = join9(templateDir, entry.source);
9674
- if (!existsSync13(templatePath)) {
9057
+ const templatePath = join8(templateDir, entry.source);
9058
+ if (!existsSync12(templatePath)) {
9675
9059
  console.warn(`Warning: Template file missing: ${templatePath}`);
9676
9060
  continue;
9677
9061
  }
9678
- const raw = readFileSync10(templatePath, "utf-8");
9062
+ const raw = readFileSync9(templatePath, "utf-8");
9679
9063
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME4);
9680
- mkdirSync7(dirname4(filePath), { recursive: true });
9681
- writeFileSync6(filePath, content);
9064
+ mkdirSync6(dirname4(filePath), { recursive: true });
9065
+ writeFileSync5(filePath, content);
9682
9066
  created.push(entry.target);
9683
9067
  }
9684
- const memoryDir = join9(workspace, "memory");
9685
- if (!existsSync13(memoryDir)) {
9686
- mkdirSync7(memoryDir, { recursive: true });
9687
- created.push(join9("memory", ""));
9068
+ const memoryDir = join8(workspace, "memory");
9069
+ if (!existsSync12(memoryDir)) {
9070
+ mkdirSync6(memoryDir, { recursive: true });
9071
+ created.push(join8("memory", ""));
9688
9072
  }
9689
- const skillsDir = join9(workspace, "skills");
9690
- if (!existsSync13(skillsDir)) {
9691
- mkdirSync7(skillsDir, { recursive: true });
9692
- created.push(join9("skills", ""));
9073
+ const skillsDir = join8(workspace, "skills");
9074
+ if (!existsSync12(skillsDir)) {
9075
+ mkdirSync6(skillsDir, { recursive: true });
9076
+ created.push(join8("skills", ""));
9693
9077
  }
9694
9078
  const seeded = this.seedBuiltinSkills(skillsDir, { force });
9695
9079
  if (seeded > 0) {
@@ -9704,16 +9088,16 @@ var WorkspaceManager = class {
9704
9088
  }
9705
9089
  const force = Boolean(options.force);
9706
9090
  let seeded = 0;
9707
- for (const entry of readdirSync3(sourceDir, { withFileTypes: true })) {
9091
+ for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
9708
9092
  if (!entry.isDirectory()) {
9709
9093
  continue;
9710
9094
  }
9711
- const src = join9(sourceDir, entry.name);
9712
- if (!existsSync13(join9(src, "SKILL.md"))) {
9095
+ const src = join8(sourceDir, entry.name);
9096
+ if (!existsSync12(join8(src, "SKILL.md"))) {
9713
9097
  continue;
9714
9098
  }
9715
- const dest = join9(targetDir, entry.name);
9716
- if (!force && existsSync13(dest)) {
9099
+ const dest = join8(targetDir, entry.name);
9100
+ if (!force && existsSync12(dest)) {
9717
9101
  continue;
9718
9102
  }
9719
9103
  try {
@@ -9731,12 +9115,12 @@ var WorkspaceManager = class {
9731
9115
  const require3 = createRequire2(import.meta.url);
9732
9116
  const entry = require3.resolve("@nextclaw/core");
9733
9117
  const pkgRoot = resolve13(dirname4(entry), "..");
9734
- const distSkills = join9(pkgRoot, "dist", "skills");
9735
- if (existsSync13(distSkills)) {
9118
+ const distSkills = join8(pkgRoot, "dist", "skills");
9119
+ if (existsSync12(distSkills)) {
9736
9120
  return distSkills;
9737
9121
  }
9738
- const srcSkills = join9(pkgRoot, "src", "agent", "skills");
9739
- if (existsSync13(srcSkills)) {
9122
+ const srcSkills = join8(pkgRoot, "src", "agent", "skills");
9123
+ if (existsSync12(srcSkills)) {
9740
9124
  return srcSkills;
9741
9125
  }
9742
9126
  return null;
@@ -9751,17 +9135,17 @@ var WorkspaceManager = class {
9751
9135
  }
9752
9136
  const cliDir = resolve13(fileURLToPath4(new URL(".", import.meta.url)));
9753
9137
  const pkgRoot = resolve13(cliDir, "..", "..");
9754
- const candidates = [join9(pkgRoot, "templates")];
9138
+ const candidates = [join8(pkgRoot, "templates")];
9755
9139
  for (const candidate of candidates) {
9756
- if (existsSync13(candidate)) {
9140
+ if (existsSync12(candidate)) {
9757
9141
  return candidate;
9758
9142
  }
9759
9143
  }
9760
9144
  return null;
9761
9145
  }
9762
9146
  getBridgeDir() {
9763
- const userBridge = join9(getDataDir10(), "bridge");
9764
- if (existsSync13(join9(userBridge, "dist", "index.js"))) {
9147
+ const userBridge = join8(getDataDir9(), "bridge");
9148
+ if (existsSync12(join8(userBridge, "dist", "index.js"))) {
9765
9149
  return userBridge;
9766
9150
  }
9767
9151
  if (!which("npm")) {
@@ -9770,12 +9154,12 @@ var WorkspaceManager = class {
9770
9154
  }
9771
9155
  const cliDir = resolve13(fileURLToPath4(new URL(".", import.meta.url)));
9772
9156
  const pkgRoot = resolve13(cliDir, "..", "..");
9773
- const pkgBridge = join9(pkgRoot, "bridge");
9774
- const srcBridge = join9(pkgRoot, "..", "..", "bridge");
9157
+ const pkgBridge = join8(pkgRoot, "bridge");
9158
+ const srcBridge = join8(pkgRoot, "..", "..", "bridge");
9775
9159
  let source = null;
9776
- if (existsSync13(join9(pkgBridge, "package.json"))) {
9160
+ if (existsSync12(join8(pkgBridge, "package.json"))) {
9777
9161
  source = pkgBridge;
9778
- } else if (existsSync13(join9(srcBridge, "package.json"))) {
9162
+ } else if (existsSync12(join8(srcBridge, "package.json"))) {
9779
9163
  source = srcBridge;
9780
9164
  }
9781
9165
  if (!source) {
@@ -9783,8 +9167,8 @@ var WorkspaceManager = class {
9783
9167
  process.exit(1);
9784
9168
  }
9785
9169
  console.log(`${this.logo} Setting up bridge...`);
9786
- mkdirSync7(resolve13(userBridge, ".."), { recursive: true });
9787
- if (existsSync13(userBridge)) {
9170
+ mkdirSync6(resolve13(userBridge, ".."), { recursive: true });
9171
+ if (existsSync12(userBridge)) {
9788
9172
  rmSync6(userBridge, { recursive: true, force: true });
9789
9173
  }
9790
9174
  cpSync3(source, userBridge, {
@@ -9840,42 +9224,44 @@ var CliRuntime = class {
9840
9224
  remote;
9841
9225
  diagnosticsCommands;
9842
9226
  constructor(options = {}) {
9227
+ logStartupTrace("cli.runtime.constructor.begin");
9843
9228
  this.logo = options.logo ?? LOGO;
9844
- this.workspaceManager = new WorkspaceManager(this.logo);
9845
- this.serviceCommands = new ServiceCommands({
9229
+ this.workspaceManager = measureStartupSync("cli.runtime.workspace_manager", () => new WorkspaceManager(this.logo));
9230
+ this.serviceCommands = measureStartupSync("cli.runtime.service_commands", () => new ServiceCommands({
9846
9231
  requestRestart: (params) => this.requestRestart(params)
9847
- });
9848
- this.configCommands = new ConfigCommands({
9232
+ }));
9233
+ this.configCommands = measureStartupSync("cli.runtime.config_commands", () => new ConfigCommands({
9849
9234
  requestRestart: (params) => this.requestRestart(params)
9850
- });
9851
- this.mcpCommands = new McpCommands();
9852
- this.secretsCommands = new SecretsCommands({
9235
+ }));
9236
+ this.mcpCommands = measureStartupSync("cli.runtime.mcp_commands", () => new McpCommands());
9237
+ this.secretsCommands = measureStartupSync("cli.runtime.secrets_commands", () => new SecretsCommands({
9853
9238
  requestRestart: (params) => this.requestRestart(params)
9854
- });
9855
- this.pluginCommands = new PluginCommands();
9856
- this.channelCommands = new ChannelCommands({
9239
+ }));
9240
+ this.pluginCommands = measureStartupSync("cli.runtime.plugin_commands", () => new PluginCommands());
9241
+ this.channelCommands = measureStartupSync("cli.runtime.channel_commands", () => new ChannelCommands({
9857
9242
  logo: this.logo,
9858
9243
  getBridgeDir: () => this.workspaceManager.getBridgeDir(),
9859
9244
  requestRestart: (params) => this.requestRestart(params)
9860
- });
9861
- this.cronCommands = new CronCommands();
9862
- this.platformAuthCommands = new PlatformAuthCommands();
9863
- this.remoteCommands = new RemoteCommands();
9864
- this.remote = new RemoteRuntimeActions({
9245
+ }));
9246
+ this.cronCommands = measureStartupSync("cli.runtime.cron_commands", () => new CronCommands());
9247
+ this.platformAuthCommands = measureStartupSync("cli.runtime.platform_auth_commands", () => new PlatformAuthCommands());
9248
+ this.remoteCommands = measureStartupSync("cli.runtime.remote_commands", () => new RemoteCommands());
9249
+ this.remote = measureStartupSync("cli.runtime.remote_runtime_actions", () => new RemoteRuntimeActions({
9865
9250
  appName: APP_NAME5,
9866
9251
  initAuto: (source) => this.init({ source, auto: true }),
9867
9252
  remoteCommands: this.remoteCommands,
9868
9253
  restartBackgroundService: (reason) => this.restartBackgroundService(reason),
9869
9254
  hasRunningManagedService: hasRunningNextclawManagedService
9870
- });
9871
- this.diagnosticsCommands = new DiagnosticsCommands({ logo: this.logo });
9872
- this.restartCoordinator = new RestartCoordinator({
9255
+ }));
9256
+ this.diagnosticsCommands = measureStartupSync("cli.runtime.diagnostics_commands", () => new DiagnosticsCommands({ logo: this.logo }));
9257
+ this.restartCoordinator = measureStartupSync("cli.runtime.restart_coordinator", () => new RestartCoordinator({
9873
9258
  readServiceState,
9874
9259
  isProcessRunning,
9875
9260
  currentPid: () => process.pid,
9876
9261
  restartBackgroundService: async (reason) => this.restartBackgroundService(reason),
9877
9262
  scheduleProcessExit: (delayMs, reason) => this.scheduleProcessExit(delayMs, reason)
9878
- });
9263
+ }));
9264
+ logStartupTrace("cli.runtime.constructor.end");
9879
9265
  }
9880
9266
  get version() {
9881
9267
  return getPackageVersion();
@@ -9933,7 +9319,7 @@ var CliRuntime = class {
9933
9319
  const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
9934
9320
  const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath5(new URL("./index.js", import.meta.url));
9935
9321
  const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
9936
- const serviceStatePath = resolve14(getDataDir11(), "run", "service.json");
9322
+ const serviceStatePath = resolve14(getDataDir10(), "run", "service.json");
9937
9323
  const helperScript = [
9938
9324
  'const { spawnSync } = require("node:child_process");',
9939
9325
  'const { readFileSync } = require("node:fs");',
@@ -10065,9 +9451,9 @@ var CliRuntime = class {
10065
9451
  const createdConfig = initializeConfigIfMissing(configPath);
10066
9452
  const config2 = loadConfig19();
10067
9453
  const workspaceSetting = config2.agents.defaults.workspace;
10068
- const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join10(getDataDir11(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
10069
- const workspaceExisted = existsSync14(workspacePath);
10070
- mkdirSync8(workspacePath, { recursive: true });
9454
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join9(getDataDir10(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
9455
+ const workspaceExisted = existsSync13(workspacePath);
9456
+ mkdirSync7(workspacePath, { recursive: true });
10071
9457
  const templateResult = this.workspaceManager.createWorkspaceTemplates(
10072
9458
  workspacePath,
10073
9459
  { force }
@@ -10258,10 +9644,10 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
10258
9644
  `${this.logo} Interactive mode (type exit or Ctrl+C to quit)
10259
9645
  `
10260
9646
  );
10261
- const historyFile = join10(getDataDir11(), "history", "cli_history");
9647
+ const historyFile = join9(getDataDir10(), "history", "cli_history");
10262
9648
  const historyDir = resolve14(historyFile, "..");
10263
- mkdirSync8(historyDir, { recursive: true });
10264
- const history = existsSync14(historyFile) ? readFileSync11(historyFile, "utf-8").split("\n").filter(Boolean) : [];
9649
+ mkdirSync7(historyDir, { recursive: true });
9650
+ const history = existsSync13(historyFile) ? readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean) : [];
10265
9651
  const rl = createInterface3({
10266
9652
  input: process.stdin,
10267
9653
  output: process.stdout
@@ -10270,7 +9656,7 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
10270
9656
  const merged = history.concat(
10271
9657
  rl.history ?? []
10272
9658
  );
10273
- writeFileSync7(historyFile, merged.join("\n"));
9659
+ writeFileSync6(historyFile, merged.join("\n"));
10274
9660
  process.exit(0);
10275
9661
  });
10276
9662
  let running = true;
@@ -10496,8 +9882,9 @@ ${this.logo} ${APP_NAME5} is ready! (${source})`);
10496
9882
  };
10497
9883
 
10498
9884
  // src/cli/index.ts
9885
+ logStartupTrace("cli.index.module_loaded");
10499
9886
  var program = new Command();
10500
- var runtime = new CliRuntime({ logo: LOGO });
9887
+ var runtime = measureStartupSync("cli.runtime.construct", () => new CliRuntime({ logo: LOGO }));
10501
9888
  program.name(APP_NAME6).description(`${LOGO} ${APP_NAME6} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
10502
9889
  program.command("onboard").description(`Initialize ${APP_NAME6} configuration and workspace`).action(async () => runtime.onboard());
10503
9890
  program.command("init").description(`Initialize ${APP_NAME6} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));